Files
DECNET/decnet/web/db/sqlmodel_repo/realism.py
anti 721122a7ef chore(types): enable warn_return_any and cast all no-any-return sites
Turn on mypy warn_return_any (pyproject) and resolve the 84 resulting
[no-any-return] errors across 43 files with typing.cast() at the return
sites — runtime no-ops that make the declared return type explicit where a
dependency (SQLAlchemy scalar/first/one, httpx .json(), subprocess, docker
SDK) hands back Any. No behavior change: no DTO/table field types altered, no
validation/coercion calls added, every cast reflects the true runtime type.

Locks in return-type strictness so the class of bug where a function silently
widens to Any can't regress. mypy decnet/ clean; adversarially verified
behavior-preserving (84 casts 1:1 with prior returns).

Bump tornado 6.5.5 -> 6.5.7 (CVE-2026-49854, transitive via snakeviz).
2026-06-12 18:21:22 -04:00

161 lines
6.2 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
"""Synthetic-file CRUD + realism config key/value store."""
from __future__ import annotations
from datetime import datetime, timedelta, timezone
from typing import Any, Optional, cast
from sqlalchemy import desc, func, select, update
from decnet.web.db.models import RealismConfig, SyntheticFile
from decnet.web.db.models.realism import SYNTHETIC_FILE_BODY_LIMIT
from decnet.web.db.sqlmodel_repo._helpers import _MixinBase
class RealismMixin(_MixinBase):
"""Mixin: composed onto ``SQLModelRepository``."""
async def record_synthetic_file(self, data: dict[str, Any]) -> str:
if "last_body" in data and data["last_body"] is not None:
data = {**data, "last_body": data["last_body"][:SYNTHETIC_FILE_BODY_LIMIT]}
async with self._session() as session:
row = SyntheticFile(**data)
session.add(row)
await session.commit()
await session.refresh(row)
return row.uuid
async def update_synthetic_file(
self, row_uuid: str, data: dict[str, Any],
) -> None:
if "last_body" in data and data["last_body"] is not None:
data = {**data, "last_body": data["last_body"][:SYNTHETIC_FILE_BODY_LIMIT]}
async with self._session() as session:
stmt = (
update(SyntheticFile)
.where(SyntheticFile.uuid == row_uuid)
.values(**data)
)
await session.execute(stmt)
await session.commit()
async def list_synthetic_files(
self,
*,
decky_uuid: Optional[str] = None,
persona: Optional[str] = None,
content_class: Optional[str] = None,
limit: int = 100,
offset: int = 0,
) -> list[dict[str, Any]]:
async with self._session() as session:
stmt = select(SyntheticFile)
if decky_uuid is not None:
stmt = stmt.where(SyntheticFile.decky_uuid == decky_uuid)
if persona is not None:
stmt = stmt.where(SyntheticFile.persona == persona)
if content_class is not None:
stmt = stmt.where(SyntheticFile.content_class == content_class)
stmt = (
stmt.order_by(desc(SyntheticFile.last_modified))
.offset(offset)
.limit(limit)
)
result = await session.execute(stmt)
return [r.model_dump(mode="json") for r in result.scalars().all()]
async def count_synthetic_files(
self,
*,
decky_uuid: Optional[str] = None,
persona: Optional[str] = None,
content_class: Optional[str] = None,
) -> int:
async with self._session() as session:
stmt = select(func.count(SyntheticFile.uuid))
if decky_uuid is not None:
stmt = stmt.where(SyntheticFile.decky_uuid == decky_uuid)
if persona is not None:
stmt = stmt.where(SyntheticFile.persona == persona)
if content_class is not None:
stmt = stmt.where(SyntheticFile.content_class == content_class)
result = await session.execute(stmt)
return int(result.scalar() or 0)
async def get_synthetic_file(
self, uuid: str,
) -> Optional[dict[str, Any]]:
async with self._session() as session:
stmt = select(SyntheticFile).where(SyntheticFile.uuid == uuid)
result = await session.execute(stmt)
row = result.scalars().first()
if row is None:
return None
return cast(dict[str, Any], row.model_dump(mode="json"))
async def get_realism_config(
self, key: str,
) -> Optional[dict[str, Any]]:
async with self._session() as session:
stmt = select(RealismConfig).where(RealismConfig.key == key)
result = await session.execute(stmt)
row = result.scalars().first()
if row is None:
return None
return cast(dict[str, Any], row.model_dump(mode="json"))
async def set_realism_config(
self, key: str, value: str,
) -> None:
"""Upsert one realism_config row. Last-write-wins."""
async with self._session() as session:
stmt = select(RealismConfig).where(RealismConfig.key == key)
result = await session.execute(stmt)
row = result.scalars().first()
if row is None:
session.add(RealismConfig(
key=key, value=value,
updated_at=datetime.now(timezone.utc),
))
else:
upd = (
update(RealismConfig)
.where(RealismConfig.uuid == row.uuid)
.values(value=value, updated_at=datetime.now(timezone.utc))
)
await session.execute(upd)
await session.commit()
async def pick_random_synthetic_file_for_edit(
self,
decky_uuid: str,
*,
max_age_days: int = 30,
) -> Optional[dict[str, Any]]:
# Editable classes: anything whose body is plain text we can
# mutate idempotently. Binary canary artifacts are out — they
# rotate via a fresh plant, not an edit.
editable = (
"note", "todo", "draft", "script", "log_cron", "log_daemon",
)
cutoff = datetime.now(timezone.utc) - timedelta(days=max_age_days)
async with self._session() as session:
stmt = (
select(SyntheticFile)
.where(
SyntheticFile.decky_uuid == decky_uuid,
SyntheticFile.content_class.in_(editable), # type: ignore[attr-defined]
SyntheticFile.last_modified >= cutoff,
)
# SQLite + MySQL both support func.random() / RAND() —
# SQLAlchemy's func.random() compiles per-dialect.
.order_by(func.random())
.limit(1)
)
result = await session.execute(stmt)
row = result.scalars().first()
if row is None:
return None
return cast(dict[str, Any], row.model_dump(mode="json"))