Initial Source code version
This commit is contained in:
parent
90f5f38812
commit
d979374cbc
23 changed files with 2628 additions and 0 deletions
110
app/services.py
Normal file
110
app/services.py
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
from uuid import UUID
|
||||
from sqlalchemy import select, update, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from datetime import datetime
|
||||
from app.models import Job, Task, TaskStatus, JobStatus, TaskDependency
|
||||
from app.job_registry import list_tasks_for_job, list_dependencies, routing_key
|
||||
from app.rabbitmq import publish_task_message
|
||||
from app.config import settings
|
||||
|
||||
async def create_job(session: AsyncSession, job_type: str, domain: str, payload: dict) -> Job:
|
||||
job = Job(job_type=job_type, domain=domain, payload=payload, status=JobStatus.PENDING)
|
||||
session.add(job)
|
||||
await session.flush()
|
||||
|
||||
# 1) Tasks anlegen
|
||||
name_to_task: dict[str, Task] = {}
|
||||
for name in list_tasks_for_job(job_type):
|
||||
t = Task(
|
||||
job_id=job.id,
|
||||
name=name,
|
||||
status=TaskStatus.PENDING,
|
||||
routing_key=routing_key(job_type, name),
|
||||
max_retries=settings.MAX_RETRIES,
|
||||
)
|
||||
session.add(t)
|
||||
await session.flush()
|
||||
name_to_task[name] = t
|
||||
|
||||
# 2) Dependencies anlegen
|
||||
for name, task in name_to_task.items():
|
||||
for dep_name in list_dependencies(job_type, name):
|
||||
dep_task = name_to_task[dep_name]
|
||||
session.add(TaskDependency(task_id=task.id, depends_on_task_id=dep_task.id))
|
||||
|
||||
await session.commit()
|
||||
|
||||
# 3) Nur Tasks ohne Dependencies veröffentlichen
|
||||
for name, task in name_to_task.items():
|
||||
deps = list_dependencies(job_type, name)
|
||||
if not deps:
|
||||
await _publish_task(session, job, task)
|
||||
|
||||
await refresh_job_status(session, job.id)
|
||||
await session.commit()
|
||||
return job
|
||||
|
||||
async def _publish_task(session: AsyncSession, job: Job, task: Task):
|
||||
await publish_task_message(
|
||||
routing_key=task.routing_key,
|
||||
message={
|
||||
"job_id": str(job.id),
|
||||
"task_id": str(task.id),
|
||||
"task_name": task.name,
|
||||
"job_type": job.job_type,
|
||||
"payload": {"domain": job.domain, **(job.payload or {})},
|
||||
},
|
||||
)
|
||||
await session.execute(update(Task).where(Task.id == task.id).values(status=TaskStatus.QUEUED))
|
||||
await session.commit()
|
||||
|
||||
async def release_dependent_tasks(session: AsyncSession, completed_task_id: UUID):
|
||||
# Finde Tasks, die von completed_task_id abhängen
|
||||
res = await session.execute(select(TaskDependency.task_id).where(TaskDependency.depends_on_task_id == completed_task_id))
|
||||
candidate_ids = [row[0] for row in res.all()]
|
||||
if not candidate_ids:
|
||||
return
|
||||
# Für jeden Kandidaten prüfen, ob ALLE Dependencies SUCCESS sind
|
||||
for task_id in candidate_ids:
|
||||
# task + job laden
|
||||
res_task = await session.execute(select(Task, Job).join(Job, Task.job_id == Job.id).where(Task.id == task_id))
|
||||
row = res_task.first()
|
||||
if not row:
|
||||
continue
|
||||
task, job = row
|
||||
# Alle deps status
|
||||
res_deps = await session.execute(
|
||||
select(func.count()).select_from(TaskDependency)
|
||||
.join(Task, TaskDependency.depends_on_task_id == Task.id)
|
||||
.where(TaskDependency.task_id == task.id, Task.status != TaskStatus.SUCCESS)
|
||||
)
|
||||
remaining = res_deps.scalar_one()
|
||||
if remaining == 0 and task.status == TaskStatus.PENDING:
|
||||
await _publish_task(session, job, task)
|
||||
|
||||
async def get_job(session: AsyncSession, job_id: UUID) -> Job | None:
|
||||
res = await session.execute(select(Job).where(Job.id == job_id))
|
||||
return res.scalar_one_or_none()
|
||||
|
||||
async def get_job_with_tasks(session: AsyncSession, job_id: UUID) -> Job | None:
|
||||
return await get_job(session, job_id)
|
||||
|
||||
async def refresh_job_status(session: AsyncSession, job_id: UUID) -> None:
|
||||
res = await session.execute(select(Task).where(Task.job_id == job_id))
|
||||
tasks = res.scalars().all()
|
||||
if not tasks:
|
||||
await session.execute(update(Job).where(Job.id == job_id).values(status=JobStatus.PENDING))
|
||||
return
|
||||
if all(t.status == TaskStatus.SUCCESS for t in tasks):
|
||||
new_status = JobStatus.SUCCESS
|
||||
elif any(t.status == TaskStatus.FAILED for t in tasks) and not any(
|
||||
t.status in (TaskStatus.RUNNING, TaskStatus.QUEUED, TaskStatus.PENDING) for t in tasks
|
||||
):
|
||||
new_status = JobStatus.FAILED
|
||||
elif any(t.status == TaskStatus.RUNNING for t in tasks):
|
||||
new_status = JobStatus.RUNNING
|
||||
elif any(t.status == TaskStatus.QUEUED for t in tasks):
|
||||
new_status = JobStatus.QUEUED
|
||||
else:
|
||||
new_status = JobStatus.PENDING
|
||||
await session.execute(update(Job).where(Job.id == job_id).values(status=new_status, updated_at=datetime.utcnow()))
|
||||
Loading…
Add table
Add a link
Reference in a new issue