Skip to content

Commit

Permalink
Add patchTask endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
nathandf committed Jul 9, 2024
1 parent b226ffe commit 0bab1d0
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 71 deletions.
61 changes: 57 additions & 4 deletions src/api/specs/WorkflowsAPI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1553,7 +1553,43 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/RespTask'

patch:
tags:
- Tasks
summary: Update task details
description: Update details for a task
operationId: patchTask
parameters:
- name: group_id
in: path
required: true
schema:
$ref: '#/components/schemas/ID'
- name: pipeline_id
in: path
required: true
schema:
$ref: '#/components/schemas/ID'
- name: task_id
in: path
required: true
schema:
$ref: '#/components/schemas/ID'
requestBody:
required: true
description: Request body for the pathTask operation.
content:
application/json:
schema:
$ref: '#/components/schemas/Task'
responses:
'200':
description: Task found.
content:
application/json:
schema:
$ref: '#/components/schemas/RespTask'

delete:
tags:
- Tasks
Expand Down Expand Up @@ -2222,6 +2258,26 @@ components:
type: string

# --- Task ---
ReqPatchTask:
anyOf:
- $ref: '#/components/schemas/ImageBuildTask'
- $ref: '#/components/schemas/RequestTask'
- $ref: '#/components/schemas/ApplicationTask'
- $ref: '#/components/schemas/TapisJobTask'
- $ref: '#/components/schemas/TapisActorTask'
- $ref: '#/components/schemas/FunctionTask'
- $ref: '#/components/schemas/TemplateTask'
discriminator:
propertyName: type
mapping:
image_build: "#/components/schemas/ImageBuildTask"
request: "#/components/schemas/RequestTask"
application: "#/components/schemas/ApplicationTask"
tapis_job: "#/components/schemas/TapisJobTask"
tapis_actor: "#/components/schemas/TapisActorTask"
function: "#/components/schemas/FunctionTask"
template: "#/components/schemas/TemplateTask"

Task:
oneOf:
- $ref: '#/components/schemas/ImageBuildTask'
Expand All @@ -2244,9 +2300,6 @@ components:

BaseTask:
type: object
required:
- id
- type
properties:
id:
type: string
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from django.forms.models import model_to_dict

from backend.serializers import UUIDSerializer
from backend.conf.constants import DATETIME_FORMAT

Expand Down
122 changes: 59 additions & 63 deletions src/api/src/backend/views/Tasks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json

from django.db import IntegrityError, OperationalError
from django.db import IntegrityError, OperationalError, DatabaseError
from django.forms import model_to_dict

from backend.models import Task, Pipeline
from backend.views.RestrictedAPIView import RestrictedAPIView
Expand All @@ -9,7 +10,9 @@
from backend.views.http.responses.models import ModelListResponse, ModelResponse
from backend.services.TaskService import service as task_service
from backend.services.GroupService import service as group_service
from backend.errors.api import ServerError as APIServerError
from backend.helpers import resource_url_builder
from backend.utils import logger


class Tasks(RestrictedAPIView):
Expand Down Expand Up @@ -103,69 +106,62 @@ def post(self, request, group_id, pipeline_id, *_, **__):
return ResourceURLResponse(
url=resource_url_builder(request.url, task.id))

def patch(self, request, group_id, pipeline_id, task_id):
try:
# Get the group
group = group_service.get(group_id, request.tenant_id)
if group == None:
return NotFound(f"No group found with id '{group_id}'")

# Check that the user belongs to the group
if not group_service.user_in_group(request.username, group_id, request.tenant_id):
return Forbidden(message="You do not have access to this group")

# Get the pipline
pipeline = Pipeline.objects.filter(
group=group,
id=pipeline_id
).first()

task_model = Task.objects.filter(pipeline=pipeline, id=task_id).first()

if task_model == None:
return NotFound(f"Task with id '{task_id}' not found in pipeline '{pipeline_id}'")

