On 4/4/07, Michael Bayer <[EMAIL PROTECTED]> wrote:
>
> Hello Pylons !
>
> as we all know, a plain jane Pylons controller typically takes a form
> like this:
>
> class HelloController(BaseController):
> def index(self):
> return render_response("/hello_world.html")
>
> where "index()" returns a "Response" object, that contains the fully
> rendered text of "hello_world.html".
>
> When combining a controller with SQLAlchemy, the need to ensure that
> the session is flush()ed becomes important. I see on the site theres
> a description of putting the session create/flush() pair at the WSGI
> request level, for example.
>
> One pretty handy way to mark a controller method as "needs flush" or
> "transactional" is through a decorator, like this:
>
> class HelloController(BaseController):
> @flush_session
> def index(self):
> obj = session_context.current.query(Obj).get(1)
> obj.foo = 'bar'
> return render_response("/hello_world.html", obj=obj)
>
> where the "flush_session" decorator is doing something like
> (extraneous decoration stuff removed):
>
> def decorate(func):
> session = session_context.current
> try:
> response = func()
> session.flush()
> return response
> finally:
> session.clear()
>
> So above, "response" is the return value of "render_response()"
> inside of "index()", i.e. a Response object.
>
> Similar approaches have been taken with "transactional" methods, i.e.
> begin a transaction, run the method, then commit.
>
> however, what is wrong with these !
>
> basically, that the full content of the response is being rendered
> before the flush()/commit() takes place. Now, even though it hasnt
> been sent *out* yet, and its only mildly wasteful to throw away the
> response in the case the flush fails, *the flush process generates
> new information*, such as new primary key values, column defaults,
> etc., which its extremely likely that the response will depend on
> (such as , "User "${user.name}" Saved. Click <a href="/foo?user_id=$
> {user.id}">here</a> to view your new user.").
>
> So my instinct is to modify this pattern to "defer" the response.
> the controller becomes this:
>
> class HelloController(BaseController):
> @flush_session
> def index(self):
> obj = session_context.current.query(Obj).get(1)
> obj.foo = 'bar'
> return lambda: render_response("/hello_world.html", obj=obj)
>
> and the decorator this:
>
> def decorate(func):
> session = session_context.current
> try:
> response = func()
> session.flush()
> assert callable(response), "Controller response is not a
> callable"
> return response()
> finally:
> session.clear()
>
> where above, the render_response() call takes place *after* the
> session has been flushed, where it will only occur if the flush was
> successful and will have full access to any flush-generated state.
> my only issue with the above is that it looks dorky, and also is a
> little inconvenient and non-intuitive (although the decorator can
> check that the return value is a callable to enforce the pattern).
>
> So, question time.
>
> - Has anyone else had this problem ? or is this not a problem ?
>
> - is there some obvious or non-obvious way to do this more cleanly?
>
> - if the answers are yes/no/no, would it make sense to add "deferred
> render" functionality to Pylons' response mechanism ? (and possibly
> for things like redirect_to(), etc?) the implementation would be
> something along the lines of a DeferredResponse, or Response taking a
> callable as an argument, or something along those lines. then some
> other part of Pylons would have to call render() or something similar
> on the response object to actually produce the content. If there
> were Response /DeferredReponse, Response.render() maybe could return
> "self", where DeferredResponse.render() could return "self.callable
> ()", which is then a Response itself.
>
> if Pylons supported "deferred" response objects natively, this would
> be more analgous to how a system like Java Struts does it; the return
> value of a controller is not anything rendered at all, its just an
> ActionForward object which contains *instructions* on how to render
> the response. all sorts of J2EE/spring transactional managers can
> cleanly wrap themselves around such a pattern, so that when the page
> is being rendered, all data has been committed to the database and
> all database-related state changes and new data have been produced.
I call flush in my parent class controller.
I have one controller where I need to try to flush before the method
is done because I need to be sure the user has picked a unique
username. The code looks like:
class Continue(Exception):
pass
...
# Update the dbsession.
...
try:
self.dbsession.flush()
c.action_results = "Account updated."
raise Continue
except SQLError, e:
try:
is_duplicate_entry = \
e.orig.args[1].startswith("Duplicate entry")
if not is_duplicate_entry:
raise ValueError
except (AttributeError, IndexError, ValueError), ignored:
raise e # We can't handle e. Re-raise it.
else:
c.action_results = """\
Sorry, that email address has already been taken."""
self.dbsession.clear()
if not request.environ.has_key('paste.testing'):
time.sleep(2) # Prevent directory harvest attacks.
raise Continue
except Continue:
return render_response('account')
I think this is fine.
Does that make sense to you?
-jj
--
http://jjinux.blogspot.com/
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"pylons-discuss" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.com/group/pylons-discuss?hl=en
-~----------~----~----~----~------~----~------~--~---