Reputation: 429
I have been stuck on this for a while now. My problem is I need to be able to use marshal_with and validate on nested fields coming from a POST. My test looks like this:
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)
and my endpoint class looks like this:
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'] always comes in as None
. Anyone have any ideas?
Upvotes: 4
Views: 12338
Reputation: 3535
You're missing 'Content-Type' header and also an action='append'
for your pets argument as described here.
I tested your code above with just adding the 'Content-Type' and action as prescribed and it works in both Python 2.7 and 3.
Now the issue is it returns a list of strings since you specified your pets argument as type=str
. In order to get a list of dicts, you'll have to write a custom parser type (as @Josh rightly pointed out). See example below:
Ex.
def pet_parser(pet):
# You can do other things here too as suggested in @Josh's repsonse
return pet
In the Resource:
parser.add_argument('pets', type=pet_parser, action='append')
and in your test function:
headers = [('Content-Type', 'application/json')]
self.client.post('url', data=data, headers=headers)
Upvotes: 1
Reputation: 1336
Here's an example in the docs of how to make a custom parser type.
Basically, you define a function that:
ValueError
if parsing or validation failsBasic example related to your question:
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)
As you might guess, this gets klunky and annoying rather quickly. The request parsing in Flask-RESTful isn't designed to handle nested data. It works very well for querystring arguments, headers, basic JSON, etc. and can be configured to handle nested data. However if you are going to do this a lot, save yourself some pain down the road and look into a marshalling library like Marshmallow.
Upvotes: 3