Skip to content

Commit

Permalink
merging zope.sqlalchemy integration branch
Browse files Browse the repository at this point in the history
  • Loading branch information
zopyx committed May 19, 2008
1 parent b750fc9 commit 64b71fb
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 255 deletions.
13 changes: 13 additions & 0 deletions CHANGES.txt
@@ -1,3 +1,16 @@
1.2.0 (unreleased)
------------------

- now using zope.sqlalchemy for ZODB transaction integration

- internal class renaming

- remove PythonBaseWrapper, there is only *one* ZopeWrapper

- requires SQLAlchemy 0.4.6 or higher

- requires zope.sqlalchemy 0.1 or higher

1.1.5 (08.05.2008)
------------------

Expand Down
4 changes: 2 additions & 2 deletions README.txt
Expand Up @@ -22,7 +22,6 @@ What z3c.sqlalchemy does not do and won't do:
- no support for Zope 3 schemas
- no support for Archetypes schemas


z3c.sqlachemy just tries to provide you with the basic functionalities you need
to write SQLAlchemy-based applications with Zope 2/3. Higher-level
functionalities like integration with Archetypes/Zope 3 schemas are subject to
Expand All @@ -33,7 +32,8 @@ Requirements:
=============

- Zope 2.8+, Zope 3.X
- SQLAlchemy 0.4.0 or higher (no support for SQLAlchemy 0.3)
- SQLAlchemy 0.4.6 or higher (no support for SQLAlchemy 0.3)
- zope.sqlalchemy 0.1.0 or higher
- Python 2.4+


Expand Down
32 changes: 5 additions & 27 deletions buildout.cfg
@@ -1,29 +1,7 @@
[buildout]
parts = plone zope2 instance
eggs =
develop =

[plone]
recipe = plone.recipe.plone

[zope2]
recipe = plone.recipe.zope2install
url = ${plone:zope2-url}

[instance]
recipe = plone.recipe.zope2instance
zope2-location = ${zope2:location}
user = admin:admin
http-port = 8080
debug-mode = on
verbose-security = on
eggs =
${buildout:eggs}
${plone:eggs}
zcml =

products =
${plone:products}
${buildout:directory}/products

parts = test
develop = .

[test]
recipe = zc.recipe.testrunner
eggs = z3c.sqlalchemy [test]
5 changes: 2 additions & 3 deletions setup.py
Expand Up @@ -7,10 +7,8 @@
##########################################################################


import os
from setuptools import setup, find_packages


