How to handle file upload validations using Flask-Marshmallow?

Question:

I’m working with Flask-Marshmallow for validating request and response schemas in Flask app. I was able to do simple validations for request.form and request.args when there are simple fields like Int, Str, Float etc.

I have a case where I need to upload a file using a form field – file_field. It should contain the file content.

How can I validate if this field is present or not and what is the format of file etc.

Is there any such field in Marshmallow that I can use like fields.Int() or fields.Str()

I have gone through the documentation here but haven’t found any such field.

Asked By: Underoos

||

Answers:

You can use fields.Raw:

import marshmallow

class CustomSchema(marshmallow.Schema):
  file = marshmallow.fields.Raw(type='file')

If you are using Swagger, you would then see something like this:

enter image description here

Then in your view you can access the file content with flask.request.files.

For a full example and more advanced topics, check out my project.

Answered By: renatodamas

You can use either Field or Raw field to represent a general field, then set the correct OpenAPI properties for this field (i.e. type and format).

In OpenAPI 3, you should set type to string instead of file, then set format to binary or byte based on your file content:

from marshmallow import Schema, fields


class MySchema(Schema):
  file = fields.Raw(metadata={'type': 'string', 'format': 'binary'})

See the docs for more details.

By the way, from marshmallow 3.10.0, using keyword arguments to pass OpenAPI properties (description, type, example etc.) was deprecated and will be removed in marshmallow 4, use the metadata dict to pass them instead.

Answered By: Grey Li

As of 7 December, 2022, marshmallow v3.19.0 doesn’t work with Raw(metadata={}) or Raw(type='binary/file'). Hence I tried Field() and it works fine.
To validate JPG, PNG files, I do that while @validates_schema by catching it’s file type. I have also saved the file after in @post_load method.

class MySchema(Schema):
    icon_url = Field(metadata={'type': 'string', 'format': 'byte'}, allow_none=True)
    
    @validates_schema
    def validate_uploaded_file(self, in_data, **kwargs):
        errors = {}
        file: FileStorage = in_data.get("icon_url", None)

        if file is None:
            # if any file is not uploaded, skip validation
            pass

        elif type(file) != FileStorage:
            errors["icon_url"] = [
                f"Invalid content. Only PNG, JPG/JPEG files accepted"]
            raise ValidationError(errors)

        elif file.content_type not in {"image/jpeg", "image/png"}:
            errors["icon_url"] = [
                f"Invalid file_type: {file.content_type}. Only PNG, JPG/JPEG images accepted."]
            raise ValidationError(errors)

        return in_data
    

    @post_load
    def post_load(self, loaded_obj, **kwargs):
        if loaded_obj.icon_url:
            sec_filename = secure_filename(
                f'{loaded_obj.name}.{loaded_obj.icon_url.filename.split(".")[-1]}')
            loaded_obj.icon_url.save(
                f"{current_app.config['PUBLIC_IMAGES_FOLDER']}{sec_filename}")
            loaded_obj.icon_url = f'{current_app.config["PUBLIC_IMAGES_URL"]}{sec_filename}'
        return loaded_obj
Answered By: Karishma Sukhwani