frol/flask-restplus-server-example

Response and Paramaters relating to file objects.

theveloped opened this issue · 2 comments

I'm wondering if there is a way to handle file uploads and responses using the current decorators. I would like to allow users to upload files using multipart/form-data and also define endpoints that return a file instead. Of course one can avoid the use of the decorators but it would be nice if these endpoints can be automatically be documented in the usual way.

frol commented

Great question! Everything is ready to do that easily. Here is a guide (these are snippets from an existing project):

  1. Define the parameters class:

    class CreateDatasetParameters(PostFormParameters, schemas.BaseDatasetSchema):
        dataset_file = base_fields.Field(type='file', location='files', required=True)
  2. Define the resource (nothing special):

    @api.route('/')
    @api.login_required(oauth_scopes=['datasets:read'])
    class Datasets(Resource):
    
        @api.login_required(oauth_scopes=['datasets:write'])
        @api.parameters(parameters.CreateDatasetParameters())
        @api.response(schemas.DetailedDatasetSchema())
        @api.response(code=HTTPStatus.CONFLICT)
        @api.doc(id='add_dataset')
        def post(self, args):
            """
            Add a new dataset.
            """
            with api.commit_or_abort(
                    db.session,
                    default_error_message="Failed to create a new dataset"
                ):
                dataset = Dataset(creator=current_user, **args)
                db.session.add(dataset)
            return dataset

    NOTE: dataset_file (passed via **args) will be a File instance (or something along those lines, so in my case, the Dataset model handles it in __init__ method, saves it to the internal storage).

When you want to respond with the file:

  1. Define the schema:

    class TaskResultAttachmentFileSchema(base_fields.Raw):
        pass
  2. Define the resource:

    @api.route('/<int:task_result_id>/attachments/<attachment_name>')
    @api.login_required(oauth_scopes=['task-results:read'], locations=('headers', 'form'))
    @api.response(
        code=HTTPStatus.NOT_FOUND,
        description="Task Result Attachment not found.",
    )
    @api.resolve_object_by_model(
        TaskResultAttachment,
        'task_result_attachment',
        ('task_result_id', 'attachment_name')
    )
    class TaskResultAttachmentByName(Resource):
    
        @api.permission_required(
            permissions.OwnerRolePermission,
            kwargs_on_request=lambda kwargs: {'obj': kwargs['task_result_attachment']}
        )
        @api.response(schemas.TaskResultAttachmentFileSchema())
        @api.doc(produces=['application/octet-stream'])
        @api.doc(id='download_task_result_attachment')
        def post(self, task_result_attachment):
            """
            Download task result attachment.
            """
            return Response(
                seaweedfs.get_file(task_result_attachment.file_seaweedfs_id),
                mimetype='application/octet-stream'
            )

Let me know if you have any further questions.

This is exactly what I was looking for! Thanks for the great info everything seems to work flawless following the snippets above.