The Updater

The process of updating for administrators is described in Upgrading. Here, we will describe the developer side of the update process.

Motivation

Some updates do not only change the code of Fietsboek, but adapt the data that is stored. New features for example might require new columns in the database, or other adaptions might move data around (commit 7a60619d for example moves the GPX data out of the database and into the data directory).

This process is commonly called data migration. In Fietsboek, we use Alembic to do the heavy lifting of SQL migrations. Alembic can automatically create migration scripts and provides an abstraction over the different database backends.

With the image uploads, and more concretely with commit 5a205756, Fietsboek has been extended to store data on disk. This has resulted in issue 20, which raises the question about how to deal with update scripts now: Alembic handles the SQL updates well, but now we have a second place to run migrations for.

This has resulted in the fietsupdate tool, which implements a similar logic as Alembic, but for general purpose Python code instead of SQL. It also acts as the user interface for administrators, providing some abstractions over the Alembic CLI.

Update scripts

An update script contains the logic that fietsupdate applies when going from one version to the next. Such a script looks like the this:

"""Revision upgrade script v0.8.0

Date created: 2023-06-05 21:00:37.464583
"""
from fietsboek.updater.script import UpdateScript

update_id = 'v0.8.0'
previous = [
    'v0.7.0',
]
alembic_revision = '3149aa2d0114'


class Up(UpdateScript):
    def pre_alembic(self, config):
        pass

    def post_alembic(self, config):
        pass


class Down(UpdateScript):
    def pre_alembic(self, config):
        pass

    def post_alembic(self, config):
        pass
  • Each script imports the UpdateScript class as a base.

  • Each update has a unique ID, which is used on the command line to identify the correct script. For most scripts, this is a randomly generated string, but some specific revisions have a more readable ID.

  • Each update (except for the initial one) has references to its previous versions. As such, the commits form a DAG. This allows fietsupdate to check which updates have been applied, and which still need to be applied.

  • Each update has a reference to the underlying alembic version. When fietsupdate applies the update, it will make sure that the database will have the given alembic version after.

  • Finally, each update has the actual update logic, in four sections: Migrations that are run before the alembic update and after the alembic update, each for the upgrade and the downgrade direction.

Creating Alembic versions

There is nothing special about how we use Alembic to generate SQL revisions. As such, we refer to the Alembic documentation.

In general, a command like this should work:

alembic -c development.ini revision --autogenerate

Creating Fietsupdate versions

The fietsupdate has a hidden command to generate a revision from the template, and it will populate it with the current alembic revision:

fietsupdate revision -c development.ini

When to create revisions

If you change Fietsboek code or documentation that does not change the way that data is stored, you do not need to bother with Alembic or Fietsupdate.

If you change the database schema, or the way that the data is stored in the existing schema, you need to create an Alembic revision.

If you change the data that is stored in the data directory, you need to create a Fietsupdate script.

If a new Fietsboek version is released, a new Fietsupdate migration is created with the version as revision ID, to ensure that fietsupdate update v0.1.2 will work. This script is usually empty, and only contains a reference to the relevant previous migration ID and the correct alembic revision.

Further notes

When running Fietsboek from master, it might happen that the repository is in a state in which Alembic revisions exist, but no Fietsupdate revision that refers to them. In this case, you must run

alembic -c development.ini upgrade head

Before creating revisions, make sure that your repository and database are in the correct state. Otherwise your revision might refer to an outdated revision. In particular, you should ensure that you have applied all previous updates.

While both Alembic and Fietsupdate have support for “branches” and “merges”, it is still preferable to use this feature as little as possible. If possible, rebase the changes to give them a linear order.

We cannot guarantee that migration scripts will always continue to work (e.g. if they depend on functionality which is later removed from Fietsboek, dependencies that are removed, …). For this reason, big update jumps might be hard to support. However, all scripts should at least stay loadable (that means their metadata is readable, there are no syntax errors), and for empty databases they should work (to allow bootstrapping of fresh instances).