Search code examples
pythonflask-restplus

Flask-restplus returning marshal model instead of the data


So I'm pretty new to implementing flask-restplus and I have encountered this road block.

I have read the restplus docs over and over again and followed several exampled. But the behavior that I'm facing is very much different from what is supposed to be.

So I have a model that is supposed to be a list of objects of another model (returned from the function drone_model()).

drones_list = api.model('drones_list', {
    'items': fields.List(fields.Nested(drone_model())),
    'message':fields.String(''),
    'code': fields.Integer('')

})

Everything works fine, no errors. But when I try the API (http://127.0.0.1:5000/datamine/v2/drones), as a response I get the Marshalling model back instead of the data itself. If I print the data, it gets printed, but for some reason in the web, the restplus model is returned.

Below I have the code that I had written. If I take the marshal_with decorator off, then the data is returned just fine.

@api.route('/')
class DronesList(Resource):

    @api.marshal_with(drones_list, envelope='data')
    @api.response(200, 'All drones successfully fetched!')
    def get(self):
        """
        Get all drones!.
        """
        from app.utils.common import get_start_end_date_from_request
        start_date, end_date = get_start_end_date_from_request(request)
        drones = []

        for drone in Drone.objects:
            drones.append({
                'id': str(drone.id),
                'serial_id': drone.serial_id,
                'maintenances': [],
                'status': get_dynamic_status(drone, start_date, end_date),
                'picture_url': drone.asset.picture_url,
                'manufacturer': drone.asset.manufacturer,
                'model_name': drone.asset.model_name,
                'drone_type': drone.asset.drone_type,
                'payload_type': drone.asset.payload_type,
                'asset_url': drone.get_url(drone.id)
            })
        success = ClientSuccessFunctionClass('All drones successfully fetched!', 200, drones)
        return (success.to_dict())

These are the outputs on the browser:

1. Without the marshal decorator:

{
    "data": {
        "items": [
            {
                "id": "5aeafcb93a33683f73827e91",
                "serial_id": "Drone 1",
                "maintenances": [],
                "status": "Decommissioned",
                "picture_url": "some img url",
                "manufacturer": "DJI",
                "model_name": "Phantom 4 Pro",
                "drone_type": "Quadcopter",
                "payload_type": "RGB Camera",
                "asset_url": "http://127.0.0.1:5000/datamine/v1/drones/5aeafcb93a33683f73827e91"
            },
            {
                "id": "5aeaff374f85747f90df2714",
                "serial_id": "Drone 2",
                "maintenances": [],
                "status": "Available",
                "picture_url": "sime url",
                "manufacturer": "DJI",
                "model_name": "Phantom 4",
                "drone_type": "Quadcopter",
                "payload_type": "RGB Camera",
                "asset_url": "http://127.0.0.1:5000/datamine/v1/drones/5aeaff374f85747f90df2714"
            }
        ],
        "message": "All drones successfully fetched!",
        "code":200
    }
}

2. With the marshal decorator:

{
    "data": {
        "items": [
            {
                "id": "Id of Drone",
                "serial_id": "Name of Drone",
                "status": "Status of Drone",
                "maintenances": null,
                "picture_url": "Picture URL",
                "manufacturer": "Manufacturer of Drone",
                "model_name": "Model name of Drone",
                "drone_type": "Type of Drone",
                "payload_type": "Payload type of Drone",
                "asset_url": "Asset URL of Drone"
            }
        ],
        "message": "",
        "code": ""
    }
}

It would be really helpful if someone could tell me what I'm doing wrong as I need to recive the output as the one shown in snippet of the output without the decorator.

Thank you.


Solution

  • Here is a diagram showing invocation order from top to bottom to help make sense of what is happening:

    get() 
       → api.response(200, 'All drones successfully fetched!') # documents the response
          → api.marshal_with(drones_list, envelope='data')` # returns marshalled dict
    

    The result from invoking get is passed to the api.response decorator function whose result is passed on to api.marshal_with decorator function.

    Looking at the shape of the dictionary returned from invoking get()

    {
        data {
            items [
                {
                    id,
                    serial_id,
                    maintenances,
                    status,
                    picture_url,
                    manufacturer,
                    model_name,
                    drone_type,
                    payload_type,
                    asset_url
                }
            ],
            message,
            code
        }
    }
    

    The message and code in the response are nested inside of the data.

    You need to model the data appropriately, to be able to marshal it. This can be done by passing an argument for what field to look up in the marshal dictionary.

    drones_list = api.model('drones_list', {
        'items': fields.List(fields.Nested(drone_model()), attribute='data.items'),
        'message':fields.String(attribute='data.message'),
        'code': fields.Integer(attribute='data.code')
    })
    

    As you can see, it's pretty redundant applying the api.marshal_with decorator function on the view given that it's only unnests then nests the result in data field.