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,60 +1,221 @@
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
10
- from sqlspec.typing import ConnectionT, PoolT
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
+ )
28
+ from sqlspec.typing import NUMPY_INSTALLED, ConnectionT, PoolT, SchemaT
11
29
  from sqlspec.utils.logging import get_logger
30
+ from sqlspec.utils.serializers import numpy_array_dec_hook, numpy_array_enc_hook, numpy_array_predicate
12
31
 
13
32
  if TYPE_CHECKING:
14
- from click import Group
33
+ from collections.abc import AsyncGenerator, Callable
34
+ from contextlib import AbstractAsyncContextManager
35
+
36
+ from litestar import Litestar
15
37
  from litestar.config.app import AppConfig
16
38
  from litestar.datastructures.state import State
17
- from litestar.types import Scope
39
+ from litestar.types import BeforeMessageSendHookHandler, Scope
40
+ from rich_click import Group
18
41
 
19
42
  from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
20
43
  from sqlspec.loader import SQLFileLoader
21
44
 
22
45
  logger = get_logger("extensions.litestar")
23
46
 
47
+ CommitMode = Literal["manual", "autocommit", "autocommit_include_redirect"]
48
+ DEFAULT_COMMIT_MODE: CommitMode = "manual"
49
+ DEFAULT_CONNECTION_KEY = "db_connection"
50
+ DEFAULT_POOL_KEY = "db_pool"
51
+ DEFAULT_SESSION_KEY = "db_session"
52
+
53
+ __all__ = (
54
+ "DEFAULT_COMMIT_MODE",
55
+ "DEFAULT_CONNECTION_KEY",
56
+ "DEFAULT_POOL_KEY",
57
+ "DEFAULT_SESSION_KEY",
58
+ "CommitMode",
59
+ "SQLSpecPlugin",
60
+ )
61
+
62
+
63
+ @dataclass
64
+ class _PluginConfigState:
65
+ """Internal state for each database configuration."""
66
+
67
+ config: "DatabaseConfigProtocol[Any, Any, Any]"
68
+ connection_key: str
69
+ pool_key: str
70
+ session_key: str
71
+ commit_mode: CommitMode
72
+ extra_commit_statuses: "set[int] | None"
73
+ extra_rollback_statuses: "set[int] | None"
74
+ enable_correlation_middleware: bool
75
+ connection_provider: "Callable[[State, Scope], AsyncGenerator[Any, None]]" = field(init=False)
76
+ pool_provider: "Callable[[State, Scope], Any]" = field(init=False)
77
+ session_provider: "Callable[..., AsyncGenerator[Any, None]]" = field(init=False)
78
+ before_send_handler: "BeforeMessageSendHookHandler" = field(init=False)
79
+ lifespan_handler: "Callable[[Litestar], AbstractAsyncContextManager[None]]" = field(init=False)
80
+ annotation: "type[DatabaseConfigProtocol[Any, Any, Any]]" = field(init=False)
81
+
82
+
83
+ class SQLSpecPlugin(InitPluginProtocol, CLIPlugin):
84
+ """Litestar plugin for SQLSpec database integration.
85
+
86
+ Automatically configures NumPy array serialization when NumPy is installed,
87
+ enabling seamless bidirectional conversion between NumPy arrays and JSON
88
+ for vector embedding workflows.
89
+
90
+ Session Table Migrations:
91
+ The Litestar extension includes migrations for creating session storage tables.
92
+ To include these migrations in your database migration workflow, add 'litestar'
93
+ to the include_extensions list in your migration configuration.
94
+
95
+ Example:
96
+ config = AsyncpgConfig(
97
+ pool_config={"dsn": "postgresql://localhost/db"},
98
+ extension_config={
99
+ "litestar": {
100
+ "session_table": "custom_sessions" # Optional custom table name
101
+ }
102
+ },
103
+ migration_config={
104
+ "script_location": "migrations",
105
+ "include_extensions": ["litestar"], # Simple string list only
106
+ }
107
+ )
24
108
 
25
- class SQLSpec(SQLSpecBase, InitPluginProtocol, CLIPlugin):
26
- """Litestar plugin for SQLSpec database integration."""
109
+ The session table migration will automatically use the appropriate column types
110
+ for your database dialect (JSONB for PostgreSQL, JSON for MySQL, TEXT for SQLite).
27
111
 
28
- __slots__ = ("_plugin_configs",)
112
+ Extension migrations use the ext_litestar_ prefix (e.g., ext_litestar_0001) to
113
+ prevent version conflicts with application migrations.
114
+ """
29
115
 
