job_status_controller.py 4.33 KB
Newer Older
David Mendez's avatar
David Mendez committed
1
2
3
"""
Module that describes and handles the requests concerned with the job status
"""
4
import werkzeug
David Mendez's avatar
David Mendez committed
5
# pylint: disable=W0622,C0103, R0201
6
from flask import abort, request
David Mendez's avatar
David Mendez committed
7
from flask_restx import Namespace, Resource, fields, reqparse
David Mendez's avatar
David Mendez committed
8

9
from app.authorisation.decorators import token_required_for_job_id
10
11
from app.models import delayed_job_models
from app.namespaces.job_status import job_status_service
12
13
14

API = Namespace('status', description='Requests related to Job Status')

15
MODIFIABLE_STATUS = API.model('ModifiableStatus', {
16
17
    'status': fields.String(required=True, description='The status of the job ',
                            enum=[str(possible_status) for possible_status in delayed_job_models.JobStatuses]),
18
    'status_comment': fields.String(required=True, description='A comment on the status of the job'),
David Mendez's avatar
David Mendez committed
19
    'log': fields.String(required=True, description='The log of messages from the job'),
20
    'progress': fields.String(required=True, description='The progress percentage of the job'),
21
22
23
24
25
26
27
28
    'api_initial_url': fields.String(required=True, description='The initial URL of the API calls'),
})


PUBLIC_STATUS = API.inherit('Status', MODIFIABLE_STATUS, {
    'id': fields.String(required=True, description='The job identifier'),
    'type': fields.String(required=True, description='The type of the job ',
                          enum=[str(possible_type) for possible_type in delayed_job_models.JobTypes]),
29
30
31
32
    'created_at': fields.String(required=True, description='The time at which the job was created'),
    'started_at': fields.String(required=True, description='The time at which the job started to run'),
    'finished_at': fields.String(required=True, description='The time at which the job finished'),
    'raw_params': fields.String(required=True, description='The stringified version of the parameters'),
33
    'expires_at': fields.String(required=True, description='The date at which the job results will expire'),
34
35
    'timezone': fields.String(required=True, description='The timezone where the job ran'),
    'output_file_url': fields.String(required=True, description='The relative url for downloading the job result file'),
36

37
38
39
40
41
42
43
44
45
46
47
})


@API.route('/<id>')
@API.param('id', 'The job identifier')
@API.response(404, 'Job not found')
class JobStatus(Resource):
    """
        Resource that handles job status requests
    """

48
49
    @API.marshal_with(PUBLIC_STATUS)
    def get(self, id):  # pylint: disable=no-self-use
50
51
52
53
        """
        Returns the status of a job
        :return: a json response with the current job status
        """
54
55
56
        try:
            return job_status_service.get_job_status(id)
        except job_status_service.JobNotFoundError:
57
            abort(404)
58
59

    @API.marshal_with(MODIFIABLE_STATUS)
60
    @API.doc(security='jobKey', body=MODIFIABLE_STATUS)
61
    @token_required_for_job_id
62
    def patch(self, id):
63
64
65
66
67
        """
            Updates a job with the data provided
            :param id:
            :return:
        """
68
69
70
71
        new_data = {}  # this is to avoid using custom data structures i.e CombinedMultiDict
        for key in request.values.keys():
            new_value = request.values.get(key)
            new_data[key] = new_value
72

73
74
75
        try:
            return job_status_service.update_job_status(id, new_data)
        except job_status_service.JobNotFoundError:
76
            abort(404)
77
78
79
80
81
82
83


RESULT_FILE_OPERATION = API.model('Result File Operation', {
    'result': fields.String(description='The result of the operation')
})

FILE_TO_UPLOAD = reqparse.RequestParser()
84
FILE_TO_UPLOAD.add_argument('file',
85
86
87
88
89
90
91
92
93
94
95
96
97
98
                            type=werkzeug.datastructures.FileStorage,
                            required=True,
                            help='Results file of the job')


@API.route('/<id>/results_file')
@API.param('id', 'The job identifier')
@API.response(404, 'Job not found')
class JobResultsFileUpload(Resource):
    """
    Resource to handle the upload of a results file for a job
    """
    @API.marshal_with(RESULT_FILE_OPERATION)
    @API.expect(FILE_TO_UPLOAD)
99
    @token_required_for_job_id
100
101
102
103
104
105
    def post(self, id):
        """
        Handles the upload of a results file for a job
        :param id: job id
        :return:
        """
106
107
108
109
110
        received_file = request.files['file']
        try:
            return job_status_service.save_uploaded_file(id, received_file)
        except job_status_service.JobNotFoundError:
            abort(404)