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

Martijn Faassen faassen at startifact.com
Thu May 29 13:10:16 EDT 2008


Laurence Rowe wrote:
> Martijn Faassen wrote:
[snip]
>> Before we talk more about session configuration, please explain why
>>  we're not talking about engine configuration. :)
> 
> Engine configuration is a subset of session configuration. You cannot
>  have a single ScopedSession for a package if you want to have
> multiple instances of that application.

I don't understand then what Mike Bayer wrote before:

Mike Bayer wrote:
> If you are running different instances each connected to a different
> engine within one process you wouldn't need any awareness of engines
> at the object level (therefore no entity_name) and also no engine
> proxying - you should have separate Session instances for each
> "process" managed by scoped_session(),  which was designed to handle
> this.    Multiple apps on one codebase within one process was an
> original requirement of Pylons as well, though nobody has ever used
> it.

That seemed to suggest to me that scoped sessions were an appropriate 
solution.

Anyway, back to you:

[snip]
 > I'm not sure whether it would be a good idea to wrap this in a session
 > property, or just register it as an adapter. The only other object that
 > would need access to the session (either as a property or through
 > adaption) would be the application instance root object. Something like:
 >
 > @adapter(MyApp)
 > @provider(ISession)
 > def root_session(context):
 >     return context._sessioncontext()

This looks to me like it could be a simple function that looks up a 
local utility instead:

def session():
    return component.getUtility(ISessionContext)()

We get the right context from the site that way. I don't see the point 
in trying to re-implement local utilities with adapters while 
zope.component already does something quite similar for you. That said, 
I still have hope we can used ScopedSession and forgo a utility lookup 
here, see below...

 > And the simplest persistent session context might be:
 >
 > class PersistentSessionContext(persistent):
 >   implements(ISessionContext)
 >
 >   def __init__(self, url, twophase=False, engine_kw={}, session_kw={}):
 >     self.url = url
 >     self.twophase = twophase
 >     self.engine_kw = engine_kw
 >     self.session_kw = session_kw
 >
 >   def __call__(self):
 >     session = getattr(self._v_session, None)
 >     if session is None:
 >       engine = getattr(self._v_engine, None)
 >       if engine is None:
 >         engine = self._v_engine = create_engine(
 >           self.url, **self.engine_kw)
 >       session = self._v_session = create_session(
 >         bind=engine, twophase=self.twophase, **self.session_kw)
 >     return session

Doesn't ScopedSession already take care of this using ScopedRegistry?

Perhaps a more clever scopefunc is necessary to introduce a per-instance 
scope? Right now it's only per-thread scope. scopefunc could also do a 
local utility lookup that gets it a way to uniquely identify the current 
application (not sure what would be best, object id? zodb id? a unique 
string generated for each installed application?).

Something like this:

def scopefunc():
    return (component.getUtility(ISessionSiteScope).applicationScope(), 
thread.getindent())

Session = scoped_session(sessionmaker(autoflush=True), scopefunc)

 > A more complex scheme might maintain a dict of ScopedSessions keyed by
 > application path, outside of the object cache. You could also ensure
 > that only a single engine is created for a given set of arguments, but
 > this seems overkill

 > Everything would then get a session consistently with a call to
 > ISession(self) or ISession(self.context) in the case of views. No parent
 > pointers involved.

I still don't understand why this is nicer than a local utility lookup. 
I understand the two cooperating applications use case, but that can be 
easily provided for with using setSite(), just like you *already* need 
to do to make everything work right with the other local utilities that 
this application might be using. Above in my scopefunc example I assume 
that setSite has been set up appropriately.

If you don't use the ZODB at all, you could still set up local sites 
(I'm not sure how hard this is, but it *should* be possible; 
zope.component has no knowledge of persistence), or if it's just one app 
per zope instance, set up a global ISessionSiteScope utility.

 > We do still need to setup parent pointers though for grok.url and
 > IAbsoluteURL to work. It looks fairly easy to add location information
 > to the containers themselves:

(note that grok.url uses IAbsoluteURL, so we just care about IAbsoluteURL)

So far this isn't a particular problem in Grok as traversal uses 
zope.location.located() to locate everything. That said, if you want to 
directly access an object by accessing ORM-mapped attributes and then 
get a URL for that object, this won't work. Since it *does* work when 
you use the ZODB, it'd be nice if it also worked properly with sqlalchemy.

Hopefully we can indeed make containers behave the right way by making 
our own MappedCollection.

 >
 > from sqlalchemy.orm.collections import MappedCollection, 
collection_adapter
 >
 > class LocatedCollection(MappedCollection):
 >
 >   @property
 >   def __name__(self):
 >     return collection_adapter(self).attr.key
 >
 >   @property
 >   def __parent__(self):
 >     return collection_adapter(self).owner_state.obj()
 >
 > But locating mapped objects themselves is more complex, they could
 > exist in more than one collection at once. Perhaps A LocationProxy
 > could solve this.

LocationProxy (using zope.location.located()) has worked quite well for 
me so far.

Regards,

Martijn



More information about the Zope-Dev mailing list