30
- def __init__(
31
- self,
32
- config: Union["SyncConfigT", "AsyncConfigT", "DatabaseConfig", list["DatabaseConfig"]],
33
- *,
34
- loader: "Optional[SQLFileLoader]" = None,
35
- ) -> None:
116
+ __slots__ = ("_plugin_configs", "_sqlspec")
117
+
118
+ def __init__(self, sqlspec: SQLSpec, *, loader: "SQLFileLoader | None" = None) -> None:
36
119
  """Initialize SQLSpec plugin.
37
120
 
38
121
  Args:
39
- config: Database configuration for SQLSpec plugin.
40
- loader: Optional SQL file loader instance.
122
+ sqlspec: Pre-configured SQLSpec instance with registered database configs.
123
+ loader: Optional SQL file loader instance (SQLSpec may already have one).
41
124
  """
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]
125
+ self._sqlspec = sqlspec
126
+
127
+ self._plugin_configs: list[_PluginConfigState] = []
128
+ for cfg in self._sqlspec.configs.values():
129
+ config_union = cast(
130
+ "SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]",
131
+ cfg,
132
+ )
133
+ settings = self._extract_litestar_settings(config_union)
134
+ state = self._create_config_state(config_union, settings)
135
+ self._plugin_configs.append(state)
136
+
137
+ def _extract_litestar_settings(
138
+ self,
139
+ config: "SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]",
140
+ ) -> "dict[str, Any]":
141
+ """Extract Litestar settings from config.extension_config."""
142
+ litestar_config = config.extension_config.get("litestar", {})
143
+
144
+ connection_key = litestar_config.get("connection_key", DEFAULT_CONNECTION_KEY)
145
+ pool_key = litestar_config.get("pool_key", DEFAULT_POOL_KEY)
146
+ session_key = litestar_config.get("session_key", DEFAULT_SESSION_KEY)
147
+ commit_mode = litestar_config.get("commit_mode", DEFAULT_COMMIT_MODE)
148
+
149
+ if not config.supports_connection_pooling and pool_key == DEFAULT_POOL_KEY:
150
+ pool_key = f"_{DEFAULT_POOL_KEY}_{id(config)}"
151
+
152
+ return {
153
+ "connection_key": connection_key,
154
+ "pool_key": pool_key,
155
+ "session_key": session_key,
156
+ "commit_mode": commit_mode,
157
+ "extra_commit_statuses": litestar_config.get("extra_commit_statuses"),
158
+ "extra_rollback_statuses": litestar_config.get("extra_rollback_statuses"),
159
+ "enable_correlation_middleware": litestar_config.get("enable_correlation_middleware", True),
160
+ }
161
+
162
+ def _create_config_state(
163
+ self,
164
+ config: "SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]",
165
+ settings: "dict[str, Any]",
166
+ ) -> _PluginConfigState:
167
+ """Create plugin state with handlers for the given configuration."""
168
+ state = _PluginConfigState(
169
+ config=config,
170
+ connection_key=settings["connection_key"],
171
+ pool_key=settings["pool_key"],
172
+ session_key=settings["session_key"],
173
+ commit_mode=settings["commit_mode"],
174
+ extra_commit_statuses=settings.get("extra_commit_statuses"),
175
+ extra_rollback_statuses=settings.get("extra_rollback_statuses"),
176
+ enable_correlation_middleware=settings["enable_correlation_middleware"],
177
+ )
178
+
179
+ self._setup_handlers(state)
180
+ return state
181
+
182
+ def _setup_handlers(self, state: _PluginConfigState) -> None:
183
+ """Setup handlers for the plugin state."""
184
+ connection_key = state.connection_key
185
+ pool_key = state.pool_key
186
+ commit_mode = state.commit_mode
187
+ config = state.config
188
+ is_async = config.is_async
189
+
190
+ state.connection_provider = connection_provider_maker(config, pool_key, connection_key)
191
+ state.pool_provider = pool_provider_maker(config, pool_key)
192
+ state.session_provider = session_provider_maker(config, connection_key)
193
+ state.lifespan_handler = lifespan_handler_maker(config, pool_key)
194
+
195
+ if commit_mode == "manual":
196
+ state.before_send_handler = manual_handler_maker(connection_key, is_async)
47
197
  else:
48
- self._plugin_configs = config
198
+ commit_on_redirect = commit_mode == "autocommit_include_redirect"
199
+ state.before_send_handler = autocommit_handler_maker(
200
+ connection_key, is_async, commit_on_redirect, state.extra_commit_statuses, state.extra_rollback_statuses
201
+ )
49
202
 
50
203
  @property
51
- def config(self) -> "list[DatabaseConfig]": # pyright: ignore[reportInvalidTypeVarUse]
52
- """Return the plugin configuration.
204
+ def config(
205
+ self,
206
+ ) -> "list[SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]]":
207
+ """Return the plugin configurations.
53
208
 
54
209
  Returns:
55
210
  List of database configurations.
56
211
  """
57
- return self._plugin_configs
212
+ return [
213
+ cast(
214
+ "SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]",
215
+ state.config,
216
+ )
217
+ for state in self._plugin_configs
218
+ ]
58
219
 
59
220
  def on_cli_init(self, cli: "Group") -> None:
60
221
  """Configure CLI commands for SQLSpec database operations.
@@ -69,63 +230,88 @@ class SQLSpec(SQLSpecBase, InitPluginProtocol, CLIPlugin):
69
230
  def on_app_init(self, app_config: "AppConfig") -> "AppConfig":
70
231
  """Configure Litestar application with SQLSpec database integration.
71
232
 
233
+ Automatically registers NumPy array serialization when NumPy is installed.
234
+
72
235
  Args:
73
236
  app_config: The Litestar application configuration instance.
74
237
 
75
238
  Returns:
76
239
  The updated application configuration instance.
77
240
  """
78
-
79
241
  self._validate_dependency_keys()
80
242
 
81
243
  def store_sqlspec_in_state() -> None:
82
244
  app_config.state.sqlspec = self
83
245
 
84
246
  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
- )
247
+ app_config.signature_types.extend([SQLSpec, DatabaseConfigProtocol, SyncConfigT, AsyncConfigT])
88
248
 
89
- signature_namespace = {}
249
+ signature_namespace = {"ConnectionT": ConnectionT, "PoolT": PoolT, "DriverT": DriverT, "SchemaT": SchemaT}
90
250
 
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]
251
+ for state in self._plugin_configs:
252
+ state.annotation = type(state.config)
253
+ app_config.signature_types.append(state.annotation)
254
+ app_config.signature_types.append(state.config.connection_type)
255
+ app_config.signature_types.append(state.config.driver_type)
96
256
 
97
- signature_namespace.update(c.config.get_signature_namespace()) # type: ignore[union-attr]
257
+ signature_namespace.update(state.config.get_signature_namespace()) # type: ignore[arg-type]
98
258
 
99
- app_config.before_send.append(c.before_send_handler)
100
- app_config.lifespan.append(c.lifespan_handler) # pyright: ignore[reportUnknownMemberType]
259
+ app_config.before_send.append(state.before_send_handler)
260
+ app_config.lifespan.append(state.lifespan_handler)
101
261
  app_config.dependencies.update(
102
262
  {
103
- c.connection_key: Provide(c.connection_provider),
104
- c.pool_key: Provide(c.pool_provider),
105
- c.session_key: Provide(c.session_provider),
263
+ state.connection_key: Provide(state.connection_provider),
264
+ state.pool_key: Provide(state.pool_provider),
265
+ state.session_key: Provide(state.session_provider),
106
266
  }
107
267
  )
108
268
 
109
269
  if signature_namespace:
110
270
  app_config.signature_namespace.update(signature_namespace)
111
271
 
