aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fietsboek/updater/__init__.py10
-rw-r--r--fietsboek/updater/__main__.py137
-rw-r--r--setup.py1
3 files changed, 148 insertions, 0 deletions
diff --git a/fietsboek/updater/__init__.py b/fietsboek/updater/__init__.py
index bc1bc96..6c154e7 100644
--- a/fietsboek/updater/__init__.py
+++ b/fietsboek/updater/__init__.py
@@ -90,6 +90,16 @@ class Updater:
for prev_id in script.previous:
self.backward_dependencies[prev_id].append(script.id)
+ def exists(self, revision_id):
+ """Checks if the revision with the given ID exists.
+
+ :param revision_id: ID of the revision to check.
+ :type revision_id: str
+ :return: True if the revision exists.
+ :rtype: bool
+ """
+ return revision_id in self.scripts
+
def current_versions(self):
"""Reads the current version of the data.
diff --git a/fietsboek/updater/__main__.py b/fietsboek/updater/__main__.py
new file mode 100644
index 0000000..38297d7
--- /dev/null
+++ b/fietsboek/updater/__main__.py
@@ -0,0 +1,137 @@
+"""Updater for Fietsboek.
+
+While this script does not download and install the latest Fietsboek version
+itself (as this step depends on where you got Fietsboek from), it takes care of
+managing migrations between Fietsboek versions. In particular, the updater
+takes care of running the database migrations, migrating the data directory and
+migrating the configuration.
+"""
+import click
+
+from pyramid.paster import setup_logging
+
+from . import Updater
+
+
+def user_confirm():
+ click.secho("Warning:", fg="yellow")
+ click.echo(
+ "Updating *may* cause data loss. Make sure to have a backup of the "
+ "database and the data directory!"
+ )
+ click.echo("For more information, please consult the documentation.")
+ click.confirm("Proceed?", abort=True)
+
+
+@click.group(help=__doc__)
+@click.option(
+ "-c", "--config",
+ type=click.Path(exists=True, dir_okay=False),
+ required=True,
+ help="Path to the Fietsboek configuration file",
+)
+@click.pass_context
+def cli(ctx, config):
+ ctx.ensure_object(dict)
+ setup_logging(config)
+ ctx.obj["INIFILE"] = config
+
+
+@cli.command("update")
+@click.option(
+ "-f", "--force",
+ is_flag=True,
+ help="Skip the safety question and just run the update",
+)
+@click.argument("VERSION", required=False)
+@click.pass_context
+def update(ctx, version, force):
+ """Run the update process.
+
+ Make sure to consult the documentation and ensure that you have a backup
+ before starting the update, to prevent any data loss!
+
+ VERSION specifies the version you want to update to. Leave empty to choose
+ the latest version.
+ """
+ updater = Updater(ctx.obj["INIFILE"])
+ updater.load()
+ if version and not updater.exists(version):
+ click.secho("Revision not found", fg="red")
+ return
+ version_str = ", ".join(updater.current_versions())
+ click.echo(f"Current version: {version_str}")
+ if not version:
+ heads = updater.heads()
+ if len(heads) != 1:
+ click.secho("Ambiguous heads, please specify the version to update to", fg="red")
+ return
+ version = heads[0]
+ click.echo(f"Selected version: {version}")
+ if not force:
+ user_confirm()
+ updater.upgrade(version)
+ version_str = ", ".join(updater.current_versions())
+ click.secho(f"Update succeeded, version: {version_str}", fg="green")
+
+
+@cli.command("downgrade")
+@click.option(
+ "-f", "--force",
+ is_flag=True,
+ help="Skip the safety question and just run the downgrade",
+)
+@click.argument("VERSION")
+@click.pass_context
+def downgrade(ctx, version, force):
+ """Run the downgrade process.
+
+ Make sure to consult the documentation and ensure that you have a backup
+ before starting the downgrade, to prevent any data loss!
+
+ VERSION specifies the version you want to downgrade to.
+ """
+ updater = Updater(ctx.obj["INIFILE"])
+ updater.load()
+ if version and not updater.exists(version):
+ click.secho("Revision not found", fg="red")
+ return
+ version_str = ", ".join(updater.current_versions())
+ click.echo(f"Current version: {version_str}")
+ click.echo(f"Downgrade to version {version}")
+ if not force:
+ user_confirm()
+ updater.downgrade(version)
+ version_str = ", ".join(updater.current_versions())
+ click.secho(f"Downgrade succeeded, version: {version_str}", fg="green")
+
+
+@cli.command("revision")
+@click.argument("REVISION_ID", required=False)
+@click.pass_context
+def revision(ctx, revision_id):
+ """Create a new revision.
+
+ This automatically populates the revision dependencies and alembic
+ versions, based on the current state.
+
+ This command is useful for developers who work on Fietsboek.
+ """
+ updater = Updater(ctx.obj["INIFILE"])
+ updater.load()
+ current = updater.current_versions()
+ heads = updater.heads()
+ if not any(version in heads for version in current):
+ click.secho("Warning:", fg="yellow")
+ click.echo("Your current revision is not a head. This will create a branch!")
+ click.echo(
+ "If this is not what you intended, make sure to update to the latest "
+ "version first before creating a new revision."
+ )
+ click.confirm("Proceed?", abort=True)
+ filename = updater.new_revision(revision_id)
+ click.echo(f"Revision saved to {filename}")
+
+
+if __name__ == "__main__":
+ cli()
diff --git a/setup.py b/setup.py
index b53a875..ad759ef 100644
--- a/setup.py
+++ b/setup.py
@@ -26,6 +26,7 @@ requires = [
'gpxpy',
'markdown',
'bleach',
+ 'Click',
]
tests_require = [