TurboGears - First Impression

6 views
Skip to first unread message

Daniel

unread,
Oct 20, 2005, 1:09:47 AM10/20/05
to TurboGears
This evening I wrote a review of TurboGears. Read it here:
http://www.openpolitics.com/pieces/archives/001763.html

Keep up the good work.

Elvelind Grandin

unread,
Oct 20, 2005, 5:46:35 AM10/20/05
to turbo...@googlegroups.com
Nice review. I've committed a patch to tg's svn that adds a allow_json
option so now you bad part should be solved :)

And I agree that raising an error to redirect isn't to beautiful
--
cheers
elvelind grandin

bon...@gmail.com

unread,
Oct 20, 2005, 6:03:41 AM10/20/05
to TurboGears
I believe raising exception is a style for python and being used quite
liberally, not just in this case. try/except in my programs looks very
much like if/else as in C.

Daniel

unread,
Oct 20, 2005, 8:48:13 AM10/20/05
to TurboGears
bon...@gmail.com wrote:
> I believe raising exception is a style for python and being used quite
> liberally, not just in this case. try/except in my programs looks very
> much like if/else as in C.

The problem is, there's no explicit condition in a try/except block.
Any statement after try before except may throw an exception that may
or may not be caught by the except clause. It's less clear how the
program will actually run than if/else.

Also, sometime in the future Python may not support raising classes
that are not derived from Exception (I read that somewhere, but can't
remember where--I think it was a PEP)--that would mean the TG raise
redirect would not work. Not to mention, it doesn't seem very Pythonic
to be raising some class that is not an error--it's just not the one
obvious way.

Finally, although I think it's a bad idea to use this as the sole
reason for ruling something out, raising/catching an exception is less
performant than using a simple if/else due to the overhead of the
exception handling mechanism.

Kevin Dangoor

unread,
Oct 20, 2005, 8:48:41 AM10/20/05
to turbo...@googlegroups.com
On 10/20/05, Daniel <4da...@gmail.com> wrote:
>
Thanks for the review! Thanks also for the positive comments about the
docs. I seem to see alternating good and bad reviews of the docs. I
think there's a ways to go yet, but they are shaping up.

You're correct that the automatic JSON may expose information that you
don't want to expose. This is particularly true once you can serialize
SQLObjects directly. Elvelind fixed this for 0.9.

As for the raise HTTPRedirect construct, we had some discussion about
that earlier on. In CherryPy 2.0, you called a function to do a
redirect, which was very unpleasant because it didn't reflect that
you're done with the request and should not do anything else. The
raise takes care of that.

I'd also be fine with

return cherrypy.HTTPRedirect(...)

The nice thing about raise is something like this:

@turbogears.expose()
def mypublicmethod(self, arg):
someval = self.dosomething(arg)
return self.process(someval)

def dosomething(self, arg):
# oops, something isn't in the right state...
raise cherrypy.HTTPRedirect(...)

Of course, that is an exceptional condition, so maybe that makes perfect sense.


As far as the whole notion of using redirects, you're right that
you're not locked into this with TurboGears, you can just shift to
another view. But, I personally think redirects solve problems: they
make the URLs in the location bar more consistent with the view and
the alleviate the need to introduce tokens to solve the multiple
posting of a form problem. But, that's a matter of taste and
application requirement, and TurboGears certainly supports returning
some view other than the one listed in expose.

Thanks again for posting the review!

Kevin

--
Kevin Dangoor
Author of the Zesty News RSS newsreader

email: k...@blazingthings.com
company: http://www.BlazingThings.com
blog: http://www.BlueSkyOnMars.com

Kevin Dangoor

unread,
Oct 20, 2005, 8:49:30 AM10/20/05
to turbo...@googlegroups.com
On 10/20/05, Elvelind Grandin <elve...@gmail.com> wrote:
>
> Nice review. I've committed a patch to tg's svn that adds a allow_json
> option so now you bad part should be solved :)

Thanks!

Kevin

bon...@gmail.com

unread,
Oct 20, 2005, 9:01:57 AM10/20/05
to TurboGears
Very true. I hate exceptions in python in general because of its
liberal use but as but I said, it is a style employed by lots of
modules.

