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,276 +1,69 @@
1
- from dataclasses import dataclass, field
2
- from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union, cast
1
+ """Configuration types for Litestar session store extension."""
3
2
 
4
- from sqlspec.exceptions import ImproperConfigurationError
5
- from sqlspec.extensions.litestar._utils import get_sqlspec_scope_state, set_sqlspec_scope_state
6
- from sqlspec.extensions.litestar.handlers import (
7
- autocommit_handler_maker,
8
- connection_provider_maker,
9
- lifespan_handler_maker,
10
- manual_handler_maker,
11
- pool_provider_maker,
12
- session_provider_maker,
13
- )
3
+ from typing_extensions import NotRequired, TypedDict
14
4
 
15
- if TYPE_CHECKING:
16
- from collections.abc import AsyncGenerator, Awaitable
17
- from contextlib import AbstractAsyncContextManager, AbstractContextManager
5
+ __all__ = ("LitestarConfig",)
18
6
 
19
- from litestar import Litestar
20
- from litestar.datastructures.state import State
21
- from litestar.types import BeforeMessageSendHookHandler, Scope
22
7
 
23
- from sqlspec.config import AsyncConfigT, DriverT, SyncConfigT
24
- from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
25
- from sqlspec.typing import ConnectionT, PoolT
8
+ class LitestarConfig(TypedDict):
9
+ """Configuration options for Litestar session store extension.
26
10
 
11
+ All fields are optional with sensible defaults. Use in extension_config["litestar"]:
27
12
 
28
- CommitMode = Literal["manual", "autocommit", "autocommit_include_redirect"]
29
- DEFAULT_COMMIT_MODE: CommitMode = "manual"
30
- DEFAULT_CONNECTION_KEY = "db_connection"
31
- DEFAULT_POOL_KEY = "db_pool"
32
- DEFAULT_SESSION_KEY = "db_session"
13
+ Example:
14
+ from sqlspec.adapters.oracledb import OracleAsyncConfig
33
15
 
34
- __all__ = (
35
- "DEFAULT_COMMIT_MODE",
36
- "DEFAULT_CONNECTION_KEY",
37
- "DEFAULT_POOL_KEY",
38
- "DEFAULT_SESSION_KEY",
39
- "AsyncDatabaseConfig",
40
- "CommitMode",
41
- "DatabaseConfig",
42
- "SyncDatabaseConfig",
43
- )
44
-
45
-
46
- @dataclass
47
- class DatabaseConfig:
48
- config: "Union[SyncConfigT, AsyncConfigT]" = field() # type: ignore[valid-type] # pyright: ignore[reportGeneralTypeIssues]
49
- connection_key: str = field(default=DEFAULT_CONNECTION_KEY)
50
- pool_key: str = field(default=DEFAULT_POOL_KEY)
51
- session_key: str = field(default=DEFAULT_SESSION_KEY)
52
- commit_mode: "CommitMode" = field(default=DEFAULT_COMMIT_MODE)
53
- extra_commit_statuses: "Optional[set[int]]" = field(default=None)
54
- extra_rollback_statuses: "Optional[set[int]]" = field(default=None)
55
- enable_correlation_middleware: bool = field(default=True)
56
- connection_provider: "Callable[[State, Scope], AsyncGenerator[ConnectionT, None]]" = field( # pyright: ignore[reportGeneralTypeIssues]
57
- init=False, repr=False, hash=False
58
- )
59
- pool_provider: "Callable[[State,Scope], Awaitable[PoolT]]" = field(init=False, repr=False, hash=False) # pyright: ignore[reportGeneralTypeIssues]
60
- session_provider: "Callable[[Any], AsyncGenerator[DriverT, None]]" = field(init=False, repr=False, hash=False) # pyright: ignore[reportGeneralTypeIssues]
61
- before_send_handler: "BeforeMessageSendHookHandler" = field(init=False, repr=False, hash=False)
62
- lifespan_handler: "Callable[[Litestar], AbstractAsyncContextManager[None]]" = field(
63
- init=False, repr=False, hash=False
64
- )
65
- annotation: "type[Union[SyncConfigT, AsyncConfigT]]" = field(init=False, repr=False, hash=False) # type: ignore[valid-type] # pyright: ignore[reportGeneralTypeIssues]
66
-
67
- def __post_init__(self) -> None:
68
- if not self.config.supports_connection_pooling and self.pool_key == DEFAULT_POOL_KEY: # type: ignore[union-attr,unused-ignore]
69
- self.pool_key = f"_{self.pool_key}_{id(self.config)}"
70
- if self.commit_mode == "manual":
71
- self.before_send_handler = manual_handler_maker(connection_scope_key=self.connection_key)
72
- elif self.commit_mode == "autocommit":
73
- self.before_send_handler = autocommit_handler_maker(
74
- commit_on_redirect=False,
75
- extra_commit_statuses=self.extra_commit_statuses,
76
- extra_rollback_statuses=self.extra_rollback_statuses,
77
- connection_scope_key=self.connection_key,
78
- )
79
- elif self.commit_mode == "autocommit_include_redirect":
80
- self.before_send_handler = autocommit_handler_maker(
81
- commit_on_redirect=True,
82
- extra_commit_statuses=self.extra_commit_statuses,
83
- extra_rollback_statuses=self.extra_rollback_statuses,
84
- connection_scope_key=self.connection_key,
85
- )
86
- else:
87
- msg = f"Invalid commit mode: {self.commit_mode}"
88
- raise ImproperConfigurationError(detail=msg)
89
- self.lifespan_handler = lifespan_handler_maker(config=self.config, pool_key=self.pool_key)
90
- self.connection_provider = connection_provider_maker(
91
- connection_key=self.connection_key, pool_key=self.pool_key, config=self.config
92
- )
93
- self.pool_provider = pool_provider_maker(config=self.config, pool_key=self.pool_key)
94
- self.session_provider = session_provider_maker(
95
- config=self.config, connection_dependency_key=self.connection_key
16
+ config = OracleAsyncConfig(
17
+ pool_config={"dsn": "oracle://localhost/XEPDB1"},
18
+ extension_config={
19
+ "litestar": {
20
+ "session_table": "my_sessions",
21
+ "in_memory": True
22
+ }
23
+ }
96
24
  )
97
25
 
98
- def get_request_session(
99
- self, state: "State", scope: "Scope"
100
- ) -> "Union[SyncDriverAdapterBase, AsyncDriverAdapterBase]":
101
- """Get a session instance from the current request.
102
-
103
- This method provides access to the database session that has been added to the request
104
- scope, similar to Advanced Alchemy's provide_session method. It first looks for an
105
- existing session in the request scope state, and if not found, creates a new one using
106
- the connection from the scope.
107
-
108
- Args:
109
- state: The Litestar application State object.
110
- scope: The ASGI scope containing the request context.
111
-
112
- Returns:
113
- A driver session instance.
114
-
115
- Raises:
116
- ImproperConfigurationError: If no connection is available in the scope.
117
- """
118
- # Create a unique scope key for sessions to avoid conflicts
119
- session_scope_key = f"{self.session_key}_instance"
120
-
121
- # Try to get existing session from scope
122
- session = get_sqlspec_scope_state(scope, session_scope_key)
123
- if session is not None:
124
- return cast("Union[SyncDriverAdapterBase, AsyncDriverAdapterBase]", session)
125
-
126
- # Get connection from scope state
127
- connection = get_sqlspec_scope_state(scope, self.connection_key)
128
- if connection is None:
129
- msg = f"No database connection found in scope for key '{self.connection_key}'. "
130
- msg += "Ensure the connection dependency is properly configured and available."
131
- raise ImproperConfigurationError(detail=msg)
132
-
133
- # Create new session using the connection
134
- # Access driver_type which is available on all config types
135
- session = self.config.driver_type(connection=connection) # type: ignore[union-attr]
136
-
137
- # Store session in scope for future use
138
- set_sqlspec_scope_state(scope, session_scope_key, session)
139
-
140
- return cast("Union[SyncDriverAdapterBase, AsyncDriverAdapterBase]", session)
141
-
142
- def get_request_connection(self, state: "State", scope: "Scope") -> "Any":
143
- """Get a connection instance from the current request.
144
-
145
- This method provides access to the database connection that has been added to the request
146
- scope. This is useful in guards, middleware, or other contexts where you need direct
147
- access to the connection that's been established for the current request.
148
-
149
- Args:
150
- state: The Litestar application State object.
151
- scope: The ASGI scope containing the request context.
152
-
153
- Returns:
154
- A database connection instance.
155
-
156
- Raises:
157
- ImproperConfigurationError: If no connection is available in the scope.
158
- """
159
- connection = get_sqlspec_scope_state(scope, self.connection_key)
160
- if connection is None:
161
- msg = f"No database connection found in scope for key '{self.connection_key}'. "
162
- msg += "Ensure the connection dependency is properly configured and available."
163
- raise ImproperConfigurationError(detail=msg)
164
-
165
- return cast("Any", connection)
166
-
167
-
168
- # Add passthrough methods to both specialized classes for convenience
169
- class SyncDatabaseConfig(DatabaseConfig):
170
- """Sync-specific DatabaseConfig with better typing for get_request_session."""
171
-
172
- def get_request_session(self, state: "State", scope: "Scope") -> "SyncDriverAdapterBase":
173
- """Get a sync session instance from the current request.
174
-
175
- This method provides access to the database session that has been added to the request
176
- scope, similar to Advanced Alchemy's provide_session method. It first looks for an
177
- existing session in the request scope state, and if not found, creates a new one using
178
- the connection from the scope.
179
-
180
- Args:
181
- state: The Litestar application State object.
182
- scope: The ASGI scope containing the request context.
183
-
184
- Returns:
185
- A sync driver session instance.
186
- """
187
- session = super().get_request_session(state, scope)
188
- return cast("SyncDriverAdapterBase", session)
189
-
190
- def provide_session(self) -> "AbstractContextManager[SyncDriverAdapterBase]":
191
- """Provide a database session context manager.
192
-
193
- This is a passthrough to the underlying config's provide_session method
194
- for convenient access to database sessions.
195
-
196
- Returns:
197
- Context manager that yields a sync driver session.
198
- """
199
- return self.config.provide_session() # type: ignore[union-attr,no-any-return]
200
-
201
- def provide_connection(self) -> "AbstractContextManager[Any]":
202
- """Provide a database connection context manager.
203
-
204
- This is a passthrough to the underlying config's provide_connection method
205
- for convenient access to database connections.
206
-
207
- Returns:
208
- Context manager that yields a sync database connection.
209
- """
210
- return self.config.provide_connection() # type: ignore[union-attr,no-any-return]
211
-
212
- def create_connection(self) -> "Any":
213
- """Create and return a new database connection.
214
-
215
- This is a passthrough to the underlying config's create_connection method
216
- for direct connection creation without context management.
217
-
218
- Returns:
219
- A new sync database connection.
220
- """
221
- return self.config.create_connection() # type: ignore[union-attr]
222
-
223
-
224
- class AsyncDatabaseConfig(DatabaseConfig):
225
- """Async-specific DatabaseConfig with better typing for get_request_session."""
226
-
227
- def get_request_session(self, state: "State", scope: "Scope") -> "AsyncDriverAdapterBase":
228
- """Get an async session instance from the current request.
229
-
230
- This method provides access to the database session that has been added to the request
231
- scope, similar to Advanced Alchemy's provide_session method. It first looks for an
232
- existing session in the request scope state, and if not found, creates a new one using
233
- the connection from the scope.
234
-
235
- Args:
236
- state: The Litestar application State object.
237
- scope: The ASGI scope containing the request context.
238
-
239
- Returns:
240
- An async driver session instance.
241
- """
242
- session = super().get_request_session(state, scope)
243
- return cast("AsyncDriverAdapterBase", session)
244
-
245
- def provide_session(self) -> "AbstractAsyncContextManager[AsyncDriverAdapterBase]":
246
- """Provide a database session context manager.
247
-
248
- This is a passthrough to the underlying config's provide_session method
249
- for convenient access to database sessions.
250
-
251
- Returns:
252
- Context manager that yields an async driver session.
253
- """
254
- return self.config.provide_session() # type: ignore[union-attr,no-any-return]
255
-
256
- def provide_connection(self) -> "AbstractAsyncContextManager[Any]":
257
- """Provide a database connection context manager.
258
-
259
- This is a passthrough to the underlying config's provide_connection method
260
- for convenient access to database connections.
261
-
262
- Returns:
263
- Context manager that yields an async database connection.
264
- """
265
- return self.config.provide_connection() # type: ignore[union-attr,no-any-return]
266
-
267
- async def create_connection(self) -> "Any":
268
- """Create and return a new database connection.
269
-
270
- This is a passthrough to the underlying config's create_connection method
271
- for direct connection creation without context management.
26
+ Notes:
27
+ This TypedDict provides type safety for extension config but is not required.
28
+ You can use plain dicts as well.
29
+ """
30
+
31
+ session_table: NotRequired[str]
32
+ """Name of the sessions table. Default: 'litestar_session'
33
+
34
+ Examples:
35
+ "app_sessions"
36
+ "user_sessions"
37
+ "tenant_acme_sessions"
38
+ """
39
+
40
+ in_memory: NotRequired[bool]
41
+ """Enable in-memory table storage (Oracle-specific). Default: False.
42
+
43
+ When enabled, tables are created with the INMEMORY clause for Oracle Database,
44
+ which stores table data in columnar format in memory for faster query performance.
45
+
46
+ This is an Oracle-specific feature that requires:
47
+ - Oracle Database 12.1.0.2 or higher
48
+ - Database In-Memory option license (Enterprise Edition)
49
+ - Sufficient INMEMORY_SIZE configured in the database instance
50
+
51
+ Other database adapters ignore this setting.
52
+
53
+ Examples:
54
+ Oracle with in-memory enabled:
55
+ config = OracleAsyncConfig(
56
+ pool_config={"dsn": "oracle://..."},
57
+ extension_config={
58
+ "litestar": {
59
+ "in_memory": True
60
+ }
61
+ }
62
+ )
272
63
 
