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.-
_capture_change_info
()¶ Capture the change info for the new version. By default calls:
_fetch_current_user_id()
which should return a string or None; and_fetch_remote_addr()
which should return an IP address string or None;_get_custom_change_info()
which should return a 1-depth dict of additional keys.
These 3 methods generate a
change_info
and with 2+ top-level keys (user_id
,remote_addr
, and any keys from_get_custom_change_info()
)
-
_fetch_current_user_id
()¶ Return the current user ID.
Returns: A unique user ID string or None
if not available.
-
_fetch_remote_addr
()¶ Return the IP address for the current user.
Returns: An IP address string or None
if not available.
-
_get_custom_change_info
()¶ Optionally return additional
change_info
fields to be inserted into the history record. By default, this checks for a Flask app config variable CHRONONAUT_EXTRA_CHANGE_INFO_FUNC and calls the callable stored there (note that this may need to be wrapped with staticfunction). If not defined, returns no additional change info. Note thatVersioned
may be subclassed to further refine how custom change info is generated and propagated.Returns: A dictionary of additional change_info
keys and values
-
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.
-
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()
.