Flacon réparateur :marshaler un objet complexe en json
-
21-12-2019 - |
Question
J'ai une question concernant l'extension reposante du flacon.Je viens juste de commencer à l'utiliser et j'ai rencontré un problème.J'ai flask-sqlalchemy
les entités qui sont connectées à une relation plusieurs-à-un et je veux que ce point de terminaison reposant renvoie une entité parent avec tous ses enfants dans json
en utilisant marshaller.Dans mon cas, Set contient de nombreux paramètres.J'ai regardé un flacon reposant documents mais il n'y avait aucune explication sur la façon de résoudre cette affaire.
On dirait qu'il me manque quelque chose d'évident mais je n'arrive pas à trouver de solution.Voici mon code :
# entities
class Set(db.Model):
id = db.Column("id", db.Integer, db.Sequence("set_id_seq"), primary_key=True)
title = db.Column("title", db.String(256))
parameters = db.relationship("Parameters", backref="set", cascade="all")
class Parameters(db.Model):
id = db.Column("id", db.Integer, db.Sequence("parameter_id_seq"), primary_key=True)
flag = db.Column("flag", db.String(256))
value = db.Column("value", db.String(256))
set_id = db.Column("set_id", db.Integer, db.ForeignKey("set.id"))
# marshallers
from flask.ext.restful import fields
parameter_marshaller = {
"flag": fields.String,
"value": fields.String
}
set_marshaller = {
'id': fields.String,
'title': fields.String,
'parameters': fields.List(fields.Nested(parameter_marshaller))
}
# endpoint
class SetApi(Resource):
@marshal_with(marshallers.set_marshaller)
def get(self, set_id):
entity = Set.query.get(set_id)
return entity
restful_api = Api(app)
restful_api.add_resource(SetApi, "/api/set/<int:set_id>")
Maintenant, quand j'appelle /api/set/1
J'obtiens une erreur de serveur :
TypeError: 'Set' object is unsubscriptable
J'ai donc besoin d'un moyen de définir correctement set_marshaller pour que le point final renvoie ce json :
{
"id": : "1",
"title": "any-title",
"parameters": [
{"flag": "any-flag", "value": "any-value" },
{"flag": "any-flag", "value": "any-value" },
.....
]
}
J'apprécie toute aide.
La solution
J'ai trouvé moi-même la solution à ce problème.
Après avoir joué avec flask-restful
je découvre que j'ai fait quelques erreurs :
Premièrement set_marshaller
devrait ressembler à ceci :
blob_marshaller = {
'id': fields.String,
'title': fields.String,
'parameters': fields.Nested(parameter_marshaller)
}
Le marshaller agité peut gérer le cas si le paramètre est une liste et les marshals vers json
liste.
Un autre problème était que dans l'API Set, les paramètres ont un chargement paresseux, donc quand j'essaie de marshall Set, j'obtiens KeyError: 'parameters'
, j'ai donc besoin de charger explicitement des paramètres comme celui-ci :
class SetApi(Resource):
@marshal_with(marshallers.set_marshaller)
def get(self, set_id):
entity = Set.query.get(set_id)
entity.parameters # loads parameters from db
return entity
Ou une autre option consiste à modifier la relation du modèle :
parameters = db.relationship("Parameters", backref="set", cascade="all" lazy="joined")
Autres conseils
Ceci est un ajout à Zygimantasc'est répondre:
J'utilise Flask-RESTful et c'est une solution pour le chargement des propriétés imbriquées.
Vous pouvez transmettre un appel au maréchal décorateur :
class OrgsController(Resource):
@marshal_with(Organization.__json__())
def get(self):
return g.user.member.orgs
Mettez ensuite à jour les modèles pour renvoyer les champs de ressources de sa propre entité.Les entités imbriquées renverront ainsi les champs de ressources de leur entité de manière relative.
class Organization(db.Model):
id = db.Column(db.Integer, primary_key=True)
...
@staticmethod
def __json__(group=None):
_json = {
'id': fields.String,
'login': fields.String,
'description': fields.String,
'avatar_url': fields.String,
'paid': fields.Boolean,
}
if group == 'flat':
return _json
from app.models import Repository
_json['repos'] = fields.Nested(Repository.__json__('flat'))
return _json
class Repository(db.Model):
id = db.Column(db.Integer, primary_key=True)
owner_id = db.Column(db.Integer, db.ForeignKey('organization.id'))
owner = db.relationship('Organization', lazy='select', backref=db.backref('repos', lazy='select'), foreign_keys=[owner_id])
...
@staticmethod
def __json__(group=None):
_json = {
'id': fields.String,
'name': fields.String,
'updated_at': fields.DateTime(dt_format='iso8601'),
}
if group == 'flat':
return _json
from app.models import Organization
_json['owner'] = fields.Nested(Organization.__json__('flat'))
return _json
Cela donne la représentation que je recherche et honore le chargement paresseux :
[
{
"avatar_url": "https://avatars.githubusercontent.com/u/18945?v=3",
"description": "lorem ipsum.",
"id": "1805",
"login": "foobar",
"paid": false,
"repos":
[
{
"id": "9813",
"name": "barbaz",
"updated_at": "2014-01-23T13:51:30"
},
{
"id": "12860",
"name": "bazbar",
"updated_at": "2015-04-17T11:06:36"
}
]
}
]
J'aime
1) comment cette approche me permet de définir mes champs de ressources par entité et est disponible pour tous mes itinéraires de ressources à travers l'application.
2) comment l'argument de groupe me permet de personnaliser la représentation comme je le souhaite.Je n'ai que « plat » ici, mais n'importe quelle logique peut être écrite et transmise à des objets imbriqués plus profonds.
3) les entités ne sont chargées que si nécessaire.