Fix: resolved sqlite concurrency errors (table users already exists) by moving DDL to explicit async initialize() and implementing lazy singleton dependency.
Some checks failed
CI / SAST (bandit) (push) Successful in 15s
CI / Lint (ruff) (push) Failing after 18s
CI / Dependency audit (pip-audit) (push) Successful in 26s
CI / Test (Standard) (3.11) (push) Has been skipped
CI / Test (Standard) (3.12) (push) Has been skipped
CI / Test (Live) (3.11) (push) Has been skipped
CI / Test (Fuzz) (3.11) (push) Has been skipped
CI / Merge dev → testing (push) Has been skipped
CI / Prepare Merge to Main (push) Has been skipped
CI / Finalize Merge to Main (push) Has been skipped
Some checks failed
CI / SAST (bandit) (push) Successful in 15s
CI / Lint (ruff) (push) Failing after 18s
CI / Dependency audit (pip-audit) (push) Successful in 26s
CI / Test (Standard) (3.11) (push) Has been skipped
CI / Test (Standard) (3.12) (push) Has been skipped
CI / Test (Live) (3.11) (push) Has been skipped
CI / Test (Fuzz) (3.11) (push) Has been skipped
CI / Merge dev → testing (push) Has been skipped
CI / Prepare Merge to Main (push) Has been skipped
CI / Finalize Merge to Main (push) Has been skipped
This commit is contained in:
BIN
decnet.db-shm
BIN
decnet.db-shm
Binary file not shown.
BIN
decnet.db-wal
BIN
decnet.db-wal
Binary file not shown.
@@ -305,16 +305,18 @@ def mutate(
|
|||||||
from decnet.mutator import mutate_decky, mutate_all, run_watch_loop
|
from decnet.mutator import mutate_decky, mutate_all, run_watch_loop
|
||||||
from decnet.web.dependencies import repo
|
from decnet.web.dependencies import repo
|
||||||
|
|
||||||
|
async def _run() -> None:
|
||||||
|
await repo.initialize()
|
||||||
if watch:
|
if watch:
|
||||||
asyncio.run(run_watch_loop(repo))
|
await run_watch_loop(repo)
|
||||||
return
|
elif decky_name:
|
||||||
|
await mutate_decky(decky_name, repo)
|
||||||
if decky_name:
|
|
||||||
asyncio.run(mutate_decky(decky_name, repo))
|
|
||||||
elif force_all:
|
elif force_all:
|
||||||
asyncio.run(mutate_all(force=True, repo=repo))
|
await mutate_all(force=True, repo=repo)
|
||||||
else:
|
else:
|
||||||
asyncio.run(mutate_all(force=False, repo=repo))
|
await mutate_all(force=False, repo=repo)
|
||||||
|
|
||||||
|
asyncio.run(_run())
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
|
|||||||
@@ -25,34 +25,27 @@ class SQLiteRepository(BaseRepository):
|
|||||||
self.session_factory = async_sessionmaker(
|
self.session_factory = async_sessionmaker(
|
||||||
self.engine, class_=AsyncSession, expire_on_commit=False
|
self.engine, class_=AsyncSession, expire_on_commit=False
|
||||||
)
|
)
|
||||||
self._initialize_sync()
|
|
||||||
|
|
||||||
def _initialize_sync(self) -> None:
|
|
||||||
"""Initialize the database schema synchronously."""
|
|
||||||
init_db(self.db_path)
|
|
||||||
|
|
||||||
from decnet.web.db.sqlite.database import get_sync_engine
|
|
||||||
engine = get_sync_engine(self.db_path)
|
|
||||||
with engine.connect() as conn:
|
|
||||||
conn.execute(
|
|
||||||
text(
|
|
||||||
"INSERT OR IGNORE INTO users (uuid, username, password_hash, role, must_change_password) "
|
|
||||||
"VALUES (:uuid, :u, :p, :r, :m)"
|
|
||||||
),
|
|
||||||
{
|
|
||||||
"uuid": str(uuid.uuid4()),
|
|
||||||
"u": DECNET_ADMIN_USER,
|
|
||||||
"p": get_password_hash(DECNET_ADMIN_PASSWORD),
|
|
||||||
"r": "admin",
|
|
||||||
"m": 1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
async def initialize(self) -> None:
|
async def initialize(self) -> None:
|
||||||
"""Async warm-up / verification."""
|
"""Async warm-up / verification. Creates tables if they don't exist."""
|
||||||
|
from sqlmodel import SQLModel
|
||||||
|
async with self.engine.begin() as conn:
|
||||||
|
await conn.run_sync(SQLModel.metadata.create_all)
|
||||||
|
|
||||||
async with self.session_factory() as session:
|
async with self.session_factory() as session:
|
||||||
await session.execute(text("SELECT 1"))
|
# Check if admin exists
|
||||||
|
result = await session.execute(
|
||||||
|
select(User).where(User.username == DECNET_ADMIN_USER)
|
||||||
|
)
|
||||||
|
if not result.scalar_one_or_none():
|
||||||
|
session.add(User(
|
||||||
|
uuid=str(uuid.uuid4()),
|
||||||
|
username=DECNET_ADMIN_USER,
|
||||||
|
password_hash=get_password_hash(DECNET_ADMIN_PASSWORD),
|
||||||
|
role="admin",
|
||||||
|
must_change_password=True,
|
||||||
|
))
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
async def reinitialize(self) -> None:
|
async def reinitialize(self) -> None:
|
||||||
"""Initialize the database schema asynchronously (useful for tests)."""
|
"""Initialize the database schema asynchronously (useful for tests)."""
|
||||||
|
|||||||
@@ -9,11 +9,16 @@ from decnet.web.db.repository import BaseRepository
|
|||||||
from decnet.web.db.factory import get_repository
|
from decnet.web.db.factory import get_repository
|
||||||
|
|
||||||
# Shared repository singleton
|
# Shared repository singleton
|
||||||
repo: BaseRepository = get_repository()
|
_repo: Optional[BaseRepository] = None
|
||||||
|
|
||||||
def get_repo() -> BaseRepository:
|
def get_repo() -> BaseRepository:
|
||||||
"""FastAPI dependency to inject the configured repository."""
|
"""FastAPI dependency to inject the configured repository."""
|
||||||
return repo
|
global _repo
|
||||||
|
if _repo is None:
|
||||||
|
_repo = get_repository()
|
||||||
|
return _repo
|
||||||
|
|
||||||
|
repo = get_repo()
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ from ..conftest import _FUZZ_SETTINGS
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def repo(tmp_path):
|
async def repo(tmp_path):
|
||||||
return get_repository(db_path=str(tmp_path / "histogram_test.db"))
|
r = get_repository(db_path=str(tmp_path / "histogram_test.db"))
|
||||||
|
await r.initialize()
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
def _log(decky="d", service="ssh", ip="1.2.3.4", timestamp=None):
|
def _log(decky="d", service="ssh", ip="1.2.3.4", timestamp=None):
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ from .conftest import _FUZZ_SETTINGS
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def repo(tmp_path):
|
async def repo(tmp_path):
|
||||||
return get_repository(db_path=str(tmp_path / "test.db"))
|
r = get_repository(db_path=str(tmp_path / "test.db"))
|
||||||
|
await r.initialize()
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
|
|||||||
Reference in New Issue
Block a user