sqlspec 0.25.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.

Files changed (199) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +256 -24
  3. sqlspec/_typing.py +71 -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 +870 -0
  7. sqlspec/adapters/adbc/config.py +69 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +340 -0
  9. sqlspec/adapters/adbc/driver.py +266 -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 +153 -0
  13. sqlspec/adapters/aiosqlite/_types.py +1 -1
  14. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  15. sqlspec/adapters/aiosqlite/adk/store.py +527 -0
  16. sqlspec/adapters/aiosqlite/config.py +88 -15
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
  18. sqlspec/adapters/aiosqlite/driver.py +143 -40
  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 +2 -2
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +493 -0
  26. sqlspec/adapters/asyncmy/config.py +68 -23
  27. sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
  28. sqlspec/adapters/asyncmy/driver.py +313 -58
  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 +450 -0
  36. sqlspec/adapters/asyncpg/config.py +59 -35
  37. sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
  38. sqlspec/adapters/asyncpg/driver.py +170 -25
  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 +576 -0
  44. sqlspec/adapters/bigquery/config.py +27 -10
  45. sqlspec/adapters/bigquery/data_dictionary.py +149 -0
  46. sqlspec/adapters/bigquery/driver.py +368 -142
  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 +125 -0
  50. sqlspec/adapters/duckdb/_types.py +1 -1
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +553 -0
  53. sqlspec/adapters/duckdb/config.py +80 -20
  54. sqlspec/adapters/duckdb/data_dictionary.py +163 -0
  55. sqlspec/adapters/duckdb/driver.py +167 -45
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +4 -4
  59. sqlspec/adapters/duckdb/type_converter.py +133 -0
  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 +1745 -0
  64. sqlspec/adapters/oracledb/config.py +122 -32
  65. sqlspec/adapters/oracledb/data_dictionary.py +509 -0
  66. sqlspec/adapters/oracledb/driver.py +353 -91
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +767 -0
  69. sqlspec/adapters/oracledb/migrations.py +348 -73
  70. sqlspec/adapters/oracledb/type_converter.py +207 -0
  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 +482 -0
  75. sqlspec/adapters/psqlpy/config.py +46 -17
  76. sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
  77. sqlspec/adapters/psqlpy/driver.py +123 -209
  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 +102 -0
  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 +944 -0
  85. sqlspec/adapters/psycopg/config.py +69 -35
  86. sqlspec/adapters/psycopg/data_dictionary.py +331 -0
  87. sqlspec/adapters/psycopg/driver.py +238 -81
  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 +572 -0
  95. sqlspec/adapters/sqlite/config.py +87 -15
  96. sqlspec/adapters/sqlite/data_dictionary.py +149 -0
  97. sqlspec/adapters/sqlite/driver.py +137 -54
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +18 -9
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +162 -89
  104. sqlspec/builder/_column.py +62 -29
  105. sqlspec/builder/_ddl.py +180 -121
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +53 -94
  109. sqlspec/builder/_insert.py +32 -131
  110. sqlspec/builder/_join.py +375 -0
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +111 -17
  113. sqlspec/builder/_select.py +1457 -24
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +307 -194
  116. sqlspec/config.py +252 -67
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +17 -17
  119. sqlspec/core/compiler.py +62 -9
  120. sqlspec/core/filters.py +37 -37
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +83 -48
  123. sqlspec/core/result.py +102 -46
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +36 -30
  126. sqlspec/core/type_conversion.py +235 -0
  127. sqlspec/driver/__init__.py +7 -6
  128. sqlspec/driver/_async.py +188 -151
  129. sqlspec/driver/_common.py +285 -80
  130. sqlspec/driver/_sync.py +188 -152
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +75 -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 +73 -53
  142. sqlspec/extensions/litestar/__init__.py +21 -4
  143. sqlspec/extensions/litestar/cli.py +54 -10
  144. sqlspec/extensions/litestar/config.py +59 -266
  145. sqlspec/extensions/litestar/handlers.py +46 -17
  146. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  147. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  148. sqlspec/extensions/litestar/plugin.py +324 -223
  149. sqlspec/extensions/litestar/providers.py +25 -25
  150. sqlspec/extensions/litestar/store.py +265 -0
  151. sqlspec/loader.py +30 -49
  152. sqlspec/migrations/__init__.py +4 -3
  153. sqlspec/migrations/base.py +302 -39
  154. sqlspec/migrations/commands.py +611 -144
  155. sqlspec/migrations/context.py +142 -0
  156. sqlspec/migrations/fix.py +199 -0
  157. sqlspec/migrations/loaders.py +68 -23
  158. sqlspec/migrations/runner.py +543 -107
  159. sqlspec/migrations/tracker.py +237 -21
  160. sqlspec/migrations/utils.py +51 -3
  161. sqlspec/migrations/validation.py +177 -0
  162. sqlspec/protocols.py +66 -36
  163. sqlspec/storage/_utils.py +98 -0
  164. sqlspec/storage/backends/fsspec.py +134 -106
  165. sqlspec/storage/backends/local.py +78 -51
  166. sqlspec/storage/backends/obstore.py +278 -162
  167. sqlspec/storage/registry.py +75 -39
  168. sqlspec/typing.py +16 -84
  169. sqlspec/utils/config_resolver.py +153 -0
  170. sqlspec/utils/correlation.py +4 -5
  171. sqlspec/utils/data_transformation.py +3 -2
  172. sqlspec/utils/deprecation.py +9 -8
  173. sqlspec/utils/fixtures.py +4 -4
  174. sqlspec/utils/logging.py +46 -6
  175. sqlspec/utils/module_loader.py +2 -2
  176. sqlspec/utils/schema.py +288 -0
  177. sqlspec/utils/serializers.py +50 -2
  178. sqlspec/utils/sync_tools.py +21 -17
  179. sqlspec/utils/text.py +1 -2
  180. sqlspec/utils/type_guards.py +111 -20
  181. sqlspec/utils/version.py +433 -0
  182. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
  183. sqlspec-0.27.0.dist-info/RECORD +207 -0
  184. sqlspec/builder/mixins/__init__.py +0 -55
  185. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
  186. sqlspec/builder/mixins/_delete_operations.py +0 -50
  187. sqlspec/builder/mixins/_insert_operations.py +0 -282
  188. sqlspec/builder/mixins/_join_operations.py +0 -389
  189. sqlspec/builder/mixins/_merge_operations.py +0 -592
  190. sqlspec/builder/mixins/_order_limit_operations.py +0 -152
  191. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  192. sqlspec/builder/mixins/_select_operations.py +0 -936
  193. sqlspec/builder/mixins/_update_operations.py +0 -218
  194. sqlspec/builder/mixins/_where_clause.py +0 -1304
  195. sqlspec-0.25.0.dist-info/RECORD +0 -139
  196. sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
  197. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
  198. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
  199. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,60 +1,216 @@
