Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Makefile
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
.PHONY: install dev dev-api dev-ui build test lint doctor migrate migration downgrade migration-history docker-up docker-down kill
.PHONY: install dev dev-api dev-ui build test lint doctor migrate migration downgrade migration-history docker-up docker-down kill new-module

# Install
install:
Expand DownExpand Up@@ -48,6 +48,12 @@ downgrade: ## Downgrade one revision
migration-history: ## Show migration history
cd host && uv run alembic history --verbose

# Scaffolding
new-module: ## Scaffold a new module (usage: make new-module name=orders)
@test -n "$(name)" || (echo "Error: Please provide a module name, e.g. make new-module name=orders" && exit 1)
uv run python scripts/new_module.py $(name)
uv sync --all-packages

# Kill dev servers
kill:
@echo "Stopping dev servers..."
Expand Down
35 changes: 27 additions & 8 deletions conftest.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,10 +2,15 @@

from __future__ import annotations

import contextlib
import importlib
from collections.abc import AsyncGenerator
from functools import lru_cache

import httpx
import pytest
from simple_module_core.discovery import discover_modules
from simple_module_db.base import all_module_bases
from simple_module_db.session import DatabaseState, init_db
from simple_module_hosting.settings import Settings
from sqlalchemy.ext.asyncio import (
Expand DownExpand Up@@ -41,13 +46,30 @@ async def engine(db_state: DatabaseState) -> AsyncEngine:
return db_state.engine


@lru_cache(maxsize=1)
def _ensure_models_imported() -> list:
"""Import all module models so all_module_bases is populated (cached)."""
for mod in discover_modules():
pkg = type(mod).__module__.split(".")[0]
with contextlib.suppress(ModuleNotFoundError):
importlib.import_module(f"{pkg}.models")
return list(all_module_bases)


async def _create_all_tables(engine) -> None:
"""Create all module tables in a single connection."""
bases = _ensure_models_imported()
async with engine.begin() as conn:
def _sync_create_all(sync_conn):
for base in bases:
base.metadata.create_all(sync_conn)
await conn.run_sync(_sync_create_all)


@pytest.fixture
async def db_session(db_state: DatabaseState) -> AsyncGenerator[AsyncSession, None]:
"""Yield an async session backed by in-memory SQLite."""
from sm_products.models import Base

async with db_state.engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
await _create_all_tables(db_state.engine)

async with db_state.session_factory() as session:
yield session
Expand All@@ -60,10 +82,7 @@ async def app(settings: Settings):

application = create_app(settings)

from sm_products.models import Base as ProductsBase

async with application.state.db.engine.begin() as conn:
await conn.run_sync(ProductsBase.metadata.create_all)
await _create_all_tables(application.state.db.engine)

# Trigger lifespan startup so app.state.migration is populated
ctx = application.router.lifespan_context(application)
Expand Down
Loading