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.

Files changed (212) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +55 -25
  3. sqlspec/_typing.py +155 -52
  4. sqlspec/adapters/adbc/_types.py +1 -1
  5. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  6. sqlspec/adapters/adbc/adk/store.py +880 -0
  7. sqlspec/adapters/adbc/config.py +62 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +74 -2
  9. sqlspec/adapters/adbc/driver.py +226 -58
  10. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  11. sqlspec/adapters/adbc/litestar/store.py +504 -0
  12. sqlspec/adapters/adbc/type_converter.py +44 -50
  13. sqlspec/adapters/aiosqlite/_types.py +1 -1
  14. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  15. sqlspec/adapters/aiosqlite/adk/store.py +536 -0
  16. sqlspec/adapters/aiosqlite/config.py +86 -16
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +34 -2
  18. sqlspec/adapters/aiosqlite/driver.py +127 -38
  19. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  20. sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
  21. sqlspec/adapters/aiosqlite/pool.py +7 -7
  22. sqlspec/adapters/asyncmy/__init__.py +7 -1
  23. sqlspec/adapters/asyncmy/_types.py +1 -1
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +503 -0
  26. sqlspec/adapters/asyncmy/config.py +59 -17
  27. sqlspec/adapters/asyncmy/data_dictionary.py +41 -2
  28. sqlspec/adapters/asyncmy/driver.py +293 -62
  29. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  30. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  31. sqlspec/adapters/asyncpg/__init__.py +2 -1
  32. sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
  33. sqlspec/adapters/asyncpg/_types.py +11 -7
  34. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  35. sqlspec/adapters/asyncpg/adk/store.py +460 -0
  36. sqlspec/adapters/asyncpg/config.py +57 -36
  37. sqlspec/adapters/asyncpg/data_dictionary.py +48 -2
  38. sqlspec/adapters/asyncpg/driver.py +153 -23
  39. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  40. sqlspec/adapters/asyncpg/litestar/store.py +253 -0
  41. sqlspec/adapters/bigquery/_types.py +1 -1
  42. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  43. sqlspec/adapters/bigquery/adk/store.py +585 -0
  44. sqlspec/adapters/bigquery/config.py +36 -11
  45. sqlspec/adapters/bigquery/data_dictionary.py +42 -2
  46. sqlspec/adapters/bigquery/driver.py +489 -144
  47. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  48. sqlspec/adapters/bigquery/litestar/store.py +327 -0
  49. sqlspec/adapters/bigquery/type_converter.py +55 -23
  50. sqlspec/adapters/duckdb/_types.py +2 -2
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +563 -0
  53. sqlspec/adapters/duckdb/config.py +79 -21
  54. sqlspec/adapters/duckdb/data_dictionary.py +41 -2
  55. sqlspec/adapters/duckdb/driver.py +225 -44
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +5 -5
  59. sqlspec/adapters/duckdb/type_converter.py +51 -21
  60. sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
  61. sqlspec/adapters/oracledb/_types.py +20 -2
  62. sqlspec/adapters/oracledb/adk/__init__.py +5 -0
  63. sqlspec/adapters/oracledb/adk/store.py +1628 -0
  64. sqlspec/adapters/oracledb/config.py +120 -36
  65. sqlspec/adapters/oracledb/data_dictionary.py +87 -20
  66. sqlspec/adapters/oracledb/driver.py +475 -86
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +765 -0
  69. sqlspec/adapters/oracledb/migrations.py +316 -25
  70. sqlspec/adapters/oracledb/type_converter.py +91 -16
  71. sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
  72. sqlspec/adapters/psqlpy/_types.py +2 -1
  73. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  74. sqlspec/adapters/psqlpy/adk/store.py +483 -0
  75. sqlspec/adapters/psqlpy/config.py +45 -19
  76. sqlspec/adapters/psqlpy/data_dictionary.py +48 -2
  77. sqlspec/adapters/psqlpy/driver.py +108 -41
  78. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  79. sqlspec/adapters/psqlpy/litestar/store.py +272 -0
  80. sqlspec/adapters/psqlpy/type_converter.py +40 -11
  81. sqlspec/adapters/psycopg/_type_handlers.py +80 -0
  82. sqlspec/adapters/psycopg/_types.py +2 -1
  83. sqlspec/adapters/psycopg/adk/__init__.py +5 -0
  84. sqlspec/adapters/psycopg/adk/store.py +962 -0
  85. sqlspec/adapters/psycopg/config.py +65 -37
  86. sqlspec/adapters/psycopg/data_dictionary.py +91 -3
  87. sqlspec/adapters/psycopg/driver.py +200 -78
  88. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  89. sqlspec/adapters/psycopg/litestar/store.py +554 -0
  90. sqlspec/adapters/sqlite/__init__.py +2 -1
  91. sqlspec/adapters/sqlite/_type_handlers.py +86 -0
  92. sqlspec/adapters/sqlite/_types.py +1 -1
  93. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  94. sqlspec/adapters/sqlite/adk/store.py +582 -0
  95. sqlspec/adapters/sqlite/config.py +85 -16
  96. sqlspec/adapters/sqlite/data_dictionary.py +34 -2
  97. sqlspec/adapters/sqlite/driver.py +120 -52
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +5 -5
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +91 -58
  104. sqlspec/builder/_column.py +5 -5
  105. sqlspec/builder/_ddl.py +98 -89
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +41 -44
  109. sqlspec/builder/_insert.py +5 -82
  110. sqlspec/builder/{mixins/_join_operations.py → _join.py} +145 -143
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +9 -11
  113. sqlspec/builder/_select.py +1313 -25
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +76 -69
  116. sqlspec/config.py +331 -62
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +18 -18
  119. sqlspec/core/compiler.py +6 -8
  120. sqlspec/core/filters.py +55 -47
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +76 -45
  123. sqlspec/core/result.py +234 -47
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +32 -31
  126. sqlspec/core/type_conversion.py +3 -2
  127. sqlspec/driver/__init__.py +1 -3
  128. sqlspec/driver/_async.py +183 -160
  129. sqlspec/driver/_common.py +197 -109
  130. sqlspec/driver/_sync.py +189 -161
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +70 -7
  134. sqlspec/extensions/adk/__init__.py +53 -0
  135. sqlspec/extensions/adk/_types.py +51 -0
  136. sqlspec/extensions/adk/converters.py +172 -0
  137. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
  138. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  139. sqlspec/extensions/adk/service.py +181 -0
  140. sqlspec/extensions/adk/store.py +536 -0
  141. sqlspec/extensions/aiosql/adapter.py +69 -61
  142. sqlspec/extensions/fastapi/__init__.py +21 -0
  143. sqlspec/extensions/fastapi/extension.py +331 -0
  144. sqlspec/extensions/fastapi/providers.py +543 -0
  145. sqlspec/extensions/flask/__init__.py +36 -0
  146. sqlspec/extensions/flask/_state.py +71 -0
  147. sqlspec/extensions/flask/_utils.py +40 -0
  148. sqlspec/extensions/flask/extension.py +389 -0
  149. sqlspec/extensions/litestar/__init__.py +21 -4
  150. sqlspec/extensions/litestar/cli.py +54 -10
  151. sqlspec/extensions/litestar/config.py +56 -266
  152. sqlspec/extensions/litestar/handlers.py +46 -17
  153. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  154. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  155. sqlspec/extensions/litestar/plugin.py +349 -224
  156. sqlspec/extensions/litestar/providers.py +25 -25
  157. sqlspec/extensions/litestar/store.py +265 -0
  158. sqlspec/extensions/starlette/__init__.py +10 -0
  159. sqlspec/extensions/starlette/_state.py +25 -0
  160. sqlspec/extensions/starlette/_utils.py +52 -0
  161. sqlspec/extensions/starlette/extension.py +254 -0
  162. sqlspec/extensions/starlette/middleware.py +154 -0
  163. sqlspec/loader.py +30 -49
  164. sqlspec/migrations/base.py +200 -76
  165. sqlspec/migrations/commands.py +591 -62
  166. sqlspec/migrations/context.py +6 -9
  167. sqlspec/migrations/fix.py +199 -0
  168. sqlspec/migrations/loaders.py +47 -19
  169. sqlspec/migrations/runner.py +241 -75
  170. sqlspec/migrations/tracker.py +237 -21
  171. sqlspec/migrations/utils.py +51 -3
  172. sqlspec/migrations/validation.py +177 -0
  173. sqlspec/protocols.py +106 -36
  174. sqlspec/storage/_utils.py +85 -0
  175. sqlspec/storage/backends/fsspec.py +133 -107
  176. sqlspec/storage/backends/local.py +78 -51
  177. sqlspec/storage/backends/obstore.py +276 -168
  178. sqlspec/storage/registry.py +75 -39
  179. sqlspec/typing.py +30 -84
  180. sqlspec/utils/__init__.py +25 -4
  181. sqlspec/utils/arrow_helpers.py +81 -0
  182. sqlspec/utils/config_resolver.py +6 -6
  183. sqlspec/utils/correlation.py +4 -5
  184. sqlspec/utils/data_transformation.py +3 -2
  185. sqlspec/utils/deprecation.py +9 -8
  186. sqlspec/utils/fixtures.py +4 -4
  187. sqlspec/utils/logging.py +46 -6
  188. sqlspec/utils/module_loader.py +205 -5
  189. sqlspec/utils/portal.py +311 -0
  190. sqlspec/utils/schema.py +288 -0
  191. sqlspec/utils/serializers.py +113 -4
  192. sqlspec/utils/sync_tools.py +36 -22
  193. sqlspec/utils/text.py +1 -2
  194. sqlspec/utils/type_guards.py +136 -20
  195. sqlspec/utils/version.py +433 -0
  196. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/METADATA +41 -22
  197. sqlspec-0.28.0.dist-info/RECORD +221 -0
  198. sqlspec/builder/mixins/__init__.py +0 -55
  199. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -253
  200. sqlspec/builder/mixins/_delete_operations.py +0 -50
  201. sqlspec/builder/mixins/_insert_operations.py +0 -282
  202. sqlspec/builder/mixins/_merge_operations.py +0 -698
  203. sqlspec/builder/mixins/_order_limit_operations.py +0 -145
  204. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  205. sqlspec/builder/mixins/_select_operations.py +0 -930
  206. sqlspec/builder/mixins/_update_operations.py +0 -199
  207. sqlspec/builder/mixins/_where_clause.py +0 -1298
  208. sqlspec-0.26.0.dist-info/RECORD +0 -157
  209. sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
  210. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/WHEEL +0 -0
  211. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/entry_points.txt +0 -0
  212. {sqlspec-0.26.0.dist-info → sqlspec-0.28.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, Optional, TypedDict, Union, cast
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, total=False):
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[Optional[str]]"
31
+ isolation_level: "NotRequired[str | None]"
27
32
  check_same_thread: NotRequired[bool]