On the other hand, I think HTTPRedirect exception in
cherrypy/Turbogears is a good use of it, in python that is. Web apps is
some special kind of programs that fits with GOTOs and this is actually
goto.

Kevin Dangoor

unread,
Oct 20, 2005, 9:07:38 AM10/20/05
to turbo...@googlegroups.com
On 10/20/05, Daniel <4da...@gmail.com> wrote:
> The problem is, there's no explicit condition in a try/except block.
> Any statement after try before except may throw an exception that may
> or may not be caught by the except clause. It's less clear how the
> program will actually run than if/else.

You'll have no argument from me on that one.

> Also, sometime in the future Python may not support raising classes
> that are not derived from Exception (I read that somewhere, but can't
> remember where--I think it was a PEP)--that would mean the TG raise
> redirect would not work. Not to mention, it doesn't seem very Pythonic
> to be raising some class that is not an error--it's just not the one
> obvious way.

Of course, it's easy enough to just make HTTPRedirect descend from Exception :)

> Finally, although I think it's a bad idea to use this as the sole
> reason for ruling something out, raising/catching an exception is less
> performant than using a simple if/else due to the overhead of the
> exception handling mechanism.

It must not be too bad, given that iterators work by raising an
exception when they're done. I agree that you probably wouldn't want a
tight loop of exception raising, though.

Daniel

unread,
Oct 20, 2005, 9:48:40 AM10/20/05
to TurboGears

Kevin Dangoor wrote:
> You're correct that the automatic JSON may expose information that you
> don't want to expose. ... Elvelind fixed this for 0.9.

That's amazing--fixed in less than 12 hours after my review was posted!
I'll update the review tonight.


> As for the raise HTTPRedirect construct
>

> I'd also be fine with
>
> return cherrypy.HTTPRedirect(...)

Yes, I like that too.


> The nice thing about raise is something like this:
>
> @turbogears.expose()
> def mypublicmethod(self, arg):
> someval = self.dosomething(arg)
> return self.process(someval)
>
> def dosomething(self, arg):
> # oops, something isn't in the right state...
> raise cherrypy.HTTPRedirect(...)
>
> Of course, that is an exceptional condition, so maybe that makes perfect sense.

Let's think about what the "dosomething" method is doing here--it's
creating something to be processed, or short-circuiting to a redirect.
IMHO that type of control flow leads to buggy apps because you're never
quite sure what to expect when you call a method--the control flow is
not explicit (very bad in a complex app). It would be more clear to do
it this way:

@turbogears.expose()
def mypublicmethod(self, arg):
try:
someval = self.dosomething(arg)
except InconsistentStateError:
return cherrypy.HTTPRedirect(...)
return self.process(someval)

def dosomething(self, arg):
# oops, something isn't in the right state...

raise InconsistentStateError()

Again, I think it's important to keep application flow clear and
explicit.

bon...@gmail.com

unread,
Oct 20, 2005, 10:01:35 AM10/20/05
to TurboGears
I would disagree here(I believe we have talk about this before).

return cherrypy.HTTPRedirect.

It looks like two incompatible meaning in one. Do you want to return or
redirect ? return to me means back to caller and in this case, it could
very well be decorated. So do I know if some logic is there ? raise
exception is cleaner to me in this case.

John Speno

unread,
Oct 20, 2005, 10:26:23 AM10/20/05
to turbo...@googlegroups.com

On Oct 20, 2005, at 10:01 AM, bon...@gmail.com wrote:

>
> I would disagree here(I believe we have talk about this before).
>
> return cherrypy.HTTPRedirect.

By way of comparison, in Subway, we do it this way:

response.redirect("/someurl")

which provides enough context to understand what is meant, at least
for me. Of course, 'raise cherrypy.HTTPRedirect("/someurl")' also
works in subway.

Kevin Dangoor

unread,
Oct 20, 2005, 10:46:16 AM10/20/05
to turbo...@googlegroups.com
On 10/20/05, John Speno <sp...@isc.upenn.edu> wrote:
> By way of comparison, in Subway, we do it this way:
>
> response.redirect("/someurl")
>
> which provides enough context to understand what is meant, at least
> for me. Of course, 'raise cherrypy.HTTPRedirect("/someurl")' also
> works in subway.

