refactor(fc2c-i): sweep blueprints + ml tasks onto shared get_session (covers tasks/ml.py, a survey gap)
This commit is contained in:
@@ -2,27 +2,15 @@
|
||||
|
||||
from quart import Blueprint, jsonify, request
|
||||
|
||||
from ..extensions import make_engine, make_session_factory
|
||||
from ..extensions import get_session
|
||||
from ..services.ml.aliases import AliasService
|
||||
|
||||
aliases_bp = Blueprint("aliases", __name__, url_prefix="/api")
|
||||
|
||||
_engine = None
|
||||
_Session = None
|
||||
|
||||
|
||||
def _session_factory():
|
||||
global _engine, _Session
|
||||
if _engine is None:
|
||||
_engine = make_engine()
|
||||
_Session = make_session_factory(_engine)
|
||||
return _Session
|
||||
|
||||
|
||||
@aliases_bp.route("/aliases", methods=["GET"])
|
||||
async def list_aliases():
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
rows = await AliasService(session).list_all()
|
||||
return jsonify(
|
||||
[
|
||||
@@ -43,8 +31,7 @@ async def create_alias():
|
||||
required = {"alias_string", "alias_category", "canonical_tag_id"}
|
||||
if not body or not required.issubset(body):
|
||||
return jsonify({"error": f"required: {sorted(required)}"}), 400
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
await AliasService(session).create(
|
||||
body["alias_string"],
|
||||
body["alias_category"],
|
||||
@@ -58,8 +45,7 @@ async def create_alias():
|
||||
"/aliases/<alias_string>/<alias_category>", methods=["DELETE"]
|
||||
)
|
||||
async def remove_alias(alias_string: str, alias_category: str):
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
await AliasService(session).remove(alias_string, alias_category)
|
||||
await session.commit()
|
||||
return "", 204
|
||||
|
||||
@@ -2,28 +2,16 @@
|
||||
|
||||
from quart import Blueprint, jsonify, request
|
||||
|
||||
from ..extensions import make_engine, make_session_factory
|
||||
from ..extensions import get_session
|
||||
from ..models import TagAllowlist
|
||||
from ..services.ml.allowlist import AllowlistService
|
||||
|
||||
allowlist_bp = Blueprint("allowlist", __name__, url_prefix="/api")
|
||||
|
||||
_engine = None
|
||||
_Session = None
|
||||
|
||||
|
||||
def _session_factory():
|
||||
global _engine, _Session
|
||||
if _engine is None:
|
||||
_engine = make_engine()
|
||||
_Session = make_session_factory(_engine)
|
||||
return _Session
|
||||
|
||||
|
||||
@allowlist_bp.route("/allowlist", methods=["GET"])
|
||||
async def list_allowlist():
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
rows = await AllowlistService(session).list_all()
|
||||
return jsonify(
|
||||
[
|
||||
@@ -40,8 +28,7 @@ async def list_allowlist():
|
||||
|
||||
@allowlist_bp.route("/tags/<int:tag_id>/allowlist", methods=["GET"])
|
||||
async def get_one(tag_id: int):
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
row = await session.get(TagAllowlist, tag_id)
|
||||
if row is None:
|
||||
return jsonify({"error": "not on allowlist"}), 404
|
||||
@@ -58,8 +45,7 @@ async def patch_threshold(tag_id: int):
|
||||
mc = float(body["min_confidence"])
|
||||
if not (0 < mc <= 1):
|
||||
return jsonify({"error": "min_confidence must be in (0, 1]"}), 400
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
await AllowlistService(session).update_threshold(tag_id, mc)
|
||||
await session.commit()
|
||||
return "", 204
|
||||
@@ -67,8 +53,7 @@ async def patch_threshold(tag_id: int):
|
||||
|
||||
@allowlist_bp.route("/tags/<int:tag_id>/allowlist", methods=["DELETE"])
|
||||
async def remove(tag_id: int):
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
await AllowlistService(session).remove(tag_id)
|
||||
await session.commit()
|
||||
return "", 204
|
||||
|
||||
@@ -2,22 +2,11 @@
|
||||
|
||||
from quart import Blueprint, jsonify, request
|
||||
|
||||
from ..extensions import make_engine, make_session_factory
|
||||
from ..extensions import get_session
|
||||
from ..services.gallery_service import GalleryService
|
||||
|
||||
gallery_bp = Blueprint("gallery", __name__, url_prefix="/api/gallery")
|
||||
|
||||
_engine = None
|
||||
_Session = None
|
||||
|
||||
|
||||
def _session_factory():
|
||||
global _engine, _Session
|
||||
if _engine is None:
|
||||
_engine = make_engine()
|
||||
_Session = make_session_factory(_engine)
|
||||
return _Session
|
||||
|
||||
|
||||
@gallery_bp.route("/scroll", methods=["GET"])
|
||||
async def scroll():
|
||||
@@ -29,8 +18,7 @@ async def scroll():
|
||||
tag_id_raw = request.args.get("tag_id")
|
||||
tag_id = int(tag_id_raw) if tag_id_raw else None
|
||||
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
svc = GalleryService(session)
|
||||
try:
|
||||
page = await svc.scroll(cursor=cursor, limit=limit, tag_id=tag_id)
|
||||
@@ -63,8 +51,7 @@ async def scroll():
|
||||
async def timeline():
|
||||
tag_id_raw = request.args.get("tag_id")
|
||||
tag_id = int(tag_id_raw) if tag_id_raw else None
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
svc = GalleryService(session)
|
||||
buckets = await svc.timeline(tag_id=tag_id)
|
||||
return jsonify([{"year": b.year, "month": b.month, "count": b.count} for b in buckets])
|
||||
@@ -79,8 +66,7 @@ async def jump():
|
||||
return jsonify({"error": "year and month query params required"}), 400
|
||||
tag_id_raw = request.args.get("tag_id")
|
||||
tag_id = int(tag_id_raw) if tag_id_raw else None
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
svc = GalleryService(session)
|
||||
cursor = await svc.jump_cursor(year=year, month=month, tag_id=tag_id)
|
||||
return jsonify({"cursor": cursor})
|
||||
@@ -88,8 +74,7 @@ async def jump():
|
||||
|
||||
@gallery_bp.route("/image/<int:image_id>", methods=["GET"])
|
||||
async def image_detail(image_id: int):
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
svc = GalleryService(session)
|
||||
payload = await svc.get_image_with_tags(image_id)
|
||||
if payload is None:
|
||||
|
||||
@@ -5,22 +5,11 @@ from datetime import UTC, datetime, timedelta
|
||||
from quart import Blueprint, jsonify, request
|
||||
from sqlalchemy import delete, select, update
|
||||
|
||||
from ..extensions import make_engine, make_session_factory
|
||||
from ..extensions import get_session
|
||||
from ..models import ImportBatch, ImportTask
|
||||
|
||||
import_admin_bp = Blueprint("import_admin", __name__, url_prefix="/api/import")
|
||||
|
||||
_engine = None
|
||||
_Session = None
|
||||
|
||||
|
||||
def _session_factory():
|
||||
global _engine, _Session
|
||||
if _engine is None:
|
||||
_engine = make_engine()
|
||||
_Session = make_session_factory(_engine)
|
||||
return _Session
|
||||
|
||||
|
||||
@import_admin_bp.route("/trigger", methods=["POST"])
|
||||
async def trigger_scan():
|
||||
@@ -39,8 +28,7 @@ async def trigger_scan():
|
||||
|
||||
@import_admin_bp.route("/status", methods=["GET"])
|
||||
async def status():
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
active = (
|
||||
await session.execute(
|
||||
select(ImportBatch)
|
||||
@@ -72,8 +60,7 @@ async def list_tasks():
|
||||
cursor_raw = request.args.get("cursor")
|
||||
cursor_id = int(cursor_raw) if cursor_raw else None
|
||||
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
stmt = select(ImportTask).order_by(ImportTask.created_at.desc(), ImportTask.id.desc())
|
||||
if status_filter:
|
||||
stmt = stmt.where(ImportTask.status == status_filter)
|
||||
@@ -107,8 +94,7 @@ async def list_tasks():
|
||||
|
||||
@import_admin_bp.route("/retry-failed", methods=["POST"])
|
||||
async def retry_failed():
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
failed_ids = (
|
||||
await session.execute(select(ImportTask.id).where(ImportTask.status == "failed"))
|
||||
).scalars().all()
|
||||
@@ -140,8 +126,7 @@ async def clear_completed():
|
||||
datetime.now(UTC) - timedelta(days=int(age_days)) if age_days else None
|
||||
)
|
||||
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
stmt = delete(ImportTask).where(ImportTask.status.in_(status_filter))
|
||||
if cutoff is not None:
|
||||
stmt = stmt.where(ImportTask.finished_at < cutoff)
|
||||
|
||||
@@ -2,22 +2,11 @@
|
||||
|
||||
from quart import Blueprint, jsonify, request
|
||||
|
||||
from ..extensions import make_engine, make_session_factory
|
||||
from ..extensions import get_session
|
||||
from ..models import MLSettings
|
||||
|
||||
ml_admin_bp = Blueprint("ml_admin", __name__, url_prefix="/api/ml")
|
||||
|
||||
_engine = None
|
||||
_Session = None
|
||||
|
||||
|
||||
def _session_factory():
|
||||
global _engine, _Session
|
||||
if _engine is None:
|
||||
_engine = make_engine()
|
||||
_Session = make_session_factory(_engine)
|
||||
return _Session
|
||||
|
||||
|
||||
_EDITABLE = (
|
||||
"suggestion_threshold_artist",
|
||||
@@ -33,8 +22,7 @@ _EDITABLE = (
|
||||
async def get_settings():
|
||||
from sqlalchemy import select
|
||||
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
s = (
|
||||
await session.execute(select(MLSettings).where(MLSettings.id == 1))
|
||||
).scalar_one()
|
||||
@@ -59,8 +47,7 @@ async def patch_settings():
|
||||
body = await request.get_json()
|
||||
if not isinstance(body, dict):
|
||||
return jsonify({"error": "body must be an object"}), 400
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
s = (
|
||||
await session.execute(select(MLSettings).where(MLSettings.id == 1))
|
||||
).scalar_one()
|
||||
|
||||
@@ -3,22 +3,11 @@
|
||||
from quart import Blueprint, jsonify, request
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from ..extensions import make_engine, make_session_factory
|
||||
from ..extensions import get_session
|
||||
from ..models import ImageRecord, ImportBatch, ImportSettings, ImportTask, Tag
|
||||
|
||||
settings_bp = Blueprint("settings", __name__, url_prefix="/api")
|
||||
|
||||
_engine = None
|
||||
_Session = None
|
||||
|
||||
|
||||
def _session_factory():
|
||||
global _engine, _Session
|
||||
if _engine is None:
|
||||
_engine = make_engine()
|
||||
_Session = make_session_factory(_engine)
|
||||
return _Session
|
||||
|
||||
|
||||
_EDITABLE_FIELDS = (
|
||||
"min_width",
|
||||
@@ -33,8 +22,7 @@ _EDITABLE_FIELDS = (
|
||||
|
||||
@settings_bp.route("/settings/import", methods=["GET"])
|
||||
async def get_import_settings():
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
row = (
|
||||
await session.execute(select(ImportSettings).where(ImportSettings.id == 1))
|
||||
).scalar_one()
|
||||
@@ -55,8 +43,7 @@ async def update_import_settings():
|
||||
if not isinstance(body, dict):
|
||||
return jsonify({"error": "body must be a JSON object"}), 400
|
||||
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
row = (
|
||||
await session.execute(select(ImportSettings).where(ImportSettings.id == 1))
|
||||
).scalar_one()
|
||||
@@ -70,8 +57,7 @@ async def update_import_settings():
|
||||
|
||||
@settings_bp.route("/system/stats", methods=["GET"])
|
||||
async def system_stats():
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
total_images = (await session.execute(select(func.count(ImageRecord.id)))).scalar_one()
|
||||
total_tags = (await session.execute(select(func.count(Tag.id)))).scalar_one()
|
||||
storage_bytes = (
|
||||
|
||||
@@ -2,28 +2,16 @@
|
||||
|
||||
from quart import Blueprint, jsonify, request
|
||||
|
||||
from ..extensions import make_engine, make_session_factory
|
||||
from ..extensions import get_session
|
||||
from ..services.ml.allowlist import AllowlistService
|
||||
from ..services.ml.suggestions import SuggestionService
|
||||
|
||||
suggestions_bp = Blueprint("suggestions", __name__, url_prefix="/api")
|
||||
|
||||
_engine = None
|
||||
_Session = None
|
||||
|
||||
|
||||
def _session_factory():
|
||||
global _engine, _Session
|
||||
if _engine is None:
|
||||
_engine = make_engine()
|
||||
_Session = make_session_factory(_engine)
|
||||
return _Session
|
||||
|
||||
|
||||
@suggestions_bp.route("/images/<int:image_id>/suggestions", methods=["GET"])
|
||||
async def get_suggestions(image_id: int):
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
sl = await SuggestionService(session).for_image(image_id)
|
||||
return jsonify(
|
||||
{
|
||||
@@ -53,8 +41,7 @@ async def accept_suggestion(image_id: int):
|
||||
if not body or "tag_id" not in body:
|
||||
return jsonify({"error": "tag_id required"}), 400
|
||||
tag_id = body["tag_id"]
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
newly_added = await AllowlistService(session).accept(image_id, tag_id)
|
||||
await session.commit()
|
||||
if newly_added:
|
||||
@@ -72,8 +59,7 @@ async def alias_suggestion(image_id: int):
|
||||
required = {"alias_string", "alias_category", "canonical_tag_id"}
|
||||
if not body or not required.issubset(body):
|
||||
return jsonify({"error": f"required: {sorted(required)}"}), 400
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
newly_added = await AllowlistService(session).add_alias_and_accept(
|
||||
image_id,
|
||||
body["alias_string"],
|
||||
@@ -95,8 +81,7 @@ async def dismiss_suggestion(image_id: int):
|
||||
body = await request.get_json()
|
||||
if not body or "tag_id" not in body:
|
||||
return jsonify({"error": "tag_id required"}), 400
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
await AllowlistService(session).dismiss(image_id, body["tag_id"])
|
||||
await session.commit()
|
||||
return "", 204
|
||||
|
||||
+7
-24
@@ -2,23 +2,12 @@
|
||||
|
||||
from quart import Blueprint, jsonify, request
|
||||
|
||||
from ..extensions import make_engine, make_session_factory
|
||||
from ..extensions import get_session
|
||||
from ..models import TagKind
|
||||
from ..services.tag_service import TagService, TagValidationError
|
||||
|
||||
tags_bp = Blueprint("tags", __name__, url_prefix="/api")
|
||||
|
||||
_engine = None
|
||||
_Session = None
|
||||
|
||||
|
||||
def _session_factory():
|
||||
global _engine, _Session
|
||||
if _engine is None:
|
||||
_engine = make_engine()
|
||||
_Session = make_session_factory(_engine)
|
||||
return _Session
|
||||
|
||||
|
||||
def _coerce_kind(raw: str | None) -> TagKind | None:
|
||||
if raw is None:
|
||||
@@ -38,8 +27,7 @@ async def autocomplete():
|
||||
except ValueError:
|
||||
return jsonify({"error": "limit must be an integer"}), 400
|
||||
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
svc = TagService(session)
|
||||
hits = await svc.autocomplete(q, kind=kind, limit=limit)
|
||||
|
||||
@@ -69,8 +57,7 @@ async def create_tag():
|
||||
return jsonify({"error": f"invalid kind {body['kind']!r}"}), 400
|
||||
fandom_id = body.get("fandom_id")
|
||||
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
svc = TagService(session)
|
||||
try:
|
||||
tag = await svc.find_or_create(name, kind, fandom_id=fandom_id)
|
||||
@@ -84,8 +71,7 @@ async def create_tag():
|
||||
|
||||
@tags_bp.route("/images/<int:image_id>/tags", methods=["GET"])
|
||||
async def list_tags_for_image(image_id: int):
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
svc = TagService(session)
|
||||
tags = await svc.list_for_image(image_id)
|
||||
return jsonify(
|
||||
@@ -108,8 +94,7 @@ async def add_tag_to_image(image_id: int):
|
||||
return jsonify({"error": "tag_id required"}), 400
|
||||
source = body.get("source", "manual")
|
||||
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
svc = TagService(session)
|
||||
await svc.add_to_image(image_id, body["tag_id"], source=source)
|
||||
await session.commit()
|
||||
@@ -118,8 +103,7 @@ async def add_tag_to_image(image_id: int):
|
||||
|
||||
@tags_bp.route("/images/<int:image_id>/tags/<int:tag_id>", methods=["DELETE"])
|
||||
async def remove_tag_from_image(image_id: int, tag_id: int):
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
svc = TagService(session)
|
||||
await svc.remove_from_image(image_id, tag_id)
|
||||
await session.commit()
|
||||
@@ -131,8 +115,7 @@ async def rename_tag(tag_id: int):
|
||||
body = await request.get_json()
|
||||
if not body or "name" not in body:
|
||||
return jsonify({"error": "name required"}), 400
|
||||
Session = _session_factory()
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
svc = TagService(session)
|
||||
try:
|
||||
tag = await svc.rename(tag_id, body["name"])
|
||||
|
||||
@@ -305,13 +305,11 @@ def _confidence_for_tag(session, tag, preds: dict) -> float | None:
|
||||
def recompute_centroid(self, tag_id: int) -> bool:
|
||||
import asyncio
|
||||
|
||||
from ..extensions import make_engine, make_session_factory
|
||||
from ..extensions import get_session
|
||||
from ..services.ml.centroids import CentroidService
|
||||
|
||||
async def _run() -> bool:
|
||||
engine = make_engine()
|
||||
Session = make_session_factory(engine)
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
svc = CentroidService(session)
|
||||
result = await svc.recompute_for_tag(tag_id)
|
||||
await session.commit()
|
||||
@@ -325,13 +323,11 @@ def recompute_centroids(self) -> int:
|
||||
"""Daily: find drifted centroids, enqueue recompute_centroid for each."""
|
||||
import asyncio
|
||||
|
||||
from ..extensions import make_engine, make_session_factory
|
||||
from ..extensions import get_session
|
||||
from ..services.ml.centroids import CentroidService
|
||||
|
||||
async def _list() -> list[int]:
|
||||
engine = make_engine()
|
||||
Session = make_session_factory(engine)
|
||||
async with Session() as session:
|
||||
async with get_session() as session:
|
||||
return await CentroidService(session).list_drifted()
|
||||
|
||||
drifted = asyncio.run(_list())
|
||||
|
||||
Reference in New Issue
Block a user