[Zope-dev] Re: zope.sqlalchemy, integration ideas
Laurence Rowe
l at lrowe.co.uk
Thu May 29 13:56:49 EDT 2008
Martijn Faassen wrote:
> 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.
That would be fine if you had the same configuration across all sessions
(e.g. they all connected to the same engine / database) or each session
was configured at the start of every request. Presumably we will want to
connect different application instances to different databases.
This means that if we have a central register of ScopedSessions, then we
must key it by some unique application id (path I guess).
def application_session(app):
try:
return registry[app.getPath()]()
except KeyError:
return registry.setdefault(app.getPath(),
scoped_session(sessionmaker(**app.getConfiguration())))()
My point about using adapters, or indeed properties to access the
session, is that the only object needing to access the session which
cannot look it up directly with Session.object_session(object) is the
application root object. To me it seems simpler to do this than to
register utilities. Also it would be nice to have a consistent way to
lookup the session.
> 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.
>
That sounds promising then.
Laurence
More information about the Zope-Dev
mailing list