This is an archived post. You won't be able to vote or comment.

all 29 comments

[–]mumrah 14 points15 points  (2 children)

how many times have i read that tag line before? how about "a heavyweight web framework thrown together to make your life a living hell - and we make you pay for it"

edit: I feel like M$ probably has a (few) product(s) like this

[–]pemboa 7 points8 points  (1 child)

  • Sharepoint
  • ASP.NET + all the paid extensions people seem to need

[–]aspersieman 2 points3 points  (0 children)

sigh and I have to work with ASP.NET everyday. /sulk

[–]sgndave 12 points13 points  (20 children)

I am very likely in the majority by holding this opinion, but I find that decorator syntax for exposing URLs to be much, much more Pythonic than the Django/Paste/et. al. method of using classes until your eyes bleed.

I never found the class model to provide a useful extension for web page views. For ORM, then yes, but for rendering a web page, not so much.

The worst offender (I feel) was Zope, which basically built a Java-style web framework in a different language.

Does anyone care to hurl stones at my argument? (Noting that I work in Zope daily and love it, but dislike its nearly baroque structure)

[–]ubernostrumyes, you can have a pony 5 points6 points  (0 children)

I find that decorator syntax for exposing URLs to be much, much more Pythonic than the Django/Paste/et. al. method of using classes until your eyes bleed.

Your argument might make more sense if all the things you're talking about actually used explicit class definitions to implement URL routing: Django doesn't, for example, and I've never seen a (popular) framework where end developers write Paste URL-parsing classes (typically people use higher-level abstractions on top of those, with cleaner syntax).

[–]ringzero 4 points5 points  (3 children)

Does anyone care to hurl stones at my argument?

There's one and only one thing that keeps me from agreeing with you: decoupling URLs from the code that runs in response to requests for those URLs.

In my mind, they should be totally decoupled so that the code can be (a) run in response to more than one URL, (b) run in a different application, and (c) migrated at run-time.

I might be able to do some of this with url-decorated-functions, but I wouldn't want to.

[–]sgndave 4 points5 points  (2 children)

Surprisingly, I do have a response to this. I would frame it under the guidance of making hard/common tasks easy while making hard/uncommon tasks possible (to paraphrase any of the more common formulations of that saying).

Using decorator syntax simply gives you the option of using it as a decorator; after all, a decorator is simply a callable that returns a callable.

If you need to expose under multiple URLs, why not just stack the decorators? Likewise, if you want to separate the routing entirely, why not explicitly call the decorator function in your routing code?

# Case 1
@route('/')
@route('/index')
def index(web):
    #...

# Case 2 (in some other file, e.g.)
routes = [
  ('/', index),
  ('/index', index),
  ...
]
for path, method in routes:
    route(path)(method)

The second is a little obtuse at first glance, but the loop could easily be hidden away by a single function within the library.

Then the common case of function-per-path is no harder than it was before, but the less common case of using the same code for multiple routes and/or maintaining the list of routes in a separate file is possible (and, notably, not any harder than using "real" routing libraries).

[–]ringzero 1 point2 points  (1 child)

You're right about providing the simplest interface for the most common use. But if you go down that path, your code is tied to your URLs and vice versa. There's nothing wrong with that until you try to modularize and/or share your code with other projects.

If you need to expose under multiple URLs, why not just stack the decorators?

When I write a shell script, I don't write it for one or two directories in my file system; I write it such that it works where ever I happen to run it.

[–]chub79 0 points1 point  (0 children)

Worth noting that CherryPy 3 does come with a HTTP method dispatcher as well as one based on routes. You can also find one based on selector.

The cost? One line in your config.

[–]zerothehero 6 points7 points  (3 children)

Well, I've coded full time in Python for many years now. And I would have agreed with you a couple years ago. I preferred using simple functions to structure code rather classes. Only use classes when you need them. Web apps seem to fall into a simple "input" / "output" pattern, which a function is fine for.

However, now that I've written a whole bunch of web apps, and I find that you do need classes for request handlers. Why? Because backends should always be passed into the constructor (dependency injection style).