1
- from typing import TYPE_CHECKING, Any, Optional, Union, cast, overload
1
+ from dataclasses import dataclass, field
2
+ from typing import TYPE_CHECKING, Any, Literal, NoReturn, cast, overload
2
3
 
3
4
  from litestar.di import Provide
4
5
  from litestar.plugins import CLIPlugin, InitPluginProtocol
5
6
 
6
- from sqlspec.base import SQLSpec as SQLSpecBase
7
- from sqlspec.config import AsyncConfigT, DatabaseConfigProtocol, DriverT, SyncConfigT
7
+ from sqlspec.base import SQLSpec
8
+ from sqlspec.config import (
9
+ AsyncConfigT,
10
+ AsyncDatabaseConfig,
11
+ DatabaseConfigProtocol,
12
+ DriverT,
13
+ NoPoolAsyncConfig,
14
+ NoPoolSyncConfig,
15
+ SyncConfigT,
16
+ SyncDatabaseConfig,
17
+ )
8
18
  from sqlspec.exceptions import ImproperConfigurationError
9
- from sqlspec.extensions.litestar.config import AsyncDatabaseConfig, DatabaseConfig, SyncDatabaseConfig
19
+ from sqlspec.extensions.litestar._utils import get_sqlspec_scope_state, set_sqlspec_scope_state
20
+ from sqlspec.extensions.litestar.handlers import (
21
+ autocommit_handler_maker,
22
+ connection_provider_maker,
23
+ lifespan_handler_maker,
24
+ manual_handler_maker,
25
+ pool_provider_maker,
26
+ session_provider_maker,
27
+ )
10
28
  from sqlspec.typing import ConnectionT, PoolT
11
29
  from sqlspec.utils.logging import get_logger
12
30
 
13
31
  if TYPE_CHECKING:
14
- from click import Group
32
+ from collections.abc import AsyncGenerator, Callable
33
+ from contextlib import AbstractAsyncContextManager
34
+
35
+ from litestar import Litestar
15
36
  from litestar.config.app import AppConfig
16
37
  from litestar.datastructures.state import State
17
- from litestar.types import Scope
38
+ from litestar.types import BeforeMessageSendHookHandler, Scope
39
+ from rich_click import Group
18
40
 
19
41
  from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
20
42
  from sqlspec.loader import SQLFileLoader
21
43
 
22
44
  logger = get_logger("extensions.litestar")
23
45
 
