Frasco repousante:empacotar objeto complexo para json
-
21-12-2019 - |
Pergunta
Eu tenho uma pergunta sobre a extensão tranquila do frasco.Acabei de começar a usá-lo e enfrentei um problema.Eu tenho flask-sqlalchemy
entidades que estão conectadas por uma relação muitos-para-um e quero que esse ponto final tranquilo retorne a entidade pai com todos os seus filhos em json
usando marshaller.No meu caso, Set contém muitos parâmetros.Eu olhei para o frasco repousante documentos mas não houve nenhuma explicação de como resolver este caso.
Parece que estou perdendo algo óbvio, mas não consigo encontrar nenhuma solução.Aqui está o meu código:
# 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>")
Agora, quando eu ligo /api/set/1
Recebo um erro no servidor:
TypeError: 'Set' object is unsubscriptable
Então, preciso de uma maneira de definir corretamente set_marshaller para que o endpoint retorne este json:
{
"id": : "1",
"title": "any-title",
"parameters": [
{"flag": "any-flag", "value": "any-value" },
{"flag": "any-flag", "value": "any-value" },
.....
]
}
Agradeço qualquer ajuda.
Solução
Eu mesmo encontrei a solução para esse problema.
Depois de brincar com flask-restful
descobri que cometi alguns erros:
Primeiramente set_marshaller
deve ficar assim:
blob_marshaller = {
'id': fields.String,
'title': fields.String,
'parameters': fields.Nested(parameter_marshaller)
}
O empacotador inquieto pode lidar com o caso se o parâmetro for lista e empacotar para json
lista.
Outro problema foi que na API Set os parâmetros têm carregamento lento, então quando tento empacotar Set recebo KeyError: 'parameters'
, então preciso carregar explicitamente parâmetros como este:
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 outra opção é alterar o relacionamento do modelo:
parameters = db.relationship("Parameters", backref="set", cascade="all" lazy="joined")
Outras dicas
Este é um acréscimo Zygimantasde responder:
Estou usando Flask-RESTful e esta é uma solução para o carregamento das propriedades aninhadas.
Você pode passar um callable para o marechal decorador:
class OrgsController(Resource):
@marshal_with(Organization.__json__())
def get(self):
return g.user.member.orgs
Em seguida, atualize os modelos para retornar os campos de recursos para sua própria entidade.As entidades aninhadas retornarão, portanto, os campos de recursos para sua entidade de forma relativa.
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
Isso fornece a representação que procuro e respeita o carregamento lento:
[
{
"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"
}
]
}
]
Eu gosto
1) como essa abordagem me permite definir meus campos de recursos por entidade e está disponível para todas as minhas rotas de recursos no aplicativo.
2) como o argumento do grupo me permite personalizar a representação da maneira que desejar.Eu só tenho 'flat' aqui, mas qualquer lógica pode ser escrita e transmitida para objetos aninhados mais profundos.
3) as entidades são carregadas apenas quando necessário.