Hi All,

Let me preface this with: "I know that long running transactions in a
web app are a worse idea than that trying to forge steel in a mould
made of butter".

With that out of the way; I have inherited a django web app that
connects to many databases (this isn't starting well). These databases
also have an auditing system which necessitates a lock being acquired
before changes are made and then this lock being committed when
changes are complete and have been validated by an extra operator.
This app was initially a gui app (Oracle Forms) and is being ported.

The way this was done (before I arrived) was to patch django's
internals so that a logged in user has a raw cx_Oracle connecton which
is used for their interaction with the db. This connection is created
when they log in the first time. They acquire a lock (by calling some
PL/SQL) and then all the changes they make are kept inside a
transaction on this connection. When they wish to confirm the changes
the transaction is committed and they log out.

The django ORM wasn't sufficient for some parts of the database (in
particular the view changes part of the app) and some of the newer
functionality. So we now have an added problem in that sqlalchemy
needs to be able to use the open cx_Oracle connection (attached to the
user) to do its stuff.

I am now in the position where I have a piece of django middleware the
adds an SQLAlchemy session to the request on the way in (also creating
a nested_transaction) which is used for the duration of the request.
The nested transaction is then committed if it's found to be dirty on
the way out.

This scenario wasn't working due to QueuePool errors after a few
requests (I guess this mean that I am missing calling Session.close
somewhere?). Secondly I've tried to call close_all on the session in
the process_response method. Somewhere between the these two extremes
stuff isn't getting written to the db.

I suspect there is some combination of session, pool, engine and
connection that will make this scenario work, and would really
appreciate any pointers. Here's some of the code:

class SATransactionMiddleware(TransactionMiddleware):
    """
    Custom Transaction middleware.
    """

    def process_request(self, request):
        """Create an SQLAlchemy Session and attach it to the
request"""
        try:
            # This is zope stuff which is fetching the user's
connection and wrapping an
            # engine and a session around it
            ses = ISQLAlchemySession(request)
            ses.begin_nested()
            setattr(request, REQUEST_SESSION_ATTRIBUTE_NAME, ses)
        except RequestSessionAdaptaionError:
            # There may not be a logged in user, or it might be an
AnonymousUser
            pass


    def process_exception(self, request, exception):
        """If a session has been set up then rollback if it's
nested"""
        ses = getattr(request, REQUEST_SESSION_ATTRIBUTE_NAME, None)
        if ses and ses.transaction.nested:
            ses.rollback()
        ses.close_all()

    def process_response(self, request, response):
        """If a session has been set up then commit and expunge if
it's nested"""
        #import ipdb; ipdb.set_trace()
        ses = getattr(request, REQUEST_SESSION_ATTRIBUTE_NAME, None)
        if ses and ses.transaction.nested:
            import ipdb; ipdb.set_trace()
            if ses.dirty or ses.new or ses.deleted:
                import ipdb; ipdb.set_trace()
                ses.commit()
            ses.close_all()
        return response

The ISQLAlchemySession bit in the process_request method is basically
calling create_session on a single instance of the class below (it
checks for the presence of a session on the request first).

class SessionFactory(object):

    def __init__(self):
        self.sessions = {} # This will contain Session *classes*

    def create_session(self, connection=None):
        """Create a Session wrapped around this connection"""
        if not connection:
            connection = django_connection.connection
        if connection in self.sessions:
            return self.sessions[connection]() # Instantiation
        con_string = 'oracle://%s/%...@%s' % (connection.username,
connection.password,
                                            connection.dsn)
        engine = create_engine(con_string, creator=lambda :
connection, echo=True)
        Session = sessionmaker(bind=engine, autocommit=False,
autoflush=False)
        self.sessions[connection] = Session
        return Session() # Instantiation

_session_factory = SessionFactory()

If anyone could explain the right way to do this, slowly and in words
of one syllable (and ignoring the fact that the whole approach smells
a bit) I would be eternally grateful!

Best,
Ben

--

You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalch...@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.


Reply via email to