28
- factory: "NotRequired[Optional[type[SqliteConnection]]]"
33
+ factory: "NotRequired[type[SqliteConnection] | None]"
29
34
  cached_statements: NotRequired[int]
30
35
  uri: NotRequired[bool]
31
36
 
32
37
 
33
- __all__ = ("SqliteConfig", "SqliteConnectionParams")
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: "Optional[Union[SqliteConnectionParams, dict[str, Any]]]" = None,
46
- pool_instance: "Optional[SqliteConnectionPool]" = None,
47
- migration_config: "Optional[dict[str, Any]]" = None,
48
- statement_config: "Optional[StatementConfig]" = None,
49
- driver_features: "Optional[dict[str, Any]]" = None,
50
- bind_key: "Optional[str]" = None,
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=driver_features or {},
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
- return SqliteConnectionPool(connection_parameters=config_dict, **self.pool_config)
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: "Optional[StatementConfig]" = None, **kwargs: "Any"
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(connection=connection, statement_config=statement_config or self.statement_config)
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, Callable, Optional, cast
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) -> "Optional[VersionInfo]":
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, Optional
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 SQLParsingError, SQLSpecError
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: Optional[sqlite3.Cursor] = None
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
- Catches SQLite-specific exceptions and converts them to appropriate SQLSpec exceptions.
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
- """Enter exception handling context.
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
- """Handle exceptions and convert to SQLSpec exceptions.
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
- exc_type: Exception type if an exception occurred
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
- SQLSpecError: For general database errors
119
- SQLParsingError: For SQL syntax or parsing errors
137
+ Specific SQLSpec exception based on error code
120
138
  """
121
- if exc_type is None:
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 issubclass(exc_type, sqlite3.IntegrityError):
125
- e = exc_val
126
- msg = f"SQLite integrity constraint violation: {e}"
127
- raise SQLSpecError(msg) from e
128
- if issubclass(exc_type, sqlite3.OperationalError):
129
- e = exc_val
130
- error_msg = str(e).lower()
131
- if "locked" in error_msg:
132
- raise
133
- if "syntax" in error_msg or "malformed" in error_msg:
134
- msg = f"SQLite SQL syntax error: {e}"
135
- raise SQLParsingError(msg) from e
136
- msg = f"SQLite operational error: {e}"
137
- raise SQLSpecError(msg) from e
138
- if issubclass(exc_type, sqlite3.DatabaseError):
139
- e = exc_val
140
- msg = f"SQLite database error: {e}"
141
- raise SQLSpecError(msg) from e
142
- if issubclass(exc_type, sqlite3.Error):
143
- e = exc_val
144
- msg = f"SQLite error: {e}"
145
- raise SQLSpecError(msg) from e
146
- if issubclass(exc_type, Exception):
147
- e = exc_val
148
- error_msg = str(e).lower()
149
- if "parse" in error_msg or "syntax" in error_msg:
150
- msg = f"SQL parsing failed: {e}"
151
- raise SQLParsingError(msg) from e
152
- msg = f"Unexpected database operation error: {e}"
153
- raise SQLSpecError(msg) from e
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: "Optional[StatementConfig]" = None,
170
- driver_features: "Optional[dict[str, Any]]" = None,
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: Optional[SyncDataDictionaryBase] = None
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") -> "Optional[SQLResult]":
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
@@ -0,0 +1,5 @@
1
+ """Litestar integration for SQLite adapter."""
2
+
3
+ from sqlspec.adapters.sqlite.litestar.store import SQLiteStore
4
+
5
+ __all__ = ("SQLiteStore",)