[Zope-dev] Re: zope.sqlalchemy, integration ideas

Laurence Rowe l at lrowe.co.uk
Sun May 25 13:03:45 EDT 2008


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"
>>> +lots
>>>
>>> (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?
>>>
>>>> default=None)
>>>>     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
> in zope.app.component.hooks?
> 
> 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 zope.app.component.hooks)
> 
> 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 zope.app.component 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 zope.app.component 
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.

Laurence



More information about the Zope-Dev mailing list