在 Marshallow / SQLAlchemy Schema 上展平数据
Flatten data on Marshallow / SQLAlchemy Schema
我在如何从端点之一取回数据方面遇到了一些问题 - 特别是 Marshmallow 和 SQLAlchemy。
我在鸡尾酒和配料之间有一个多对多的关系,但我也有比关系 table、ings_in_cocktail 上的外键更多的数据,例如 ounces.
当我得到 /cocktails/
时,它 returns 是这样的:
{
"cocktails": [
{
"glass": "rocks",
"ingredients": [
{
"ingredient": {
"ing_type": "liquor",
"id": 1,
"name": "gin"
},
"ounces": 20
}
],
"finish": "stirred",
"id": 1,
"name": "gin and tonic"
}
]
}
我想做的是将 ounces
属性 的传播与 ingredient
字典结合起来。
我希望数据如下所示:
{
"cocktails": [
{
"glass": "rocks",
"ingredients": [
{
"ing_type": "liquor",
"id": 1,
"name": "gin",
"ounces": 20
}
],
"finish": "stirred",
"id": 1,
"name": "gin and tonic"
}
]
}
在网上搜索了几个小时后,我找不到使用 Marshmallow 轻松完成此操作的方法。有没有我想念的简单方法?
代码
ingredients.py
from flask import Flask
from settings import db, ma
class Ingredient(db.Model):
__tablename__ = 'ingredients'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
ing_type = db.Column(db.String(20), nullable=False)
class IngredientSchema(ma.ModelSchema):
class Meta:
model = Ingredient
ings_in_cocktail.py
from flask import Flask
from settings import db, ma
from models.ingredients import Ingredient, IngredientSchema
class CocktailIngredient(db.Model):
__tablename__ = 'ings_in_cocktail'
ing_id = db.Column(db.Integer, db.ForeignKey('ingredients.id'), primary_key=True)
cocktail_id = db.Column(db.Integer, db.ForeignKey('cocktails.id'), primary_key=True)
ounces = db.Column(db.Integer, nullable=False)
ingredient = db.relationship('Ingredient')
# Necessary for transforming sqlalchemy data into serialized JSON
class CocktailIngredientSchema(ma.ModelSchema):
ingredient = ma.Nested(IngredientSchema, strict=True)
class Meta:
model = CocktailIngredient
cocktails.py
from flask import Flask
from settings import db, ma
from models.ing_in_cocktails import CocktailIngredient, CocktailIngredientSchema
from models.ingredients import Ingredient, IngredientSchema
class Cocktail(db.Model):
__tablename__ = 'cocktails'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
glass = db.Column(db.String(20), nullable=False)
finish = db.Column(db.String(20), nullable=True)
ingredients = db.relationship(
'CocktailIngredient',
# secondary='ings_in_cocktail',
backref=db.backref('cocktails'),
# primaryjoin=id == CocktailIngredient.cocktail_id
)
# Necessary for transforming sqlalchemy data into serialized JSON
class CocktailSchema(ma.ModelSchema):
# this is responsible for returning all the ingredient data on the cocktail
ingredients = ma.Nested(CocktailIngredientSchema, many=True, strict=True)
class Meta:
model = Cocktail
您可以在 IngredientSchema
中使用方法字段
https://marshmallow.readthedocs.io/en/stable/custom_fields.html#method-fields
请检查此以了解如何在文档中使用此字段。
我最终是这样解决的:
class CocktailSchema(ma.ModelSchema):
# this is responsible for returning all the ingredient data on the cocktail
ingredients = ma.Nested(CocktailIngredientSchema, many=True, strict=True)
ingredients = fields.Method('concat_ingredients_dicts')
"""
at this point the ingredients field on the cocktail object looks something like this
ingredients: [{
ingredient: {
name: 'white russian',
glass: 'rocks',
finish: 'stirred'
},
ounces: 2,
action: 'muddle',
step: 1
}]
what we want is to concat this data so "ingredients" just turns
into an list of dicts
"""
def concat_ingredients_dicts(self, obj):
result_ingredients_list = []
i = 0
while i < len(list(obj.ingredients)):
# create a dict from the fields that live in the relational table
relational_fields_dict = {
'ounces': obj.ingredients[i].ounces,
'action': obj.ingredients[i].action,
'step': obj.ingredients[i].step
}
# create a dict from the fields on each ingredient in the cocktail
ingredients_dict = obj.ingredients[i].ingredient.__dict__
ingredients_dict_extracted_values = {
'name': ingredients_dict.get('name'),
'type': ingredients_dict.get('ing_type'),
'id': ingredients_dict.get('id')
}
# merge the two dicts together
merged = dict()
merged.update(ingredients_dict_extracted_values)
merged.update(relational_fields_dict)
# append this merged dict a result array
result_ingredients_list.append(merged)
i += 1
# return the array of ingredients
return result_ingredients_list
class Meta:
model = Cocktail
更新:我把更好的解决方案放在了底部。
您可以通过使用 Method
或 Function
字段允许父项从其子项获取数据来实现此目的。这是您需要做的:
- 别管
CocktailSchema
。
- 完全摆脱
IngredientSchema
(除非您还需要它做其他事情)。
- 在
CocktailIngredientSchema
中,用几个 Function
字段替换 Nested
字段,这些字段直接从内部 "ingredient" 对象中提取数据。
之前的模式
class IngredientSchema(ma.ModelSchema):
class Meta:
model = Ingredient
class CocktailIngredientSchema(ma.ModelSchema):
ingredient = ma.Nested(IngredientSchema)
class Meta:
model = CocktailIngredient
class CocktailSchema(ma.ModelSchema):
ingredients = ma.Nested(CocktailIngredientSchema, many=True)
class Meta:
model = Cocktail
之后的架构
class CocktailIngredientSchema(ma.ModelSchema):
ing_type = ma.Function(lambda obj: obj.ingredient.ing_type)
id = ma.Function(lambda obj: obj.ingredient.id)
name = ma.Function(lambda obj: obj.ingredient.name)
class Meta:
model = CocktailIngredient
class CocktailSchema(ma.ModelSchema):
ingredients = ma.Nested(CocktailIngredientSchema, many=True)
class Meta:
model = Cocktail
棉花糖版本 3+ 的更清洁替代品(需要 Python 3)
- 别管
CocktailSchema
。
- 单独留下
IngredientSchema
(而不是如上所示删除它)。
- 在
CocktailIngredientSchema
中,将 Nested
字段替换为几个直接从内部模式中提取数据的 Pluck
字段。
之后的架构
class IngredientSchema(ma.ModelSchema):
class Meta:
model = Ingredient
class CocktailIngredientSchema(ma.ModelSchema):
ing_type = ma.Pluck(IngredientSchema, 'ing_type')
id = ma.Pluck(IngredientSchema, 'id')
name = ma.Pluck(IngredientSchema, 'name')
class Meta:
model = CocktailIngredient
class CocktailSchema(ma.ModelSchema):
ingredients = ma.Nested(CocktailIngredientSchema, many=True)
class Meta:
model = Cocktail
我在如何从端点之一取回数据方面遇到了一些问题 - 特别是 Marshmallow 和 SQLAlchemy。
我在鸡尾酒和配料之间有一个多对多的关系,但我也有比关系 table、ings_in_cocktail 上的外键更多的数据,例如 ounces.
当我得到 /cocktails/
时,它 returns 是这样的:
{
"cocktails": [
{
"glass": "rocks",
"ingredients": [
{
"ingredient": {
"ing_type": "liquor",
"id": 1,
"name": "gin"
},
"ounces": 20
}
],
"finish": "stirred",
"id": 1,
"name": "gin and tonic"
}
]
}
我想做的是将 ounces
属性 的传播与 ingredient
字典结合起来。
我希望数据如下所示:
{
"cocktails": [
{
"glass": "rocks",
"ingredients": [
{
"ing_type": "liquor",
"id": 1,
"name": "gin",
"ounces": 20
}
],
"finish": "stirred",
"id": 1,
"name": "gin and tonic"
}
]
}
在网上搜索了几个小时后,我找不到使用 Marshmallow 轻松完成此操作的方法。有没有我想念的简单方法?
代码
ingredients.py
from flask import Flask
from settings import db, ma
class Ingredient(db.Model):
__tablename__ = 'ingredients'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
ing_type = db.Column(db.String(20), nullable=False)
class IngredientSchema(ma.ModelSchema):
class Meta:
model = Ingredient
ings_in_cocktail.py
from flask import Flask
from settings import db, ma
from models.ingredients import Ingredient, IngredientSchema
class CocktailIngredient(db.Model):
__tablename__ = 'ings_in_cocktail'
ing_id = db.Column(db.Integer, db.ForeignKey('ingredients.id'), primary_key=True)
cocktail_id = db.Column(db.Integer, db.ForeignKey('cocktails.id'), primary_key=True)
ounces = db.Column(db.Integer, nullable=False)
ingredient = db.relationship('Ingredient')
# Necessary for transforming sqlalchemy data into serialized JSON
class CocktailIngredientSchema(ma.ModelSchema):
ingredient = ma.Nested(IngredientSchema, strict=True)
class Meta:
model = CocktailIngredient
cocktails.py
from flask import Flask
from settings import db, ma
from models.ing_in_cocktails import CocktailIngredient, CocktailIngredientSchema
from models.ingredients import Ingredient, IngredientSchema
class Cocktail(db.Model):
__tablename__ = 'cocktails'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
glass = db.Column(db.String(20), nullable=False)
finish = db.Column(db.String(20), nullable=True)
ingredients = db.relationship(
'CocktailIngredient',
# secondary='ings_in_cocktail',
backref=db.backref('cocktails'),
# primaryjoin=id == CocktailIngredient.cocktail_id
)
# Necessary for transforming sqlalchemy data into serialized JSON
class CocktailSchema(ma.ModelSchema):
# this is responsible for returning all the ingredient data on the cocktail
ingredients = ma.Nested(CocktailIngredientSchema, many=True, strict=True)
class Meta:
model = Cocktail
您可以在 IngredientSchema
https://marshmallow.readthedocs.io/en/stable/custom_fields.html#method-fields
请检查此以了解如何在文档中使用此字段。
我最终是这样解决的:
class CocktailSchema(ma.ModelSchema):
# this is responsible for returning all the ingredient data on the cocktail
ingredients = ma.Nested(CocktailIngredientSchema, many=True, strict=True)
ingredients = fields.Method('concat_ingredients_dicts')
"""
at this point the ingredients field on the cocktail object looks something like this
ingredients: [{
ingredient: {
name: 'white russian',
glass: 'rocks',
finish: 'stirred'
},
ounces: 2,
action: 'muddle',
step: 1
}]
what we want is to concat this data so "ingredients" just turns
into an list of dicts
"""
def concat_ingredients_dicts(self, obj):
result_ingredients_list = []
i = 0
while i < len(list(obj.ingredients)):
# create a dict from the fields that live in the relational table
relational_fields_dict = {
'ounces': obj.ingredients[i].ounces,
'action': obj.ingredients[i].action,
'step': obj.ingredients[i].step
}
# create a dict from the fields on each ingredient in the cocktail
ingredients_dict = obj.ingredients[i].ingredient.__dict__
ingredients_dict_extracted_values = {
'name': ingredients_dict.get('name'),
'type': ingredients_dict.get('ing_type'),
'id': ingredients_dict.get('id')
}
# merge the two dicts together
merged = dict()
merged.update(ingredients_dict_extracted_values)
merged.update(relational_fields_dict)
# append this merged dict a result array
result_ingredients_list.append(merged)
i += 1
# return the array of ingredients
return result_ingredients_list
class Meta:
model = Cocktail
更新:我把更好的解决方案放在了底部。
您可以通过使用 Method
或 Function
字段允许父项从其子项获取数据来实现此目的。这是您需要做的:
- 别管
CocktailSchema
。 - 完全摆脱
IngredientSchema
(除非您还需要它做其他事情)。 - 在
CocktailIngredientSchema
中,用几个Function
字段替换Nested
字段,这些字段直接从内部 "ingredient" 对象中提取数据。
之前的模式
class IngredientSchema(ma.ModelSchema):
class Meta:
model = Ingredient
class CocktailIngredientSchema(ma.ModelSchema):
ingredient = ma.Nested(IngredientSchema)
class Meta:
model = CocktailIngredient
class CocktailSchema(ma.ModelSchema):
ingredients = ma.Nested(CocktailIngredientSchema, many=True)
class Meta:
model = Cocktail
之后的架构
class CocktailIngredientSchema(ma.ModelSchema):
ing_type = ma.Function(lambda obj: obj.ingredient.ing_type)
id = ma.Function(lambda obj: obj.ingredient.id)
name = ma.Function(lambda obj: obj.ingredient.name)
class Meta:
model = CocktailIngredient
class CocktailSchema(ma.ModelSchema):
ingredients = ma.Nested(CocktailIngredientSchema, many=True)
class Meta:
model = Cocktail
棉花糖版本 3+ 的更清洁替代品(需要 Python 3)
- 别管
CocktailSchema
。 - 单独留下
IngredientSchema
(而不是如上所示删除它)。 - 在
CocktailIngredientSchema
中,将Nested
字段替换为几个直接从内部模式中提取数据的Pluck
字段。
之后的架构
class IngredientSchema(ma.ModelSchema):
class Meta:
model = Ingredient
class CocktailIngredientSchema(ma.ModelSchema):
ing_type = ma.Pluck(IngredientSchema, 'ing_type')
id = ma.Pluck(IngredientSchema, 'id')
name = ma.Pluck(IngredientSchema, 'name')
class Meta:
model = CocktailIngredient
class CocktailSchema(ma.ModelSchema):
ingredients = ma.Nested(CocktailIngredientSchema, many=True)
class Meta:
model = Cocktail