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
-~----------~----~----~----~------~----~------~--~---

Reply via email to