Flask-restful,marshal_with + 嵌套数据

4

我已经卡住了一段时间了。我的问题是我需要能够在从POST中获取的嵌套字段上使用 marshal_with 和 validate。我的测试看起来像这样:

def test_user_can_apply_with_multiple_dogs(self):

    data = {
        # User data
        'registration_type': "guest",
        'first_name': 'Alex',
        'last_name': 'Daro',
        'phone_number': '805-910-9198',
        'street': '13950 NW Passage',
        'street2': '#208',
        'city': 'Marina del Rey',
        'state': 'CA',
        'zipcode': '90292',
        'photo': 'test_image.png',
        'how_did_you_hear': 0,
        #Dog data
        'pets': [
            {
                'dog_photo': "dog.png",
                'name': 'Genghis Khan',
                'breed': 'Shih Tzu',
                'age': 'Puppy',
                'size': 'Small',
            },
            {
                'dog_photo': "dog2.png",
                'name': 'Archibald',
                'breed': 'Great Dane',
                'age': 'Adult',
                'size': 'Extra Large',
            },
        ]
    }

    resp = self.client.post('/profile/registration', data=json.dumps(data))
    self.assertEqual(resp.status_code, 200)

我的端点类如下所示:

nested_fields = {
    'dog_photo': fields.String,
    'name': fields.String,
    'breed': fields.String,
    'age': fields.String, 
    'size': fields.String, 
}

profile_fields = {
    # 'user_email':fields.String,
    'token': fields.String,
    'registration_type': fields.String,
    'first_name': fields.String,
    'last_name': fields.String,
    'phone_number': fields.String,
    'street': fields.String,
    'street2': fields.String,
    'city': fields.String,
    'state': fields.String,
    'zipcode': fields.Integer,
    'photo': fields.String,
    'how_did_you_hear': fields.String,
    #Dog data
    'pets': fields.Nested(nested_fields)

}
class GuestProfile(Resource):
    @marshal_with(profile_fields)
    def post(self):
        # User data
        parser = reqparse.RequestParser()
        parser.add_argument('registration_type', type=str)
        parser.add_argument('first_name', type=str, required=True, help="First Name cannot be blank.")
        parser.add_argument('last_name', type=str, required=True, help="Last Name cannot be blank.")
        parser.add_argument('phone_number', type=str, required=True, help="Phone Number cannot be blank.")
        parser.add_argument('street', type=str, required=True, help="Street cannot be blank.")
        parser.add_argument('street2', type=str)
        parser.add_argument('city', type=str, required=True, help="City cannot be blank.")
        parser.add_argument('state', type=str, required=True, help="State cannot be blank.")
        parser.add_argument('zipcode', type=str, required=True, help="Zipcode cannot be blank.")
        parser.add_argument('photo', type=str, required=True, help="Please select a photo.")
        parser.add_argument('how_did_you_hear', type=str, required=True, help="How did you hear about us cannot be "
                                                                              "blank.")
        parser.add_argument('pets', type=str)
        kwargs = parser.parse_args()
        print kwargs, "KWWW" 

kwargs['pet'] 总是以 None 的形式出现。有人有什么想法吗?


1
你应该停止使用 type=str。从你的打印语句来看,我猜测你在使用 Python 2,因此你的解析器将无法处理 Unicode。如果你完全删除 type 参数,那么默认类型将是 Unicode 字符串。 - Josh
@Josh 谢谢你的评论,很有道理,但似乎并没有帮助。你知道有没有使用marshal_with/reqparse处理嵌套对象列表的示例/文档? - Alex Daro
您还可以通过不为每个请求实例化新的RequestParser来清理代码。将它们放在模块级别上也完全没有问题。 - Josh
2个回答

3

这是文档中如何创建自定义解析器类型的示例。

基本上,您需要定义一个函数:

  • 获取从请求中提取的参数的原始值
  • 如果解析或验证失败,则引发ValueError
  • 如果验证成功,则返回该参数

与您的问题相关的基本示例:

def pet_list_parser(pets):
    if type(pets) != list:
        raise ValueError('Expected a list!')

    # Do your validation of the pet objects here. For example:
    for pet in pets:
        if 'name' not in pet:
            raise ValueError('Pet name is required')

        # Also do any conversion of data types here
        pet['name'] = pet['name'].capitalize()

    return pets

parser = RequestParser()
parser.add_argument('pets', type=pet_list_parser)

你可能已经猜到了,这很快就变得笨重和恼人。Flask-RESTful中的请求解析器并不设计用于处理嵌套数据。它非常适用于查询字符串参数、标头、基本JSON等,并可以配置以处理嵌套数据。但是如果你需要经常处理嵌套数据,为自己省些麻烦,建议使用像Marshmallow这样的编排库。


我的设置有一个问题。传入pet_list_parser的pets参数是作为字典而不是列表传入的。你觉得我的profile_fields看起来正确吗,特别是'pets': fields.Nested(nested_fields)这一行? - Alex Daro
2
请参考@junnytony的建议,使用'pets': fields.List(fields.Nested(nested_fields))(您的代码中没有明确说明您需要一个对象列表)。 - Josh

1
你缺少“Content-Type”头,并且对于你的宠物参数也需要一个action='append',如这里所述。
我测试了你上面的代码,只需添加建议的'Content-Type'和action,它可以在Python 2.7和3中工作。
现在问题是它返回一个字符串列表,因为你将你的宠物参数指定为type=str。为了获得字典列表,你需要编写一个自定义解析器类型(正如@Josh所指出的)。请参见下面的示例:
Ex.
def pet_parser(pet):
    # You can do other things here too as suggested in @Josh's repsonse
    return pet

在资源中:
parser.add_argument('pets', type=pet_parser, action='append')

在你的测试函数中:
headers = [('Content-Type', 'application/json')]
self.client.post('url', data=data, headers=headers)

@DirkDigler。这个回答没有帮到你的地方在哪里?如果你能更具体一些,我可能能够提供一个更好的答案。请看上面我的编辑。 - junnytony
看起来 @Josh 的答案更符合我的需求。你的编辑更像是我期望的内容。 - Alex Daro
我的设置有一个问题。进入pet_list_parser的pets参数以字典形式而不是列表形式出现。你觉得我的profile_fields看起来正确吗,特别是'pets': fields.Nested(nested_fields)这一行? - Alex Daro
1
我没有完全理解你的第一条评论。针对你的问题,如果你希望返回一个宠物列表,那么应该使用'pets': fields.List(fields.Nested(nested_fields)) - junnytony
@junnytony,除非您希望参数包含一个json编码的字符串,否则不应在自定义验证器中使用json.loads。当输入到达此验证器时,请求的json已经被解码并作为dictlist传递给验证器和解析器。 - Josh
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接