Brian Sutherland wrote:
On Sat, May 24, 2008 at 09:30:18PM +0100, Laurence Rowe wrote:
Brian Sutherland wrote:
On Fri, May 23, 2008 at 11:39:39PM +0100, Laurence Rowe wrote:
We need to differentiate between the interface for session configuration and session usage from an application.

For session usage I think it is fairly simple. We should define an ISessionContext interface such that:

class ISessionContext(Interface):
    def __call__():
        "return a session in this context"

(I was thinking about proposing an interface called ISessionMaker doing
much the same thing)

I'm not sure what "in this context" means?
The context of this application instance. Say you have two instances of an application, one pointed at database A, another at database B. It is possible to involve both applications in a single request / transaction. You must be sure that you are working with the correct session.

Are the instances you are talking about persistent objects in the ZODB?

Yes, I'm assuming that the application instances live in the ZODB and are local site managers for this example.

A future version of collective.lead could implement an ISessionContext. Client code however should have a cleaner interface, a plain ISession. This is accessed through a lookup on the context, translated into a simple adapter:

def session_adapter(context):
session_context = queryUtility(ISessionContext, context=context,
Why call queryUtility with the context keyword?

    if session_context is not None:
        return session_context()

This will allow application code to do something like:

session = ISession(context)
ob = session.query(MyClass)
This really confuses me. What is the context? Does it have any meaning?
Or is it just a shorter way to write:

    session = getUtility(ISessionContext)()

Does the value of context have an effect on what you get from the
ISession adaptation?
Yes, as it translates directly to the getUtility lookup. It ensures that lookups in /appA go to a local utility defined in /appA and lookups in /appB go to a local utility defined in /appB.

I've been burned by using context as a keyword to getUtility before.
When your context doesn't have a proper __parent__ pointer, the default
IComponentLookup adapter falls back to the global site manager and
ignores the local one. That causes no end of confusion and hard to debug
bugs as people will call ISession on objects with and without __parent__
pointers and then wonder why it fails.

Perhaps a more robust way is to rely on the site stored while traversing

For example:
    * appA implements ISite and has a local ISessionContext utility
    * The path appA is traversed over (i.e. the url path is /appA/foo)
    * queryUtility(ISessionContext) then returns the local utility from
      appA without the need to specify a "context"
      (thanks to the magic in

I think the only situation where you really want to specify a "context"
for queryUtility is when you want to access a local utility registered
in /appB from a URL path like /appA/foo. That seems pretty rare and
almost broken.

You could implement a function like:

def query_session(name=u''):
    return queryUtility(ISessionContext, name=name)()

That does the right thing in almost all cases (i.e. uses the closest
local site), and is much more robust.

I fear that if we rely on the site manager set during traversal, then applications will rely on that when looking up their session in application code, and it will be impossible to involve objects from different application instances in the same transaction.

getUtility already looks up in the global component registry if a site manager cannot be found in the context's parents (or where __parent__ is missing). I guess this concept could be extended with something like:

def session_adapter(context):
    smgr = getSiteManager(context)
    if smgr is getGlobalSiteManager():
        smgr = None
    return getUtility(ISessionContext, context=smgr)()

This would defer the bugs to the point you start working with two instances of an application in a single request.

I wouldn't consider accessing objects from /appA and /appB in the same request broken, for someone coming from a ZODB background it would seems quite reasonable.

Of course it would be possible to register a ScopedSession globally as such a utility, but more usually a local utility should be registered.
Depends what you're doing. If you are running without a ZODB, you have
mostly just global utilities.

It would be a pity if zope.sqlalchemy started to depend on the ZODB.
Wihout ZODB and it seems unlikely that you would be able to register a local utility, the idea of ISessionContext is so that you might be able to register a ScopedSession as a global utility too.

I very much like the idea of ISessionContext!

I'm just not sure about how you suggest using it in client code.

I definitely want to avoid a dependency on ZODB or for zope.sqlalchemy, but I would like to find some way of allowing the possibility of local utility registrations so that you don't hit a brick wall when you start invloving multiple application instances in a single request.

I guess I'm suggesting that applications whose root lives in the ZODB should be local site managers and have a local ISessionContext utility registered.

Applications that live solely on the file system should just register a ScopedSession as the ISessionContext utility globally. If that code wanted to allow for the possibility of more than one application instance, then it could register an ISessionContext as a named utility and register an ISession adapter that had logic like:

def session_adapter(context):
    name = get_my_application_id(context)
    return getUtility(ISessionContext, name)()

Code that needs access to a session object could then use the session = ISession(context) pattern in all the above cases.


Zope-Dev maillist  -
**  No cross posts or HTML encoding!  **
(Related lists - )

Reply via email to