Skip to content

Commit 54d6a63

Browse files
authored
feat: add create_all configuration parameter for Litestar (#70)
1 parent 3a2ced4 commit 54d6a63

8 files changed

Lines changed: 87 additions & 2 deletions

File tree

advanced_alchemy/config/asyncio.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,8 @@ class SQLAlchemyAsyncConfig(GenericSQLAlchemyConfig[AsyncEngine, AsyncSession, a
6363
6464
The configuration options are documented in the Alembic documentation.
6565
"""
66+
67+
def __post_init__(self) -> None:
68+
if self.metadata:
69+
self.alembic_config.target_metadata = self.metadata
70+
super().__post_init__()

advanced_alchemy/config/common.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ class GenericSQLAlchemyConfig(Generic[EngineT, SessionT, SessionMakerT]):
117117
118118
If set, the plugin will use the provided instance rather than instantiate an engine.
119119
"""
120+
create_all: bool = False
121+
"""If true, all models are automatically created on engine creation."""
122+
123+
metadata: MetaData | None = None
124+
"""Optional metadata to use.
125+
126+
If set, the plugin will use the provided instance rather than the default metadata."""
120127

121128
def __post_init__(self) -> None:
122129
if self.connection_string is not None and self.engine_instance is not None:

advanced_alchemy/config/sync.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,8 @@ class SQLAlchemySyncConfig(GenericSQLAlchemyConfig[Engine, Session, sessionmaker
4848
4949
The configuration options are documented in the Alembic documentation.
5050
"""
51+
52+
def __post_init__(self) -> None:
53+
if self.metadata:
54+
self.alembic_config.target_metadata = self.metadata
55+
super().__post_init__()

advanced_alchemy/extensions/litestar/plugins/init/config/asyncio.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def autocommit_handler_maker(
5353
extra_commit_statuses: set[int] | None = None,
5454
extra_rollback_statuses: set[int] | None = None,
5555
) -> Callable[[Message, Scope], Coroutine[Any, Any, None]]:
56-
"""Set up the handler to issue a transactin commit or rollback based on specified status codes
56+
"""Set up the handler to issue a transaction commit or rollback based on specified status codes
5757
Args:
5858
commit_on_redirect: Issue a commit when the response status is a redirect (``3XX``)
5959
extra_commit_statuses: A set of additional status codes that trigger a commit
@@ -196,6 +196,15 @@ async def on_shutdown(self, app: Litestar) -> None:
196196
engine = cast("AsyncEngine", app.state.pop(self.engine_app_state_key))
197197
await engine.dispose()
198198

199+
async def create_all_metadata(self, app: Litestar) -> None:
200+
"""Create all metadata
201+
202+
Args:
203+
app (Litestar): The ``Litestar`` instance
204+
"""
205+
async with self.get_engine().begin() as conn:
206+
await conn.run_sync(self.alembic_config.target_metadata.create_all)
207+
199208
def create_app_state_items(self) -> dict[str, Any]:
200209
"""Key/value pairs to be stored in application state."""
201210
return {

advanced_alchemy/extensions/litestar/plugins/init/config/sync.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,15 @@ def on_shutdown(self, app: Litestar) -> None:
195195
engine = cast("Engine", app.state.pop(self.engine_app_state_key))
196196
engine.dispose()
197197

198+
def create_all_metadata(self, app: Litestar) -> None:
199+
"""Create all metadata
200+
201+
Args:
202+
app (Litestar): The ``Litestar`` instance
203+
"""
204+
with self.get_engine().begin() as conn:
205+
self.alembic_config.target_metadata.create_all(bind=conn)
206+
198207
def create_app_state_items(self) -> dict[str, Any]:
199208
"""Key/value pairs to be stored in application state."""
200209
return {

advanced_alchemy/extensions/litestar/plugins/init/plugin.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ def on_app_init(self, app_config: AppConfig) -> AppConfig:
7777
)
7878
app_config.before_send.append(self._config.before_send_handler)
7979
app_config.on_startup.insert(0, self._config.update_app_state)
80+
if self._config.create_all:
81+
app_config.on_startup.append(self._config.create_all_metadata)
8082
app_config.on_shutdown.append(self._config.on_shutdown)
8183
app_config.signature_namespace.update(self._config.signature_namespace)
8284
app_config.signature_namespace.update(signature_namespace_values)

tests/unit/test_extensions/test_litestar/test_init_plugin/test_asyncio.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from litestar.testing import create_test_client
99
from litestar.types.asgi_types import HTTPResponseStartEvent
1010
from litestar.utils import set_litestar_scope_state
11+
from pytest import MonkeyPatch
1112
from sqlalchemy.ext.asyncio import AsyncSession
1213

1314
from advanced_alchemy.extensions.litestar.plugins import SQLAlchemyAsyncConfig, SQLAlchemyInitPlugin
@@ -42,6 +43,28 @@ def test_handler(db_session: AsyncSession, scope: Scope) -> None:
4243
assert config.session_dependency_key not in captured_scope_state # pyright: ignore
4344

4445

46+
async def test_create_all_default(monkeypatch: MonkeyPatch) -> None:
47+
"""Test default_before_send_handler."""
48+
49+
config = SQLAlchemyAsyncConfig(connection_string="sqlite+aiosqlite://")
50+
plugin = SQLAlchemyInitPlugin(config=config)
51+
mock_fx = MagicMock()
52+
monkeypatch.setattr(config, "create_all_metadata", mock_fx)
53+
with create_test_client(route_handlers=[], plugins=[plugin]) as _client:
54+
mock_fx.assert_not_called()
55+
56+
57+
async def test_create_all(monkeypatch: MonkeyPatch) -> None:
58+
"""Test default_before_send_handler."""
59+
60+
config = SQLAlchemyAsyncConfig(connection_string="sqlite+aiosqlite://", create_all=True)
61+
plugin = SQLAlchemyInitPlugin(config=config)
62+
mock_fx = MagicMock()
63+
monkeypatch.setattr(config, "create_all_metadata", mock_fx)
64+
with create_test_client(route_handlers=[], plugins=[plugin]) as _client:
65+
mock_fx.assert_called_once()
66+
67+
4568
async def test_before_send_handler_success_response(create_scope: Callable[..., Scope]) -> None:
4669
"""Test that the session is committed given a success response."""
4770
config = SQLAlchemyAsyncConfig(

tests/unit/test_extensions/test_litestar/test_init_plugin/test_sync.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
import random
44
from typing import TYPE_CHECKING
5-
from unittest.mock import MagicMock
5+
from unittest.mock import MagicMock, patch
66

77
from litestar import Litestar, get
88
from litestar.testing import create_test_client
99
from litestar.types.asgi_types import HTTPResponseStartEvent
1010
from litestar.utils import set_litestar_scope_state
11+
from pytest import MonkeyPatch
1112
from sqlalchemy.orm import Session
1213

1314
from advanced_alchemy.extensions.litestar.plugins import (
@@ -45,6 +46,30 @@ def test_handler(db_session: Session, scope: Scope) -> None:
4546
assert config.session_dependency_key not in captured_scope_state
4647

4748

49+
def test_create_all_default(monkeypatch: MonkeyPatch) -> None:
50+
"""Test default_before_send_handler."""
51+
52+
config = SQLAlchemySyncConfig(connection_string="sqlite+aiosqlite://")
53+
plugin = SQLAlchemyInitPlugin(config=config)
54+
with patch.object(
55+
config,
56+
"create_all_metadata",
57+
) as create_all_metadata_mock, create_test_client(route_handlers=[], plugins=[plugin]) as _client:
58+
create_all_metadata_mock.assert_not_called()
59+
60+
61+
def test_create_all(monkeypatch: MonkeyPatch) -> None:
62+
"""Test default_before_send_handler."""
63+
64+
config = SQLAlchemySyncConfig(connection_string="sqlite+aiosqlite://", create_all=True)
65+
plugin = SQLAlchemyInitPlugin(config=config)
66+
with patch.object(
67+
config,
68+
"create_all_metadata",
69+
) as create_all_metadata_mock, create_test_client(route_handlers=[], plugins=[plugin]) as _client:
70+
create_all_metadata_mock.assert_called_once()
71+
72+
4873
def test_before_send_handler_success_response(create_scope: Callable[..., Scope]) -> None:
4974
"""Test that the session is committed given a success response."""
5075
config = SQLAlchemySyncConfig(connection_string="sqlite://", before_send_handler=autocommit_before_send_handler)

0 commit comments

Comments
 (0)