Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's a good general way to look SQLAlchemy transactions, complete with authenticated user, etc?

I'm using SQLAlchemy's declarative extension. I'd like all changes to tables logs, including changes in many-to-many relationships (mapping tables). Each table should have a separate "log" table with a similar schema, but additional columns specifying when the change was made, who made the change, etc.

My programming model would be something like this:

row.foo = 1
row.log_version(username, change_description, ...)

Ideally, the system wouldn't allow the transaction to commit without row.log_version being called.

Thoughts?

like image 503
Ken Kinder Avatar asked Dec 07 '09 18:12

Ken Kinder


People also ask

What is Session flush in Sqlalchemy?

session. flush() communicates a series of operations to the database (insert, update, delete). The database maintains them as pending operations in a transaction.

How do I start a transaction in Sqlalchemy?

begin() method may also be used to begin the Session level transaction; calling upon Session. connection() subsequent to that call may be used to set up the per-connection-transaction isolation level: sess = Session(bind=engine) with sess. begin(): # call connection() with options before any other operations proceed.

What is Sessionmaker Sqlalchemy?

Advertisements. In order to interact with the database, we need to obtain its handle. A session object is the handle to database. Session class is defined using sessionmaker() – a configurable session factory method which is bound to the engine object created earlier.

What is Autocommit in Sqlalchemy?

The “autocommit” feature is only in effect when no Transaction has otherwise been declared. This means the feature is not generally used with the ORM, as the Session object by default always maintains an ongoing Transaction .


1 Answers

There are too many questions in one, so they that full answers to all them won't fit StackOverflow answer format. I'll try to describe hints in short, so ask separate question for them if it's not enough.

Assigning user and description to transaction

The most popular way to do so is assigning user (and other info) to some global object (threading.local() in threaded application). This is very bad way, that causes hard to discover bugs.

A better way is assigning user to the session. This is OK when session is created for each web request (in fact, it's the best design for application with authentication anyway), since there is the only user using this session. But passing description this way is not as good.

And my favorite solution is to extent Session.commit() method to accept optional user (and probably other info) parameter and assign it current transaction. This is the most flexible, and it suites well to pass description too. Note that info is bound to single transaction and is passed in obvious way when transaction is closed.

Discovering changes

There is a sqlalchemy.org.attributes.instance_state(obj) contains all information you need. The most useful for you is probably state.committed_state dictionary which contains original state for changed fields (including many-to-many relations!). There is also state.get_history() method (or sqlalchemy.org.attributes.get_history() function) returning a history object with has_changes() method and added and deleted properties for new and old value respectively. In later case use state.manager.keys() (or state.manager.attributes) to get a list of all fields.

Automatically storing changes

SQLAlchemy supports mapper extension that can provide hooks before and after update, insert and delete. You need to provide your own extension with all before hooks (you can't use after since the state of objects is changed on flush). For declarative extension it's easy to write a subclass of DeclarativeMeta that adds a mapper extension for all your models. Note that you have to flush changes twice if you use mapped objects for log, since a unit of work doesn't account objects created in hooks.

like image 160
Denis Otkidach Avatar answered Nov 13 '22 22:11

Denis Otkidach