Professional Documents
Culture Documents
Release 0.10.0
Konsta Vesterinen
1 Installation 3
1.1 Supported platforms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Installing an official release . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Installing the development version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4 Checking the installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2 SQLAlchemy integration 5
2.1 Excluding columns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Versioning many-to-many tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.3 Tracking inserts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.4 Tracking updates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.5 Tracking deletes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.6 Finding history of specific record . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.7 Temporarily disabling inserts to the activity table . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3 Flask extension 9
3.1 Overriding activity values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2 Recording IP address behind proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
4 Migrations 13
4.1 Changing column name . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
4.2 Alter column type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
4.3 Removing columns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
4.4 Adding columns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.5 Rename table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
i
ii
PostgreSQL-Audit Documentation, Release 0.10.0
Contents:
Contents 1
PostgreSQL-Audit Documentation, Release 0.10.0
2 Contents
CHAPTER 1
Installation
You can install the most recent official PostgreSQL-Audit version using pip:
To install the latest version of PostgreSQL-Audit, you need first obtain a copy of the source. You can do that by
cloning the git repository:
Then you can install the source distribution using the setup.py script:
3
PostgreSQL-Audit Documentation, Release 0.10.0
cd postgresql-audit
python setup.py install
To check that PostgreSQL-Audit has been properly installed, type python from your shell. Then at the Python
prompt, try to import PostgreSQL-Audit, and check the installed version:
>>> import postgresql_audit
>>> postgresql_audit.__version__
0.10.0
4 Chapter 1. Installation
CHAPTER 2
SQLAlchemy integration
SQLAlchemy integration offers easy way of using PostgreSQL-Audit with SQLAlchemy ORM. It has the following
features:
• Automatically marks all declarative classes which have __versioned__ class property defined as versioned.
• Attaches after_create DDL listeners that create versioning triggers for all versioned tables.
• Provides Activity model for easy ORM level access of activities
from postgresql_audit import versioning_manager
versioning_manager.init(Base)
class Article(Base):
__tablename__ = 'article'
__versioned__ = {}
id = Column(Integer, primary_key=True)
name = Column(String)
You can easily exclude columns from being versioned by adding them as a list to ‘exclude’ key of __versioned__ dict.
class Article(Base):
__tablename__ = 'article'
__versioned__ = {'exclude': 'created_at'}
(continues on next page)
5
PostgreSQL-Audit Documentation, Release 0.10.0
Versioning Table objects is easy. Just call audit_table method with the desired table.
class User(Base):
__tablename__ = 'user'
__versioned__ = {}
id = Column(Integer, primary_key=True)
name = Column(String)
class Group(Base):
__tablename__ = 'group'
__versioned__ = {}
id = Column(Integer, primary_key=True)
name = Column(String)
group_user = Table(
'group_user',
Base.metadata,
Column(
'user_id',
Integer,
ForeignKey('user.id', ondelete='CASCADE'),
nullable=False,
primary_key=True
),
Column(
'group_id',
Integer,
ForeignKey('place.id', ondelete='CASCADE'),
nullable=False,
primary_key=True
)
versioning_manager.audit_table(group_user)
Activity = versioning_manager.activity_cls
activity = Activity.query.first()
activity.id # 1
activity.table_name # 'article'
(continues on next page)
activity = Activity.query.order_by(db.desc(Activity.id)).first()
activity.id # 2
activity.table_name # 'article'
activity.verb # 'update'
activity.old_data # {'id': '1', 'name': 'Some article'}
activity.changed_data # {'name': 'Some other article'}
session.delete(article)
session.commit()
activity = Activity.query.order_by(db.desc(Activity.id)).first()
activity.id # 3
activity.table_name # 'article'
activity.verb # 'delete'
activity.old_data # {'id': '1', 'name': 'Some other article'}
activity.changed_data # {}
In this example we want to find all changes made to article with id=3. The query is a bit complex since we have to
check old_data and changed_data separately. Luckily the Activity model has a hybrid_property_ called ‘data which
is a combination of these two. Hence you can get the desired activities as follows:
activities = session.query(Activity).filter(
Activity.table_name == 'article',
Activity.data['id'].astext.cast(db.Integer) == 3
)
There are cases where you might not want to track changes to your data, such as when doing big changes to a table.
In those cases you can use the VersioningManager.disable context manager.
with versioning_manager.disable(session):
for i in range(1, 10000):
db.session.add(db.Product(name='Product %s' % i))
db.session.commit()
Flask extension
Flask extension provides means for easy integration with PostgreSQL-Audit, Flask-Login and Flask-SQLAlchemy. It
provides all the goodies that SQLAlchemy integration provides along with:
• By default the Flask extensions tries to get the current user from Flask-Login and assigns the id of this object
as the actor_id of all activities in given transaction. It also assigns the current user ip address to all present
activities.
• Easy overriding of current activity values using activity_values context manager
versioning_manager.init(db.Model)
class Article(db.Model):
__tablename__ = 'article'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
In some situations you may want to override the current activity values. One scenario is where you want to track
changes to associated objects and mark those changes with target_id property of Activity model.
9
PostgreSQL-Audit Documentation, Release 0.10.0
Consider for example the following model structure with Articles and Tags. Let’s say we want to show the changelog
of an article that contains all changes to this article and its tags.
from postgresql_audit.flask import versioning_manager
versioning_manager.init(db.Model)
class Article(db.Model):
__tablename__ = 'article'
__versioned__ = {}
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
class Tag(db.Model):
__tablename__ = 'tag'
__versioned__ = {}
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
article_id = db.Column(
db.Integer,
db.ForeignKey(Article.id, ondelete='CASCADE')
)
article = db.relationship(Article, backref='tags')
When adding tags we need to make the generated activities use the article id as the target_id, so that we can track them
later on.
from postgresql_audit.flask import activity_values
with activity_values(target_id=str(article.id)):
article.tags = [Tag(name='Some tag')]
db.session.commit()
Now we can find all activities for given article with the following query.
Activity = versioning_manager.activity_cls
activities = Activity.query.filter(
db.or_(
db.and_(
Activity.target_id == str(article.id),
Activity.target_table_name == 'article'
),
db.and_(
db.or_(
Activity.row_data['id'] == article.id,
(continues on next page)
By default PostgreSQL-Audit stores the client address as found in the request and does not attempt to make assump-
tions on server proxy configuration. Thus, in case the flask app runs after an http server (e.g nginx), and depending on
configuration, flask may receive no IP. To overcome this, it is advised to follow flask documentation on proxy setups.
Migrations
Usually your schema changes over time. The schema of PostgreSQL-Audit is very flexible, since it stores the data in
JSONB columns. Your schema can change without the need of changing the version history JSONB data columns.
However in case you want to show the version history on the application side you may want to reflect the changes
you make to your schema to old_data and changed_data columns of activity table. The other solution is to make your
application code aware of all the schema changes that have happened over time. This can get a bit tedious if your
schema is quickly evolving.
def upgrade():
op.alter_column(
'my_table',
'my_column',
new_column_name='some_column'
)
Parameters
• conn – An object that is able to execute SQL (either SQLAlchemy Connection, Engine or
Alembic Operations object)
13
PostgreSQL-Audit Documentation, Release 0.10.0
def upgrade():
op.alter_column(
'user',
'age',
type_=sa.Integer
)
alter_column(
op,
'user',
'age',
lambda value, activity_table: sa.cast(value, sa.Integer)
)
Parameters
• conn – An object that is able to execute SQL (either SQLAlchemy Connection, Engine or
Alembic Operations object)
• table – The table to run the column name changes against
• column_name – Name of the column to run callable against
• func – A callable to run against specific column in activity table jsonb data columns. The
callable should take two parameters the jsonb value corresponding to given column_name
and activity table object.
• schema – Optional name of schema to use.
14 Chapter 4. Migrations
PostgreSQL-Audit Documentation, Release 0.10.0
def upgrade():
op.remove_column('article', 'created_at')
remove_column(op, 'article', 'created_at')
Parameters
• conn – An object that is able to execute SQL (either SQLAlchemy Connection, Engine or
Alembic Operations object)
• table – The table to remove the column from
• column_name – Name of the column to remove
• schema – Optional name of schema to use.
import sqlalchemy as sa
from alembic import op
from postgresql_audit import add_column
def upgrade():
op.add_column('article', sa.Column('created_at', sa.DateTime()))
add_column(op, 'article', 'created_at')
Parameters
• conn – An object that is able to execute SQL (either SQLAlchemy Connection, Engine or
Alembic Operations object)
• table – The table to remove the column from
• column_name – Name of the column to add
• default_value – The default value of the column
• schema – Optional name of schema to use.
import sqlalchemy as sa
from postgresql_audit import jsonb_change_key_name
def upgrade():
op.rename_table('article', 'article_v2')
rename_table(op, 'article', 'article_v2')
Parameters
• conn – An object that is able to execute SQL (either SQLAlchemy Connection, Engine or
Alembic Operations object)
• old_table_name – The name of table to rename
• new_table_name – New name of the renamed table
• schema – Optional name of schema to use.
16 Chapter 4. Migrations
Python Module Index
p
postgresql_audit.migrations, 13
17
PostgreSQL-Audit Documentation, Release 0.10.0
A
add_column() (in module postgresql_audit.migrations),
15
alter_column() (in module postgresql_audit.migrations),
14
C
change_column_name() (in module post-
gresql_audit.migrations), 13
J
jsonb_change_key_name (class in post-
gresql_audit.migrations), 15
P
postgresql_audit.migrations (module), 13
R
remove_column() (in module post-
gresql_audit.migrations), 14
rename_table() (in module postgresql_audit.migrations),
16
19