272
+ if NUMPY_INSTALLED:
273
+ import numpy as np
274
+
275
+ if app_config.type_encoders is None:
276
+ app_config.type_encoders = {np.ndarray: numpy_array_enc_hook}
277
+ else:
278
+ encoders_dict = dict(app_config.type_encoders)
279
+ encoders_dict[np.ndarray] = numpy_array_enc_hook
280
+ app_config.type_encoders = encoders_dict
281
+
282
+ if app_config.type_decoders is None:
283
+ app_config.type_decoders = [(numpy_array_predicate, numpy_array_dec_hook)] # type: ignore[list-item]
284
+ else:
285
+ decoders_list = list(app_config.type_decoders)
286
+ decoders_list.append((numpy_array_predicate, numpy_array_dec_hook)) # type: ignore[arg-type]
287
+ app_config.type_decoders = decoders_list
288
+
112
289
  return app_config
113
290
 
114
- def get_annotations(self) -> "list[type[Union[SyncConfigT, AsyncConfigT]]]": # pyright: ignore[reportInvalidTypeVarUse]
291
+ def get_annotations(
292
+ self,
293
+ ) -> "list[type[SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]]]":
115
294
  """Return the list of annotations.
116
295
 
117
296
  Returns:
118
297
  List of annotations.
119
298
  """
120
- return [c.annotation for c in self.config]
299
+ return [
300
+ cast(
301
+ "type[SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]]",
302
+ state.annotation,
303
+ )
304
+ for state in self._plugin_configs
305
+ ]
121
306
 
122
307
  def get_annotation(
123
- self, key: "Union[str, SyncConfigT, AsyncConfigT, type[Union[SyncConfigT, AsyncConfigT]]]"
124
- ) -> "type[Union[SyncConfigT, AsyncConfigT]]":
308
+ self,
309
+ 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]]",
310
+ ) -> "type[SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]]":
125
311
  """Return the annotation for the given configuration.
126
312
 
127
313
  Args:
128
- key: The configuration instance or key to lookup
314
+ key: The configuration instance or key to lookup.
129
315
 
130
316
  Raises:
131
317
  KeyError: If no configuration is found for the given key.
@@ -133,51 +319,38 @@ class SQLSpec(SQLSpecBase, InitPluginProtocol, CLIPlugin):
133
319
  Returns:
134
320
  The annotation for the configuration.
135
321
  """
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
322
+ for state in self._plugin_configs:
323
+ if key in {state.config, state.annotation} or key in {state.connection_key, state.pool_key}:
324
+ return cast(
325
+ "type[SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]]",
326
+ state.annotation,
327
+ )
328
+
146
329
  msg = f"No configuration found for {key}"
147
330
  raise KeyError(msg)
148
331
 
149
332
  @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": ...
333
+ def get_config(
334
+ self, name: "type[SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any]]"
335
+ ) -> "SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any]": ...
157
336
 
158
337
  @overload
159
- def get_config(self, name: "type[SyncDatabaseConfig]") -> "SyncDatabaseConfig": ...
338
+ def get_config(
339
+ self, name: "type[AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]]"
340
+ ) -> "AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]": ...
160
341
 
161
342
  @overload
162
- def get_config(self, name: "type[AsyncDatabaseConfig]") -> "AsyncDatabaseConfig": ...
163
-
164
343
  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.
344
+ self, name: str
345
+ ) -> "SyncDatabaseConfig[Any, Any, Any] | NoPoolSyncConfig[Any, Any] | AsyncDatabaseConfig[Any, Any, Any] | NoPoolAsyncConfig[Any, Any]": ...
168
346
 
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.
347
+ def get_config(
348
+ self, name: "type[DatabaseConfigProtocol[Any, Any, Any]] | str | Any"
349
+ ) -> "DatabaseConfigProtocol[Any, Any, Any]":
350
+ """Get a configuration instance by name.
172
351
 
173
352
  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
353
+ name: The configuration identifier.
181
354
 
182
355
  Raises:
183
356
  KeyError: If no configuration is found for the given name.
@@ -185,202 +358,154 @@ class SQLSpec(SQLSpecBase, InitPluginProtocol, CLIPlugin):
185
358
  Returns:
186
359
  The configuration instance for the specified name.
187
360
  """
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
361
  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
362
+ for state in self._plugin_configs:
363
+ if name in {state.connection_key, state.pool_key, state.session_key}:
364
+ return cast("DatabaseConfigProtocol[Any, Any, Any]", state.config) # type: ignore[redundant-cast]
202
365
 
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
366
+ for state in self._plugin_configs:
367
+ if name in {state.config, state.annotation}:
368
+ return cast("DatabaseConfigProtocol[Any, Any, Any]", state.config) # type: ignore[redundant-cast]
208
369
 