273
- Returns:
274
- A new async database connection.
275
- """
276
- return await self.config.create_connection() # type: ignore[union-attr]
64
+ Notes:
65
+ - Improves query performance for session lookups (10-100x faster)
66
+ - Tables created with INMEMORY clause
67
+ - Requires Oracle Database In-Memory option license
68
+ - Ignored by non-Oracle adapters
69
+ """
@@ -1,10 +1,11 @@
1
1
  import contextlib
2
2
  import inspect
3
- from collections.abc import AsyncGenerator
3
+ from collections.abc import AsyncGenerator, Callable
4
4
  from contextlib import AbstractAsyncContextManager
5
- from typing import TYPE_CHECKING, Any, Callable, Optional, cast
5
+ from typing import TYPE_CHECKING, Any, cast
6
6
 
7
7
  from litestar.constants import HTTP_DISCONNECT, HTTP_RESPONSE_START, WEBSOCKET_CLOSE, WEBSOCKET_DISCONNECT
8
+ from litestar.params import Dependency
8
9
 
9
10
  from sqlspec.exceptions import ImproperConfigurationError
10
11
  from sqlspec.extensions.litestar._utils import (
@@ -37,11 +38,14 @@ __all__ = (
37
38
  )
38
39
 
39
40
 
40
- def manual_handler_maker(connection_scope_key: str) -> "Callable[[Message, Scope], Coroutine[Any, Any, None]]":
41
+ def manual_handler_maker(
42
+ connection_scope_key: str, is_async: bool = False
43
+ ) -> "Callable[[Message, Scope], Coroutine[Any, Any, None]]":
41
44
  """Create handler for manual connection management.
42
45
 
43
46
  Args:
44
47
  connection_scope_key: The key used to store the connection in the ASGI scope.
48
+ is_async: Whether the database driver is async (uses direct await) or sync (uses ensure_async_).
45
49
 
46
50
  Returns:
47
51
  The handler callable.
@@ -56,7 +60,9 @@ def manual_handler_maker(connection_scope_key: str) -> "Callable[[Message, Scope
56
60
  """
57
61
  connection = get_sqlspec_scope_state(scope, connection_scope_key)
58
62
  if connection and message["type"] in SESSION_TERMINUS_ASGI_EVENTS:
59
- if hasattr(connection, "close") and callable(connection.close):
63
+ if is_async:
64
+ await connection.close()
65
+ else:
60
66
  await ensure_async_(connection.close)()
61
67
  delete_sqlspec_scope_state(scope, connection_scope_key)
62
68
 
@@ -65,14 +71,16 @@ def manual_handler_maker(connection_scope_key: str) -> "Callable[[Message, Scope
65
71
 
66
72
  def autocommit_handler_maker(
67
73
  connection_scope_key: str,
74
+ is_async: bool = False,
68
75
  commit_on_redirect: bool = False,
69
- extra_commit_statuses: "Optional[set[int]]" = None,
70
- extra_rollback_statuses: "Optional[set[int]]" = None,
76
+ extra_commit_statuses: "set[int] | None" = None,
77
+ extra_rollback_statuses: "set[int] | None" = None,
71
78
  ) -> "Callable[[Message, Scope], Coroutine[Any, Any, None]]":
72
79
  """Create handler for automatic transaction commit/rollback based on response status.
73
80
 
74
81
  Args:
75
82
  connection_scope_key: The key used to store the connection in the ASGI scope.
83
+ is_async: Whether the database driver is async (uses direct await) or sync (uses ensure_async_).
76
84
  commit_on_redirect: Issue a commit when the response status is a redirect (3XX).
77
85
  extra_commit_statuses: A set of additional status codes that trigger a commit.
78
86
  extra_rollback_statuses: A set of additional status codes that trigger a rollback.
@@ -108,13 +116,19 @@ def autocommit_handler_maker(
108
116
  if (message["status"] in commit_range or message["status"] in extra_commit_statuses) and message[
109
117
  "status"
110
118
  ] not in extra_rollback_statuses:
111
- if hasattr(connection, "commit") and callable(connection.commit):
119
+ if is_async:
120
+ await connection.commit()
121
+ else:
112
122
  await ensure_async_(connection.commit)()
113
- elif hasattr(connection, "rollback") and callable(connection.rollback):
123
+ elif is_async:
124
+ await connection.rollback()
125
+ else:
114
126
  await ensure_async_(connection.rollback)()
115
127
  finally:
116
128
  if connection and message["type"] in SESSION_TERMINUS_ASGI_EVENTS:
117
- if hasattr(connection, "close") and callable(connection.close):
129
+ if is_async:
130
+ await connection.close()
131
+ else:
118
132
  await ensure_async_(connection.close)()
119
133
  delete_sqlspec_scope_state(scope, connection_scope_key)
120
134
 
@@ -144,14 +158,23 @@ def lifespan_handler_maker(
144
158
  Yields:
145
159
  Control to application during pool lifetime.
146
160
  """
147
- db_pool = await ensure_async_(config.create_pool)()
161
+ db_pool: Any
162
+ if config.is_async:
163
+ db_pool = await config.create_pool()
164
+ else:
165
+ db_pool = await ensure_async_(config.create_pool)()
148
166
  app.state.update({pool_key: db_pool})
149
167
  try:
150
168
  yield
151
169
  finally:
152
170
  app.state.pop(pool_key, None)
153
171
  try:
154
- await ensure_async_(config.close_pool)()
172
+ if config.is_async:
173
+ close_result = config.close_pool()
174
+ if close_result is not None:
175
+ await close_result
176
+ else:
177
+ await ensure_async_(config.close_pool)()
155
178
  except Exception as e:
156
179
  if app.logger:
157
180
  app.logger.warning("Error closing database pool for %s. Error: %s", pool_key, e)
@@ -215,11 +238,11 @@ def connection_provider_maker(
215
238
  msg = f"Database pool with key '{pool_key}' not found. Cannot create a connection."
216
239
  raise ImproperConfigurationError(msg)
217
240
 
218
- connection_cm = config.provide_connection(db_pool)
241
+ connection_cm: Any = config.provide_connection(db_pool)
219
242
 
220
243
  if not isinstance(connection_cm, AbstractAsyncContextManager):
221
244
  conn_instance: ConnectionT
222
- if hasattr(connection_cm, "__await__"):
245
+ if inspect.isawaitable(connection_cm):
223
246
  conn_instance = await cast("Awaitable[ConnectionT]", connection_cm)
224
247
  else:
225
248
  conn_instance = cast("ConnectionT", connection_cm)
@@ -252,12 +275,18 @@ def session_provider_maker(
252
275
  """
253
276
 
254
277
  async def provide_session(*args: Any, **kwargs: Any) -> "AsyncGenerator[DriverT, None]":
255
- yield cast("DriverT", config.driver_type(connection=args[0] if args else kwargs.get(connection_dependency_key))) # pyright: ignore
278
+ connection_obj = args[0] if args else kwargs.get(connection_dependency_key)
279
+ yield cast(
280
+ "DriverT",
281
+ config.driver_type(
282
+ connection=connection_obj,
283
+ statement_config=config.statement_config,
284
+ driver_features=config.driver_features,
285
+ ),
286
+ ) # pyright: ignore
256
287
 
257
288
  conn_type_annotation = config.connection_type
258
289
 
259
- from litestar.params import Dependency
260
-
261
290
  db_conn_param = inspect.Parameter(
262
291
  name=connection_dependency_key,
263
292
  kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
@@ -272,7 +301,7 @@ def session_provider_maker(
272
301
 
273
302
  provide_session.__signature__ = provider_signature # type: ignore[attr-defined]
274
303
 
275
- if not hasattr(provide_session, "__annotations__") or provide_session.__annotations__ is None:
304
+ if provide_session.__annotations__ is None:
276
305
  provide_session.__annotations__ = {}
277
306
 
278
307
  provide_session.__annotations__[connection_dependency_key] = conn_type_annotation
@@ -0,0 +1,137 @@
1
+ """Create Litestar session table migration using store DDL definitions."""
2
+
3
+ from typing import TYPE_CHECKING, NoReturn
4
+
5
+ from sqlspec.exceptions import SQLSpecError
6
+ from sqlspec.utils.logging import get_logger
7
+ from sqlspec.utils.module_loader import import_string
8
+
9
+ if TYPE_CHECKING:
10
+ from sqlspec.extensions.litestar.store import BaseSQLSpecStore
11
+ from sqlspec.migrations.context import MigrationContext
12
+
13
+ logger = get_logger("migrations.litestar.session")
14
+
15
+ __all__ = ("down", "up")
16
+
17
+
18
+ def _get_store_class(context: "MigrationContext | None") -> "type[BaseSQLSpecStore]":
19
+ """Get the appropriate store class based on the config's module path.
20
+
21
+ Args:
22
+ context: Migration context containing config.
23
+
24
+ Returns:
25
+ Store class matching the config's adapter.
26
+
27
+ Notes:
28
+ Dynamically imports the store class from the config's module path.
29
+ For example, AsyncpgConfig at 'sqlspec.adapters.asyncpg.config'
30
+ maps to AsyncpgStore at 'sqlspec.adapters.asyncpg.litestar.store.AsyncpgStore'.
31
+ """
32
+ if not context or not context.config:
33
+ _raise_missing_config()
34
+
35
+ config_class = type(context.config)
36
+ config_module = config_class.__module__
37
+ config_name = config_class.__name__
38
+
39
+ if not config_module.startswith("sqlspec.adapters."):
40
+ _raise_unsupported_config(f"{config_module}.{config_name}")
41
+
42
+ adapter_name = config_module.split(".")[2]
43
+ store_class_name = config_name.replace("Config", "Store")
44
+
45
+ store_path = f"sqlspec.adapters.{adapter_name}.litestar.store.{store_class_name}"
46
+
47
+ try:
48
+ store_class: type[BaseSQLSpecStore] = import_string(store_path)
49
+ except ImportError as e:
50
+ _raise_store_import_failed(store_path, e)
51
+
52
+ return store_class
53
+
54
+
55
+ def _raise_missing_config() -> NoReturn:
56
+ """Raise error when migration context has no config.
57
+
58
+ Raises:
59
+ SQLSpecError: Always raised.
60
+ """
61
+ msg = "Migration context must have a config to determine store class"
62
+ raise SQLSpecError(msg)
63
+
64
+
65
+ def _raise_unsupported_config(config_type: str) -> NoReturn:
66
+ """Raise error for unsupported config type.
67
+
68
+ Args:
69
+ config_type: The unsupported config type name.
70
+
71
+ Raises:
72
+ SQLSpecError: Always raised with config type info.
73
+ """
74
+ msg = f"Unsupported config type for Litestar session migration: {config_type}"
75
+ raise SQLSpecError(msg)
76
+
77
+
78
+ def _raise_store_import_failed(store_path: str, error: ImportError) -> NoReturn:
79
+ """Raise error when store class import fails.
80
+
81
+ Args:
82
+ store_path: The import path that failed.
83
+ error: The original import error.
84
+
85
+ Raises:
86
+ SQLSpecError: Always raised with import details.
87
+ """
88
+ msg = f"Failed to import Litestar store class from {store_path}: {error}"
89
+ raise SQLSpecError(msg) from error
90
+
91
+
92
+ async def up(context: "MigrationContext | None" = None) -> "list[str]":
93
+ """Create the litestar session table using store DDL definitions.
94
+
95
+ This migration delegates to the appropriate store class to generate
96
+ dialect-specific DDL. The store classes contain the single source of
97
+ truth for session table schemas.
98
+
99
+ Args:
100
+ context: Migration context containing config.
101
+
102
+ Returns:
103
+ List of SQL statements to execute for upgrade.
104
+
105
+ Notes:
106
+ Table configuration is read from context.config.extension_config["litestar"].
107
+ """
108
+ store_class = _get_store_class(context)
109
+ if context is None or context.config is None:
110
+ _raise_missing_config()
111
+ store = store_class(config=context.config)
112
+
113
+ return [store._get_create_table_sql()] # pyright: ignore[reportPrivateUsage]
114
+
115
+
116
+ async def down(context: "MigrationContext | None" = None) -> "list[str]":
117
+ """Drop the litestar session table using store DDL definitions.
118
+
119
+ This migration delegates to the appropriate store class to generate
120
+ dialect-specific DROP statements. The store classes contain the single
121
+ source of truth for session table schemas.
122
+
123
+ Args:
124
+ context: Migration context containing config.
125
+
126
+ Returns:
127
+ List of SQL statements to execute for downgrade.
128
+
129
+ Notes:
130
+ Table configuration is read from context.config.extension_config["litestar"].
131
+ """
132
+ store_class = _get_store_class(context)
133
+ if context is None or context.config is None:
134
+ _raise_missing_config()
135
+ store = store_class(config=context.config)
136
+
137
+ return store._get_drop_table_sql() # pyright: ignore[reportPrivateUsage]
@@ -0,0 +1,3 @@
1
+ """Litestar extension migrations for session table creation."""
2
+
3
+ __all__ = ()