Reputation: 1628
This seems like something that would come up a lot, but I can't find any documentation on it.
I'm writing an api and I want urls to look like this:
'/api/v1.0/restaurant/Name&Address'
Using Flask-restful, I've defined the url as
'/api/v1.0/restaurant/<name>&<address>'
Werkzeug doesn't like this however and raises a BuildError in werkzeug/routing.py
When I define the url, with add_resource, as
'/api/v1.0/restaurant/<name>'
and hard-wire the address, everything works fine.
How do I define the urls to take two variables?
Edit
Traceback (most recent call last):
File "/home/ubuntu/.virtualenvs/data/local/lib/python2.7/site-packages/flask/app.py", line 1475, in full_dispatch_request
rv = self.dispatch_request()
File "/home/ubuntu/.virtualenvs/data/local/lib/python2.7/site-packages/flask/app.py", line 1461, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/home/ubuntu/.virtualenvs/data/local/lib/python2.7/site-packages/flask_restful/__init__.py", line 397, in wrapper
resp = resource(*args, **kwargs)
File "/home/ubuntu/.virtualenvs/data/local/lib/python2.7/site-packages/flask/views.py", line 84, in view
return self.dispatch_request(*args, **kwargs)
File "/home/ubuntu/.virtualenvs/data/local/lib/python2.7/site-packages/flask_restful/__init__.py", line 487, in dispatch_request
resp = meth(*args, **kwargs)
File "/home/ubuntu/Hotsauce/api/app/views.py", line 75, in get
resto = {'restaurant': marshal(restaurant, resto_fields)}
File "/home/ubuntu/.virtualenvs/data/local/lib/python2.7/site-packages/flask_restful/__init__.py", line 533, in marshal
return OrderedDict(items)
File "/usr/lib/python2.7/collections.py", line 52, in __init__
self.__update(*args, **kwds)
File "/home/ubuntu/.virtualenvs/data/lib/python2.7/_abcoll.py", line 547, in update
for key, value in other:
File "/home/ubuntu/.virtualenvs/data/local/lib/python2.7/site-packages/flask_restful/__init__.py", line 532, in <genexpr>
for k, v in fields.items())
File "/home/ubuntu/.virtualenvs/data/local/lib/python2.7/site-packages/flask_restful/fields.py", line 232, in output
o = urlparse(url_for(self.endpoint, _external = self.absolute, **data))
File "/home/ubuntu/.virtualenvs/data/local/lib/python2.7/site-packages/flask/helpers.py", line 312, in url_for
return appctx.app.handle_url_build_error(error, endpoint, values)
File "/home/ubuntu/.virtualenvs/data/local/lib/python2.7/site-packages/flask/app.py", line 1641, in handle_url_build_error
reraise(exc_type, exc_value, tb)
File "/home/ubuntu/.virtualenvs/data/local/lib/python2.7/site-packages/flask/helpers.py", line 305, in url_for
force_external=external)
File "/home/ubuntu/.virtualenvs/data/local/lib/python2.7/site-packages/werkzeug/routing.py", line 1620, in build
raise BuildError(endpoint, values, method)
BuildError: ('restaurant', {u'city_id': 2468, u’score’: Decimal('0E-10'), 'id': 37247, u'nbhd_id': 6596, u'address_region': u'NY', u'phone_number': u'(718) 858-6700', '_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x26f33d0>, u'complete': False, u'name': u'Asya', u'address_locality': u'New York', u'address_updated': True, u'street_address': u'46 Henry St'}, None)
And here is the relevant code that generates the error:
resto_fields = {
'id': fields.Integer,
'name': fields.String,
'street_address': fields.String,
'address_locality': fields.String,
'address_region': fields.String,
‘score’: fields.Float,
'phone_number': fields.String,
'uri': fields.Url('restaurant')
}
def get(self, name, address):
restaurant = session.query(Restaurant).filter_by(name=name).filter_by(address=address)
resto = {'restaurant': marshal(restaurant, resto_fields)}
return resto
Upvotes: 0
Views: 2434
Reputation: 960
Took me a while to figure this out, so a corrected answer...
@Martijn's answer is not quite correct for this case.
Correct is: You have to have the attributes required for the get
method in your data dictionary (Not in the output fields).
So your code should work like this:
resto_fields = {
'id': fields.Integer,
'name': fields.String,
'street_address': fields.String,
'address_locality': fields.String,
'address_region': fields.String,
‘score’: fields.Float,
'phone_number': fields.String,
'uri': fields.Url('restaurant')
}
def get(self, name, address):
restaurant = session.query(Restaurant).filter_by(name=name).filter_by(address=address)
# restaurant must have an 'address' field
restaurant['address'] = ' '.join[restaurant['street_address'], restaurant['address_locality']]
resto = {'restaurant': marshal(restaurant, resto_fields)}
return resto
address
will not be part of the generated response
Upvotes: 0
Reputation: 1121246
This has nothing to do with the &
ampersand nor with with using more than one URL parameter.
You can only use entries from the resto_fields
output fields mapping in your endpoint; you don't have an address
entry in your resto_fields
mapping, but your restaurant
endpoint requires it to build the URL.
Add an address
field to your output fields, or use one of the existing fields in the route.
Upvotes: 2
Reputation: 1628
This is not ideal, but it gets things working.
The problem was occurring when flask-restful was trying to create the uri for the resource during marshalling with resto_fields.
This wasn't a problem when the url only took name as a variable, but, once the url required name&address, a BuildError would get raised.
To workaround this problem I removed
'uri': fields.Url('restaurant')
from restos_fields, and constructed the uri after marshalling the resource and added it to the marshalled resource before returning it.
resto = {'restaurant': marshal(restaurant, resto_fields)}
resto['restaurant']['uri'] = '/api/v1.0/restaurant/{0}&{1}'.format(name, address)
return resto
If anyone has a more elegant way of making this work I'd be eager to hear about it.
Upvotes: 1