I have a Flask-RESTful based API, which currently has two blueprints, allowing me to version the API with backwards-incompatible changes.
from api_1_0 import api_bp as api_1_0_blueprint
app.register_blueprint(api_1_0_blueprint, url_prefix='/api/v1.0')
from api_1_1 import api_bp as api_1_1_blueprint
app.register_blueprint(api_1_1_blueprint, url_prefix='/api/v1.1')
Whenever I need to make a backwards incompatible change (e.g. removing an endpoint), I create a new blueprint. Currently, the blueprints all share the same models.py
file, defining the database tables and the JSON representation of each model.
I now have a need to create a new version of the API where a particular resource field email
will be changed from a string
data type to array[string]
. The existing versions of the API must retain the original resource representation.
I have tried placing a models.py
file in each blueprint folder so that the newer blueprint v1.2
can have it's own resource_fields
definition, but in doing so I end up with this error:
sqlalchemy.exc.InvalidRequestError: Table '' is already defined for this MetaData instance. Specify 'extend_existing=True' to redefine options and columns on an existing Table object.
I understand this is happening because I'm essentially defining the same database tables for every blueprint. Really all I want is to change the resource_fields
per blueprint, as the database schema is always going to be the same across all API versions, it is just the JSON response which might change. (I will use the @property
decorator to create the new fields)
Given this setup, how can I change the resource_fields
per blueprint?
Below is some sample (simplified) code from my project.
app/models.py - https://gist.github.com/MattHealy/4c9d2c03615e3381774235bbbc398437
from app import db
from flask.ext.restful import fields
@swagger.model
class Contact(db.Model):
resource_fields = {
'email': fields.String
}
email = db.Column(db.String(100))
app/api_1_1/resources/contacts.py - https://gist.github.com/MattHealy/556c93fe33a929e469ae18bf76db83b1
from flask.ext.restful import Resource, marshal, reqparse
from ... models import Contact
class ContactAPI(Resource):
"Retrieve details of a single contact"
@swagger.operation(
nickname = "contact",
responseClass=Contact.__name__,
parameters=[
{
"name": "id",
"description": "ID of the contact",
"required": True,
"allowMultiple": False,
"dataType": "int",
"paramType": "path"
},
],
responseMessages=[
{
"code": 200,
"message": "Contact retrieved"
},
],
summary="Get details of a single contact",
)
def get(self, id):
contact = Contact.query.get(id)
return { 'contact': marshal(contact, Contact.resource_fields) }
You can just use a different (locally generated) resource_fields dict per blueprint. In your specific case, you could use resource_fields as an attribute of the ContactAPI
class and pass it to the marshalling function.
In the specific case you show above (change of type for the email field) I think you will also need to build a custom Fields class (based on fields.Raw) to accommodate the type of output you might want to get.