The part I don't like about this is that it doesn't reflect that
you're done with the request as far as program control flow goes. You
can keep doing other things and even try to return an HTML page at the
end.

Kevin Dangoor

unread,
Oct 20, 2005, 11:31:26 AM10/20/05
to turbo...@googlegroups.com
My general philosophy is that exceptions are for abnormal situations,
not standard control flow. That's a philosophy I've had for several
years. The "raise cherrypy.HTTPRedirect" construct violates that rule,
but is far better than calling a function that sets up a redirect on
one line and returns on another (opening up the possibility of doing
other things instead of returning).

returning a signal to redirect is a little more pleasant to me,
because it reflects that it's a normal and expected situation.

By the way, I'm not going to add code to TurboGears to prevent "raise
cherrypy.HTTPRedirect" from working...

The real question is whether having two ways to do it with the
"return" mechanism considered preferred is better than having one way
to do it. (Actually, I'm guessing that the CherryPy 2.0 way, which is
similar to John Speno's Subway example, is still available in some
form... so there's probably already two ways to do it.)

So, there's definitely a no vote on that from bonono and a yes vote
from Daniel.

Kevin

bon...@gmail.com

unread,
Oct 20, 2005, 11:47:12 AM10/20/05
to TurboGears
That is my view as well but I am afraid to say that exceptions in
python has been expanded into something like normal flow(when you have
lots of exceptions, it is no longer exception). And modern language
don't have goto which is what is going on here.

The problem with returning signal(or any form of return, even just let
the function fall through to the end) is that if there is N decorators,
each of them must code into this situation:

if redirect: return #let whoever knows how to do it do it, not me
else: do the proper decorator stuff

Leandro Lucarella

unread,
Oct 20, 2005, 12:52:33 PM10/20/05
to turbo...@googlegroups.com
bon...@gmail.com, el jueves 20 de octubre a las 14:01 me escribiste:
>
> I would disagree here(I believe we have talk about this before).
>
> return cherrypy.HTTPRedirect.
>
> It looks like two incompatible meaning in one. Do you want to return or
> redirect ?

You want to return the result of the operation. It can be a simple dict to
be processed by the template (or something else) or it can be a HTTP error
code.

What about:
if I_need_redirect:
cherrypy.setResponseCode(cherry.py.HTTPRedirect)
return

(I don't know that much about cherrypy so I'm making up the
setResponseCode function, but I guess cherrypy have something like it)

In this way you can even have a redirect and process the template too
(with some special values), because you can have a redirect error code and
a beatiful webpage explaining what happend in case the browser don't
support redirect (and the same for NotFound, Forbiden, etc).

--
LUCA - Leandro Lucarella - JID: luca(en)lugmen.org.ar - Debian GNU/Linux
.------------------------------------------------------------------------,
\ GPG: 5F5A8D05 // F8CD F9A7 BF00 5431 4145 104C 949E BFB6 5F5A 8D05 /
'--------------------------------------------------------------------'
El techo de mi cuarto lleno de cometas

bon...@gmail.com

unread,
Oct 20, 2005, 1:06:38 PM10/20/05
to TurboGears
As mentioned in another post, when you have other decorators, the
situation becomes complicated.

I would say that return means it is a "normal" flow that the page is
properly handled by the current function, may be it stucks different
value into the returned dictionary(including an alternative template)
but still normal. Redirect is "I don't know how to handle this and
returning to my caller is meaningless, another URL is better for what
follows".

Of course it is still a matter of choice(and convention) as I can
simply return simply string which in general would bypass all
turbogears handling.

I just want to point out the possible scenario one may be facing.

Daniel

unread,
Oct 20, 2005, 8:03:29 PM10/20/05
to TurboGears
> Redirect is "I don't know how to handle this and returning to my
> caller is meaningless, another URL is better for what follows"

Can you give me a use case for that?

--

The root of the problem is the expected behavior of the function that
may cause a redirect.

>From Kevin's original example, the "dosomething" method returns an
object to be processed before the view is rendered. However, in the
case of an inconsistent state, it hijacks the control flow and does an
effective "GOTO someOtherPage". The problem here is subtle. In one case
we expect the function to return a piece of data that does not directly
affect the control flow (i.e. the return value does not contain a view
choice). In the other case (inconsistent state) the function plays a
direct role in handling the control flow, superceding any other
"normal" control flow handling.

I would argue that this is bad logic. If a function is not normally
aware of control flow it should NEVER directly cause a redirect. It may
raise an exception in case of inconsistent state, in which case the
parent controller (that knows about views) will cause a redirect if
needed. The parent controller must always have the final say in what
view is rendered, although it may delegate the view choice to a child
controller. In that case, we expect a single type of return value from
the child--an object that includes a view choice. The parent controller
has the final say because it can inspect the child return value and
keep or change it as needed.

I hope this is clear. I'm not trying to be a prick. In the end, I would
be happy to let this issue rest if there was simply a way to redirect
by passing a signal through the return path.

bon...@gmail.com

unread,
Oct 20, 2005, 8:49:44 PM10/20/05
to TurboGears
That depends on style IMO. GOTO to me is perfectly acceptable(and
sometimes desirable) for web apps. Of course you can return back
through the stack but what it actually like is :

f(g(h(i(your_function))))

Which is the controller ? all f, g, h, i need to know your signal and
short circuit it back to the caller. Of course, you can say that is
more flexible as each can do different things because of the signal(and
in some case needed).
If all are written by the same person/same group of people, that may be
fine. But what if you use someone's stuff (say h) which knows nothing
about your exception situation(and the return value). It may boom.

One thing I do notice(and mentioned repeatedly) that many python coders
like exceptions so even you don't raise it, one of f/g/h/i may do so
because your return value may not be something they want. As simple as
dict[key] can boom when the key is not there.

So the end result is still, it depends.

Daniel

unread,
Oct 20, 2005, 11:21:19 PM10/20/05
to TurboGears
> GOTO to me is perfectly acceptable(and sometimes desirable)
> for web apps.

The problem with GOTO is that it makes the logic hard to trace. That's
a bad thing when the application starts to get complex.

> Of course you can return back
> through the stack but what it actually like is :
>
> f(g(h(i(your_function))))

What's wrong with that? It's very predictable, which is what we want. I
would also like to point out that that's an unusually deep stack of
controllers to be handling the same request. In most situations there
would be one or two layers of controller nesting, not five. The deeper
calls (into model objects, etc.) know nothing about the web framework
and therefore cannot possibly raise an HTTPRedirect.

It would be nice to have a concrete use case for a situation where a
function five levels beyond the initial controller is raising an
HTTPRedirect.

> Which is the controller ?

If they can all cause a redirect, then they're all technically
controllers since they all have the ability to directly affect the
control flow (as I belive you are suggesting that any one of them may
raise a redirect exception). Normally as you get deeper in the call
stack (toward "your_function") the objects know very little or nothing
about the web tier, and therefore cannot possibly raise HTTPRedirect.

> all f, g, h, i need to know your signal and
> short circuit it back to the caller.

If they can all raise an HTTPRedirect then they all know about
TurboGears (or at least CherryPy) and it follows that they will all be
aware of the signal used to denote that the request should be
redirected.

> If all are written by the same person/same group of people, that may be
> fine. But what if you use someone's stuff (say h) which knows nothing
> about your exception situation(and the return value). It may boom.
>
> One thing I do notice(and mentioned repeatedly) that many python coders
> like exceptions so even you don't raise it, one of f/g/h/i may do so
> because your return value may not be something they want. As simple as
> dict[key] can boom when the key is not there.

I'm sorry, I don't understand what you're trying to say here. Can you
show me some code?


My question still stands...

bon...@gmail.com

unread,
Oct 20, 2005, 11:53:52 PM10/20/05
to TurboGears
I would try to :

def controller(x):
if x.redirect: raise HTTPRedirect

def encoding(x):
x.content = unicode(x.content)
return x
def your_func():
if dblookup:
x.content = lookup_content
else x.redirect = true
return x

controller(encoding(your_func())

Now if you don't find the value you want and just return a signal,
encoding would boom.

And in this case, the logic of GOTO is easier to trace. It just means,
the next stage is the HTTPRedirect. I view Web app as a pipe, not
conventional function calls from main style.

Unusually deep ? My page already needs that. turbogear.expose, some
login gator, some encoding stuff and other things.

But as I said, this is still a style, not that one is better than
another. I just highlight what one may face when certain style is
adopted.

Kevin Dangoor

unread,
Oct 21, 2005, 8:12:58 AM10/21/05
to turbo...@googlegroups.com
I should mention here what I was thinking of in terms of
implementation, because it may short circuit further discussion along
these lines.

Currently, the expose function ultimately determines if it was handed
a dict or not. The simplest thing to make return HTTPRedirect work
would be to watch for it in expose and then raise it when it gets
there. Doing that makes the user code cleaner, imho. That way should
also avoid the possible decorator issues that bonono mentions. There
is no efficiency gain, but that would likely be trivial, anyhow.

This is one of those discussions where it's essentially impossible to
say something like "there's a 50% chance your code will crash if you
do this" or "this will make things 25% faster". It's style and
preferences. Which mean that this discussion *could* go on for as long
as an emacs vs. vi discussion (when, clearly, TextMate is better than
both :).

My temptation here is to just add the "return HTTPRedirect" ability
and start using it myself, because I prefer it. raise will still work
for those who prefer that or find themselves in an odd situation where
return wouldn't work properly. Are there any real objections to that?

Kevin

On 10/20/05, bon...@gmail.com <bon...@gmail.com> wrote:
>

Elvelind Grandin

unread,
Oct 21, 2005, 8:17:47 AM10/21/05
to turbo...@googlegroups.com
No objections here. for me it feels like a clean solution.
and Vim is best ;)
--
cheers
elvelind grandin

Daniel

unread,
Oct 21, 2005, 8:19:30 AM10/21/05
to TurboGears
> Are there any real objections to that?

None here. Sounds good.

Jeff Watkins

unread,
Oct 21, 2005, 8:25:14 AM10/21/05
to turbo...@googlegroups.com
On 20 Oct, 2005, at 5:46 am, Elvelind Grandin wrote:

Nice review. I've committed a patch to tg's svn that adds a allow_json

option so now you bad part should be solved :)