46
+ CommitMode = Literal["manual", "autocommit", "autocommit_include_redirect"]
47
+ DEFAULT_COMMIT_MODE: CommitMode = "manual"
48
+ DEFAULT_CONNECTION_KEY = "db_connection"
49
+ DEFAULT_POOL_KEY = "db_pool"
50
+ DEFAULT_SESSION_KEY = "db_session"
51
+
52
+ __all__ = (
53
+ "DEFAULT_COMMIT_MODE",
54
+ "DEFAULT_CONNECTION_KEY",
55
+ "DEFAULT_POOL_KEY",
56
+ "DEFAULT_SESSION_KEY",
57
+ "CommitMode",
58
+ "SQLSpecPlugin",
59
+ )
60
+
61
+
62
+ @dataclass
63
+ class _PluginConfigState:
64
+ """Internal state for each database configuration."""
65
+
66
+ config: "DatabaseConfigProtocol[Any, Any, Any]"
67
+ connection_key: str
68
+ pool_key: str
69
+ session_key: str
70
+ commit_mode: CommitMode
71
+ extra_commit_statuses: "set[int] | None"
72
+ extra_rollback_statuses: "set[int] | None"
73
+ enable_correlation_middleware: bool
74
+ connection_provider: "Callable[[State, Scope], AsyncGenerator[Any, None]]" = field(init=False)
75
+ pool_provider: "Callable[[State, Scope], Any]" = field(init=False)
76
+ session_provider: "Callable[..., AsyncGenerator[Any, None]]" = field(init=False)
77
+ before_send_handler: "BeforeMessageSendHookHandler" = field(init=False)
78
+ lifespan_handler: "Callable[[Litestar], AbstractAsyncContextManager[None]]" = field(init=False)
79
+ annotation: "type[DatabaseConfigProtocol[Any, Any, Any]]" = field(init=False)
80
+
81
+
82
+ class SQLSpecPlugin(InitPluginProtocol, CLIPlugin):
83
+ """Litestar plugin for SQLSpec database integration.
84
+
85
+ Session Table Migrations:
86
+ The Litestar extension includes migrations for creating session storage tables.
87
+ To include these migrations in your database migration workflow, add 'litestar'
88
+ to the include_extensions list in your migration configuration.
89
+
90
+ Example:
91
+ config = AsyncpgConfig(
92
+ pool_config={"dsn": "postgresql://localhost/db"},
93
+ extension_config={
94
+ "litestar": {
95
+ "session_table": "custom_sessions" # Optional custom table name
96
+ }
97
+ },
98
+ migration_config={
99
+ "script_location": "migrations",
100
+ "include_extensions": ["litestar"], # Simple string list only
101
+ }
102
+ )
24
103
 
25
- class SQLSpec(SQLSpecBase, InitPluginProtocol, CLIPlugin):
26
- """Litestar plugin for SQLSpec database integration."""
104
+ The session table migration will automatically use the appropriate column types
105
+ for your database dialect (JSONB for PostgreSQL, JSON for MySQL, TEXT for SQLite).
27
106
 
28
- __slots__ = ("_plugin_configs",)
107
+ Extension migrations use the ext_litestar_ prefix (e.g., ext_litestar_0001) to
108
+ prevent version conflicts with application migrations.
109
+ """
29
110
 
30
- def __init__(
31
- self,
32
- config: Union["SyncConfigT", "AsyncConfigT", "DatabaseConfig", list["DatabaseConfig"]],
33
- *,
34
- loader: "Optional[SQLFileLoader]" = None,
35
- ) -> None:
111
+ __slots__ = ("_plugin_configs", "_sqlspec")
112
+
113
+ def __init__(self, sqlspec: SQLSpec, *, loader: "SQLFileLoader | None" = None) -> None:
36
114
  """Initialize SQLSpec plugin.
37
115
 
38
116
  Args:
39
- config: Database configuration for SQLSpec plugin.
40
- loader: Optional SQL file loader instance.
117
+ sqlspec: Pre-configured SQLSpec instance with registered database configs.
118
+ loader: Optional SQL file loader instance (SQLSpec may already have one).
41
119
  """
