Hands-On Docker for Microservices with Python
上QQ阅读APP看书,第一时间看更新

Serializing results

We need to return our results. The easiest way to do so is by defining the shape the JSON result should have through a serializer or marshalling model (https://flask-restplus.readthedocs.io/en/stable/marshalling.html).

A serializer model is defined as a dictionary with the expected fields and a field type:

from flask_restplus import fields

model = {
'id': fields.Integer(),
'username': fields.String(),
'text': fields.String(),
'timestamp': fields.DateTime(),
}
thought_model = api_namespace.model('Thought', model)

The model will take a Python object, and convert each of the attributes into the corresponding JSON element, as defined in the field:

@api_namespace.route('/me/thoughts/')
class MeThoughtListCreate(Resource):

@api_namespace.marshal_with(thought_model)
def post(self):
...
new_thought = ThoughtModel(...)
return new_thought

Note that new_thought is a ThoughtModel object, as retrieved by SQLAlchemy. We'll see it in detail next, but for now, it suffices to say that it has all the attributes defined in the model: id, username, text, and timestamp.

Any attribute not present in the memory object will have a value of None by default. You can change this default to a value that will be returned. You can specify a function, so it will be called to retrieve a value when the response is generated. This is a way of adding dynamic information to your object:

model = {
    'timestamp': fields.DateTime(default=datetime.utcnow),
}

You can also add the name of the attribute to be serialized, in case it's different than the expected outcome, or add a lambda function that will be called to retrieve the value:

model = {
    'thought_text': fields.String(attribute='text'),
'thought_username': fields.String(attribute=lambda x: x.username),
}

For more complex objects, you can nest values like this. Note this defines two models from the point of view of the documentation and that each Nested element creates a new scope. You can also use List to add multiple instances of the same kind:

extra = {
'info': fields.String(),
}
extra_info = api_namespace.model('ExtraInfo', extra)

model
= { 'extra': fields.Nested(extra),
'extra_list': fields.List(fields.Nested(extra)),
}

Some of the available fields have more options, such as the date format for the DateTime fields. Check the full field's documentation (https://flask-restplus.readthedocs.io/en/stable/api.html#models) for more details. 

If you return a list of elements, add the as_list=True parameter in the marshal_with decorator:

@api_namespace.route('/me/thoughts/')
class MeThoughtListCreate(Resource):

@api_namespace.marshal_with(thought_model, as_list=True)
def get(self):
...
thoughts = (
ThoughtModel.query.filter(
ThoughtModel.username == username
)
.order_by('id').all()
)
return thoughts

The marshal_with decorator will transform the result object from a Python object into the corresponding JSON data object. 

By default, it will return a http.client.OK (200) status code, but we can return a different status code returning two values: the first is the object to marshal and the second is the status code. The code parameter in the marshal_with decorator is used for documentation purposes. Note, in this case, we need to add the specific marshal call:

@api_namespace.route('/me/thoughts/')
class MeThoughtListCreate(Resource):

@api_namespace.marshal_with(thought_model,
code=http.client.CREATED)

def post(self):
...
result = api_namespace.marshal(new_thought, thought_model)
return result, http.client.CREATED

The Swagger documentation will display all your used-defined marshal objects:

The end of the Swagger page
One inconvenience of Flask-RESTPlus is that to input and output the same objects, they need to be defined twice, as the modules for input and output are different. This is not the case in some other RESTful frameworks, for example, in the Django REST framework ( https://www.django-rest-framework.org/). The maintainers of Flask-RESTPlus are aware of this, and, according to them, they'll be integrating an external module, probably marshmallow ( https://marshmallow.readthedocs.io/en/stable/). You can integrate it manually if you like, as Flask is flexible enough to do so, take a look at this example ( https://marshmallow.readthedocs.io/en/stable/examples.html#quotes-api-flask-sqlalchemy).

For more details, you can check the full marshalling documentation at https://flask-restplus.readthedocs.io/en/stable/marshalling.html) of Flask-RESTPlus.