I assume this means there's a config setting to set the default value for allow_json. And does allow_json default to True when I've specified format="json"?

Jeff

-- 

Jeff Watkins

http://metrocat.org/


Computers, they're just a fad.



Elvelind Grandin

unread,
Oct 21, 2005, 8:38:20 AM10/21/05
to turbo...@googlegroups.com
> I assume this means there's a config setting to set the default value for
> allow_json. And does allow_json default to True when I've specified
> format="json"?
>
> Jeff

yes there is. you can set tg.allow_json to True if you want to have
the old behavior.
I'm just about to commit a patch to change so that format="json" sets
allow_json to True.


--
cheers
elvelind grandin

Jeff Watkins

unread,
Oct 21, 2005, 8:57:56 AM10/21/05
to turbo...@googlegroups.com
I don't want to add fuel to this fire, but the IdentityFilter must
use InternalRedirects (not HTTPRedirect).

The problem is that CherryPy doesn't allow filters to handle
exceptions. The filter can perform clean-up after an exception, but
it can't catch the exception and say, "oh, that's OK. I know what to
do with this exception."

Every exception thrown from the controller results in a 500 status
code and a traceback (on dev systems).

No exceptions.

This is one of my biggest gripes with CherryPy.

I chose to use InternalRedirect rather than HTTPRedirect because I
don't want the browser to change URLs. As much as possible, the
intermediate step for login should be as seamless as possible.

