Files
2026-07-13 13:22:34 +08:00

136 lines
3.6 KiB
Python

"""
Internal job APIs for UI invocation
"""
import json
from typing import Any
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from mlflow.entities._job import Job as JobEntity
from mlflow.entities._job_status import JobStatus
from mlflow.exceptions import MlflowException
job_api_router = APIRouter(prefix="/ajax-api/3.0/jobs", tags=["Job"])
class Job(BaseModel):
"""
Pydantic model for job query response.
"""
job_id: str
creation_time: int
job_name: str
params: dict[str, Any]
timeout: float | None
status: JobStatus
result: Any
retry_count: int
last_update_time: int
status_details: dict[str, Any] | None = None
@classmethod
def from_job_entity(cls, job: JobEntity) -> "Job":
return cls(
job_id=job.job_id,
creation_time=job.creation_time,
job_name=job.job_name,
params=json.loads(job.params),
timeout=job.timeout,
status=job.status,
result=job.parsed_result,
retry_count=job.retry_count,
last_update_time=job.last_update_time,
status_details=job.status_details,
)
@job_api_router.get("/{job_id}", response_model=Job)
def get_job(job_id: str) -> Job:
from mlflow.server.jobs import get_job
try:
job = get_job(job_id)
return Job.from_job_entity(job)
except MlflowException as e:
raise HTTPException(
status_code=e.get_http_status_code(),
detail=e.message,
)
class SubmitJobPayload(BaseModel):
job_name: str
params: dict[str, Any]
timeout: float | None = None
@job_api_router.post("/", response_model=Job)
def submit_job(payload: SubmitJobPayload) -> Job:
from mlflow.server.jobs import submit_job
from mlflow.server.jobs.utils import _load_function, get_job_fn_fullname
job_name = payload.job_name
try:
function_fullname = get_job_fn_fullname(job_name)
function = _load_function(function_fullname)
job = submit_job(function, payload.params, payload.timeout)
return Job.from_job_entity(job)
except MlflowException as e:
raise HTTPException(
status_code=e.get_http_status_code(),
detail=e.message,
)
@job_api_router.patch("/cancel/{job_id}", response_model=Job)
def cancel_job(job_id: str) -> Job:
from mlflow.server.jobs import cancel_job
try:
job = cancel_job(job_id)
return Job.from_job_entity(job)
except MlflowException as e:
raise HTTPException(
status_code=e.get_http_status_code(),
detail=e.message,
)
class SearchJobPayload(BaseModel):
job_name: str | None = None
params: dict[str, Any] | None = None
statuses: list[JobStatus] | None = None
class SearchJobsResponse(BaseModel):
"""
Pydantic model for job searching response.
"""
jobs: list[Job]
@job_api_router.post("/search", response_model=SearchJobsResponse)
def search_jobs(payload: SearchJobPayload) -> SearchJobsResponse:
from mlflow.server.handlers import _get_job_store
try:
store = _get_job_store()
job_results = [
Job.from_job_entity(job)
for job in store.list_jobs(
job_name=payload.job_name,
statuses=payload.statuses,
params=payload.params,
)
]
return SearchJobsResponse(jobs=job_results)
except MlflowException as e:
raise HTTPException(
status_code=e.get_http_status_code(),
detail=e.message,
)