# Resolve the the proper pydantic object for this task type
TaskSchema = task_service.resolve_request_type(task_model.type)

task = TaskSchema(
**model_to_dict(task_model),
**self.request_body
)

# Disallow updating the type property
if (task_model.type != task.type):
return BadRequest(f"Updating the type of a task is not allowed. Expected task.type: {task_model.type} - Recieved: {task.type}")

# The pipeline_id property will be set to the previous value as we do not
# allow that property to be updated
Task.objects.filter(
pipeline_id=pipeline_id,
id=task_id
).update(**{
**json.loads(task.json())
})

return ModelResponse(Task.objects.filter(id=task.id, pipeline_id=pipeline_id).first())
except (DatabaseError, OperationalError, IntegrityError) as e:
logger.exception(e.__cause__)
return ServerError(message=e.__cause__)
except APIServerError as e:
logger.exception(e.__cause__)
return ServerError(message=e)
except Exception as e:
logger.exception(str(e))
return ServerError(message=e)

def put(self, request, group_id, pipeline_id, task_id=None):
# Get the group
group = group_service.get(group_id, request.tenant_id)
if group == None:
return NotFound(f"No group found with id '{group_id}'")

# Check that the user belongs to the group
if not group_service.user_in_group(request.username, group_id, request.tenant_id):
return Forbidden(message="You do not have access to this group")

# Get the pipline
pipeline = Pipeline.objects.filter(
group=group,
id=pipeline_id
).first()

task = Task.objects.filter(pipeline=pipeline, id=task_id).first()

if task == None:
return NotFound(f"Task with id '{task_id}' not found in pipeline '{pipeline_id}'")

# Validate the request body
if "type" not in self.request_body:
return BadRequest(message="Request body missing 'type' property")

if not task_service.is_valid_task_type(self.request_body["type"]):
return BadRequest(message=f"Invalid task type: Expected one of: {task_service.get_task_request_types()} - Recieved: {self.request_body['type']}. ")

# Resolve the the proper request for the type of task provided in the request body
TaskRequest = task_service.resolve_request_type(self.request_body["type"])

# Validate and prepare the create reqeust
prepared_request = self.prepare(TaskRequest)

# Return the failure view instance if validation failed
if not prepared_request.is_valid:
return prepared_request.failure_view

# Get the JSON encoded body from the validation result
body = prepared_request.body

# Disallow updating the type property
if body.type != task.type:
return BadRequest(f"Updating the type of an task is not allowed. Expected task.type: {task.type} - Recieved: {'null' if body.type == None else body.type}")

# The pipeline_id property will be set to the previous value as we do not
# allow that property to be updated
Task.objects.filter(
pipeline_id=pipeline_id,
id=task_id
).update(**{
**json.loads(body.json()),
"type": task.type,
"pipeline_id": pipeline_id,
"context": task.context,
"destination": task.destination
})

return ModelResponse(Task.objects.filter(id=body.id, pipeline_id=pipeline_id).first())

def patch(self, *_, **__):
return MethodNotAllowed("Method 'PATCH' not allowed for 'Task' objects")
def put(self, *_, **__):
return MethodNotAllowed("Method 'PUT' not allowed for 'Task' objects")

def delete(self, request, group_id, pipeline_id, task_id):
# Get the group
Expand Down
6 changes: 4 additions & 2 deletions src/api/src/backend/views/UpdateTaskExecutionStatus.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ def patch(self, request, task_execution_uuid, status, *_, **__):
stdout=body.stdout,
stderr=body.stderr
)

return BaseResponse(result="TaskExecution status updated")

except (DatabaseError, IntegrityError, OperationalError) as e:
return ServerError(f"Server Error: {e.__cause__}")
except Exception as e:
return ServerError(f"Server Error: {e}")

return BaseResponse(result="TaskExecution status updated")

0 comments on commit 0bab1d0

Please sign in to comment.