sqlspec 0.26.0__py3-none-any.whl → 0.27.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 +62 -52
- sqlspec/adapters/adbc/_types.py +1 -1
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +870 -0
- sqlspec/adapters/adbc/config.py +62 -12
- sqlspec/adapters/adbc/data_dictionary.py +52 -2
- sqlspec/adapters/adbc/driver.py +144 -45
- 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 +527 -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 +493 -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 +450 -0
- sqlspec/adapters/asyncpg/config.py +57 -36
- sqlspec/adapters/asyncpg/data_dictionary.py +41 -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 +576 -0
- sqlspec/adapters/bigquery/config.py +25 -11
- sqlspec/adapters/bigquery/data_dictionary.py +42 -2
- sqlspec/adapters/bigquery/driver.py +352 -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 +553 -0
- sqlspec/adapters/duckdb/config.py +79 -21
- sqlspec/adapters/duckdb/data_dictionary.py +41 -2
- sqlspec/adapters/duckdb/driver.py +138 -43
- 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 +1745 -0
- sqlspec/adapters/oracledb/config.py +120 -36
- sqlspec/adapters/oracledb/data_dictionary.py +87 -20
- sqlspec/adapters/oracledb/driver.py +292 -84
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +767 -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 +482 -0
- sqlspec/adapters/psqlpy/config.py +45 -19
- sqlspec/adapters/psqlpy/data_dictionary.py +41 -2
- sqlspec/adapters/psqlpy/driver.py +101 -31
- 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 +944 -0
- sqlspec/adapters/psycopg/config.py +65 -37
- sqlspec/adapters/psycopg/data_dictionary.py +77 -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 +572 -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 +231 -60
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +18 -18
- sqlspec/core/compiler.py +6 -8
- sqlspec/core/filters.py +37 -37
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +76 -45
- sqlspec/core/result.py +102 -46
- 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 +95 -161
- sqlspec/driver/_common.py +133 -80
- sqlspec/driver/_sync.py +95 -162
- 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 +73 -53
- sqlspec/extensions/litestar/__init__.py +21 -4
- sqlspec/extensions/litestar/cli.py +54 -10
- sqlspec/extensions/litestar/config.py +59 -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 +324 -223
- sqlspec/extensions/litestar/providers.py +25 -25
- sqlspec/extensions/litestar/store.py +265 -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 +66 -36
- sqlspec/storage/_utils.py +98 -0
- sqlspec/storage/backends/fsspec.py +134 -106
- sqlspec/storage/backends/local.py +78 -51
- sqlspec/storage/backends/obstore.py +278 -162
- sqlspec/storage/registry.py +75 -39
- sqlspec/typing.py +14 -84
- 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 +2 -2
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +3 -3
- sqlspec/utils/sync_tools.py +21 -17
- sqlspec/utils/text.py +1 -2
- sqlspec/utils/type_guards.py +111 -20
- sqlspec/utils/version.py +433 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
- sqlspec-0.27.0.dist-info/RECORD +207 -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.27.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,36 +1,60 @@
|
|
|
1
1
|
"""SQLite database configuration with thread-local connections."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import uuid
|
|
4
5
|
from contextlib import contextmanager
|
|
5
|
-
from typing import TYPE_CHECKING, Any, ClassVar,
|
|
6
|
+
from typing import TYPE_CHECKING, Any, ClassVar, TypedDict, cast
|
|
6
7
|
|
|
7
8
|
from typing_extensions import NotRequired
|
|
8
9
|
|
|
10
|
+
from sqlspec.adapters.sqlite._type_handlers import register_type_handlers
|
|
9
11
|
from sqlspec.adapters.sqlite._types import SqliteConnection
|
|
10
12
|
from sqlspec.adapters.sqlite.driver import SqliteCursor, SqliteDriver, sqlite_statement_config
|
|
11
13
|
from sqlspec.adapters.sqlite.pool import SqliteConnectionPool
|
|
12
14
|
from sqlspec.config import SyncDatabaseConfig
|
|
15
|
+
from sqlspec.utils.serializers import from_json, to_json
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
13
18
|
|
|
14
19
|
if TYPE_CHECKING:
|
|
15
|
-
from collections.abc import Generator
|
|
20
|
+
from collections.abc import Callable, Generator
|
|
16
21
|
|
|
17
22
|
from sqlspec.core.statement import StatementConfig
|
|
18
23
|
|
|
19
24
|
|
|
20
|
-
class SqliteConnectionParams(TypedDict
|
|
25
|
+
class SqliteConnectionParams(TypedDict):
|
|
21
26
|
"""SQLite connection parameters."""
|
|
22
27
|
|
|
23
28
|
database: NotRequired[str]
|
|
24
29
|
timeout: NotRequired[float]
|
|
25
30
|
detect_types: NotRequired[int]
|
|
26
|
-
isolation_level: "NotRequired[
|
|
31
|
+
isolation_level: "NotRequired[str | None]"
|
|
27
32
|
check_same_thread: NotRequired[bool]
|
|
28
|
-
factory: "NotRequired[
|
|
33
|
+
factory: "NotRequired[type[SqliteConnection] | None]"
|
|
29
34
|
cached_statements: NotRequired[int]
|
|
30
35
|
uri: NotRequired[bool]
|
|
31
36
|
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
class SqliteDriverFeatures(TypedDict):
|
|
39
|
+
"""SQLite driver feature configuration.
|
|
40
|
+
|
|
41
|
+
Controls optional type handling and serialization features for SQLite connections.
|
|
42
|
+
|
|
43
|
+
enable_custom_adapters: Enable custom type adapters for JSON/UUID/datetime conversion.
|
|
44
|
+
Defaults to True for enhanced Python type support.
|
|
45
|
+
Set to False only if you need pure SQLite behavior without type conversions.
|
|
46
|
+
json_serializer: Custom JSON serializer function.
|
|
47
|
+
Defaults to sqlspec.utils.serializers.to_json.
|
|
48
|
+
json_deserializer: Custom JSON deserializer function.
|
|
49
|
+
Defaults to sqlspec.utils.serializers.from_json.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
enable_custom_adapters: NotRequired[bool]
|
|
53
|
+
json_serializer: "NotRequired[Callable[[Any], str]]"
|
|
54
|
+
json_deserializer: "NotRequired[Callable[[str], Any]]"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
__all__ = ("SqliteConfig", "SqliteConnectionParams", "SqliteDriverFeatures")
|
|
34
58
|
|
|
35
59
|
|
|
36
60
|
class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, SqliteDriver]):
|
|
@@ -38,16 +62,18 @@ class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, Sq
|
|
|
38
62
|
|
|
39
63
|
driver_type: "ClassVar[type[SqliteDriver]]" = SqliteDriver
|
|
40
64
|
connection_type: "ClassVar[type[SqliteConnection]]" = SqliteConnection
|
|
65
|
+
supports_transactional_ddl: "ClassVar[bool]" = True
|
|
41
66
|
|
|
42
67
|
def __init__(
|
|
43
68
|
self,
|
|
44
69
|
*,
|
|
45
|
-
pool_config: "
|
|
46
|
-
pool_instance: "
|
|
47
|
-
migration_config: "
|
|
48
|
-
statement_config: "
|
|
49
|
-
driver_features: "
|
|
50
|
-
bind_key: "
|
|
70
|
+
pool_config: "SqliteConnectionParams | dict[str, Any] | None" = None,
|
|
71
|
+
pool_instance: "SqliteConnectionPool | None" = None,
|
|
72
|
+
migration_config: "dict[str, Any] | None" = None,
|
|
73
|
+
statement_config: "StatementConfig | None" = None,
|
|
74
|
+
driver_features: "SqliteDriverFeatures | dict[str, Any] | None" = None,
|
|
75
|
+
bind_key: "str | None" = None,
|
|
76
|
+
extension_config: "dict[str, dict[str, Any]] | None" = None,
|
|
51
77
|
) -> None:
|
|
52
78
|
"""Initialize SQLite configuration.
|
|
53
79
|
|
|
@@ -58,12 +84,33 @@ class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, Sq
|
|
|
58
84
|
statement_config: Default SQL statement configuration
|
|
59
85
|
driver_features: Optional driver feature configuration
|
|
60
86
|
bind_key: Optional bind key for the configuration
|
|
87
|
+
extension_config: Extension-specific configuration (e.g., Litestar plugin settings)
|
|
61
88
|
"""
|
|
62
89
|
if pool_config is None:
|
|
63
90
|
pool_config = {}
|
|
64
91
|
if "database" not in pool_config or pool_config["database"] == ":memory:":
|
|
65
92
|
pool_config["database"] = f"file:memory_{uuid.uuid4().hex}?mode=memory&cache=private"
|
|
66
93
|
pool_config["uri"] = True
|
|
94
|
+
elif "database" in pool_config:
|
|
95
|
+
database_path = str(pool_config["database"])
|
|
96
|
+
if database_path.startswith("file:") and not pool_config.get("uri"):
|
|
97
|
+
logger.debug(
|
|
98
|
+
"Database URI detected (%s) but uri=True not set. "
|
|
99
|
+
"Auto-enabling URI mode to prevent physical file creation.",
|
|
100
|
+
database_path,
|
|
101
|
+
)
|
|
102
|
+
pool_config["uri"] = True
|
|
103
|
+
|
|
104
|
+
processed_driver_features: dict[str, Any] = dict(driver_features) if driver_features else {}
|
|
105
|
+
|
|
106
|
+
if "enable_custom_adapters" not in processed_driver_features:
|
|
107
|
+
processed_driver_features["enable_custom_adapters"] = True
|
|
108
|
+
|
|
109
|
+
if "json_serializer" not in processed_driver_features:
|
|
110
|
+
processed_driver_features["json_serializer"] = to_json
|
|
111
|
+
|
|
112
|
+
if "json_deserializer" not in processed_driver_features:
|
|
113
|
+
processed_driver_features["json_deserializer"] = from_json
|
|
67
114
|
|
|
68
115
|
super().__init__(
|
|
69
116
|
bind_key=bind_key,
|
|
@@ -71,7 +118,8 @@ class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, Sq
|
|
|
71
118
|
pool_config=cast("dict[str, Any]", pool_config),
|
|
72
119
|
migration_config=migration_config,
|
|
73
120
|
statement_config=statement_config or sqlite_statement_config,
|
|
74
|
-
driver_features=
|
|
121
|
+
driver_features=processed_driver_features,
|
|
122
|
+
extension_config=extension_config,
|
|
75
123
|
)
|
|
76
124
|
|
|
77
125
|
def _get_connection_config_dict(self) -> "dict[str, Any]":
|
|
@@ -84,7 +132,24 @@ class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, Sq
|
|
|
84
132
|
"""Create connection pool from configuration."""
|
|
85
133
|
config_dict = self._get_connection_config_dict()
|
|
86
134
|
|
|
87
|
-
|
|
135
|
+
pool = SqliteConnectionPool(connection_parameters=config_dict, **self.pool_config)
|
|
136
|
+
|
|
137
|
+
if self.driver_features.get("enable_custom_adapters", False):
|
|
138
|
+
self._register_type_adapters()
|
|
139
|
+
|
|
140
|
+
return pool
|
|
141
|
+
|
|
142
|
+
def _register_type_adapters(self) -> None:
|
|
143
|
+
"""Register custom type adapters and converters for SQLite.
|
|
144
|
+
|
|
145
|
+
Called once during pool creation if enable_custom_adapters is True.
|
|
146
|
+
Registers JSON serialization handlers if configured.
|
|
147
|
+
"""
|
|
148
|
+
if self.driver_features.get("enable_custom_adapters", False):
|
|
149
|
+
register_type_handlers(
|
|
150
|
+
json_serializer=self.driver_features.get("json_serializer"),
|
|
151
|
+
json_deserializer=self.driver_features.get("json_deserializer"),
|
|
152
|
+
)
|
|
88
153
|
|
|
89
154
|
def _close_pool(self) -> None:
|
|
90
155
|
"""Close the connection pool."""
|
|
@@ -113,7 +178,7 @@ class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, Sq
|
|
|
113
178
|
|
|
114
179
|
@contextmanager
|
|
115
180
|
def provide_session(
|
|
116
|
-
self, *args: "Any", statement_config: "
|
|
181
|
+
self, *args: "Any", statement_config: "StatementConfig | None" = None, **kwargs: "Any"
|
|
117
182
|
) -> "Generator[SqliteDriver, None, None]":
|
|
118
183
|
"""Provide a SQLite driver session.
|
|
119
184
|
|
|
@@ -121,7 +186,11 @@ class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, Sq
|
|
|
121
186
|
SqliteDriver: A driver instance with thread-local connection
|
|
122
187
|
"""
|
|
123
188
|
with self.provide_connection(*args, **kwargs) as connection:
|
|
124
|
-
yield self.driver_type(
|
|
189
|
+
yield self.driver_type(
|
|
190
|
+
connection=connection,
|
|
191
|
+
statement_config=statement_config or self.statement_config,
|
|
192
|
+
driver_features=self.driver_features,
|
|
193
|
+
)
|
|
125
194
|
|
|
126
195
|
def get_signature_namespace(self) -> "dict[str, type[Any]]":
|
|
127
196
|
"""Get the signature namespace for SQLite types.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""SQLite-specific data dictionary for metadata queries."""
|
|
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 SyncDataDictionaryBase, SyncDriverAdapterBase, 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.sqlite.driver import SqliteDriver
|
|
11
13
|
|
|
12
14
|
logger = get_logger("adapters.sqlite.data_dictionary")
|
|
@@ -20,7 +22,7 @@ __all__ = ("SqliteSyncDataDictionary",)
|
|
|
20
22
|
class SqliteSyncDataDictionary(SyncDataDictionaryBase):
|
|
21
23
|
"""SQLite-specific sync data dictionary."""
|
|
22
24
|
|
|
23
|
-
def get_version(self, driver: SyncDriverAdapterBase) -> "
|
|
25
|
+
def get_version(self, driver: SyncDriverAdapterBase) -> "VersionInfo | None":
|
|
24
26
|
"""Get SQLite database version information.
|
|
25
27
|
|
|
26
28
|
Args:
|
|
@@ -97,6 +99,36 @@ class SqliteSyncDataDictionary(SyncDataDictionaryBase):
|
|
|
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
|
+
def get_columns(
|
|
103
|
+
self, driver: SyncDriverAdapterBase, 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: SQLite 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
|
+
sqlite_driver = cast("SqliteDriver", driver)
|
|
120
|
+
result = sqlite_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,13 +4,24 @@ import contextlib
|
|
|
4
4
|
import datetime
|
|
5
5
|
import sqlite3
|
|
6
6
|
from decimal import Decimal
|
|
7
|
-
from typing import TYPE_CHECKING, Any
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
9
|
from sqlspec.core.cache import get_cache_config
|
|
10
10
|
from sqlspec.core.parameters import ParameterStyle, ParameterStyleConfig
|
|
11
11
|
from sqlspec.core.statement import StatementConfig
|
|
12
12
|
from sqlspec.driver import SyncDriverAdapterBase
|
|
13
|
-
from sqlspec.exceptions import
|
|
13
|
+
from sqlspec.exceptions import (
|
|
14
|
+
CheckViolationError,
|
|
15
|
+
DatabaseConnectionError,
|
|
16
|
+
DataError,
|
|
17
|
+
ForeignKeyViolationError,
|
|
18
|
+
IntegrityError,
|
|
19
|
+
NotNullViolationError,
|
|
20
|
+
OperationalError,
|
|
21
|
+
SQLParsingError,
|
|
22
|
+
SQLSpecError,
|
|
23
|
+
UniqueViolationError,
|
|
24
|
+
)
|
|
14
25
|
from sqlspec.utils.serializers import to_json
|
|
15
26
|
|
|
16
27
|
if TYPE_CHECKING:
|
|
@@ -24,6 +35,14 @@ if TYPE_CHECKING:
|
|
|
24
35
|
|
|
25
36
|
__all__ = ("SqliteCursor", "SqliteDriver", "SqliteExceptionHandler", "sqlite_statement_config")
|
|
26
37
|
|
|
38
|
+
SQLITE_CONSTRAINT_UNIQUE_CODE = 2067
|
|
39
|
+
SQLITE_CONSTRAINT_FOREIGNKEY_CODE = 787
|
|
40
|
+
SQLITE_CONSTRAINT_NOTNULL_CODE = 1811
|
|
41
|
+
SQLITE_CONSTRAINT_CHECK_CODE = 531
|
|
42
|
+
SQLITE_CONSTRAINT_CODE = 19
|
|
43
|
+
SQLITE_CANTOPEN_CODE = 14
|
|
44
|
+
SQLITE_IOERR_CODE = 10
|
|
45
|
+
SQLITE_MISMATCH_CODE = 20
|
|
27
46
|
|
|
28
47
|
sqlite_statement_config = StatementConfig(
|
|
29
48
|
dialect="sqlite",
|
|
@@ -66,7 +85,7 @@ class SqliteCursor:
|
|
|
66
85
|
connection: SQLite database connection
|
|
67
86
|
"""
|
|
68
87
|
self.connection = connection
|
|
69
|
-
self.cursor:
|
|
88
|
+
self.cursor: sqlite3.Cursor | None = None
|
|
70
89
|
|
|
71
90
|
def __enter__(self) -> "sqlite3.Cursor":
|
|
72
91
|
"""Create and return a new cursor.
|
|
@@ -93,64 +112,113 @@ class SqliteCursor:
|
|
|
93
112
|
class SqliteExceptionHandler:
|
|
94
113
|
"""Context manager for handling SQLite database exceptions.
|
|
95
114
|
|
|
96
|
-
|
|
115
|
+
Maps SQLite extended result codes to specific SQLSpec exceptions
|
|
116
|
+
for better error handling in application code.
|
|
97
117
|
"""
|
|
98
118
|
|
|
99
119
|
__slots__ = ()
|
|
100
120
|
|
|
101
121
|
def __enter__(self) -> None:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
Returns:
|
|
105
|
-
None
|
|
106
|
-
"""
|
|
107
|
-
return
|
|
122
|
+
return None
|
|
108
123
|
|
|
109
124
|
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
110
|
-
|
|
125
|
+
if exc_type is None:
|
|
126
|
+
return
|
|
127
|
+
if issubclass(exc_type, sqlite3.Error):
|
|
128
|
+
self._map_sqlite_exception(exc_val)
|
|
129
|
+
|
|
130
|
+
def _map_sqlite_exception(self, e: Any) -> None:
|
|
131
|
+
"""Map SQLite exception to SQLSpec exception.
|
|
111
132
|
|
|
112
133
|
Args:
|
|
113
|
-
|
|
114
|
-
exc_val: Exception value if an exception occurred
|
|
115
|
-
exc_tb: Exception traceback if an exception occurred
|
|
134
|
+
e: sqlite3.Error instance
|
|
116
135
|
|
|
117
136
|
Raises:
|
|
118
|
-
|
|
119
|
-
SQLParsingError: For SQL syntax or parsing errors
|
|
137
|
+
Specific SQLSpec exception based on error code
|
|
120
138
|
"""
|
|
121
|
-
|
|
139
|
+
error_code = getattr(e, "sqlite_errorcode", None)
|
|
140
|
+
error_name = getattr(e, "sqlite_errorname", None)
|
|
141
|
+
error_msg = str(e).lower()
|
|
142
|
+
|
|
143
|
+
if "locked" in error_msg:
|
|
144
|
+
self._raise_operational_error(e, error_code or 0)
|
|
145
|
+
|
|
146
|
+
if not error_code:
|
|
147
|
+
if "unique constraint" in error_msg:
|
|
148
|
+
self._raise_unique_violation(e, 0)
|
|
149
|
+
elif "foreign key constraint" in error_msg:
|
|
150
|
+
self._raise_foreign_key_violation(e, 0)
|
|
151
|
+
elif "not null constraint" in error_msg:
|
|
152
|
+
self._raise_not_null_violation(e, 0)
|
|
153
|
+
elif "check constraint" in error_msg:
|
|
154
|
+
self._raise_check_violation(e, 0)
|
|
155
|
+
elif "syntax" in error_msg:
|
|
156
|
+
self._raise_parsing_error(e, None)
|
|
157
|
+
else:
|
|
158
|
+
self._raise_generic_error(e)
|
|
122
159
|
return
|
|
123
160
|
|
|
124
|
-
if
|
|
125
|
-
e
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
e
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
e
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
e
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
161
|
+
if error_code == SQLITE_CONSTRAINT_UNIQUE_CODE or error_name == "SQLITE_CONSTRAINT_UNIQUE":
|
|
162
|
+
self._raise_unique_violation(e, error_code)
|
|
163
|
+
elif error_code == SQLITE_CONSTRAINT_FOREIGNKEY_CODE or error_name == "SQLITE_CONSTRAINT_FOREIGNKEY":
|
|
164
|
+
self._raise_foreign_key_violation(e, error_code)
|
|
165
|
+
elif error_code == SQLITE_CONSTRAINT_NOTNULL_CODE or error_name == "SQLITE_CONSTRAINT_NOTNULL":
|
|
166
|
+
self._raise_not_null_violation(e, error_code)
|
|
167
|
+
elif error_code == SQLITE_CONSTRAINT_CHECK_CODE or error_name == "SQLITE_CONSTRAINT_CHECK":
|
|
168
|
+
self._raise_check_violation(e, error_code)
|
|
169
|
+
elif error_code == SQLITE_CONSTRAINT_CODE or error_name == "SQLITE_CONSTRAINT":
|
|
170
|
+
self._raise_integrity_error(e, error_code)
|
|
171
|
+
elif error_code == SQLITE_CANTOPEN_CODE or error_name == "SQLITE_CANTOPEN":
|
|
172
|
+
self._raise_connection_error(e, error_code)
|
|
173
|
+
elif error_code == SQLITE_IOERR_CODE or error_name == "SQLITE_IOERR":
|
|
174
|
+
self._raise_operational_error(e, error_code)
|
|
175
|
+
elif error_code == SQLITE_MISMATCH_CODE or error_name == "SQLITE_MISMATCH":
|
|
176
|
+
self._raise_data_error(e, error_code)
|
|
177
|
+
elif error_code == 1 or "syntax" in error_msg:
|
|
178
|
+
self._raise_parsing_error(e, error_code)
|
|
179
|
+
else:
|
|
180
|
+
self._raise_generic_error(e)
|
|
181
|
+
|
|
182
|
+
def _raise_unique_violation(self, e: Any, code: int) -> None:
|
|
183
|
+
msg = f"SQLite unique constraint violation [code {code}]: {e}"
|
|
184
|
+
raise UniqueViolationError(msg) from e
|
|
185
|
+
|
|
186
|
+
def _raise_foreign_key_violation(self, e: Any, code: int) -> None:
|
|
187
|
+
msg = f"SQLite foreign key constraint violation [code {code}]: {e}"
|
|
188
|
+
raise ForeignKeyViolationError(msg) from e
|
|
189
|
+
|
|
190
|
+
def _raise_not_null_violation(self, e: Any, code: int) -> None:
|
|
191
|
+
msg = f"SQLite not-null constraint violation [code {code}]: {e}"
|
|
192
|
+
raise NotNullViolationError(msg) from e
|
|
193
|
+
|
|
194
|
+
def _raise_check_violation(self, e: Any, code: int) -> None:
|
|
195
|
+
msg = f"SQLite check constraint violation [code {code}]: {e}"
|
|
196
|
+
raise CheckViolationError(msg) from e
|
|
197
|
+
|
|
198
|
+
def _raise_integrity_error(self, e: Any, code: int) -> None:
|
|
199
|
+
msg = f"SQLite integrity constraint violation [code {code}]: {e}"
|
|
200
|
+
raise IntegrityError(msg) from e
|
|
201
|
+
|
|
202
|
+
def _raise_parsing_error(self, e: Any, code: "int | None") -> None:
|
|
203
|
+
code_str = f"[code {code}]" if code else ""
|
|
204
|
+
msg = f"SQLite SQL syntax error {code_str}: {e}"
|
|
205
|
+
raise SQLParsingError(msg) from e
|
|
206
|
+
|
|
207
|
+
def _raise_connection_error(self, e: Any, code: int) -> None:
|
|
208
|
+
msg = f"SQLite connection error [code {code}]: {e}"
|
|
209
|
+
raise DatabaseConnectionError(msg) from e
|
|
210
|
+
|
|
211
|
+
def _raise_operational_error(self, e: Any, code: int) -> None:
|
|
212
|
+
msg = f"SQLite operational error [code {code}]: {e}"
|
|
213
|
+
raise OperationalError(msg) from e
|
|
214
|
+
|
|
215
|
+
def _raise_data_error(self, e: Any, code: int) -> None:
|
|
216
|
+
msg = f"SQLite data error [code {code}]: {e}"
|
|
217
|
+
raise DataError(msg) from e
|
|
218
|
+
|
|
219
|
+
def _raise_generic_error(self, e: Any) -> None:
|
|
220
|
+
msg = f"SQLite database error: {e}"
|
|
221
|
+
raise SQLSpecError(msg) from e
|
|
154
222
|
|
|
155
223
|
|
|
156
224
|
class SqliteDriver(SyncDriverAdapterBase):
|
|
@@ -166,8 +234,8 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
166
234
|
def __init__(
|
|
167
235
|
self,
|
|
168
236
|
connection: "SqliteConnection",
|
|
169
|
-
statement_config: "
|
|
170
|
-
driver_features: "
|
|
237
|
+
statement_config: "StatementConfig | None" = None,
|
|
238
|
+
driver_features: "dict[str, Any] | None" = None,
|
|
171
239
|
) -> None:
|
|
172
240
|
"""Initialize SQLite driver.
|
|
173
241
|
|
|
@@ -186,7 +254,7 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
186
254
|
)
|
|
187
255
|
|
|
188
256
|
super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
|
|
189
|
-
self._data_dictionary:
|
|
257
|
+
self._data_dictionary: SyncDataDictionaryBase | None = None
|
|
190
258
|
|
|
191
259
|
def with_cursor(self, connection: "SqliteConnection") -> "SqliteCursor":
|
|
192
260
|
"""Create context manager for SQLite cursor.
|
|
@@ -207,7 +275,7 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
207
275
|
"""
|
|
208
276
|
return SqliteExceptionHandler()
|
|
209
277
|
|
|
210
|
-
def _try_special_handling(self, cursor: "sqlite3.Cursor", statement: "SQL") -> "
|
|
278
|
+
def _try_special_handling(self, cursor: "sqlite3.Cursor", statement: "SQL") -> "SQLResult | None":
|
|
211
279
|
"""Hook for SQLite-specific special operations.
|
|
212
280
|
|
|
213
281
|
Args:
|
|
@@ -282,7 +350,7 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
282
350
|
fetched_data = cursor.fetchall()
|
|
283
351
|
column_names = [col[0] for col in cursor.description or []]
|
|
284
352
|
|
|
285
|
-
data = [dict(zip(column_names, row)) for row in fetched_data]
|
|
353
|
+
data = [dict(zip(column_names, row, strict=False)) for row in fetched_data]
|
|
286
354
|
|
|
287
355
|
return self.create_execution_result(
|
|
288
356
|
cursor, selected_data=data, column_names=column_names, data_row_count=len(data), is_select_result=True
|