Jeff


--
Jeff Watkins
http://metrocat.org/

"Not everything that can be counted counts, and not everything that
counts can be counted."
-- Albert Einstein

bon...@gmail.com

unread,
Oct 21, 2005, 9:01:23 AM10/21/05
to TurboGears
What exactly is InternalRedirect ? Does it mean I don't direct it to
the client but short circuit the flow back to cherrypy as if the client
want to access the redirect url(sort of a loopback and let cherrypy
redo the traverse to find the next handler) ?

I was looking for this kind of redirect.

Jeff Watkins

unread,
Oct 21, 2005, 9:02:38 AM10/21/05
to turbo...@googlegroups.com
On 21 Oct, 2005, at 8:38 am, Elvelind Grandin wrote:

I'm just about to commit a patch to change so that format="json" sets

allow_json to True.


Excellent. That's all I really care about. I either expect to return JSON or I don't. I almost never want things both ways.

--

Jeff Watkins

http://metrocat.org/


"Advertising directed at children is inherently deceptive and exploits children under eight years of age."

-- American Academy of Pediatrics


Jeff Watkins

unread,
Oct 21, 2005, 9:05:16 AM10/21/05
to turbo...@googlegroups.com
On 21 Oct, 2005, at 9:01 am, bon...@gmail.com wrote:

