feat: extract workflow output save phase 3

This commit is contained in:
2026-04-07 09:50:58 +02:00
parent 9c93ecef49
commit 160c198bb3
5 changed files with 232 additions and 81 deletions
@@ -70,10 +70,11 @@ def render_order_line_task(self, order_line_id: str):
emit(order_line_id, "Celery render task started")
try:
from sqlalchemy import create_engine, select, update as sql_update
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session
from app.config import settings as app_settings
from app.domains.rendering.workflow_runtime_services import (
persist_order_line_output,
prepare_order_line_render_context,
resolve_order_line_template_context,
resolve_render_position_context,
@@ -85,7 +86,6 @@ def render_order_line_task(self, order_line_id: str):
with Session(engine) as session:
set_tenant_context_sync(session, _tenant_id)
from app.models.order_line import OrderLine
from pathlib import Path as _Path
setup = prepare_order_line_render_context(
@@ -458,82 +458,18 @@ def render_order_line_task(self, order_line_id: str):
new_status = "completed" if success else "failed"
render_end = datetime.utcnow()
elapsed = (render_end - render_start).total_seconds()
update_values = dict(
render_status=new_status,
render_completed_at=render_end,
render_log=render_log,
)
if success:
update_values["result_path"] = output_path
session.execute(
sql_update(OrderLine)
.where(OrderLine.id == line.id)
.values(**update_values)
)
session.commit()
if success:
# Create MediaAsset so the render appears in the Media Browser
try:
import os as _os
from app.domains.media.models import MediaAsset, MediaAssetType as MAT
from app.config import settings as _cfg2
_ext = str(output_path).rsplit(".", 1)[-1].lower() if "." in str(output_path) else "bin"
_mime = (
"video/mp4" if _ext in ("mp4", "webm")
else "image/webp" if _ext == "webp"
else "image/jpeg" if _ext in ("jpg", "jpeg")
else "image/png"
)
# Extension determines type — poster frames (.jpg/.png) from animations are still stills
_at = MAT.turntable if _ext in ("mp4", "webm") else MAT.still
_tenant_id = line.product.cad_file.tenant_id if (line.product and line.product.cad_file) else None
# Normalize storage_key to relative path
_raw_key = str(output_path)
_upload_prefix = str(_cfg2.upload_dir).rstrip("/") + "/"
_norm_key = _raw_key[len(_upload_prefix):] if _raw_key.startswith(_upload_prefix) else _raw_key
_existing = session.execute(
select(MediaAsset.id).where(MediaAsset.storage_key == _norm_key).limit(1)
).scalar_one_or_none()
if not _existing:
# Probe output file for metadata
_file_size = None
_width = None
_height = None
if _os.path.exists(output_path):
try:
_file_size = _os.path.getsize(output_path)
except OSError:
pass
# Snapshot key render settings into render_config
_render_config = None
if isinstance(render_log, dict):
_render_config = {
k: render_log[k]
for k in (
"renderer", "engine_used", "engine", "samples",
"device_used", "compute_type", "total_duration_s",
)
if k in render_log
}
_asset = MediaAsset(
tenant_id=_tenant_id,
order_line_id=line.id,
product_id=line.product_id,
asset_type=_at,
storage_key=_norm_key,
mime_type=_mime,
file_size_bytes=_file_size,
width=_width,
height=_height,
render_config=_render_config,
)
session.add(_asset)
session.commit()
except Exception:
logger.exception("Failed to create MediaAsset for order_line %s", order_line_id)
try:
persist_order_line_output(
session,
line,
success=success,
output_path=output_path,
render_log=render_log if isinstance(render_log, dict) else None,
render_completed_at=render_end,
)
except Exception:
logger.exception("Failed to persist render output for order_line %s", order_line_id)
raise
if success:
emit(order_line_id, f"Render completed in {elapsed:.1f}s", "success")