42
- super().__init__(loader=loader)
43
- if isinstance(config, DatabaseConfigProtocol):
44
- self._plugin_configs: list[DatabaseConfig] = [DatabaseConfig(config=config)] # pyright: ignore
45
- elif isinstance(config, DatabaseConfig):
46
- self._plugin_configs = [config]
120
+ self._sqlspec = sqlspec
121
+
122
+ self._plugin_configs: list[_PluginConfigState] = []
123
+ for cfg in self._sqlspec.configs.values():
124
+ config_union = cast(
125
+ "SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]",
126
+ cfg,
127
+ )
128
+ settings = self._extract_litestar_settings(config_union)
129
+ state = self._create_config_state(config_union, settings)
130
+ self._plugin_configs.append(state)
131
+
132
+ def _extract_litestar_settings(
133
+ self,
134
+ config: "SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]",
135
+ ) -> "dict[str, Any]":
136
+ """Extract Litestar settings from config.extension_config."""
137
+ litestar_config = config.extension_config.get("litestar", {})
138
+
139
+ connection_key = litestar_config.get("connection_key", DEFAULT_CONNECTION_KEY)
140
+ pool_key = litestar_config.get("pool_key", DEFAULT_POOL_KEY)
141
+ session_key = litestar_config.get("session_key", DEFAULT_SESSION_KEY)
142
+ commit_mode = litestar_config.get("commit_mode", DEFAULT_COMMIT_MODE)
143
+
144
+ if not config.supports_connection_pooling and pool_key == DEFAULT_POOL_KEY:
145
+ pool_key = f"_{DEFAULT_POOL_KEY}_{id(config)}"
146
+
147
+ return {
148
+ "connection_key": connection_key,
149
+ "pool_key": pool_key,
150
+ "session_key": session_key,
151
+ "commit_mode": commit_mode,
152
+ "extra_commit_statuses": litestar_config.get("extra_commit_statuses"),
153
+ "extra_rollback_statuses": litestar_config.get("extra_rollback_statuses"),
154
+ "enable_correlation_middleware": litestar_config.get("enable_correlation_middleware", True),
155
+ }
156
+
157
+ def _create_config_state(
158
+ self,
159
+ config: "SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]",
160
+ settings: "dict[str, Any]",
161
+ ) -> _PluginConfigState:
162
+ """Create plugin state with handlers for the given configuration."""
163
+ state = _PluginConfigState(
164
+ config=config,
165
+ connection_key=settings["connection_key"],
166
+ pool_key=settings["pool_key"],
167
+ session_key=settings["session_key"],
168
+ commit_mode=settings["commit_mode"],
169
+ extra_commit_statuses=settings.get("extra_commit_statuses"),
170
+ extra_rollback_statuses=settings.get("extra_rollback_statuses"),
171
+ enable_correlation_middleware=settings["enable_correlation_middleware"],
172
+ )
173
+
174
+ self._setup_handlers(state)
175
+ return state
176
+
177
+ def _setup_handlers(self, state: _PluginConfigState) -> None:
178
+ """Setup handlers for the plugin state."""
179
+ connection_key = state.connection_key
180
+ pool_key = state.pool_key
181
+ commit_mode = state.commit_mode
182
+ config = state.config
183
+ is_async = config.is_async
184
+
185
+ state.connection_provider = connection_provider_maker(config, pool_key, connection_key)
186
+ state.pool_provider = pool_provider_maker(config, pool_key)
187
+ state.session_provider = session_provider_maker(config, connection_key)
188
+ state.lifespan_handler = lifespan_handler_maker(config, pool_key)
189
+
190
+ if commit_mode == "manual":
191
+ state.before_send_handler = manual_handler_maker(connection_key, is_async)
47
192
  else:
48
- self._plugin_configs = config
193
+ commit_on_redirect = commit_mode == "autocommit_include_redirect"
194
+ state.before_send_handler = autocommit_handler_maker(
195
+ connection_key, is_async, commit_on_redirect, state.extra_commit_statuses, state.extra_rollback_statuses
196
+ )
49
197
 
50
198
  @property
51
- def config(self) -> "list[DatabaseConfig]": # pyright: ignore[reportInvalidTypeVarUse]
52
- """Return the plugin configuration.
199
+ def config(
200
+ self,
201
+ ) -> "list[SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]]":
202
+ """Return the plugin configurations.
53
203
 
54
204
  Returns:
55
205
  List of database configurations.
56
206
  """
57
- return self._plugin_configs
207
+ return [
208
+ cast(
209
+ "SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]",
210
+ state.config,
211
+ )
212
+ for state in self._plugin_configs
213
+ ]
58
214
 
59
215
  def on_cli_init(self, cli: "Group") -> None:
60
216
  """Configure CLI commands for SQLSpec database operations.
@@ -75,34 +231,31 @@ class SQLSpec(SQLSpecBase, InitPluginProtocol, CLIPlugin):
75
231
  Returns:
76
232
  The updated application configuration instance.
77
233
  """
78
-
79
234
  self._validate_dependency_keys()
80
235
 
81
236
  def store_sqlspec_in_state() -> None:
82
237
  app_config.state.sqlspec = self
83
238
 
84
239
  app_config.on_startup.append(store_sqlspec_in_state)
85
- app_config.signature_types.extend(
86
- [SQLSpec, ConnectionT, PoolT, DriverT, DatabaseConfig, DatabaseConfigProtocol, SyncConfigT, AsyncConfigT]
87
- )
240
+ app_config.signature_types.extend([SQLSpec, DatabaseConfigProtocol, SyncConfigT, AsyncConfigT])
88
241
 
89
- signature_namespace = {}
242
+ signature_namespace = {"ConnectionT": ConnectionT, "PoolT": PoolT, "DriverT": DriverT}
90
243
 
91
- for c in self._plugin_configs:
92
- c.annotation = self.add_config(c.config)
93
- app_config.signature_types.append(c.annotation)
94
- app_config.signature_types.append(c.config.connection_type) # type: ignore[union-attr]
95
- app_config.signature_types.append(c.config.driver_type) # type: ignore[union-attr]
244
+ for state in self._plugin_configs:
245
+ state.annotation = type(state.config)
246
+ app_config.signature_types.append(state.annotation)
247
+ app_config.signature_types.append(state.config.connection_type)
248
+ app_config.signature_types.append(state.config.driver_type)
96
249
 
97
- signature_namespace.update(c.config.get_signature_namespace()) # type: ignore[union-attr]
250
+ signature_namespace.update(state.config.get_signature_namespace()) # type: ignore[arg-type]
98
251
 
99
- app_config.before_send.append(c.before_send_handler)
100
- app_config.lifespan.append(c.lifespan_handler) # pyright: ignore[reportUnknownMemberType]
252
+ app_config.before_send.append(state.before_send_handler)
253
+ app_config.lifespan.append(state.lifespan_handler)
101
254
  app_config.dependencies.update(
102
255
  {
103
- c.connection_key: Provide(c.connection_provider),
104
- c.pool_key: Provide(c.pool_provider),
105
- c.session_key: Provide(c.session_provider),
256
+ state.connection_key: Provide(state.connection_provider),
257
+ state.pool_key: Provide(state.pool_provider),
258
+ state.session_key: Provide(state.session_provider),
106
259
  }
107
260
  )
108
261
 
@@ -111,21 +264,30 @@ class SQLSpec(SQLSpecBase, InitPluginProtocol, CLIPlugin):
111
264
 
112
265
  return app_config
113
266
 
114
- def get_annotations(self) -> "list[type[Union[SyncConfigT, AsyncConfigT]]]": # pyright: ignore[reportInvalidTypeVarUse]
267
+ def get_annotations(
268
+ self,
269
+ ) -> "list[type[SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]]]":
115
270
  """Return the list of annotations.
