Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions openeo_driver/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,13 @@ def get_user_jobs(
"""
raise NotImplementedError

@not_implemented
def update_job(self, job_id: str, user_id: str, data: Optional[dict]):
"""
https://openeo.org/documentation/1.0/developers/api/reference.html#operation/update-job
"""
raise NotImplementedError

def start_job(self, job_id: str, user: User):
"""
https://openeo.org/documentation/1.0/developers/api/reference.html#operation/start-job
Expand Down
19 changes: 19 additions & 0 deletions openeo_driver/dummy/dummy_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@
from openeo_driver.dry_run import SourceConstraint
from openeo_driver.errors import (
FeatureUnsupportedException,
JobLockedException,
JobNotFinishedException,
JobNotFoundException,
PropertyNotEditableException,
PermissionsInsufficientException,
ProcessGraphNotFoundException,
)
Expand Down Expand Up @@ -724,6 +726,7 @@ class DummyBatchJobs(BatchJobs):
_job_registry: Dict[Tuple[str, str], BatchJobMetadata] = {}
_custom_job_logs = {}
_job_result_registry: Dict[Tuple[str, str], BatchJobResultMetadata] = {}
EDITABLE_JOB_FIELDS = {"title", "description", "process", "plan", "budget"}

def generate_job_id(self):
return generate_unique_id(prefix="j")
Expand Down Expand Up @@ -810,6 +813,18 @@ def start_job(self, job_id: str, user: User):
job_id=job_id, user_id=user.user_id, status=JOB_STATUS.RUNNING
)

def update_job(self, job_id: str, user_id: str, data: Optional[dict]):
if data is None:
data = {}
job = self._get_job_info(job_id=job_id, user_id=user_id)
if job.status in {JOB_STATUS.QUEUED, JOB_STATUS.RUNNING}:
raise JobLockedException()
for field_name in data:
if field_name not in self.EDITABLE_JOB_FIELDS:
raise PropertyNotEditableException(property=field_name)
updates = {k: v for k, v in data.items() if k in self.EDITABLE_JOB_FIELDS}
self._job_registry[(user_id, job_id)] = job._replace(**updates)

def _output_root(self) -> str:
return "/data/jobs"

Expand Down Expand Up @@ -1055,6 +1070,10 @@ def cancel_job(self, job_id: str, user_id: str):

def delete_job(self, job_id: str, user_id: str):
self.cancel_job(job_id, user_id)
registry_key = (user_id, job_id)
result_key = (job_id, user_id)
self._job_registry.pop(registry_key, None)
self._job_result_registry.pop(result_key, None)


class DummyUserDefinedProcesses(UserDefinedProcesses):
Expand Down
6 changes: 3 additions & 3 deletions openeo_driver/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1008,12 +1008,12 @@ def delete_job(job_id, user: User):
backend_implementation.batch_jobs.delete_job(job_id=job_id, user_id=user.user_id)
return response_204_no_content()

@api_endpoint(hidden=True)
@api_endpoint(hidden=is_not_implemented(backend_implementation.batch_jobs.update_job))
@blueprint.route('/jobs/<job_id>', methods=['PATCH'])
@auth_handler.requires_bearer_auth
def modify_job(job_id, user: User):
# TODO
raise FeatureUnsupportedException()
backend_implementation.batch_jobs.update_job(job_id=job_id, user_id=user.user_id, data=request.get_json())
return response_204_no_content()

@api_endpoint
@blueprint.route('/jobs/<job_id>/results', methods=['POST'])
Expand Down
44 changes: 43 additions & 1 deletion tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def test_capabilities_endpoints(self, api100):
assert endpoints["/services"] == ["GET", "POST"]
assert endpoints["/services/{service_id}"] == ["DELETE", "GET"]
# assert endpoints["/subscription"] == ["GET"]
assert endpoints["/jobs/{job_id}"] == ["DELETE", "GET"]
assert endpoints["/jobs/{job_id}"] == ["DELETE", "GET", "PATCH"]
assert endpoints["/jobs/{job_id}/results"] == ["DELETE", "GET", "POST"]
assert endpoints["/credentials/basic"] == ["GET"]
assert endpoints["/credentials/oidc"] == ["GET"]
Expand Down Expand Up @@ -4217,6 +4217,48 @@ def test_delete_job(self, api):
resp = api.delete("/jobs/07024ee9-7847-4b8a-b260-6c879a2b3cdc", headers=self.AUTH_HEADER)
assert resp.status_code == 204

def test_patch_job(self, api):
with self._fresh_job_registry():
resp = api.patch(
"/jobs/53c71345-09b4-46b4-b6b0-03fd6fe1f199",
headers=self.AUTH_HEADER,
json={
"title": "Better title",
"description": "More context",
"budget": 9.87,
},
)
assert resp.status_code == 204
job = dummy_backend.DummyBatchJobs._job_registry[TEST_USER, "53c71345-09b4-46b4-b6b0-03fd6fe1f199"]
assert job.title == "Better title"
assert job.description == "More context"
assert job.budget == 9.87
assert job.status == "finished"

def test_patch_job_locked(self, api):
with self._fresh_job_registry():
resp = api.patch(
"/jobs/07024ee9-7847-4b8a-b260-6c879a2b3cdc",
headers=self.AUTH_HEADER,
json={"title": "nope"},
)
resp.assert_error(400, "JobLocked")

def test_patch_job_read_only_property(self, api):
with self._fresh_job_registry():
resp = api.patch(
"/jobs/53c71345-09b4-46b4-b6b0-03fd6fe1f199",
headers=self.AUTH_HEADER,
json={"status": "created"},
)
resp.assert_error(400, "PropertyNotEditable")

def test_delete_job_removes_registry_entry(self, api):
with self._fresh_job_registry():
assert (TEST_USER, "07024ee9-7847-4b8a-b260-6c879a2b3cdc") in dummy_backend.DummyBatchJobs._job_registry
api.delete("/jobs/07024ee9-7847-4b8a-b260-6c879a2b3cdc", headers=self.AUTH_HEADER).assert_status_code(204)
assert (TEST_USER, "07024ee9-7847-4b8a-b260-6c879a2b3cdc") not in dummy_backend.DummyBatchJobs._job_registry


class TestSecondaryServices:
AUTH_HEADER = TEST_USER_AUTH_HEADER
Expand Down