diff options
-rw-r--r-- | fietsboek/updater/__init__.py | 10 | ||||
-rw-r--r-- | fietsboek/updater/__main__.py | 137 | ||||
-rw-r--r-- | setup.py | 1 |
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() @@ -26,6 +26,7 @@ requires = [ 'gpxpy', 'markdown', 'bleach', + 'Click', ] tests_require = [ |