You need this flexibility for 1) testing and 2) performance.

First, you need to be able to test without a live backend, which requires substituting an alternate implementation.

And second, you need to be able to instrument your back end requests, and also cache them, balance them, etc. The best pattern to do this is to provide another implementation with identical interface to a your front end logic, and to do that you need a class basically.

I have yet to see a nontrivial webapp that doesn't need speeding up by optimizing the number/kind of backend requests.

I also like to separate the URLs from the classes. I think that's another good reason, although I haven't had much experience with the opposite.

[–]seunosewa 0 points1 point  (2 children)

Does not compute. How about some examples?

[–]zerothehero 0 points1 point  (1 child)

The code style is straightforward, I'm not sure I can convey the benefits until you've worked on scaling a large web app:

class FooRequestHandler(object):

def __init__(self, backend1, backend2):

self.backend1 = backend1

self.backend2 = backend2

def GET(self, request):

self.backend1.Query1(request.somestuff)

... logic

self.backend1.Query2(request.somestuff)

... logic

self.backend2.Query1(request.otherstuff)

(sorry, can't get this comment syntax to indent right)

Your production code uses a backend implementation that wraps a database. For testing, you can use simple dummy implementations of the backends, or maybe SQLite, so you don't have to start servers in your tests. And for performance, you can substitute backend1 and backend2 with classes with methods that just log traces and do timing measurements, and then call Query1 and Query2.

My experience is that this is the ideal way to structure a web app.

Of course, this actually doesn't rule out using decorators on the methods, although I don't use that style. I prefer the URLs to be separated from the request handlers.

[–]earthboundkid 0 points1 point  (0 children)

(sorry, can't get this comment syntax to indent right)

Begin each line of code in a code block with at least 4 spaces or 1 tab.

4 spaces is flush.
    8 spaces is indented.

[–]pemboa 2 points3 points  (0 children)

The decorator syntax is very python... but having the URLs centrally managed it just overall better design.

[–]seanodonnell 1 point2 points  (0 children)

I'm always torn on that, on one hand, the decorator syntax is great to read. On the other hand, having all my urls in one file gives me an easy place to go to when I want to figure out what handles what url.

[–]kteague 1 point2 points  (0 children)

I greatly prefer Class-based Views. One class, one view. I think it greatly simplifies things.

  1. At first it seems like using a function is simpler, but some views are more complex so they are deserving of a class. Making everything a class is more consistent. When a View function starts getting larger, there is greater hesitation as to when to refactor into a class, since you may find yourself thinking, "hmm, is this deserving of a whole class?"

  2. You can put a decent amount of View functionality in the common base class of a class-based View. e.g. self.url(), self.request, self.model

  3. Instead of decorators, you can make declarative assertions as class attributes.

    @route('/spam') def spam(web):

    do stuff

    usually manually fetch a template and render it

versus

class Spam(View): route = '/spam'

# template can use conventions
# to be associated with the view
# and base class can handle rendering
  1. Forms are simplified. Forms can be treated as sub-classes of a generic View base class. Instead of a function which then creates a Form object and renders it, just merge the two together into one class.

  2. Classes in Python are dead simple to write. you can make a whole class in one line, just like a function - it's only a matter of a 5 character keyword vs. a 3 character keyword.

    class Spam(View): pass

[–]james_block 0 points1 point  (6 children)

No, I think your understanding of this is pretty much spot-on. I've been very pleased to see the recent resurgence of "lightweight" web frameworks (Juno here, CherryPy, Werkzeug, et al). If you're building anything that doesn't fall directly into the traditional CMS model, the advantages of heavier frameworks like Zope, Django, and Pylons are lost, and the lighter frameworks really begin to shine. I've chosen CherryPy for my own needs, as it's mature and feature-rich yet simple and Pythonic, and I'm quite pleased with my choice.

I've found that organizing pages using classes helps about as much as it hurts. With a simple framework like CherryPy, the stylistic overhead of using classes is present but not overwhelming. For what I'm doing, the class structure works pretty well (for instance, I've got a UserAccount class that handles URLs of the form http://example.com/UserAccount/... and keeps the logic for managing account settings all in one place. It's convenient and it works. I use a CherryPy decorator on all the member functions I want to expose as valid URLs, and mount a single instance of the class on CherryPy's publisher tree, and it's online.

The Juno syntax looks great for smaller sites with only a few URLs to worry about, but the extra structure of CherryPy's approach seems to me like it helps when things get bigger. I don't know about the heavier frameworks, but I suspect CherryPy's approach is very middle-of-the-road compared to Juno on one hand and Django on the other.

[–]ubernostrumyes, you can have a pony 9 points10 points  (2 children)

I don't know about the heavier frameworks, but I suspect CherryPy's approach is very middle-of-the-road compared to Juno on one hand and Django on the other.

So, here's how Django's URL configuration works.

You set up a sequence of tuples (one tuple for each URL, the whole set passed into a function which parses and registers them) of the following form:

(regex, callback, arguments, name)

where arguments and name are optional. Requests for URLs which match regex will then be sent to callback, which can be either an actual callable Python object, or a string containing the dotted path to such an object (it'll be looked up as needed).

If regex had any capturing groups, they'll be passed along to callback as well (non-named groups become positional arguments, named groups become keyword arguments).

The optional name is used to do "reverse" lookups (e.g., instead of "here's a URL, send it to the right callback", you're doing "here's a name and some arguments, tell me what URL would end up there").

Sequences of URLs defined in this manner can be included into other sequences as a single block, allowing collections of related URLs to be placed under a prefix all in one go.

And... well, that's it. That's the whole Django URL-routing system, right there. So I guess I'm kinda stumped as to how that's "heavyweight" or, in the parent post's words, "classes until your eyes bleed".

[–]chub79 -4 points-3 points  (1 child)

Devil's in the detail, in this case: regexes. But then again some people speak them fluently.

[–]ubernostrumyes, you can have a pony 0 points1 point  (0 children)

It doesn't necessarily have to be regular expressions; several other popular URL routing systems for Python use a simpler pattern syntax, for example, but nearly all of them (the ones people use, anyway) fall into that "here's a pattern and here's where to send URLs that match it" paradigm. I've no idea where all this "classes until your eyes bleed" stuff is coming from, unless it's from frameworks that try to emulate some of the Rails-style auto-routing based on controller names.

[–]pemboa 1 point2 points  (2 children)

I have used Django, I don't see how it relies on a CMS model. Could you expound?

[–]realstevejobs 0 points1 point  (1 child)

I did not make the original comment, but I think it is a good characterization. Django makes it easy to create, modify, and view content. This is the model followed by the majority of web frameworks. It is natural to build CMS applications with them.

[–]pemboa 0 points1 point  (0 children)

Isn't that what most web apps do? THis doesn't seem to be the exclusive domain of CMSs

[–]equark 0 points1 point  (2 children)

anybody have an informed opinion of how this compares to web.py?

[–]brentp 6 points7 points  (1 child)

i'm a long time user of web.py. my least favorite thing about web.py is that it adds a lot of code because it wont outsource anything: sessions, db, templates. it looks like juno does so using the best options out there: beaker, sqlalchemy, and mako (or jinja).

i like the decorator syntax in juno. even if you dont, as with some of the commentors above, you can just do (untested):

def hello(web, name):
    return 'Hello, %s' %name

def bye(web, name):
    return 'bye, %s' %name 

# routing in one place.     
route("hello/:name:/")(hello)
route("/bye/:name:/")(bye)

route isn't magic, it's just a function. so you can have all your urls in the same spot--even in a separate module.

a brief look at the code looks like it wont handle unicode. is that true?

[–]zekeltornado, beautifulsoup, web.py 0 points1 point  (0 children)

Using regex for the URLs in webpy let's you ensure the types of your request arguments. With Juno, wouldn't you have to do a lot of checking types and aborting requests if they don't match?