Search code examples
pythonpython-3.xflaskflask-restfulflasgger

How can I make Flasgger automatically validate flask-restful resource endpoints against the template_file?


TLDR; What I'm looking to achieve:
Since there's the option to load an universal/app-wide schema in flasgger, as defined by the template_file parameter when instantiating Swagger, how can I automatically validate all data sent to endpoints that have associated flask-restful Resource classes when using a universal json schema file?


I'm currently designing an API and have run into a situation where when I define my entire schema from a json template file and am utilizing flask-restful Resource classes the data provided in the API calls is not validated.

Posting to /product with a valid payload results in the expected 501 response. But, posting with an invalid payload also results in a 501 response.

Expected Payload:

{
  "id": 0,
  "name": "Toy",
  "photoUrls": [
    "string"
  ],
  "status": "available"
}

Payload that should fail validation:

{
  "id": 0,
  "name": "test",
  "status": "available"
}

Below is a snippet of the Resource class and how I have flasgger configured

# https://github.com/flasgger/flasgger
# pip install flask flasgger flask-restful
from flasgger import Swagger, LazyString, LazyJSONEncoder
from flask import Flask, jsonify, request, url_for
from flask_restful import Api, Resource

app = Flask(__name__)
api = Api(app)

app.json_encoder = LazyJSONEncoder
app.config['SWAGGER'] = {
    'title': 'TestAPI',
    'uiversion': 3,
    'favicon': LazyString(lambda: url_for('static', filename='logo.png')),
    'swagger_ui_css': LazyString(lambda: url_for('static', filename='swagger-ui.css')),
    'specs_route': '/docs/'
}

swagger = Swagger(app, template_file='static/Swagger.json')

class NewProduct(Resource):
    def post(self):
        return '', 501

api.add_resource(NewProduct, '/product')

if __name__ == "__main__":
    app.run(debug=True)

And below is the contents of the Swagger.json file

{
  "swagger": "2.0",
  "info": {
    "description": "",
    "version": "1.0.0",
    "title": "POC for Non-validation Issue",
    "termsOfService": "http://swagger.io/terms/",
    "contact": {
      "email": "testing@abc.com"
    },
    "license": {
      "name": "Apache 2.0",
      "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
    }
  },
  "host": "",
  "basePath": "/",
  "tags": [
    {
      "name": "Product",
      "description": "Operations to manage product info",
      "externalDocs": {
        "description": "Find out more",
        "url": "http://swagger.io"
      }
    }
  ],
  "schemes": [
    "http"
  ],
  "paths": {
    "/product": {
      "post": {
        "tags": [
          "Product"
        ],
        "summary": "Add a new product",
        "description": "",
        "operationId": "addProduct",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "in": "body",
            "name": "body",
            "description": "",
            "required": true,
            "schema": {
              "$ref": "#/definitions/Product"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Product created"
          },
          "405": {
            "description": "Invalid input"
          },
          "501": {
            "description": "Not Yet Implemented"
          }
        }
      }
    }
  },
  "definitions": {
    "Product": {
      "type": "object",
      "required": [
        "name",
        "photoUrls"
      ],
      "properties": {
        "id": {
          "type": "integer",
          "format": "int64"
        },
        "name": {
          "type": "string",
          "example": "Toy"
        },
        "photoUrls": {
          "type": "array",
          "xml": {
            "name": "photoUrl",
            "wrapped": true
          },
          "items": {
            "type": "string"
          }
        },
        "status": {
          "type": "string",
          "description": "State of availability",
          "enum": [
            "available",
            "pending",
            "sold"
          ]
        }
      },
      "xml": {
        "name": "Toy"
      }
    }
  }
}

I was originally using individual functions and the @swag_from('myfile.yml', validation=True) decorator on each function, but for sake of OOP best-practices I wanted to using classes to represent the respective endpoints.

I figured since I loaded the json template_file when I instantiated Swagger that the endpoints would be validated against the definitions within that file, but it seems that's not the case for some reason (or I'm doing something wrong).

Can anyone offer some insight on how I can validate all endpoints of my classes against the template_file definitions? Can it even be done with the current state of the Flasgger project or is that functionality missing?

Notes:
1. I've created an issue on the Flasgger github repo, which is what I closely mirrored this post after. But, since the repo is pretty uninhabited nowadays I felt it'd be more likely that I'd get an answer here.
2. I'm not looking to use Marshmallow schema, I want to be able to load my swagger schema from the json file when first instantiating Flasgger and have it applied (have all applicable routes validated based on the Definitions within the json file) as a whole to all routes.


Solution

  • I guess the problem is in swagger = Swagger(app, template_file='static/Swagger.json'). Could you please add the option parse and let me know the behaviour.

    swagger = Swagger(app, template_file='static/Swagger.json', parse=True)