From 6b81f6728b0331b4d0e9c4c640449d0b3025949a Mon Sep 17 00:00:00 2001 From: henryruhs Date: Mon, 24 Aug 2026 00:17:51 +0200 Subject: [PATCH] first piece for /jobs endpoint part2 --- facefusion/apis/core.py | 4 +- facefusion/apis/endpoints/jobs.py | 39 ++++++++++++++++- facefusion/apis/locales.py | 2 + tests/test_api_jobs.py | 69 ++++++++++++++++++++++++++++++- 4 files changed, 111 insertions(+), 3 deletions(-) diff --git a/facefusion/apis/core.py b/facefusion/apis/core.py index 9555ac89..c16f66b9 100644 --- a/facefusion/apis/core.py +++ b/facefusion/apis/core.py @@ -8,7 +8,7 @@ from starlette.routing import Route, WebSocketRoute from facefusion.apis.endpoints.assets import delete_assets, get_asset, get_assets, upload_asset from facefusion.apis.endpoints.capabilities import get_capabilities -from facefusion.apis.endpoints.jobs import create_job +from facefusion.apis.endpoints.jobs import create_job, get_job, get_jobs from facefusion.apis.endpoints.metrics import get_metrics, websocket_metrics from facefusion.apis.endpoints.ping import websocket_ping from facefusion.apis.endpoints.session import create_session, destroy_session, get_session, refresh_session @@ -48,6 +48,8 @@ def create_api() -> Starlette: Route('/metrics', get_metrics, methods = [ 'GET' ], middleware = [ session_guard ]), Route('/stream', post_stream, methods = [ 'POST' ], middleware = [ session_guard ]), Route('/stream', delete_stream, methods = [ 'DELETE' ], name = 'delete_stream', middleware = [ session_guard ]), + Route('/jobs', get_jobs, methods = [ 'GET' ], middleware = [ session_guard ]), + Route('/jobs/{job_id}', get_job, methods = [ 'GET' ], middleware = [ session_guard ]), Route('/jobs', create_job, methods = [ 'POST' ], middleware = [ session_guard ]), WebSocketRoute('/metrics', websocket_metrics, middleware = [ session_guard ]), WebSocketRoute('/ping', websocket_ping, middleware = [ session_guard ]), diff --git a/facefusion/apis/endpoints/jobs.py b/facefusion/apis/endpoints/jobs.py index 8103cd17..000300c9 100644 --- a/facefusion/apis/endpoints/jobs.py +++ b/facefusion/apis/endpoints/jobs.py @@ -1,11 +1,48 @@ from starlette.requests import Request from starlette.responses import JSONResponse -from starlette.status import HTTP_201_CREATED, HTTP_400_BAD_REQUEST +from starlette.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND +import facefusion.choices from facefusion import translator from facefusion.jobs import job_helper, job_manager +async def get_jobs(request : Request) -> JSONResponse: + job_status = request.query_params.get('status') + + if job_status in facefusion.choices.job_statuses: + job_set = job_manager.find_jobs(job_status) + job_summaries = {} + + for job_id, job in job_set.items(): + job_summaries[job_id] =\ + { + 'version': job.get('version'), + 'date_created': job.get('date_created'), + 'date_updated': job.get('date_updated') + } + + return JSONResponse(job_summaries, status_code = HTTP_200_OK) + + return JSONResponse( + { + 'message': translator.get('invalid_job_status', 'facefusion.apis') + }, status_code = HTTP_400_BAD_REQUEST) + + +async def get_job(request : Request) -> JSONResponse: + job_id = request.path_params.get('job_id') + job = job_manager.read_job_file(job_id) + + if job: + return JSONResponse(job, status_code = HTTP_200_OK) + + return JSONResponse( + { + 'message': translator.get('job_not_found', 'facefusion.apis') + }, status_code = HTTP_404_NOT_FOUND) + + async def create_job(request : Request) -> JSONResponse: job_id = job_helper.suggest_job_id() diff --git a/facefusion/apis/locales.py b/facefusion/apis/locales.py index a514fdc7..8d74da30 100644 --- a/facefusion/apis/locales.py +++ b/facefusion/apis/locales.py @@ -11,6 +11,8 @@ LOCALES : Locales =\ 'source_asset_not_found': 'source asset not found', 'target_asset_not_found': 'target asset not found', 'invalid_state_key': 'invalid state key', + 'invalid_job_status': 'invalid job status', + 'job_not_found': 'job not found', 'job_not_created': 'job not created' } } diff --git a/tests/test_api_jobs.py b/tests/test_api_jobs.py index 7e472941..9275f75e 100644 --- a/tests/test_api_jobs.py +++ b/tests/test_api_jobs.py @@ -6,7 +6,7 @@ from starlette.testclient import TestClient from facefusion import metadata, session_manager from facefusion.apis.core import create_api -from facefusion.jobs.job_manager import clear_jobs, find_job_ids, init_jobs +from facefusion.jobs.job_manager import clear_jobs, create_job, find_job_ids, init_jobs from .assert_helper import get_test_jobs_directory @@ -23,6 +23,73 @@ def test_client() -> Iterator[TestClient]: yield test_client +def test_get_jobs(test_client : TestClient) -> None: + get_jobs_response = test_client.get('/jobs?status=drafted') + + assert get_jobs_response.status_code == 401 + + create_session_response = test_client.post('/session', json = + { + 'client_version': metadata.get('version') + }) + create_session_body = create_session_response.json() + access_token = create_session_body.get('access_token') + + get_jobs_response = test_client.get('/jobs?status=invalid', headers = + { + 'Authorization': 'Bearer ' + access_token + }) + get_jobs_body = get_jobs_response.json() + + assert get_jobs_body.get('message') == 'invalid job status' + assert get_jobs_response.status_code == 400 + + create_job('job-test-get-jobs') + + get_jobs_response = test_client.get('/jobs?status=drafted', headers = + { + 'Authorization': 'Bearer ' + access_token + }) + get_jobs_body = get_jobs_response.json() + + assert 'job-test-get-jobs' in get_jobs_body + assert get_jobs_body.get('job-test-get-jobs').get('step_total') == 0 + assert get_jobs_response.status_code == 200 + + +def test_get_job(test_client : TestClient) -> None: + get_job_response = test_client.get('/jobs/job-test-get-job') + + assert get_job_response.status_code == 401 + + create_session_response = test_client.post('/session', json = + { + 'client_version': metadata.get('version') + }) + create_session_body = create_session_response.json() + access_token = create_session_body.get('access_token') + + get_job_response = test_client.get('/jobs/job-test-unknown', headers = + { + 'Authorization': 'Bearer ' + access_token + }) + get_job_body = get_job_response.json() + + assert get_job_body.get('message') == 'job not found' + assert get_job_response.status_code == 404 + + create_job('job-test-get-job') + + get_job_response = test_client.get('/jobs/job-test-get-job', headers = + { + 'Authorization': 'Bearer ' + access_token + }) + get_job_body = get_job_response.json() + + assert get_job_body.get('version') == '1' + assert get_job_response.status_code == 200 + + def test_create_job(test_client : TestClient) -> None: create_job_response = test_client.post('/jobs')