209
370
  msg = f"No database configuration found for name '{name}'. Available keys: {self._get_available_keys()}"
210
371
  raise KeyError(msg)
211
372
 
212
373
  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]":
374
+ self, key: "str | SyncConfigT | AsyncConfigT | type[SyncConfigT | AsyncConfigT]", state: "State", scope: "Scope"
375
+ ) -> "SyncDriverAdapterBase | AsyncDriverAdapterBase":
218
376
  """Provide a database session for the specified configuration key from request scope.
219
377
 
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
378
  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
379
+ key: The configuration identifier (same as get_config).
380
+ state: The Litestar application State object.
381
+ scope: The ASGI scope containing the request context.
227
382
 
228
383
  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
- ... )
384
+ A driver session instance for the specified database configuration.
240
385
  """
241
- # Get DatabaseConfig wrapper for Litestar methods
242
- db_config = self._get_database_config(key)
243
- return db_config.get_request_session(state, scope)
386
+ plugin_state = self._get_plugin_state(key)
387
+ session_scope_key = f"{plugin_state.session_key}_instance"
388
+
389
+ session = get_sqlspec_scope_state(scope, session_scope_key)
390
+ if session is not None:
391
+ return cast("SyncDriverAdapterBase | AsyncDriverAdapterBase", session)
392
+
393
+ connection = get_sqlspec_scope_state(scope, plugin_state.connection_key)
394
+ if connection is None:
395
+ self._raise_missing_connection(plugin_state.connection_key)
396
+
397
+ session = plugin_state.config.driver_type(
398
+ connection=connection,
399
+ statement_config=plugin_state.config.statement_config,
400
+ driver_features=plugin_state.config.driver_features,
401
+ )
402
+ set_sqlspec_scope_state(scope, session_scope_key, session)
403
+
404
+ return cast("SyncDriverAdapterBase | AsyncDriverAdapterBase", session)
244
405
 
245
406
  def provide_sync_request_session(
246
- self, key: "Union[str, SyncConfigT, type[SyncConfigT]]", state: "State", scope: "Scope"
407
+ self, key: "str | SyncConfigT | type[SyncConfigT]", state: "State", scope: "Scope"
247
408
  ) -> "SyncDriverAdapterBase":
248
409
  """Provide a sync database session for the specified configuration key from request scope.
249
410
 
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
411
  Args:
254
- key: The sync configuration identifier
255
- state: The Litestar application State object
256
- scope: The ASGI scope containing the request context
412
+ key: The sync configuration identifier.
413
+ state: The Litestar application State object.
414
+ scope: The ASGI scope containing the request context.
257
415
 
258
416
  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
417
+ A sync driver session instance for the specified database configuration.
267
418
  """
268
- # Get DatabaseConfig wrapper for Litestar methods
269
- db_config = self._get_database_config(key)
270
- session = db_config.get_request_session(state, scope)
419
+ session = self.provide_request_session(key, state, scope)
271
420
  return cast("SyncDriverAdapterBase", session)
272
421
 
273
422
  def provide_async_request_session(
274
- self, key: "Union[str, AsyncConfigT, type[AsyncConfigT]]", state: "State", scope: "Scope"
423
+ self, key: "str | AsyncConfigT | type[AsyncConfigT]", state: "State", scope: "Scope"
275
424
  ) -> "AsyncDriverAdapterBase":
276
425
  """Provide an async database session for the specified configuration key from request scope.
277
426
 
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
427
  Args:
282
- key: The async configuration identifier
283
- state: The Litestar application State object
284
- scope: The ASGI scope containing the request context
428
+ key: The async configuration identifier.
429
+ state: The Litestar application State object.
430
+ scope: The ASGI scope containing the request context.
285
431
 
286
432
  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
433
+ An async driver session instance for the specified database configuration.
295
434
  """
296
- # Get DatabaseConfig wrapper for Litestar methods
297
- db_config = self._get_database_config(key)
298
- session = db_config.get_request_session(state, scope)
435
+ session = self.provide_request_session(key, state, scope)
299
436
  return cast("AsyncDriverAdapterBase", session)
300
437
 
