Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flask-restplus fields.Nested() with raw Dict (not model)

Spoiler alert: I posted my solution as an answer to this question

I am using flastk-resptlus to create an API. I have to provide the data in a specific structure, which I have problems to get, see an example below:

What I need to get is this structure:

{
    "metadata": {
        "files": [] 
    },
    "result" : {
        "data": [
                {
                 "user_id": 1,
                  "user_name": "user_1",
                  "user_role": "editor"
                },
                {
                  "user_id": 2
                  "user_name": "user_2",
                  "user_role": "editor"
                },
                {
                  "user_id": 3,
                  "user_name": "user_3",
                  "user_role": "curator"
                }
            ]
    }
}

But the problem comes that I cannot manage to get the structure of "result" : { "data": []} without making "data" a model itself.

What I tried to do so far (and did not work)

# define metadata model
metadata_model = api.model('MetadataModel', {
          "files": fields.List(fields.String(required=False, description='')),
}
# define user model 
user_model = api.model('UserModel', {
          "user_id": fields.Integer(required=True, description=''),
          "user_name": fields.String(required=True, description=''),
          "user_role": fields.String(required=False, description='')
}

# here is where I have the problems
user_list_response =  api.model('ListUserResponse', {
            'metadata': fields.Nested(metadata_model),
            'result' :  {"data" : fields.List(fields.Nested(user_model))}
             })

Complains that cannot get the "schema" from "data" (because is not a defined model), but I don't want to be a new api model, just want to append a key called "data". Any suggestions?

This I tried and works, but is not what I want (because I miss the "data"):

user_list_response =  api.model('ListUserResponse', {
            'metadata': fields.Nested(metadata_model),
            'result' :  fields.List(fields.Nested(user_model))
            })

I don't want data to be a model because the common structure of the api is the following:

{
    "metadata": {
        "files": [] 
    },
    "result" : {
        "data": [
                <list of objects> # here must be listed the single model
            ]
    }
}

Then, <list of objects> can be users, addresses, jobs, whatever.. so I want to make a "general structure" in which then I can just inject the particular models (UserModel, AddressModel, JobModel, etc) without creating a special data model for each one.

like image 973
Dovi Avatar asked Jan 25 '23 14:01

Dovi


1 Answers

A possible approach is to use fields.Raw which returns whatever serializable object you pass. Then, you can define a second function, which creates your result and uses marshal. marshal transforms your data according to a model and accepts an additional parameter called envelope. envelope surrounds your modeled data by a given key and does the trick.

from flask import Flask
from flask_restplus import Api, fields, Resource, marshal

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

metadata_model = api.model("metadata", {
    'file': fields.String()
})

user_model = api.model('UserModel', {
          "user_id": fields.Integer(required=True, description=''),
          "user_name": fields.String(required=True, description=''),
          "user_role": fields.String(required=False, description='')
})

response_model = api.model("Result", {
    'metadata': fields.List(fields.Nested(metadata_model)),
    'result': fields.Raw()
})


@api.route("/test")
class ApiView(Resource):

    @api.marshal_with(response_model)
    def get(self):

        data = {'metadata': {},
                'result': self.get_user()}
        return data


    def get_user(self):
        # Access database and get data
        user_data = [{'user_id': 1, 'user_name': 'John', 'user_role': 'editor'},
                     {'user_id': 2, 'user_name': 'Sue', 'user_role': 'curator'}]

        # The kwarg envelope does the trick
        return marshal(user_data, user_model, envelope='data')


app.run(host='0.0.0.0', debug=True)

Response

like image 145
Molitoris Avatar answered Jan 31 '23 09:01

Molitoris