Tuesday, 17 November 2020

flask -- change output from response

I have a flask api I'm trying to make (it's new territory for me) and I'm unsatisfied with how the data is returned.

It works and connects to the database and returns data (yay) but it's a list of dictionaries (boo).

I would like reformat it so when it is called it appears different.

flask model:

from flask import Flask, jsonify
from flask_restful import Api, Resource, reqparse, abort, fields, marshal_with
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
api = Api(app)
app.config["SQLALCHEMY_DATABASE_URI"] = 'postgres://postgres:[password]@127.0.0.1:5432/usagestats'
db = SQLAlchemy(app)

class StatsModel(db.Model):
    #added in from edit1
    def __getitem__(self, key):
        return self.__dict__[key]
    __tablename__ = "smogon_usage_stats"

    id_ = db.Column(db.Integer, primary_key=True)
    rank = db.Column(db.Integer, nullable=False)
    pokemon = db.Column(db.String(50), nullable=False)
    usage_pct = db.Column(db.Float, nullable=False)
    raw_usage = db.Column(db.Integer, nullable=False)
    raw_pct = db.Column(db.Float, nullable=False)
    real = db.Column(db.Integer, nullable=False)
    real_pct = db.Column(db.Float, nullable=False)
    dex = db.Column(db.Integer, nullable=False)
    date = db.Column(db.String(10), nullable=False)
    tier = db.Column(db.String(50), nullable=False)
    
    def __repr__(self):
        return f"Stats(id = {id_}, rank = {rank}, pokemon = {pokemon}, usage_pct = {usage_pct}, raw_usage = {raw_usage}, raw_pct = {raw_pct}, real = {real}, real_pct = {real_pct})"

resource_fields = {
    'id_': fields.Integer,
    'rank': fields.Integer,
    'pokemon': fields.String,
    'usage_pct': fields.Float,
    'raw_usage': fields.Integer,
    'raw_pct': fields.Float,
    'real': fields.Integer,
    'real_pct': fields.Float,
    'dex': fields.Integer,
    'date': fields.String,
    'tier': fields.String
}

class Stats(Resource):
    @marshal_with(resource_fields)
    def get(self, date, tier):
        result = StatsModel.query.filter_by(date=date, tier=tier + "-1500").all()

        return result

api.add_resource(Stats, "/stats/<string:date>/<string:tier>-1500")
if __name__ == "__main__":
    app.run(host='127.0.0.1', port=3000, debug=True)

Which returns below when called:

[
  {'id_': 669551, 'rank': 153, 'pokemon': 'snorunt', 'usage_pct': 0.07347, 'raw_usage': 104, 'raw_pct': 0.127, 'real': 96, 'real_pct': 0.148, 'dex': 361, 'date': '2020-04', 'tier': 'gen8lc-1500'}, 
  {'id_': 669552, 'rank': 154, 'pokemon': 'milcery', 'usage_pct': 0.0672, 'raw_usage': 108, 'raw_pct': 0.131, 'real': 87, 'real_pct': 0.134, 'dex': 868, 'date': '2020-04', 'tier': 'gen8lc-1500'}, 
  {'id_': 669553, 'rank': 156, 'pokemon': 'cosmog', 'usage_pct': 0.0199, 'raw_usage': 26, 'raw_pct': 0.032, 'real': 19, 'real_pct': 0.029, 'dex': 789, 'date': '2020-04', 'tier': 'gen8lc-1500'}
]

but I would like it to look like this:


{
  "data": 
    'snorunt': {
      'id_': 669551, 'rank': 153, 'pokemon': 'snorunt', 'usage_pct': 0.07347, 'raw_usage': 104, 'raw_pct': 0.127, 'real': 96, 'real_pct': 0.148, 'dex': 361, 'date': '2020-04', 'tier': 'gen8lc-1500'}, 
    'milcery': {
      'id_': 669552, 'rank': 154, 'pokemon': 'milcery', 'usage_pct': 0.0672, 'raw_usage': 108, 'raw_pct': 0.131, 'real': 87, 'real_pct': 0.134, 'dex': 868, 'date': '2020-04', 'tier': 'gen8lc-1500'}, 
    'cosmog': {
      'id_': 669553, 'rank': 156, 'pokemon': 'cosmog', 'usage_pct': 0.0199, 'raw_usage': 26, 'raw_pct': 0.032, 'real': 19, 'real_pct': 0.029, 'dex': 789, 'date': '2020-04', 'tier': 'gen8lc-1500'
  }
}

I've tried manipulating result in the Stats class, but it throws an error (I've since taken it out, but it was an error of not being iterable). I can always change the data with my webapp code, I guess, but I'd rather have it packaged and ready to go.

edit 1

so I found some solutions but nothing has quite worked yet.

To make the object subscriptable, I added this to the model:

    def __getitem__(self, key):
        return self.__dict__[key]
    __tablename__ = "smogon_usage_stats"

and made the Stats class' get function return statement:

return {"data": {x["pokemon"]: x for x in result}}

but that only gave me a single output. which is I guess technically improvement. (also got the same result when trying the proposed answer below

edit 2

I tried simplifying it if maybe I was missing something and stopped trying to be fancy with one line, and it's still giving me a single output. I've verified that result is a list with length 143. But for some reason or other, I'm not getting the results I want, and I'm running out of airspace and ideas.

class Stats(Resource):
    @marshal_with(resource_fields)
    def get(self, date, tier):
        result = StatsModel.query.filter_by(date=date, tier=tier + "-1500").all()
        resp = {"data":{}}
        for i in range(len(result)): 
            t = {result[i]["pokemon"]: result[i]}
            resp["data"].update(t)
        return resp

and that returns this when I do a request:

{
'id_': 0, 
'rank': 0, 
'pokemon': None, 
'usage_pct': None, 
#... to save space but you get the idea
'tier': None
}

also, just to be sure, I did a type() check on the iterated objects, and they returned <class '__main__.StatsModel'>.



from flask -- change output from response

No comments:

Post a Comment