Reputation: 4378
I've been playing around with Tornado, and I've written some code that doesn't seem very nice.
I'm writing an app to store recipes as an example. These are my handlers:
handlers = [
(r"/recipes/", RecipeHandler),
(r"/recipes", RecipeSearchHandler), #so query params can be used to search
]
This lead me to writing this:
class RecipeHandler(RequestHandler):
def get(self):
self.render('recipes/index.html')
class RecipeSearchHandler(RequestHandler):
def get(self):
try:
name = self.get_argument('name', True)
self.write(name)
# will do some searching
except AssertionError:
self.write("no params")
# will probably redirect to /recipes/
Is there a better way to approach these URLs without a try/except? I'd like /recipes and /recipes/ to show the same thing, whereas /recipes?name=something would do a search, and ideally be a different handler.
Upvotes: 46
Views: 81082
Reputation: 21382
If you want to use a more dynamic approach for filtering (instead of a hard coded URL) you can get all the passed URL parameters/arguments using self.request.arguments
in the request handler.
class ApiHandler(RequestHandler):
def get(self, path):
filters = self.request.arguments
for k,v in filters.items():
# Do filtering etc...
See http://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPServerRequest.arguments
Upvotes: 6
Reputation: 555
Tornado also has a get_arguments
function. It returns a list of arguments with the given name. If not present, it returns an empty list ( []
). I found it cleaner this way to sanitize your web service inputs instead of try..catch
blocks.
Sample:
Assume I have a following URL handler:
(r"/recipe",GetRecipe)
And the request handler:
class GetRecipe(RequestHandler):
def get(self):
recipe_id = self.get_arguments("rid")
if recipe_id == []:
# Handle me
self.set_status(400)
return self.finish("Invalid recipe id")
self.write({"recipe_id":self.get_argument("rid")})
recipe_id
list will also hold the value but I found self.get_argument
usage convenient this way.
Now for the results:
curl "http://localhost:8890/recipe" -v
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8890 (#0)
> GET /recipe HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8890
> Accept: */*
>
< HTTP/1.1 400 Bad Request
< Content-Length: 17
< Content-Type: text/html; charset=UTF-8
* Server TornadoServer/1.1.1 is not blacklisted
< Server: TornadoServer/1.1.1
<
* Connection #0 to host localhost left intact
Invalid recipe id
curl "http://localhost:8890/recipe?rid=230" -v
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8890 (#0)
> GET /recipe?rid=230 HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8890
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Length: 20
< Etag: "d69ecb9086a20160178ade6b13eb0b3959aa13c6"
< Content-Type: text/javascript; charset=UTF-8
* Server TornadoServer/1.1.1 is not blacklisted
< Server: TornadoServer/1.1.1
<
* Connection #0 to host localhost left intact
{"recipe_id": "230"}
Upvotes: 16
Reputation: 3971
There is a better way for GET requests. There is a demo in the tornado source on github here
# url handler
handlers = [(r"/entry/([^/]+)", EntryHandler),]
class EntryHandler(BaseHandler):
def get(self, slug):
entry = self.db.get("SELECT * FROM entries WHERE slug = %s", slug)
if not entry: raise tornado.web.HTTPError(404)
self.render("entry.html", entry=entry)
Any "text" that matches the regular expression will be passed to the EntryHandler's get method as slug argument. If the url doesn't match any handler, the user will receive a 404 error.
If you wanted to provide another fallback, you could make the parameter optional
(r"/entry/([^/]*)", EntryHandler),
class EntryHandler(BaseHandler):
def get(self, slug=None):
pass
Update:
+1 for the link. However does this URL pattern extend to include more parameters if I wanted to search like this... /recipes?ingredient=chicken&style=indian – colinjameswebb
Yes it does.
handlers = [
(r'/(\d{4})/(\d{2})/(\d{2})/([a-zA-Z\-0-9\.:,_]+)/?', DetailHandler)
]
class DetailHandler(BaseHandler):
def get(self, year, month, day, slug):
pass
Upvotes: 55
Reputation: 119012
get_argument
allows you to provide a default value:
details=self.get_argument("details", None, True)
If it is provided, then no exception will occur if the argument isn't provided
Upvotes: 46