1

I am trying to document my request body with Flask-restplus' api.model, and I have to provide the data in a specific structure. Here is the request body looks like:

request body

{
  "version": 0,
  "subject": "SUBJECT10",
  "organization": "company",
  "time": "2010-02-10T09:30:00Z",
  "features": {
    "age": 18,
    "gender": "M",
    "hear_rate": {
      "value": 120,
      "time": "2010-02-10T09:30:00Z"
    },
    "blood pressure": {
      "value": 72,
      "time": "2012-02-10T09:30:00Z"
    },
    "wbc": {
      "value": 10,
      "time": "2010-02-10T09:30:00Z"
    }
  }
}

my attempt:

in order to model my request body as Flask-Restplus' api.model, I came up following solution which raised attribute error.

from flask_restplus import fields
from flask import Flask, jsonify
from flask_restplus import Api, Resource, fields, reqparse, inputs

app = Flask(__name__)
api = Api(app)
ns = api.namespace('test api')

features_attr = api.model('features', {
    # 'name': fields.String(required=True),
    'value': fields.Integer(required=True),
    'time': fields.Date(required=True)
})

class feats_objs(fields.Nested):
    __schema_type__ = ['string', 'object']

    def output(self, key, obj):
        if key =='value':
            return obj
        else:
            return 'default value'
        return super().output(key, obj)

    def schema(self):
        schema_dict = super().schema()
        schema_dict.pop('type')
        nested_ref = schema_dict.pop('$ref')
        schema_dict['oneof'] = [
        {'type': 'string'},
        {'$ref': nested_ref}
        ]
        return schema_dict

feat_root_objs = api.model('feats_root_objs', {
    'age': fields.String(required=True),
    'gender': fields.String(required=True),
    'time': fields.Date(required=True),
    'features': fields.List(fields.biom_feats_objs(features_attr))
    })

@ns.route('/my_features')
class my_features(Resource):
    @ns.expect(feat_root_objs)
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument('age', type=str, required=True),
        parser.add_argument('gender', type=str, required=True),
        parser.add_argument('features', type=str, required=True),
        parser.add_argument('time', type=inputs.datetime_from_iso8601, required=True)

        try:
            args = parser.parse_args()
            return jsonify(args)
        except:
            return None, 400

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

error message:

here is the error message that I got from the above code.

Traceback (most recent call last):
  File ".\my_features.py", line 41, in <module>
    'features': fields.List(fields.feats_objs(features_attr))
AttributeError: module 'flask_restplus.fields' has no attribute 'feats_objs'

but above code gave me attribute error, I don't know why above code can't be compiled. How can I make this work? How can I make correct api.model for above request body and response body? any idea to make this right? thanks

jyson
  • 245
  • 1
  • 8
  • 27
  • 1
    can you post the error and stacktrace that you are getting? – kennyvh Apr 21 '20 at 15:48
  • @khuynh thanks for heads up. I pasted the error. Your possible help would be highly appreciated. – jyson Apr 21 '20 at 15:51
  • @khuynh would you help me how to make this works as I expected? – jyson Apr 21 '20 at 16:57
  • You're using `fields` incorrectly. If you're trying to model a list of `feature_attr`, you can use [`fields.Nested`](https://flask-restplus.readthedocs.io/en/stable/marshalling.html#nested-field). You should modify this line: `'features': fields.List(fields.Nested(features_attr))` to get rid of your error. – kennyvh Apr 21 '20 at 19:02
  • @khuynh you meant modified to `fields.Nested(features_attr)` ? I still have same error. can you post a workable solution? how am I gonna deal with response body? – jyson Apr 21 '20 at 19:11
  • The error goes away for me if you change `'features': fields.List(fields.biom_feats_objs(features_attr))` to `'features': fields.List(fields.Nested(features_attr))`. This is a separate issue from modeling your response body though. – kennyvh Apr 21 '20 at 19:19
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/212197/discussion-between-user88911-and-khuynh). – jyson Apr 21 '20 at 19:21

1 Answers1

2

There are a few things missing from your code:

1) Import the Namespace object from flask_restfulplus

2) Create the namespace using the Namespace object

3) Register the namespace with the API

See the revised code snippet:

from flask_restplus import fields
from flask import Flask, jsonify
from flask_restplus import Api, Namespace, Resource, fields, reqparse, inputs
# 1) import `Namespace` above


app = Flask(__name__)
api = Api(app)
ns = Namespace('test api')       # 2) create the namespace 'test api'

features_attr = api.model('biomarker features', {
    # 'name': fields.String(required=True),
    'value': fields.Integer(required=True),
    'time': fields.Date(required=True)
})

class biom_feats_objs(fields.Nested):
    __schema_type__ = ['string', 'object']

    def output(self, key, obj):
        if key =='value':
            return obj
        else:
            return 'default value'
        return super().output(key, obj)

    def schema(self):
        schema_dict = super().schema()
        schema_dict.pop('type')
        nested_ref = schema_dict.pop('$ref')
        schema_dict['oneof'] = [
        {'type': 'string'},
        {'$ref': nested_ref}
        ]
        return schema_dict

feat_root_objs = api.model('biom_feats_root_objs', {
    'age': fields.String(required=True),
    'gender': fields.String(required=True),
    'time': fields.Date(required=True),
    'features': fields.List(fields.Nested(features_attr))
    })

@ns.route('/immunomatch_Ed_features')
class immunomatch_Ed_features(Resource):
    @ns.expect(feat_root_objs)
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument('age', type=str, required=True),
        parser.add_argument('gender', type=str, required=True),
        parser.add_argument('features', type=str, required=True),
        parser.add_argument('time', type=inputs.datetime_from_iso8601, required=True)

        try:
            args = parser.parse_args()
            return jsonify(args)
        except:
            return None, 400

if __name__ == '__main__':
    api.add_namespace(ns)      # 3) register your namespace with your api
    app.run(debug=True)

See the docs for using multiple namespaces and how to create and register them.

kennyvh
  • 2,526
  • 1
  • 17
  • 26