Chrononaut’s API¶
Core library classes¶
-
class
chrononaut.
Versioned
¶ A mixin for use with Flask-SQLAlchemy declarative models. To get started, simply add the
Versioned
mixin to one of your models:class User(db.Model, Versioned): __tablename__ = 'appuser' id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(255)) ...
The above will then automatically track updates to the
User
model and create anappuser_history
table for tracking prior versions of each record. By default, all columns are tracked. By default, change information includes auser_id
andremote_addr
, which are set to automatically populate from Flask-Login’scurrent_user
in the_capture_change_info()
method. SubclassVersioned
and override a combination of_capture_change_info()
,_fetch_current_user_id()
, and_get_custom_change_info()
. Thischange_info
is stored in a JSON column in your application’s database and has the following rough layout:{ "user_id": "A unique user ID (string) or None", "remote_addr": "The user IP (string) or None", "extra": { ... # Optional extra fields }, "hidden_cols_changed": [ ... # A list of any hidden fields changed in the version ] }
Note that the latter two keys will not exist if they would otherwise be empty. You may provide a list of column names that you do not want to track using the optional
__chrononaut_untracked__
field or you may provide a list of columns you’d like to “hide” (i.e., track updates to the columns but not their values) using the__chrononaut_hidden__
field. This can be useful for sensitive values, e.g., passwords, which you do not want to retain indefinitely.-
diff
(from_model, to=None, include_hidden=False)¶ Enumerate the changes from a prior history model to a later history model or the current model’s state (if
to
isNone
).Parameters: - from_model – A history model to diff from.
- to – A history model or
None
.
Returns: A dict of column names and
(from, to)
value tuples
-
has_changed_since
(since)¶ Check if there are any changes since a given time.
Parameters: since – The DateTime from which to find any history records Returns: True
if there have been any changes.False
if not.
-
previous_version
()¶ Fetch the previous version of this model (or None)
Returns: A history model, or None
if no history exists
-
version_at
(at)¶ Fetch the history model at a specific time (or None)
Parameters: at – The DateTime at which to find the history record. Returns: A history model at the given point in time or the model itself if that is current.
-
versions
(before=None, after=None, return_query=False)¶ Fetch the history of the given object from its history table.
Parameters: - before – Return changes only _before_ the provided
DateTime
. - before – Return changes only _after_ the provided
DateTime
. - return_query – Return a SQLAlchemy query instead of a list of models.
Returns: List of history models for the given object (or a query object).
- before – Return changes only _before_ the provided
-
-
class
chrononaut.
VersionedSQLAlchemy
(app=None, use_native_unicode=True, session_options=None, metadata=None, query_class=<class 'flask_sqlalchemy.BaseQuery'>, model_class=<class 'flask_sqlalchemy.model.Model'>)¶ A subclass of the
SQLAlchemy
used to control a SQLAlchemy integration to a Flask application.Two usage modes are supported (as in Flask-SQLAlchemy). One is directly binding to a Flask application:
app = Flask(__name__) db = VersionedSQLAlchemy(app)
The other is by creating the
db
object and then later initializing it for the application:db = VersionedSQLAlchemy() # Later/elsewhere def configure_app(): app = Flask(__name__) db.init_app(app) return app
At its core, the
VersionedSQLAlchemy
class simply ensures that databasesession
objects properly listen to events and create version records for models with theVersioned
mixin.
Helper functions¶
-
chrononaut.
extra_change_info
(*args, **kwds)¶ A context manager for appending extra
change_info
into Chrononaut history records forVersioned
models. Supports appending changes to multiple individual objects of the same or varied classes.Usage:
with extra_change_info(change_rationale='User request'): user.email = 'new-email@example.com' letter.subject = 'Welcome New User!' db.session.commit()
Note that the
db.session.commit()
change needs to occur within the context manager block for additional fields to get injected into the history tablechange_info
JSON within anextra
info field. Any number of keyword arguments with string values are supported.The above example yields a
change_info
like the following:{ "user_id": "admin@example.com", "remote_addr": "127.0.0.1", "extra": { "change_rationale": "User request" } }
-
chrononaut.
append_change_info
(*args, **kwds)¶ A context manager for appending extra
change
info directly onto a single model instance. Useextra_change_info()
for tracking multiple objects of the same or different classes.Usage:
with append_change_info(user, change_rationale='User request'): user.email = 'new-email@example.com' db.session.commit()
Note that
db.session.commit()
does not need to occur within the context manager block for additional fields to be appended. Changes take the same form as withextra_change_info()
.
-
chrononaut.
rationale
(*args, **kwds)¶ A simplified version of the
extra_change_info()
context manager that accepts only a rationale string and stores it in the extra change info.Usage:
with rationale('Updating per user request, see GH #1732'): user.email = 'updated@example.com' db.session.commit()
This would yield a
change_info
like the following:{ "user_id": "admin@example.com", "remote_addr": "127.0.0.1", "extra": { "rationale": "Updating per user request, see GH #1732" } }