Source code for app.orchestrator.scheduler
"""Scheduler — submit job DAG and enqueue tasks for execution.
Persists Job + Task rows and hands each non-cached task off to the
configured queue (see ``app.orchestrator.queue.get_queue``). Whether
the queue is ARQ-backed or in-process is a runtime decision driven by
``settings.queue_backend`` / ``settings.inline_tasks``.
"""
from __future__ import annotations
import contextlib
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import get_settings
from app.core.ids import new_id
from app.db.models import Task
from app.orchestrator.dag import TaskNode
from app.orchestrator.queue import InlineQueue, get_queue
from app.services import job_service, runtime_version_service
[docs]
async def submit_job_dag(
session: AsyncSession,
*,
tenant_id: str,
project_id: str,
recipe: str,
spec: dict | None,
nodes: list[TaskNode],
inline: bool = False,
) -> tuple[str, list[Task]]:
"""Persist Job + Task rows and return them. ``inline=True`` forces
the InlineQueue regardless of settings (used by tests). ARQ enqueue
failures (e.g. Redis absent in dev) are suppressed — the tasks
remain ``pending`` and will be picked up on the next worker boot."""
rv = await runtime_version_service.ensure_runtime_version(session)
job = await job_service.create_job(
session,
tenant_id=tenant_id,
project_id=project_id,
recipe=recipe,
spec=spec,
)
for n in nodes:
if not n.task_id:
n.task_id = new_id()
tasks = await job_service.materialize_dag(
session,
tenant_id=tenant_id,
job_id=job.job_id,
runtime_version_id=rv.rv_id,
nodes=nodes,
)
await session.commit()
pending = [t for t in tasks if t.status != "succeeded"]
if not pending:
return job.job_id, tasks
queue = InlineQueue(get_settings()) if inline else get_queue()
try:
for t in pending:
# Inline mode surfaces task errors directly; ARQ enqueue
# failures are tolerated (Redis may be absent in dev).
if isinstance(queue, InlineQueue):
await queue.enqueue(t.task_id)
else:
with contextlib.suppress(Exception):
await queue.enqueue(t.task_id)
finally:
await queue.close()
return job.job_id, tasks