116
271
 
117
272
  Returns:
118
273
  List of annotations.
119
274
  """
120
- return [c.annotation for c in self.config]
275
+ return [
276
+ cast(
277
+ "type[SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]]",
278
+ state.annotation,
279
+ )
280
+ for state in self._plugin_configs
281
+ ]
121
282
 
122
283
  def get_annotation(
123
- self, key: "Union[str, SyncConfigT, AsyncConfigT, type[Union[SyncConfigT, AsyncConfigT]]]"
124
- ) -> "type[Union[SyncConfigT, AsyncConfigT]]":
284
+ self,
285
+ key: "str | SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any] | type[SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]]",
286
+ ) -> "type[SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]]":
125
287
  """Return the annotation for the given configuration.
126
288
 
127
289
  Args:
128
- key: The configuration instance or key to lookup
290
+ key: The configuration instance or key to lookup.
129
291
 
130
292
  Raises:
131
293
  KeyError: If no configuration is found for the given key.
@@ -133,51 +295,38 @@ class SQLSpec(SQLSpecBase, InitPluginProtocol, CLIPlugin):
133
295
  Returns:
134
296
  The annotation for the configuration.
135
297
  """
136
- for c in self.config:
137
- # Check annotation only if it's been set (during on_app_init)
138
- annotation_match = hasattr(c, "annotation") and key == c.annotation
139
- if key == c.config or annotation_match or key in {c.connection_key, c.pool_key}:
140
- if not hasattr(c, "annotation"):
141
- msg = (
142
- "Annotation not set for configuration. Ensure the plugin has been initialized with on_app_init."
143
- )
144
- raise AttributeError(msg)
145
- return c.annotation
298
+ for state in self._plugin_configs:
299
+ if key in {state.config, state.annotation} or key in {state.connection_key, state.pool_key}:
300
+ return cast(
301
+ "type[SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]]",
302
+ state.annotation,
303
+ )
304
+
146
305
  msg = f"No configuration found for {key}"
147
306
  raise KeyError(msg)
148
307
 
149
308
  @overload
150
- def get_config(self, name: "type[SyncConfigT]") -> "SyncConfigT": ...
151
-
152
- @overload
153
- def get_config(self, name: "type[AsyncConfigT]") -> "AsyncConfigT": ...
154
-
155
- @overload
156
- def get_config(self, name: str) -> "DatabaseConfig": ...
309
+ def get_config(
310
+ self, name: "type[SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any]]"
311
+ ) -> "SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any]": ...
157
312
 
158
313
  @overload
159
- def get_config(self, name: "type[SyncDatabaseConfig]") -> "SyncDatabaseConfig": ...
314
+ def get_config(
315
+ self, name: "type[AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]]"
316
+ ) -> "AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]": ...
160
317
 
161
318
  @overload
162
- def get_config(self, name: "type[AsyncDatabaseConfig]") -> "AsyncDatabaseConfig": ...
163
-
164
319
  def get_config(
165
- self, name: "Union[type[DatabaseConfigProtocol[ConnectionT, PoolT, DriverT]], str, Any]"
166
- ) -> "Union[DatabaseConfigProtocol[ConnectionT, PoolT, DriverT], DatabaseConfig, SyncDatabaseConfig, AsyncDatabaseConfig]":
167
- """Get a configuration instance by name, supporting both base behavior and Litestar extensions.
320
+ self, name: str
321
+ ) -> "SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]": ...
168
322
 
