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

Laurence Rowe l at lrowe.co.uk
Fri May 23 18:39:39 EDT 2008


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"

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, 
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)

Of course it would be possible to register a ScopedSession globally as 
such a utility, but more usually a local utility should be registered.

(I haven't though about the consequences of this in pre-traversal, 
before the site and local utilities are set up)

session.remove() is not important, sessions are closed by the 
zope.sqlalchemy datamanager and closed sessions are recyclable. 
Presumably the session object would be referred to by a volatile 
attribute on the local utility and the session would be GC'd along with 
the local utility object itself.

Table creation is another matter that I don't think too important. 
Implicit creation of tables seems wrong, instead tables should only be 
created explicitly, by the use clicking a button in the zope web 
interface (or automatically on adding an application). An exception to 
this is sqlalchemy in memory databases, which must be created on first 
access.

Session configuration would be somewhat similar to collective.lead 
currently (registering one as a local utility).

Laurence

Martijn Faassen wrote:
> Hi there,
> 
> Today I had a discussion with Jasper Spaans about how to go about 
> improving megrok.rdb, Grok's relational database integration which aims 
> to integrate Grok with SQLAlchemy. We started developing megrok.rdb at 
> the Grokkerdam sprint a few weeks ago. We reread the discussion 
> surrounding zope.sqlalchemy for ideas on how to go about integration and 
> configuration. I think these discussions reach wider than just Grok's 
> concerns. Note that I'm not proposing we fold any of these ideas into 
> zope.sqlalchemy itself, which should remain as free of policy as 
> possible; it could become (yet another) extension.
> 
> Let me run our current thinking by the list.
> 
> What would be nice in Zope applications (and we think would be good for 
> Grok) would be per-instance database configuration. That is, we want to 
> be able to install multiple instances of the same application and then 
> configure each of them with a different database URN and it should all 
> work, each talking to their own database.
> 
> Michael Bayer's suggestion involves the use of scoped sessions. He 
> proposed the following code:
> 
> Session = scoped_session()
> 
> # start of request
> engine = get_appropriate_engine()
> Session(bind=engine)
> try:
>     # do request
> finally:
>     Session.remove()
> 
> Let's go through the steps. First it makes a scoped session object, it 
> then configures it with the right engine at the start of the request (it 
> can do this on a per-class level), and then at the end of the request it 
> removes the Session again, which results in the actual session being 
> closed.
> 
> Our get_appropriate_engine() would probably look the engine up as a 
> local utility, as Laurence suggested. There is a bit of question about 
> engine configuration, though.
> 
> If we want to support the use case of looking up the engine URL in a 
> persistent datastore (for instance one URL per location), we have a 
> question of ordering. We cannot do it too early; at the start of the 
> transaction there isn't a ZODB yet to talk to so we can't look up a 
> local utility. We can try doing it just in time:
> 
> _Session = scoped_session()
> 
> def Session(*args, **kw):
>     engine = get_appropriate_engine()
>     _Session.bind(bind=engine)
>     return _Session(*args, **kw)
> 
> Here get_appropriate_engine() could do a component.getUtility() and look 
> up the engine for us, possibly in an application-local way. There's 
> still the question of how this engine got configured in the first place. 
> How does it know the database URL? How does the engine get created after 
> the database URL is known (this might be quite late in the game; it 
> could be stored in the ZODB). It then starts to look more and more 
> attractive to do something similar like collective.lead's IDatabase 
> utility, which can be stored persistently in the ZODB and has a 
> getEngine() method which actually gets the engine (creating it if 
> necessary).
> 
> If we use sqlalchemy.ext.declarative, we also need to make the 
> declarative extension of SQLALchemy load up the tables at the right 
> point in time.
> 
> We would also like a way to hook into matters and register some of our 
> own tables and mappers manually. We figured perhaps the utility could 
> fire an event that you can then write a handler for. This way there's 
> less need to subclass the utility just to change some configuration 
> (this is what collective.lead currently requires you to do). If a 
> persistent local utility is in play, it shouldn't fire the configuration 
> event during its own creation, as that would mean it'd only be fired 
> once ever. We want to fire it just after engine creation.
> 
> I guess the database utility can remain quite simple. Its main tasks 
> would be:
> 
> * allow access to the engine (creating it the first time)
> 
> * fire the event for additional configuration when the engine is first 
> created
> 
> * maintain or somehow obtain the database URL. This could be retrieved 
> from the ZODB if it's a local utility, or it could be hardcoded into a 
> global utility, or it could be retrieved from some config file by a 
> global utility.
> 
> We could have an expanded variety which also configures things using the 
> SQLAlchemy declarative extension.
> 
> We still have the question of the 'remove()' bit in Michael's code. We 
> looked at ScopedSession's remove() method, and it looks like it removes 
> the session from the thread-local storage, and it actually closes the 
> session.
> 
> Closing the session should be taken care of: zope.sqlalchemy's 
> integration with Zope's transaction machinery will close the session. 
> What about the registry cleanup that remove() appears to do? Is this 
> currently being done by zope.sqlalchemy? Should it be?
> 
> Anyway, a whole lot of abstract talk. I still hope to get some feedback 
> on this.
> 
> Regards,
> 
> Martijn
> 
> _______________________________________________
> Zope-Dev maillist  -  Zope-Dev at zope.org
> http://mail.zope.org/mailman/listinfo/zope-dev
> **  No cross posts or HTML encoding!  **
> (Related lists - http://mail.zope.org/mailman/listinfo/zope-announce
> http://mail.zope.org/mailman/listinfo/zope )
> 



More information about the Zope-Dev mailing list