46

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.

4 Answers 4

55

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 
Sign up to request clarification or add additional context in comments.

1 Comment

+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
46

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

Comments

16

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"} 

Comments

6

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

1 Comment

Good hint. Just one remark regarding Unicode Strings. The documentation says: Names are of type str, while arguments are byte strings. Note that this is different from RequestHandler.get_argument, which returns argument values as unicode strings.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.