169
- This method extends the base get_config to support Litestar-specific lookup patterns
170
- while maintaining compatibility with the base class signature. It supports lookup by
171
- connection key, pool key, session key, config instance, or annotation type.
323
+ def get_config(
324
+ self, name: "type[DatabaseConfigProtocol[Any, Any, Any]] | str | Any"
325
+ ) -> "DatabaseConfigProtocol[Any, Any, Any]":
326
+ """Get a configuration instance by name.
172
327
 
173
328
  Args:
174
- name: The configuration identifier - can be:
175
- - Type annotation (base class behavior)
176
- - connection_key (e.g., "auth_db_connection")
177
- - pool_key (e.g., "analytics_db_pool")
178
- - session_key (e.g., "reporting_db_session")
179
- - config instance
180
- - annotation type
329
+ name: The configuration identifier.
181
330
 
182
331
  Raises:
183
332
  KeyError: If no configuration is found for the given name.
@@ -185,202 +334,154 @@ class SQLSpec(SQLSpecBase, InitPluginProtocol, CLIPlugin):
185
334
  Returns:
186
335
  The configuration instance for the specified name.
187
336
  """
188
- # First try base class behavior for type-based lookup
189
- # Only call super() if name matches the expected base class types
190
- if not isinstance(name, str):
191
- try:
192
- return super().get_config(name) # type: ignore[no-any-return]
193
- except (KeyError, AttributeError):
194
- # Fall back to Litestar-specific lookup patterns
195
- pass
196
-
197
- # Litestar-specific lookups by string keys
198
337
  if isinstance(name, str):
199
- for c in self.config:
200
- if name in {c.connection_key, c.pool_key, c.session_key}:
201
- return c # Return the DatabaseConfig wrapper for string lookups
338
+ for state in self._plugin_configs:
339
+ if name in {state.connection_key, state.pool_key, state.session_key}:
340
+ return cast("DatabaseConfigProtocol[Any, Any, Any]", state.config) # type: ignore[redundant-cast]
202
341
 
203
- # Lookup by config instance or annotation
204
- for c in self.config:
205
- annotation_match = hasattr(c, "annotation") and name == c.annotation
206
- if name == c.config or annotation_match:
207
- return c.config # Return the underlying config for type-based lookups
342
+ for state in self._plugin_configs:
343
+ if name in {state.config, state.annotation}:
344
+ return cast("DatabaseConfigProtocol[Any, Any, Any]", state.config) # type: ignore[redundant-cast]
208
345
 
209
346
  msg = f"No database configuration found for name '{name}'. Available keys: {self._get_available_keys()}"
210
347
  raise KeyError(msg)
211
348
 
212
349
  def provide_request_session(
213
- self,
214
- key: "Union[str, SyncConfigT, AsyncConfigT, type[Union[SyncConfigT, AsyncConfigT]]]",
215
- state: "State",
216
- scope: "Scope",
217
- ) -> "Union[SyncDriverAdapterBase, AsyncDriverAdapterBase]":
350
+ self, key: "str | SyncConfigT | AsyncConfigT | type[SyncConfigT | AsyncConfigT]", state: "State", scope: "Scope"
351
+ ) -> "SyncDriverAdapterBase | AsyncDriverAdapterBase":
218
352
  """Provide a database session for the specified configuration key from request scope.
219
353
 
220
- This is a convenience method that combines get_config and get_request_session
221
- into a single call, similar to Advanced Alchemy's provide_session pattern.
222
-
223
354
  Args:
224
- key: The configuration identifier (same as get_config)
225
- state: The Litestar application State object
226
- scope: The ASGI scope containing the request context
355
+ key: The configuration identifier (same as get_config).
356
+ state: The Litestar application State object.
357
+ scope: The ASGI scope containing the request context.
227
358
 
228
359
  Returns:
229
- A driver session instance for the specified database configuration
230
-
231
- Example:
232
- >>> sqlspec_plugin = connection.app.state.sqlspec
233
- >>> # Direct session access by key
234
- >>> auth_session = sqlspec_plugin.provide_request_session(
235
- ... "auth_db", state, scope
236
- ... )
237
- >>> analytics_session = sqlspec_plugin.provide_request_session(
238
- ... "analytics_db", state, scope
239
- ... )
360
+ A driver session instance for the specified database configuration.
240
361
  """
241
- # Get DatabaseConfig wrapper for Litestar methods
242
- db_config = self._get_database_config(key)
243
- return db_config.get_request_session(state, scope)
362
+ plugin_state = self._get_plugin_state(key)
363
+ session_scope_key = f"{plugin_state.session_key}_instance"
364
+
365
+ session = get_sqlspec_scope_state(scope, session_scope_key)
366
+ if session is not None:
367
+ return cast("SyncDriverAdapterBase | AsyncDriverAdapterBase", session)
368
+
369
+ connection = get_sqlspec_scope_state(scope, plugin_state.connection_key)
370
+ if connection is None:
371
+ self._raise_missing_connection(plugin_state.connection_key)
372
+
373
+ session = plugin_state.config.driver_type(
374
+ connection=connection,
375
+ statement_config=plugin_state.config.statement_config,
376
+ driver_features=plugin_state.config.driver_features,
377
+ )
378
+ set_sqlspec_scope_state(scope, session_scope_key, session)
379
+
380
+ return cast("SyncDriverAdapterBase | AsyncDriverAdapterBase", session)
244
381
 
