flask — изменить вывод из ответа

#python #json #flask #request #flask-sqlalchemy

#python #json #flask #запрос #flask-sqlalchemy

Вопрос:

У меня есть API flask, который я пытаюсь создать (это новая территория для меня), и я недоволен тем, как возвращаются данные.

Он работает и подключается к базе данных и возвращает данные (yay), но это список словарей (boo).

Я хотел бы переформатировать его так, чтобы при его вызове он выглядел иначе.

модель flask:

 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)
  

Который возвращается ниже при вызове:

 [
  {'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'}
]
  

но я бы хотел, чтобы это выглядело так:

 
{
  "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'
  }
}
  

Я пытался манипулировать result в Stats классе, но он выдает ошибку (с тех пор я ее удалил, но это была ошибка, связанная с невозможностью повторения). Я думаю, я всегда могу изменить данные с помощью своего кода веб-приложения, но я бы предпочел, чтобы он был упакован и готов к работе.

редактировать 1

итак, я нашел несколько решений, но пока ничего не сработало.

Чтобы сделать объект подписываемым, я добавил это в модель:

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

и сделал Stats оператор возврата функции get класса:

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

но это дало мне только один вывод. я думаю, это техническое улучшение. (также получил тот же результат при попытке предложить ответ ниже

редактировать 2

Я попытался упростить его, если, возможно, я что-то упустил, и перестал пытаться быть причудливым с одной строкой, и это все еще дает мне один вывод. Я проверил, что result это список с длиной 143 . Но по той или иной причине я не получаю желаемых результатов, и у меня заканчивается пространство и идеи.

 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
  

и это возвращает это, когда я выполняю запрос:

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

кроме того, на всякий случай я type() проверил повторяющиеся объекты, и они вернулись <class '__main__.StatsModel'> .

Ответ №1:

Я не уверен, что это лучший подход, но он работал так, как вы хотели.

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

        return {"data": list(map(lambda x: {x['pokemon']: x}, result))}
  

Комментарии:

1. К сожалению, это не сработало. Я получил сообщение об ошибке TypeError: 'StatsModel' object is not iterable

Ответ №2:

Попробуйте это. Это позволит считывать каждый элемент из вашего результата и сохранять его в dict. Затем сохраните этот dict в другой dict в соответствии с вашим форматом:

 class Stats(Resource):
    @marshal_with(resource_fields)
    def get(self, date, tier):
        dict1, dict2 = {}, {}
        #result = StatsModel.query.filter_by(date=date, tier=tier   "-1500").all()
        for x in StatsModel.query.filter_by(date=date, tier=tier   "-1500").all():
            dict1[x["pokemon"]] = x.__dict__
        dict2["data"] = dict1
        return dict2
  

Комментарии:

1. Спасибо за ответ, но он все равно не сработал. Я не уверен, что происходит. Я протестировал, напечатав значения dict [key], и они печатаются, поэтому он функционирует как словарь. Это был результат, который он дал мне {'id_': 0, 'rank': 0, 'pokemon': None, 'usage_pct': None, 'raw_usage': 0, 'raw_pct': None, 'real': 0, 'real_pct': None, 'dex': 0, 'date': None, 'tier': None} , и не дал ошибок.

2. Хорошо, можете ли вы вставить вывод, полученный при выполнении кода, о котором я упоминал?

3. Извините, мой предыдущий комментарий был сбивающим с толку, это было создано, когда я пытался использовать ваше решение.

4. Хорошо. Можете ли вы сказать, каков тип данных результата? Попробуйте type(result)

5. type(result) дал мне <class 'list'> и каждому type из каждого элемента в result <class '__main__.StatsModel'>

Ответ №3:

Я не использовал Flask-RESTful, но это похоже на простую проблему преобразования данных.

В интерактивной оболочке Python:

 >>> rowList = [
...   {'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'}
... ]
>>> 
>>> transformed = {"data": {row['pokemon']: row for row in rowList}}
>>> 
>>> import pprint
>>> pprint.pprint(transformed)
{'data': {'cosmog': {'date': '2020-04',
                     'dex': 789,
                     'id_': 669553,
                     'pokemon': 'cosmog',
                     'rank': 156,
                     'raw_pct': 0.032,
                     'raw_usage': 26,
                     'real': 19,
                     'real_pct': 0.029,
                     'tier': 'gen8lc-1500',
                     'usage_pct': 0.0199},
          'milcery': {'date': '2020-04',
                      'dex': 868,
                      'id_': 669552,
                      'pokemon': 'milcery',
                      'rank': 154,
                      'raw_pct': 0.131,
                      'raw_usage': 108,
                      'real': 87,
                      'real_pct': 0.134,
                      'tier': 'gen8lc-1500',
                      'usage_pct': 0.0672},
          'snorunt': {'date': '2020-04',
                      'dex': 361,
                      'id_': 669551,
                      'pokemon': 'snorunt',
                      'rank': 153,
                      'raw_pct': 0.127,
                      'raw_usage': 104,
                      'real': 96,
                      'real_pct': 0.148,
                      'tier': 'gen8lc-1500',
                      'usage_pct': 0.07347}}}
>>> 
  

Если я чего-то не упустил, похоже, transformed это именно то, что вы ищете. (Чего мне не хватает?)

Конечно, может быть, что result в вашем коде нет a list , в то время rowList как выше есть. В этом случае сначала рассмотрите возможность преобразования его в список (и преобразования содержащихся записей в dicts).

Ответ №4:

Для простого переключения между

 [
    {"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"}
]
  

и

 {"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"
    }
}}
  

Попробуйте просто

 new_result = {"data": {}}
for item in dictionary:
    new_result["data"][item["pokemon"]] = item
  

где dictionary первый и new_result второй.

Ответ №5:

 class Stats(Resource):
@marshal_with(resource_fields)
def get(self, date, tier):
    data_dict, result = {}, {}
    for item in StatsModel.query.filter_by(date=date, tier=tier   "-1500").all():
        data_dict[item["pokemon"]] = item
    result["data"] = data_dict
    return result