CLASSIFIERS = [
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
Expand Down Expand Up @@ -48,7 +46,8 @@
zip_safe=True,
namespace_packages=['z3c'],
install_requires=['setuptools',
'SQLAlchemy>=0.4.0',
'SQLAlchemy>=0.4.6',
'zope.sqlalchemy',
# 'zope.component==3.3',
# 'zope.interface==3.3',
# 'zope.schema==3.3',
Expand Down
210 changes: 24 additions & 186 deletions src/z3c/sqlalchemy/base.py
Expand Up @@ -6,51 +6,22 @@
# and ZOPYX Ltd. & Co. KG, Tuebingen, Germany
##########################################################################

import random
import threading

import sqlalchemy
from sqlalchemy.engine.url import make_url
from sqlalchemy.orm import sessionmaker

from zope.interface import implements
from zope.component import getUtility
from zope.component.interfaces import ComponentLookupError

from z3c.sqlalchemy.interfaces import ISQLAlchemyWrapper, IModelProvider
from z3c.sqlalchemy.model import Model
from z3c.sqlalchemy.mapper import LazyMapperCollection
from z3c.sqlalchemy.interfaces import ISQLAlchemyWrapper, IModelProvider

import transaction
from transaction.interfaces import ISavepointDataManager, IDataManagerSavepoint


class SynchronizedThreadCache(object):

def __init__(self):
self.lock = threading.Lock()
self.cache = threading.local()

def set(self, id, d):
self.lock.acquire()
setattr(self.cache, id, d)
self.lock.release()

def get(self, id):
self.lock.acquire()
result = getattr(self.cache, id, None)
self.lock.release()
return result

def remove(self, id):
self.lock.acquire()
if hasattr(self.cache, id):
delattr(self.cache, id)
self.lock.release()

from sqlalchemy import create_engine, MetaData
from sqlalchemy.engine.url import make_url
from sqlalchemy.orm import scoped_session, sessionmaker, relation
from zope.sqlalchemy import ZopeTransactionExtension


class BaseWrapper(object):
class ZopeWrapper(object):

implements(ISQLAlchemyWrapper)

Expand Down Expand Up @@ -82,7 +53,6 @@ def __init__(self, dsn, model=None, transactional=True, engine_options={}, sessi
self.session_options = session_options
self._model = None
self._createEngine()
self._id = str(random.random()) # used as unique key for session/connection cache

if model:

Expand Down Expand Up @@ -117,13 +87,23 @@ def __init__(self, dsn, model=None, transactional=True, engine_options={}, sessi
@property
def metadata(self):
if not hasattr(self, '_v_metadata'):
self._v_metadata = sqlalchemy.MetaData(self._engine)
self._v_metadata = MetaData(self._engine)
return self._v_metadata

@property
def session(self):
""" Return thread-local session """
return self._sessionmaker()

@property
def connection(self):
""" Return underlying connection """
session = self.session
# Return the ConnectionFairy
return session.connection().connection
# instead of the raw connection
#return session.connection().connection.connection

def registerMapper(self, mapper, name):
self._mappers.registerMapper(mapper, name)

Expand All @@ -144,152 +124,10 @@ def model(self):
return self._model

def _createEngine(self):
self._engine = sqlalchemy.create_engine(self.dsn, **self.engine_options)
self._sessionmaker = sqlalchemy.orm.sessionmaker(bind=self._engine,
autoflush=True,
transactional=True,
**self.session_options)


connection_cache = SynchronizedThreadCache()


class SessionDataManager(object):
""" Wraps session into transaction context of Zope """

implements(ISavepointDataManager)

def __init__(self, connection, session, id, transactional=True):

self.connection = connection
self.session = session
self.transactional = True
self._id = id
self.transaction = None
if self.transactional:
self.transaction = connection.begin()

def abort(self, trans):

try:
if self.transaction is not None:
self.transaction.rollback()
# DM: done in "_cleanup" (similar untidy code at other places as well)
## self.session.clear()
## connection_cache.remove(self._id)
finally:
# ensure '_cleanup' is called even when 'rollback' causes an exception
self._cleanup()

def _flush(self):

# check if the session contains something flushable
if self.session.new or self.session.deleted or self.session.dirty:

# Check if a session-bound transaction has been created so far.
# If not, create a new transaction
# if self.transaction is None:
# self.transaction = connection.begin()

# Flush
self.session.flush()

def commit(self, trans):
self._flush()

def tpc_begin(self, trans):
pass

def tpc_vote(self, trans):
self._flush()

def tpc_finish(self, trans):

if self.transaction is not None:
self.transaction.commit()

self.session.clear()
self._cleanup()


# DM: no need to duplicate this code (identical to "abort")
## def tpc_abort(self, trans):
## if self.transaction is not None:
## self.transaction.rollback()
## self._cleanup()
tpc_abort = abort

def sortKey(self):
return 'z3c.sqlalchemy_' + str(id(self))

def _cleanup(self):
self.session.clear()
if self.connection:
self.connection.close()
self.connection = None
connection_cache.remove(self._id)
# DM: maybe, we should set "transaction" to "None"?

def savepoint(self):
""" return a dummy savepoint """
return AlchemySavepoint()



# taken from z3c.zalchemy

class AlchemySavepoint(object):
"""A dummy saveoint """

implements(IDataManagerSavepoint)

def __init__(self):
pass

def rollback(self):
pass



class ZopeBaseWrapper(BaseWrapper):
""" A wrapper to be used from within Zope. It connects
the session with the transaction management of Zope.
"""


def __getOrCreateConnectionCacheItem(self, cache_id):

cache_item = connection_cache.get(cache_id)

# return cached session if we are within the same transaction
# and same thread
if cache_item is not None:
return cache_item

# no cached session, let's create a new one
connection = self.engine.connect()
session = sessionmaker(connection)()

# register a DataManager with the current transaction
transaction.get().join(SessionDataManager(connection, session, self._id))

# update thread-local cache
cache_item = dict(connection=connection, session=session)
connection_cache.set(self._id, cache_item)
return cache_item


@property
def session(self):
""" Return a (cached) session object for the current transaction """
return self.__getOrCreateConnectionCacheItem(self._id)['session']


@property
def connection(self):
""" This property is _private_ and only intented to be used
by SQLAlchemyDA and therefore it is not part of the
public API.
"""

return self.__getOrCreateConnectionCacheItem(self._id)['connection']
self._engine = create_engine(self.dsn, **self.engine_options)
self._sessionmaker = scoped_session(sessionmaker(bind=self._engine,
transactional=True,
autoflush=True,
extension=ZopeTransactionExtension(),
**self.session_options
))
Binary file removed src/z3c/sqlalchemy/doc/api.pdf
Binary file not shown.
9 changes: 2 additions & 7 deletions src/z3c/sqlalchemy/postgres.py
Expand Up @@ -15,7 +15,7 @@
from zope.interface import implements

from z3c.sqlalchemy.interfaces import ISQLAlchemyWrapper
from z3c.sqlalchemy.base import BaseWrapper, ZopeBaseWrapper
from z3c.sqlalchemy.base import ZopeWrapper


_cache = threading.local() # module-level cache
Expand Down Expand Up @@ -68,12 +68,7 @@ def findDependentTables(self, schema='public', ignoreErrors=False):
return _cache.ref_mapping


class PythonPostgresWrapper(BaseWrapper, PostgresMixin):
""" Wrapper to be used with Python with extended
Postgres functionality.
"""

class ZopePostgresWrapper(ZopeBaseWrapper, PostgresMixin):
class ZopePostgresWrapper(ZopeWrapper, PostgresMixin):
""" A wrapper to be used from within Zope. It connects
the session with the transaction management of Zope.
"""
Expand Down

0 comments on commit 64b71fb

Please sign in to comment.