245
382
  def provide_sync_request_session(
246
- self, key: "Union[str, SyncConfigT, type[SyncConfigT]]", state: "State", scope: "Scope"
383
+ self, key: "str | SyncConfigT | type[SyncConfigT]", state: "State", scope: "Scope"
247
384
  ) -> "SyncDriverAdapterBase":
248
385
  """Provide a sync database session for the specified configuration key from request scope.
249
386
 
250
- This method provides better type hints for sync database sessions, ensuring the returned
251
- session is properly typed as SyncDriverAdapterBase for better IDE support and type safety.
252
-
253
387
  Args:
254
- key: The sync configuration identifier
255
- state: The Litestar application State object
256
- scope: The ASGI scope containing the request context
388
+ key: The sync configuration identifier.
389
+ state: The Litestar application State object.
390
+ scope: The ASGI scope containing the request context.
257
391
 
258
392
  Returns:
259
- A sync driver session instance for the specified database configuration
260
-
261
- Example:
262
- >>> sqlspec_plugin = connection.app.state.sqlspec
263
- >>> auth_session = sqlspec_plugin.provide_sync_request_session(
264
- ... "auth_db", state, scope
265
- ... )
266
- >>> # auth_session is now correctly typed as SyncDriverAdapterBase
393
+ A sync driver session instance for the specified database configuration.
267
394
  """
268
- # Get DatabaseConfig wrapper for Litestar methods
269
- db_config = self._get_database_config(key)
270
- session = db_config.get_request_session(state, scope)
395
+ session = self.provide_request_session(key, state, scope)
271
396
  return cast("SyncDriverAdapterBase", session)
272
397
 
273
398
  def provide_async_request_session(
274
- self, key: "Union[str, AsyncConfigT, type[AsyncConfigT]]", state: "State", scope: "Scope"
399
+ self, key: "str | AsyncConfigT | type[AsyncConfigT]", state: "State", scope: "Scope"
275
400
  ) -> "AsyncDriverAdapterBase":
276
401
  """Provide an async database session for the specified configuration key from request scope.
277
402
 
278
- This method provides better type hints for async database sessions, ensuring the returned
279
- session is properly typed as AsyncDriverAdapterBase for better IDE support and type safety.
280
-
281
403
  Args:
282
- key: The async configuration identifier
283
- state: The Litestar application State object
284
- scope: The ASGI scope containing the request context
404
+ key: The async configuration identifier.
405
+ state: The Litestar application State object.
406
+ scope: The ASGI scope containing the request context.
285
407
 
286
408
  Returns:
287
- An async driver session instance for the specified database configuration
288
-
289
- Example:
290
- >>> sqlspec_plugin = connection.app.state.sqlspec
291
- >>> auth_session = sqlspec_plugin.provide_async_request_session(
292
- ... "auth_db", state, scope
293
- ... )
294
- >>> # auth_session is now correctly typed as AsyncDriverAdapterBase
409
+ An async driver session instance for the specified database configuration.
295
410
  """
296
- # Get DatabaseConfig wrapper for Litestar methods
297
- db_config = self._get_database_config(key)
298
- session = db_config.get_request_session(state, scope)
411
+ session = self.provide_request_session(key, state, scope)
299
412
  return cast("AsyncDriverAdapterBase", session)
300
413
 
301
414
  def provide_request_connection(
302
- self,
303
- key: "Union[str, SyncConfigT, AsyncConfigT, type[Union[SyncConfigT, AsyncConfigT]]]",
304
- state: "State",
305
- scope: "Scope",
306
- ) -> Any:
415
+ self, key: "str | SyncConfigT | AsyncConfigT | type[SyncConfigT | AsyncConfigT]", state: "State", scope: "Scope"
416
+ ) -> "Any":
307
417
  """Provide a database connection for the specified configuration key from request scope.
308
418
 
309
- This is a convenience method that combines get_config and get_request_connection
310
- into a single call.
311
-
312
419
  Args:
313
- key: The configuration identifier (same as get_config)
314
- state: The Litestar application State object
315
- scope: The ASGI scope containing the request context
420
+ key: The configuration identifier (same as get_config).
421
+ state: The Litestar application State object.
422
+ scope: The ASGI scope containing the request context.
316
423
 
317
424
  Returns:
318
- A database connection instance for the specified database configuration
319
-
320
- Example:
321
- >>> sqlspec_plugin = connection.app.state.sqlspec
322
- >>> # Direct connection access by key
323
- >>> auth_conn = sqlspec_plugin.provide_request_connection(
324
- ... "auth_db", state, scope
325
- ... )
326
- >>> analytics_conn = sqlspec_plugin.provide_request_connection(
327
- ... "analytics_db", state, scope
328
- ... )
425
+ A database connection instance for the specified database configuration.
329
426
  """