What exactly is InternalRedirect ? Does it mean I don't direct it to

the client but short circuit the flow back to cherrypy as if the client

want to access the redirect url(sort of a loopback and let cherrypy

redo the traverse to find the next handler) ?


Yes. That's exactly what it is.


I was looking for this kind of redirect.


And now you've found it. It is entirely undocumented. I only found it by diving into the code for CherryPy trying to figure out why Exception handling in filters was so broken.

Jeff


--

Jeff Watkins

http://metrocat.org/


"Computers are like Old Testament gods; lots of rules and no mercy."

-- Joseph Campbell



bon...@gmail.com

unread,
Oct 21, 2005, 9:08:02 AM10/21/05
to TurboGears
Million thanks. not surprisingly, I was also doing the login decorator
and find this feature is needed.

Kevin Dangoor

unread,
Oct 21, 2005, 9:21:00 AM10/21/05
to turbo...@googlegroups.com
On 10/21/05, Jeff Watkins <je...@metrocat.org> wrote:
>
> I don't want to add fuel to this fire, but the IdentityFilter must
> use InternalRedirects (not HTTPRedirect).
>
> The problem is that CherryPy doesn't allow filters to handle
> exceptions. The filter can perform clean-up after an exception, but
> it can't catch the exception and say, "oh, that's OK. I know what to
> do with this exception."
>
> Every exception thrown from the controller results in a 500 status
> code and a traceback (on dev systems).
>
> No exceptions.
>
> This is one of my biggest gripes with CherryPy.

I'd recommend bringing this up on the cherrypy (possibly
cherrypy-devel) list. Maybe something can be done for CP2.2. I need to
mention making object traversal hookable in some way too. As it is,
TurboGears is monkeypatching CP's object traversal (but you didn't
hear that from me :)

> I chose to use InternalRedirect rather than HTTPRedirect because I
> don't want the browser to change URLs. As much as possible, the
> intermediate step for login should be as seamless as possible.

I've seen plenty of sites where your redirected to a login page and
then redirected back to your intended destination (yahoo comes quickly
to mind). I don't personally have a problem with external redirects.

By the way, I was planning to have expose handle both HTTPRedirect and
InternalRedirect. I didn't realize that InternalRedirect was
undocumented, because I knew about it, probably from some email
message.

Kevin

Krys Wilken

unread,
Oct 22, 2005, 5:06:12 PM10/22/05
to turbo...@googlegroups.com
Hi Kevin,

I think that philosophically, you might be correct, but from a practical
point of view, to me at least, raise is a very clear indicator of a
change in program flow and the work Redirect in the exception makes it
clear what is happening.

Also, and I think this has been mentioned before, even in the standard
library, exceptions are used for program flow. Iterators, for example,
raise an exception when they hit the end of whatever they are looping
over. That's not an exceptional situation. Eventually an iterator WILL
end (in most cases). So it is just a normal part of it's program flow.

There is also the issue of having too many ways to do it (a.k.a.
Perl-itis). I think that it is a stronger argument to have one, obvious
way to do it (Python philosophy), than it is to have the most "pure" way
to do it (Java-itis).

I guess what I am saying is that CherryPy already has a way to handle
it, and it is not completely horrible. Why add extra code (more moving
parts = more things that can break) just for program flow conceptual purity.

Like I said, conceptually, you are probably right, but there is
definitely a value to KISS. (And keeping the differences between TG and
it's base components to a minimum.)

Just my $0.02. Hope it provides food for thought.

Krys
Reply all
Reply to author
Forward
0 new messages