301
438
  def provide_request_connection(
302
- self,
303
- key: "Union[str, SyncConfigT, AsyncConfigT, type[Union[SyncConfigT, AsyncConfigT]]]",
304
- state: "State",
305
- scope: "Scope",
306
- ) -> Any:
439
+ self, key: "str | SyncConfigT | AsyncConfigT | type[SyncConfigT | AsyncConfigT]", state: "State", scope: "Scope"
440
+ ) -> "Any":
307
441
  """Provide a database connection for the specified configuration key from request scope.
308
442
 
309
- This is a convenience method that combines get_config and get_request_connection
310
- into a single call.
311
-
312
443
  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
444
+ key: The configuration identifier (same as get_config).
445
+ state: The Litestar application State object.
446
+ scope: The ASGI scope containing the request context.
316
447
 
317
448
  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
- ... )
449
+ A database connection instance for the specified database configuration.
329
450
  """
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.
451
+ plugin_state = self._get_plugin_state(key)
452
+ connection = get_sqlspec_scope_state(scope, plugin_state.connection_key)
453
+ if connection is None:
454
+ self._raise_missing_connection(plugin_state.connection_key)
341
455
 
342
- Args:
343
- key: The configuration identifier
344
-
345
- Returns:
346
- The DatabaseConfig wrapper instance
456
+ return connection
347
457
 
348
- Raises:
349
- KeyError: If no configuration is found for the given key
350
- """
351
- # For string keys, lookup by connection/pool/session keys
458
+ def _get_plugin_state(
459
+ self, key: "str | SyncConfigT | AsyncConfigT | type[SyncConfigT | AsyncConfigT]"
460
+ ) -> _PluginConfigState:
461
+ """Get plugin state for a configuration by key."""
352
462
  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
463
+ for state in self._plugin_configs:
464
+ if key in {state.connection_key, state.pool_key, state.session_key}:
465
+ return state
356
466
 
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
467
+ for state in self._plugin_configs:
468
+ if key in {state.config, state.annotation}:
469
+ return state
362
470
 
363
- msg = f"No database configuration found for name '{key}'. Available keys: {self._get_available_keys()}"
364
- raise KeyError(msg)
471
+ self._raise_config_not_found(key)
472
+ return None
365
473
 
366
474
  def _get_available_keys(self) -> "list[str]":
367
475
  """Get a list of all available configuration keys for error messages."""
368
476
  keys = []
369
- for c in self.config:
370
- keys.extend([c.connection_key, c.pool_key, c.session_key])
477
+ for state in self._plugin_configs:
478
+ keys.extend([state.connection_key, state.pool_key, state.session_key])
371
479
  return keys
372
480
 
373
481
  def _validate_dependency_keys(self) -> None:
374
- """Validate that connection and pool keys are unique across configurations.
482
+ """Validate that connection and pool keys are unique across configurations."""
483
+ connection_keys = [state.connection_key for state in self._plugin_configs]
484
+ pool_keys = [state.pool_key for state in self._plugin_configs]
375
485
 
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
486
  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)
487
+ self._raise_duplicate_connection_keys()
488
+
384
489
  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)
490
+ self._raise_duplicate_pool_keys()
491
+
492
+ def _raise_missing_connection(self, connection_key: str) -> None:
493
+ """Raise error when connection is not found in scope."""
494
+ msg = f"No database connection found in scope for key '{connection_key}'. "
495
+ msg += "Ensure the connection dependency is properly configured and available."
496
+ raise ImproperConfigurationError(detail=msg)
497
+
498
+ def _raise_config_not_found(self, key: Any) -> NoReturn:
499
+ """Raise error when configuration is not found."""
500
+ msg = f"No database configuration found for name '{key}'. Available keys: {self._get_available_keys()}"
501
+ raise KeyError(msg)
502
+
503
+ def _raise_duplicate_connection_keys(self) -> None:
504
+ """Raise error when connection keys are not unique."""
505
+ msg = "When using multiple database configuration, each configuration must have a unique `connection_key`."
506
+ raise ImproperConfigurationError(detail=msg)
507
+
508
+ def _raise_duplicate_pool_keys(self) -> None:
509
+ """Raise error when pool keys are not unique."""
510
+ msg = "When using multiple database configuration, each configuration must have a unique `pool_key`."
511
+ raise ImproperConfigurationError(detail=msg)