330
- # Get DatabaseConfig wrapper for Litestar methods
331
- db_config = self._get_database_config(key)
332
- return db_config.get_request_connection(state, scope)
333
-
334
- def _get_database_config(
335
- self, key: "Union[str, SyncConfigT, AsyncConfigT, type[Union[SyncConfigT, AsyncConfigT]]]"
336
- ) -> DatabaseConfig:
337
- """Get a DatabaseConfig wrapper instance by name.
338
-
339
- This is used internally by provide_request_session and provide_request_connection
340
- to get the DatabaseConfig wrapper that has the request session methods.
341
-
342
- Args:
343
- key: The configuration identifier
427
+ plugin_state = self._get_plugin_state(key)
428
+ connection = get_sqlspec_scope_state(scope, plugin_state.connection_key)
429
+ if connection is None:
430
+ self._raise_missing_connection(plugin_state.connection_key)
344
431
 
345
- Returns:
346
- The DatabaseConfig wrapper instance
432
+ return connection
347
433
 
348
- Raises:
349
- KeyError: If no configuration is found for the given key
350
- """
351
- # For string keys, lookup by connection/pool/session keys
434
+ def _get_plugin_state(
435
+ self, key: "str | SyncConfigT | AsyncConfigT | type[SyncConfigT | AsyncConfigT]"
436
+ ) -> _PluginConfigState:
437
+ """Get plugin state for a configuration by key."""
352
438
  if isinstance(key, str):
353
- for c in self.config:
354
- if key in {c.connection_key, c.pool_key, c.session_key}:
355
- return c
439
+ for state in self._plugin_configs:
440
+ if key in {state.connection_key, state.pool_key, state.session_key}:
441
+ return state
356
442
 
357
- # For other keys, lookup by config instance or annotation
358
- for c in self.config:
359
- annotation_match = hasattr(c, "annotation") and key == c.annotation
360
- if key == c.config or annotation_match:
361
- return c
443
+ for state in self._plugin_configs:
444
+ if key in {state.config, state.annotation}:
445
+ return state
362
446
 
363
- msg = f"No database configuration found for name '{key}'. Available keys: {self._get_available_keys()}"
364
- raise KeyError(msg)
447
+ self._raise_config_not_found(key)
448
+ return None
365
449
 
366
450
  def _get_available_keys(self) -> "list[str]":
367
451
  """Get a list of all available configuration keys for error messages."""
368
452
  keys = []
369
- for c in self.config:
370
- keys.extend([c.connection_key, c.pool_key, c.session_key])
453
+ for state in self._plugin_configs:
454
+ keys.extend([state.connection_key, state.pool_key, state.session_key])
371
455
  return keys
372
456
 
373
457
  def _validate_dependency_keys(self) -> None:
374
- """Validate that connection and pool keys are unique across configurations.
458
+ """Validate that connection and pool keys are unique across configurations."""
459
+ connection_keys = [state.connection_key for state in self._plugin_configs]
460
+ pool_keys = [state.pool_key for state in self._plugin_configs]
375
461
 
376
- Raises:
377
- ImproperConfigurationError: If connection keys or pool keys are not unique.
378
- """
379
- connection_keys = [c.connection_key for c in self.config]
380
- pool_keys = [c.pool_key for c in self.config]
381
462
  if len(set(connection_keys)) != len(connection_keys):
382
- msg = "When using multiple database configuration, each configuration must have a unique `connection_key`."
383
- raise ImproperConfigurationError(detail=msg)
463
+ self._raise_duplicate_connection_keys()
464
+
384
465
  if len(set(pool_keys)) != len(pool_keys):
385
- msg = "When using multiple database configuration, each configuration must have a unique `pool_key`."
386
- raise ImproperConfigurationError(detail=msg)
466
+ self._raise_duplicate_pool_keys()
467
+
468
+ def _raise_missing_connection(self, connection_key: str) -> None:
469
+ """Raise error when connection is not found in scope."""
470
+ msg = f"No database connection found in scope for key '{connection_key}'. "
471
+ msg += "Ensure the connection dependency is properly configured and available."
472
+ raise ImproperConfigurationError(detail=msg)
473
+
474
+ def _raise_config_not_found(self, key: Any) -> NoReturn:
475
+ """Raise error when configuration is not found."""
476
+ msg = f"No database configuration found for name '{key}'. Available keys: {self._get_available_keys()}"
477
+ raise KeyError(msg)
478
+
479
+ def _raise_duplicate_connection_keys(self) -> None:
480
+ """Raise error when connection keys are not unique."""
481
+ msg = "When using multiple database configuration, each configuration must have a unique `connection_key`."
482
+ raise ImproperConfigurationError(detail=msg)
483
+
484
+ def _raise_duplicate_pool_keys(self) -> None:
485
+ """Raise error when pool keys are not unique."""
486
+ msg = "When using multiple database configuration, each configuration must have a unique `pool_key`."
487
+ raise ImproperConfigurationError(detail=msg)