Jobs API cleanup

This commit is contained in:
Brett Williams
2026-06-06 08:01:48 -05:00
parent d49cd9df79
commit 5e154b6bab
6 changed files with 53 additions and 52 deletions
+3 -3
View File
@@ -109,9 +109,9 @@ curl -X POST http://localhost:8080/api/add_job \
- **UI**: View job status, progress, logs, and worker availability in real-time.
- **API Endpoints**:
- `GET /api/jobs`: List all jobs
- `GET /api/job/<job_id>`: Get job details
- `POST /api/job/<job_id>/cancel`: Cancel a job
- `POST /api/job/<job_id>/delete`: Delete a job
- `GET /api/jobs/<job_id>`: Get job details
- `POST /api/jobs/<job_id>/cancel`: Cancel a job
- `POST /api/jobs/<job_id>/delete`: Delete a job
- `GET /api/status`: Get server and queue status
- `GET /api/engines`: List engine information
+12 -12
View File
@@ -48,7 +48,7 @@ Known callers:
- `RenderServerProxy.get_all_jobs()`
- `src/ui/main_window.py`
### `GET /api/jobs_long_poll`
### `GET /api/jobs/long_poll`
Long-polls the job list until the supplied cache token changes or 30 seconds
elapse.
@@ -68,7 +68,7 @@ Known callers:
- `RenderServerProxy.get_all_jobs()` through the background cache updater.
### `GET /api/jobs/<status_val>`
### `GET /api/jobs/status/<status_val>`
Returns jobs matching a render status.
@@ -86,7 +86,7 @@ Responses:
Review note: this route is not currently wrapped by `RenderServerProxy` and no
in-repo callers were found.
### `GET /api/job/<job_id>`
### `GET /api/jobs/<job_id>`
Returns one job as JSON.
@@ -98,7 +98,7 @@ Known callers:
- `src/distributed_job_manager.py`
- `tests/job_creation_tests.py`
### `GET /api/job/<job_id>/logs`
### `GET /api/jobs/<job_id>/logs`
Returns the job log file as `text/plain`.
@@ -106,7 +106,7 @@ Known callers:
- `src/ui/main_window.py` opens this URL directly.
### `GET /api/job/<job_id>/file_list`
### `GET /api/jobs/<job_id>/files`
Returns a list of output filenames for the job.
@@ -115,7 +115,7 @@ Known callers:
- `RenderServerProxy.get_job_files_list()`
- `src/utilities/server_helper.py`
### `GET /api/job/<job_id>/download`
### `GET /api/jobs/<job_id>/download`
Downloads one output file.
@@ -136,7 +136,7 @@ Known callers:
- `RenderServerProxy.download_job_file()`
- `src/utilities/server_helper.py`
### `GET /api/job/<job_id>/download_all`
### `GET /api/jobs/<job_id>/download_all`
Creates a temporary zip of the job output directory and downloads it.
@@ -146,7 +146,7 @@ Known callers:
- `src/ui/main_window.py`
- `src/utilities/server_helper.py`
### `GET /api/job/<job_id>/thumbnail`
### `GET /api/jobs/<job_id>/thumbnail`
Returns a generated preview image or video for a job.
@@ -207,7 +207,7 @@ Known callers:
- `src/distributed_job_manager.py`
- `tests/job_creation_tests.py`
### `POST /api/job/<job_id>/cancel`
### `POST /api/jobs/<job_id>/cancel`
Cancels a job.
@@ -224,7 +224,7 @@ Known callers:
- `src/ui/main_window.py`
- `src/distributed_job_manager.py`
### `POST /api/job/<job_id>/delete`
### `POST /api/jobs/<job_id>/delete`
Deletes a job, stops it first, deletes previews, and removes owned upload/output
directories when safe.
@@ -240,7 +240,7 @@ Known callers:
- `RenderServerProxy.delete_job()`
- `src/ui/main_window.py`
### `POST /api/job/<job_id>/send_subjob_update_notification`
### `POST /api/jobs/<job_id>/subjob_update`
Notifies a parent job that a child/subjob changed state.
@@ -498,7 +498,7 @@ Likely redundant or overlapping routes:
used internally by `/api/full_status`.
Routes with no in-repo callers found:
- `GET /api/jobs/<status_val>`
- `GET /api/jobs/status/<status_val>`
- `GET /api/presets`
- `GET /api/disk_benchmark`
- `GET /api/engines/<engine_name>/args`
+12 -12
View File
@@ -288,7 +288,7 @@
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs_long_poll</span></div>
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/long_poll</span></div>
<p>Long-polls the job list until the supplied cache token changes or 30 seconds elapse.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
@@ -298,30 +298,30 @@
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/&lt;status_val&gt;</span></div>
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/status/&lt;status_val&gt;</span></div>
<p>Returns jobs matching a render status converted by <code>string_to_status()</code>.</p>
<div class="note">No <code>RenderServerProxy</code> wrapper or in-repo caller was found.</div>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/job/&lt;job_id&gt;</span></div>
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/&lt;job_id&gt;</span></div>
<p>Returns one job as JSON.</p>
<p>Known callers include <code>RenderServerProxy.get_job_info()</code>, <code>add_job.py</code>, <code>src/ui/main_window.py</code>, <code>src/distributed_job_manager.py</code>, and <code>tests/job_creation_tests.py</code>.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/job/&lt;job_id&gt;/logs</span></div>
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/&lt;job_id&gt;/logs</span></div>
<p>Returns the job log file as <code>text/plain</code>. The main window opens this URL directly.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/job/&lt;job_id&gt;/file_list</span></div>
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/&lt;job_id&gt;/files</span></div>
<p>Returns a list of output filenames for the job.</p>
<p>Known callers: <code>RenderServerProxy.get_job_files_list()</code> and <code>src/utilities/server_helper.py</code>.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/job/&lt;job_id&gt;/download</span></div>
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/&lt;job_id&gt;/download</span></div>
<p>Downloads one output file.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
@@ -331,13 +331,13 @@
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/job/&lt;job_id&gt;/download_all</span></div>
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/&lt;job_id&gt;/download_all</span></div>
<p>Creates a temporary zip of the job output directory and downloads it.</p>
<p>Known callers: <code>RenderServerProxy.download_all_job_files()</code>, <code>src/ui/main_window.py</code>, and <code>src/utilities/server_helper.py</code>.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/job/&lt;job_id&gt;/thumbnail</span></div>
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/&lt;job_id&gt;/thumbnail</span></div>
<p>Returns a generated preview image or video for a job.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
@@ -369,7 +369,7 @@
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method post">POST</span><span class="path">/api/job/&lt;job_id&gt;/cancel</span></div>
<div class="endpoint-header"><span class="method post">POST</span><span class="path">/api/jobs/&lt;job_id&gt;/cancel</span></div>
<p>Cancels a job. Requires a truthy <code>confirm</code> query parameter.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
@@ -379,7 +379,7 @@
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method post">POST</span><span class="path">/api/job/&lt;job_id&gt;/delete</span></div>
<div class="endpoint-header"><span class="method post">POST</span><span class="path">/api/jobs/&lt;job_id&gt;/delete</span></div>
<p>Deletes a job, stops it first, deletes previews, and removes owned upload/output directories when safe.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
@@ -388,7 +388,7 @@
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method post">POST</span><span class="path">/api/job/&lt;job_id&gt;/send_subjob_update_notification</span></div>
<div class="endpoint-header"><span class="method post">POST</span><span class="path">/api/jobs/&lt;job_id&gt;/subjob_update</span></div>
<p>Notifies a parent job that a child/subjob changed state. The request body is the JSON representation of the subjob.</p>
</article>
</section>
@@ -571,7 +571,7 @@
<div class="panel" style="margin-top: 14px;">
<h3>Routes With No In-Repo Callers Found</h3>
<ul>
<li><code>GET /api/jobs/&lt;status_val&gt;</code></li>
<li><code>GET /api/jobs/status/&lt;status_val&gt;</code></li>
<li><code>GET /api/presets</code></li>
<li><code>GET /api/disk_benchmark</code></li>
<li><code>GET /api/engines/&lt;engine_name&gt;/args</code></li>
+13 -13
View File
@@ -84,7 +84,7 @@ def jobs_json() -> Dict[str, Any]:
return {'jobs': all_jobs, 'token': job_cache_token}
@server.get('/api/jobs_long_poll')
@server.get('/api/jobs/long_poll')
def long_polling_jobs():
hash_token = request.args.get('token', None)
start_time = time.time()
@@ -98,7 +98,7 @@ def long_polling_jobs():
time.sleep(1)
@server.get('/api/jobs/<status_val>')
@server.get('/api/jobs/status/<status_val>')
def filtered_jobs_json(status_val):
state = string_to_status(status_val)
jobs = [x.json() for x in RenderQueue.jobs_with_status(state)]
@@ -112,7 +112,7 @@ def filtered_jobs_json(status_val):
# Job Details / File Handling
# --------------------------------------------
@server.get('/api/job/<job_id>')
@server.get('/api/jobs/<job_id>')
def get_job_details(job_id):
"""Retrieves the details of a requested job in JSON format
@@ -125,7 +125,7 @@ def get_job_details(job_id):
return RenderQueue.job_with_id(job_id).json()
@server.get('/api/job/<job_id>/logs')
@server.get('/api/jobs/<job_id>/logs')
def get_job_logs(job_id):
"""Retrieves the log file for a specific render job.
@@ -144,12 +144,12 @@ def get_job_logs(job_id):
return Response(log_data, mimetype='text/plain')
@server.get('/api/job/<job_id>/file_list')
def get_file_list(job_id):
return [Path(p).name for p in RenderQueue.job_with_id(job_id).file_list()]
@server.get('/api/jobs/<job_id>/files')
def get_job_files(job_id):
return [Path(p).name for p in RenderQueue.job_with_id(job_id).file_list()]
@server.route('/api/job/<job_id>/download')
@server.route('/api/jobs/<job_id>/download')
def download_requested_file(job_id):
requested_filename = request.args.get("filename")
if not requested_filename:
@@ -165,7 +165,7 @@ def download_requested_file(job_id):
return f"File '{requested_filename}' not found", 404
@server.route('/api/job/<job_id>/download_all')
@server.route('/api/jobs/<job_id>/download_all')
def download_all_files(job_id):
zip_filename = None
@@ -307,7 +307,7 @@ def add_job_handler():
return 'unknown error', 500
@server.post('/api/job/<job_id>/cancel')
@server.post('/api/jobs/<job_id>/cancel')
def cancel_job(job_id):
if not request.args.get('confirm', False):
return 'Confirmation required to cancel job', 400
@@ -321,7 +321,7 @@ def cancel_job(job_id):
return "Unknown error", 500
@server.post('/api/job/<job_id>/delete')
@server.post('/api/jobs/<job_id>/delete')
def delete_job(job_id):
try:
if not request.args.get("confirm", False):
@@ -543,14 +543,14 @@ def delete_engine_download():
def heartbeat():
return datetime.now().isoformat(), 200
@server.post('/api/job/<job_id>/send_subjob_update_notification')
@server.post('/api/jobs/<job_id>/subjob_update')
def subjob_update_notification(job_id):
subjob_details = request.json
DistributedJobManager.handle_subjob_update_notification(RenderQueue.job_with_id(job_id), subjob_data=subjob_details)
return Response(status=200)
@server.route('/api/job/<job_id>/thumbnail')
@server.route('/api/jobs/<job_id>/thumbnail')
def job_thumbnail(job_id):
try:
+8 -8
View File
@@ -139,7 +139,7 @@ class RenderServerProxy:
if self.__offline_flags: # if we're offline, don't bother with the long poll
ignore_token = True
url = f'jobs_long_poll?token={self.__jobs_cache_token}' if (self.__jobs_cache_token and
url = f'jobs/long_poll?token={self.__jobs_cache_token}' if (self.__jobs_cache_token and
not ignore_token) else 'jobs'
status_result = self.request_data(url, timeout=timeout)
if status_result is not None:
@@ -181,10 +181,10 @@ class RenderServerProxy:
# --------------------------------------------
def get_job_info(self, job_id, timeout=5):
return self.request_data(f'job/{job_id}', timeout=timeout)
return self.request_data(f'jobs/{job_id}', timeout=timeout)
def get_job_files_list(self, job_id):
return self.request_data(f"job/{job_id}/file_list")
return self.request_data(f'jobs/{job_id}/files')
# --------------------------------------------
# Job Lifecycle:
@@ -230,10 +230,10 @@ class RenderServerProxy:
return response
def cancel_job(self, job_id, confirm=False):
return self._post(f'job/{job_id}/cancel', params={'confirm': confirm})
return self._post(f'jobs/{job_id}/cancel', params={'confirm': confirm})
def delete_job(self, job_id, confirm=False):
return self._post(f'job/{job_id}/delete', params={'confirm': confirm})
return self._post(f'jobs/{job_id}/delete', params={'confirm': confirm})
def send_subjob_update_notification(self, parent_id, subjob):
"""
@@ -246,7 +246,7 @@ class RenderServerProxy:
Returns:
Response: The response from the server.
"""
return requests.post(f'http://{self.hostname}:{self.port}/api/job/{parent_id}/send_subjob_update_notification',
return requests.post(f'http://{self.hostname}:{self.port}/api/jobs/{parent_id}/subjob_update',
json=subjob.json())
# --------------------------------------------
@@ -312,11 +312,11 @@ class RenderServerProxy:
# --------------------------------------------
def download_all_job_files(self, job_id, save_path):
url = f"http://{self.hostname}:{self.port}/api/job/{job_id}/download_all"
url = f'http://{self.hostname}:{self.port}/api/jobs/{job_id}/download_all'
return self.__download_file_from_url(url, output_filepath=save_path)
def download_job_file(self, job_id, job_filename, save_path):
url = f"http://{self.hostname}:{self.port}/api/job/{job_id}/download?filename={job_filename}"
url = f'http://{self.hostname}:{self.port}/api/jobs/{job_id}/download?filename={job_filename}'
return self.__download_file_from_url(url, output_filepath=save_path)
@staticmethod
+5 -4
View File
@@ -334,7 +334,7 @@ class MainWindow(QMainWindow):
default_image_path = "error.png"
before_fetch_hostname = self.current_server_proxy.hostname
response = self.current_server_proxy.request(f'job/{job_id}/thumbnail?size=big')
response = self.current_server_proxy.request(f'jobs/{job_id}/thumbnail?size=big')
if response.ok:
try:
with io.BytesIO(response.content) as image_data_stream:
@@ -547,7 +547,8 @@ class MainWindow(QMainWindow):
"""
selected_job_ids = self.selected_job_ids()
if selected_job_ids:
url = f'http://{self.current_server_proxy.hostname}:{self.current_server_proxy.port}/api/job/{selected_job_ids[0]}/logs'
url = (f'http://{self.current_server_proxy.hostname}:{self.current_server_proxy.port}'
f'/api/jobs/{selected_job_ids[0]}/logs')
self.log_viewer_window = LogViewer(url)
self.log_viewer_window.show()
@@ -616,8 +617,8 @@ class MainWindow(QMainWindow):
return
import webbrowser
download_url = (f"http://{self.current_server_proxy.hostname}:{self.current_server_proxy.port}"
f"/api/job/{job_ids[0]}/download_all")
download_url = (f'http://{self.current_server_proxy.hostname}:{self.current_server_proxy.port}'
f'/api/jobs/{job_ids[0]}/download_all')
webbrowser.open(download_url)
def open_files(self, event):