sqlspec 0.26.0__py3-none-any.whl → 0.27.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sqlspec might be problematic. Click here for more details.
- sqlspec/__init__.py +7 -15
- sqlspec/_serialization.py +55 -25
- sqlspec/_typing.py +62 -52
- sqlspec/adapters/adbc/_types.py +1 -1
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +870 -0
- sqlspec/adapters/adbc/config.py +62 -12
- sqlspec/adapters/adbc/data_dictionary.py +52 -2
- sqlspec/adapters/adbc/driver.py +144 -45
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +44 -50
- sqlspec/adapters/aiosqlite/_types.py +1 -1
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +527 -0
- sqlspec/adapters/aiosqlite/config.py +86 -16
- sqlspec/adapters/aiosqlite/data_dictionary.py +34 -2
- sqlspec/adapters/aiosqlite/driver.py +127 -38
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
- sqlspec/adapters/aiosqlite/pool.py +7 -7
- sqlspec/adapters/asyncmy/__init__.py +7 -1
- sqlspec/adapters/asyncmy/_types.py +1 -1
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +493 -0
- sqlspec/adapters/asyncmy/config.py +59 -17
- sqlspec/adapters/asyncmy/data_dictionary.py +41 -2
- sqlspec/adapters/asyncmy/driver.py +293 -62
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +2 -1
- sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
- sqlspec/adapters/asyncpg/_types.py +11 -7
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +450 -0
- sqlspec/adapters/asyncpg/config.py +57 -36
- sqlspec/adapters/asyncpg/data_dictionary.py +41 -2
- sqlspec/adapters/asyncpg/driver.py +153 -23
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +253 -0
- sqlspec/adapters/bigquery/_types.py +1 -1
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +576 -0
- sqlspec/adapters/bigquery/config.py +25 -11
- sqlspec/adapters/bigquery/data_dictionary.py +42 -2
- sqlspec/adapters/bigquery/driver.py +352 -144
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +55 -23
- sqlspec/adapters/duckdb/_types.py +2 -2
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +553 -0
- sqlspec/adapters/duckdb/config.py +79 -21
- sqlspec/adapters/duckdb/data_dictionary.py +41 -2
- sqlspec/adapters/duckdb/driver.py +138 -43
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +5 -5
- sqlspec/adapters/duckdb/type_converter.py +51 -21
- sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
- sqlspec/adapters/oracledb/_types.py +20 -2
- sqlspec/adapters/oracledb/adk/__init__.py +5 -0
- sqlspec/adapters/oracledb/adk/store.py +1745 -0
- sqlspec/adapters/oracledb/config.py +120 -36
- sqlspec/adapters/oracledb/data_dictionary.py +87 -20
- sqlspec/adapters/oracledb/driver.py +292 -84
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +767 -0
- sqlspec/adapters/oracledb/migrations.py +316 -25
- sqlspec/adapters/oracledb/type_converter.py +91 -16
- sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
- sqlspec/adapters/psqlpy/_types.py +2 -1
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +482 -0
- sqlspec/adapters/psqlpy/config.py +45 -19
- sqlspec/adapters/psqlpy/data_dictionary.py +41 -2
- sqlspec/adapters/psqlpy/driver.py +101 -31
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +40 -11
- sqlspec/adapters/psycopg/_type_handlers.py +80 -0
- sqlspec/adapters/psycopg/_types.py +2 -1
- sqlspec/adapters/psycopg/adk/__init__.py +5 -0
- sqlspec/adapters/psycopg/adk/store.py +944 -0
- sqlspec/adapters/psycopg/config.py +65 -37
- sqlspec/adapters/psycopg/data_dictionary.py +77 -3
- sqlspec/adapters/psycopg/driver.py +200 -78
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/sqlite/__init__.py +2 -1
- sqlspec/adapters/sqlite/_type_handlers.py +86 -0
- sqlspec/adapters/sqlite/_types.py +1 -1
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +572 -0
- sqlspec/adapters/sqlite/config.py +85 -16
- sqlspec/adapters/sqlite/data_dictionary.py +34 -2
- sqlspec/adapters/sqlite/driver.py +120 -52
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +5 -5
- sqlspec/base.py +45 -26
- sqlspec/builder/__init__.py +73 -4
- sqlspec/builder/_base.py +91 -58
- sqlspec/builder/_column.py +5 -5
- sqlspec/builder/_ddl.py +98 -89
- sqlspec/builder/_delete.py +5 -4
- sqlspec/builder/_dml.py +388 -0
- sqlspec/{_sql.py → builder/_factory.py} +41 -44
- sqlspec/builder/_insert.py +5 -82
- sqlspec/builder/{mixins/_join_operations.py → _join.py} +145 -143
- sqlspec/builder/_merge.py +446 -11
- sqlspec/builder/_parsing_utils.py +9 -11
- sqlspec/builder/_select.py +1313 -25
- sqlspec/builder/_update.py +11 -42
- sqlspec/cli.py +76 -69
- sqlspec/config.py +231 -60
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +18 -18
- sqlspec/core/compiler.py +6 -8
- sqlspec/core/filters.py +37 -37
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +76 -45
- sqlspec/core/result.py +102 -46
- sqlspec/core/splitter.py +16 -17
- sqlspec/core/statement.py +32 -31
- sqlspec/core/type_conversion.py +3 -2
- sqlspec/driver/__init__.py +1 -3
- sqlspec/driver/_async.py +95 -161
- sqlspec/driver/_common.py +133 -80
- sqlspec/driver/_sync.py +95 -162
- sqlspec/driver/mixins/_result_tools.py +20 -236
- sqlspec/driver/mixins/_sql_translator.py +4 -4
- sqlspec/exceptions.py +70 -7
- sqlspec/extensions/adk/__init__.py +53 -0
- sqlspec/extensions/adk/_types.py +51 -0
- sqlspec/extensions/adk/converters.py +172 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +181 -0
- sqlspec/extensions/adk/store.py +536 -0
- sqlspec/extensions/aiosql/adapter.py +73 -53
- sqlspec/extensions/litestar/__init__.py +21 -4
- sqlspec/extensions/litestar/cli.py +54 -10
- sqlspec/extensions/litestar/config.py +59 -266
- sqlspec/extensions/litestar/handlers.py +46 -17
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +324 -223
- sqlspec/extensions/litestar/providers.py +25 -25
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/loader.py +30 -49
- sqlspec/migrations/base.py +200 -76
- sqlspec/migrations/commands.py +591 -62
- sqlspec/migrations/context.py +6 -9
- sqlspec/migrations/fix.py +199 -0
- sqlspec/migrations/loaders.py +47 -19
- sqlspec/migrations/runner.py +241 -75
- sqlspec/migrations/tracker.py +237 -21
- sqlspec/migrations/utils.py +51 -3
- sqlspec/migrations/validation.py +177 -0
- sqlspec/protocols.py +66 -36
- sqlspec/storage/_utils.py +98 -0
- sqlspec/storage/backends/fsspec.py +134 -106
- sqlspec/storage/backends/local.py +78 -51
- sqlspec/storage/backends/obstore.py +278 -162
- sqlspec/storage/registry.py +75 -39
- sqlspec/typing.py +14 -84
- sqlspec/utils/config_resolver.py +6 -6
- sqlspec/utils/correlation.py +4 -5
- sqlspec/utils/data_transformation.py +3 -2
- sqlspec/utils/deprecation.py +9 -8
- sqlspec/utils/fixtures.py +4 -4
- sqlspec/utils/logging.py +46 -6
- sqlspec/utils/module_loader.py +2 -2
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +3 -3
- sqlspec/utils/sync_tools.py +21 -17
- sqlspec/utils/text.py +1 -2
- sqlspec/utils/type_guards.py +111 -20
- sqlspec/utils/version.py +433 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
- sqlspec-0.27.0.dist-info/RECORD +207 -0
- sqlspec/builder/mixins/__init__.py +0 -55
- sqlspec/builder/mixins/_cte_and_set_ops.py +0 -253
- sqlspec/builder/mixins/_delete_operations.py +0 -50
- sqlspec/builder/mixins/_insert_operations.py +0 -282
- sqlspec/builder/mixins/_merge_operations.py +0 -698
- sqlspec/builder/mixins/_order_limit_operations.py +0 -145
- sqlspec/builder/mixins/_pivot_operations.py +0 -157
- sqlspec/builder/mixins/_select_operations.py +0 -930
- sqlspec/builder/mixins/_update_operations.py +0 -199
- sqlspec/builder/mixins/_where_clause.py +0 -1298
- sqlspec-0.26.0.dist-info/RECORD +0 -157
- sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,276 +1,69 @@
|
|
|
1
|
-
|
|
2
|
-
from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union, cast
|
|
1
|
+
"""Configuration types for Litestar session store extension."""
|
|
3
2
|
|
|
4
|
-
from
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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,
|
|
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(
|
|
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
|
|
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: "
|
|
70
|
-
extra_rollback_statuses: "
|
|
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
|
|
119
|
+
if is_async:
|
|
120
|
+
await connection.commit()
|
|
121
|
+
else:
|
|
112
122
|
await ensure_async_(connection.commit)()
|
|
113
|
-
elif
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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]
|