0
votes

flask - modifier la sortie de la réponse

J'ai une API flask que j'essaie de créer (c'est un nouveau territoire pour moi) et je ne suis pas satisfait de la manière dont les données sont renvoyées.

Cela fonctionne et se connecte à la base de données et renvoie des données (yay) mais c'est une liste de dictionnaires (boo).

Je voudrais le reformater afin que, quand il est appelé, il semble différent.

modèle de flacon:

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

Qui revient ci-dessous lorsqu'il est appelé:

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

mais je voudrais que cela ressemble à ceci:

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

J'ai essayé de manipuler le result dans la classe Stats , mais cela génère une erreur (je l'ai depuis supprimé, mais c'était une erreur de ne pas être itérable). Je peux toujours modifier les données avec le code de mon application Web, je suppose, mais je préfère l'avoir emballé et prêt à l'emploi.

modifier 1

j'ai donc trouvé des solutions mais rien n'a encore fonctionné.

Pour rendre l'objet indexable, j'ai ajouté ceci au modèle:

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

et a fait l'instruction de retour de la fonction get de la classe Stats :

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

mais cela ne m'a donné qu'une seule sortie. ce qui est, je suppose, une amélioration technique. (a également obtenu le même résultat en essayant la réponse proposée ci-dessous

modifier 2

J'ai essayé de le simplifier si quelque chose me manquait peut-être et j'ai arrêté d'essayer d'être sophistiqué avec une ligne, et cela me donne toujours une seule sortie. J'ai vérifié que le result est une liste d'une longueur de 143 . Mais pour une raison ou une autre, je n'obtiens pas les résultats que je souhaite, et je manque d'espace aérien et d'idées.

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

et cela renvoie ceci quand je fais une demande:

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)

aussi, juste pour être sûr, j'ai fait une vérification de type() sur les objets itérés, et ils ont renvoyé <class '__main__.StatsModel'> .


0 commentaires

5 Réponses :


2
votes

Je ne sais pas si c'est la meilleure approche, mais cela a fonctionné comme vous le souhaitiez.

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 commentaires

Malheureusement, cela n'a pas fonctionné. J'ai eu une erreur TypeError: 'StatsModel' object is not iterable



1
votes

Essaye ça. Cela lira chaque élément de votre résultat et l'enregistrera dans un dict. Ensuite, enregistrez ce dict dans un autre dict, selon votre format:

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


12 commentaires

Merci pour la réponse, mais cela n'a toujours pas fonctionné. Je ne suis pas sûr de ce qui se passe. J'ai testé en imprimant les valeurs de dict [clé] et elles s'impriment, donc cela fonctionne comme un dictionnaire. C'était le résultat qu'il m'a donné {'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} et n'a donné aucune erreur.


D'accord, pouvez-vous coller le résultat que vous avez reçu lors de l'exécution du code que j'ai mentionné?


Désolé, mon commentaire précédent était déroutant, il a été produit lorsque j'ai essayé d'utiliser votre solution.


D'accord. Pouvez-vous dire quel est le type de données du résultat? Essayez le type(result)


type(result) m'a donné <class 'list'> et chaque type de chaque élément dans result est <class '__main__.StatsModel'>


J'ai un peu modifié le code. Pouvez-vous essayer ça? J'ai fouillé et j'ai trouvé que pour extraire les données des objets SQLAlchemy, vous devriez les utiliser directement dans la boucle for, au lieu de les enregistrer et de les appeler à nouveau. Voici un exemple de lien


Je l'ai essayé et j'ai obtenu le même résultat que mon premier commentaire. Cependant, j'ai ajouté une instruction d'impression pour déboguer et j'ai obtenu ceci: {'data': {'goldeen': <StatsModel 669508>, 'vullaby': <StatsModel 669411>, 'onix': <StatsModel 669412>, 'timburr': <StatsModel 669413>}}


OK, essayez x .__ dict__. Cela aidera à accéder au dict interne de SQLAlchemy. J'ai édité le code ci-dessus.


Soupir. Cela n'a pas fonctionné non plus. J'ai essayé le codage en dur dans les valeurs de dict même: dict1[x["pokemon"]] = {'id_': x["id_"], 'rank': x["rank"], 'pokemon': x["pokemon"], 'usage_pct': x["usage_pct"], 'raw_usage': x["raw_usage"], 'raw_pct': x["raw_pct"], 'real': x["real"], 'real_pct': x["real_pct"], 'dex': x["dex"], 'date': x["date"], 'tier': x["tier"]} et même cela a donné les mêmes résultats.


x.__dict__ imprimé: {'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x7fa5d616b310>, 'date': '2020-04', 'real_pct': 6.806, 'raw_pct': 6.328, 'raw_usage': 5200, 'pokemon': 'koffing', 'id_': 669434, 'tier': 'gen8lc-1500', 'dex': 109, 'real': 4429, 'usage_pct': 6.79894, 'rank': 26}


Je pense que cette chose x .__ dict__ fonctionne. Il vous suffit de supprimer les clés '_sa_instance_state'. Droite?


On l'aurait pensé. Cependant, pas de chance après avoir essayé avec la compréhension de liste ou avec .pop('_sa_insatnce_state') ou avec del . J'ai obtenu le résultat: TypeError: 'InstanceState' object is not subscriptable . J'ai utilisé à la fois x.pop et x.__dict__.pop et le meilleur des cas est le résultat empy dict dans le commentaire supérieur. c'est comme une de ces choses où le jus ne vaut pas la peine d'être pressé.



0
votes

Je n'ai pas utilisé Flask-RESTful, mais cela semble être un simple problème de transformation de données.

Dans un shell Python interactif:

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

À moins que je ne manque quelque chose, il semble que la transformed soit exactement ce que vous recherchez. (Qu'est-ce que je rate?)

Il pourrait être, bien sûr, ce result dans votre code n'est pas une list , tandis que rowList ci - dessus est. Dans ce cas, envisagez d'abord de la convertir en liste (et de convertir les enregistrements contenus en dictionnaires).


0 commentaires

0
votes

Pour changer simplement entre

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

et

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

Essayez simplement

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

dictionary est le premier et new_result le second.


0 commentaires

0
votes
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

0 commentaires