Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #378: Add alembic support for database migrations #387

Draft
wants to merge 98 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
2934ad5
Basic infrastructure for `versiondb` subcommand
baileythegreen Feb 4, 2022
cbc6a71
Add new requirement
baileythegreen Feb 4, 2022
8fcd224
Add `get_version()` for alembic
baileythegreen Feb 7, 2022
e7a5c30
Hook `pyani versiondb` infrastructure to functional code
baileythegreen Feb 9, 2022
6b88b2b
Add generic versions of files required for `alembic` to function
baileythegreen Feb 13, 2022
bfd6a60
Alter handling of `stderr` and `stdout`from `alembic`
baileythegreen Feb 13, 2022
ea6adf1
Add logging and use environmental variables
baileythegreen Feb 13, 2022
aed669d
Add migration script for `fastANI` changes to the ORM
baileythegreen Feb 13, 2022
3d66953
Backup existing database before migrating
baileythegreen Feb 13, 2022
4a35636
Use a local file to track versions in dry-run mode
baileythegreen Feb 14, 2022
18788ed
Update parser to use `const` values instead of defaults
baileythegreen Feb 14, 2022
0531a65
Update `versiondb` to accommodate `--dry-run` option
baileythegreen Feb 14, 2022
b297630
Clean up `subcmd_versiondb`
baileythegreen Feb 14, 2022
51bac6f
Add initial tests for `versiondb`
baileythegreen Feb 14, 2022
b37a1ca
Fix test comparison value
baileythegreen Feb 23, 2022
b2c74f4
Specify which parser arguments are optional
baileythegreen Feb 23, 2022
0b9d47a
Abstract migration functions to one and implement dry-run
baileythegreen Feb 23, 2022
7f933ba
Add `alembic_version` table to database model
baileythegreen Mar 1, 2022
216778a
Add current database version to `alembic_version` table
baileythegreen Mar 1, 2022
ac061d5
Update .gitignore to ignore some testing files
baileythegreen Mar 1, 2022
f875508
Change unique constraints for `comparisons` table with Alembic
baileythegreen Mar 1, 2022
f8140b9
Groundwork for `tests/test_versiondb.py`
baileythegreen Mar 1, 2022
2c3500c
Add timestamp to dry-run output file name
baileythegreen Mar 2, 2022
9c642b3
Update migration file to allow for reflection in offline mode
baileythegreen Mar 2, 2022
79e06f2
Add empty database fixtures for tests
baileythegreen Mar 3, 2022
54e814f
Update `test_versiondb.py` input file names
baileythegreen Mar 3, 2022
a747d46
Add docs for `versiondb`
baileythegreen Mar 9, 2022
4ea0b67
Add docs for `versiondbAdd a directory for `versionb` output files
baileythegreen Mar 14, 2022
51fb696
Rework tests to look for meaningful differences between output and ta…
baileythegreen Mar 14, 2022
871a149
Update starting point for database downgrade tests
baileythegreen Mar 14, 2022
d9da009
Fix errors in `versiondb.py` arg names
baileythegreen Mar 14, 2022
c1ff74f
Add `sqlite3` as a third-party dependency
baileythegreen Mar 14, 2022
5b74fc7
Fix failing `cli` test; (failure caused by code added for alembic)
baileythegreen Mar 14, 2022
6e18256
Add `sqlite` installation code to `config.yml`
baileythegreen Mar 14, 2022
d2f9f28
Test alembic command-line generation and add docstrings
baileythegreen Mar 15, 2022
938440b
Change how `sqlite3` is installed
baileythegreen Mar 16, 2022
0efe216
Change how namespaces are retrieved to use fixtures
baileythegreen Mar 16, 2022
8da38c7
Change how `expected_diffs()` returns values
baileythegreen Mar 16, 2022
84f4a35
Change handling of `stdout` and `stderr` from `subprocess.run` calls
baileythegreen Mar 16, 2022
d4d8293
Change cleanup procedure to prevent artefacts being overwritten
baileythegreen Mar 16, 2022
02bb750
Replace remaining use of `capture_output` keyword; previously missed
baileythegreen Mar 16, 2022
55480ea
Alter `sed` command to be OS-specific
baileythegreen Mar 16, 2022
b0c0947
Add targets for `versiondb` tests
baileythegreen Mar 17, 2022
fdcf580
Add target location for `versiondb` tests
baileythegreen Mar 17, 2022
d434f23
Add alt_config file for `versiondb` tests
baileythegreen Mar 17, 2022
9459068
Update tests for `versiondb` to make `diff` comparison more robust
baileythegreen Mar 17, 2022
34a694b
Updated config.yml
baileythegreen Mar 17, 2022
12daf2c
Updated config.yml
baileythegreen Mar 17, 2022
ed18de5
Updated config.yml
baileythegreen Mar 17, 2022
a3efcbc
Updated config.yml
baileythegreen Mar 17, 2022
84b71e6
Updated config.yml
baileythegreen Mar 17, 2022
27ee6bb
Updated config.yml
baileythegreen Mar 17, 2022
0bfef52
Updated config.yml
baileythegreen Mar 17, 2022
373725f
Updated config.yml
baileythegreen Mar 17, 2022
baaec35
Updated config.yml
baileythegreen Mar 17, 2022
d3a967c
Updated config.yml
baileythegreen Mar 17, 2022
359c00d
Updated config.yml
baileythegreen Mar 17, 2022
1a549e4
Updated config.yml
baileythegreen Mar 17, 2022
3c44d35
Add default location and install instructions for `sqlite3`
baileythegreen Mar 17, 2022
1aed40c
Merge branch 'alembic_378' of https://github.com/widdowquinn/pyani in…
baileythegreen Mar 17, 2022
983cd87
Fix import syntax
baileythegreen Mar 17, 2022
8e71334
Updated config.yml
baileythegreen Mar 17, 2022
72dd920
Updated config.yml
baileythegreen Mar 17, 2022
0892fc4
Updated config.yml
baileythegreen Mar 17, 2022
b3b31f9
Updated config.yml
baileythegreen Mar 17, 2022
1f0e3d4
Updated config.yml
baileythegreen Mar 17, 2022
720e0af
Updated config.yml
baileythegreen Mar 17, 2022
8b34edc
Updated config.yml
baileythegreen Mar 17, 2022
f351084
Remove some `dry-run` specific stuff for the time being
baileythegreen Mar 18, 2022
6f224dd
Merge branch 'alembic_378' of https://github.com/widdowquinn/pyani in…
baileythegreen Mar 18, 2022
86b79a4
Set `create_constraint=True` for compatibility between sqlalchemy ver…
baileythegreen Mar 18, 2022
8aa105e
Set `create_constraint` flag only if `sqlalchemy` version is >= 1.4
baileythegreen Mar 19, 2022
9a10cb5
Update `versiondb` test targets
baileythegreen Mar 19, 2022
3529691
Update `versiondb` test fixtures
baileythegreen Mar 19, 2022
cf810c5
Send diffs to stdout so we can debug why tests sporadically fail
baileythegreen Mar 20, 2022
03c0057
Send diffs to stdout for debugging
baileythegreen Mar 20, 2022
bc27460
Use `sqldiff` instead of `diff`
baileythegreen Mar 20, 2022
cd5306c
Move `sqldiff` specification to `pyani_config.py`
baileythegreen Mar 28, 2022
2ee0636
Remove code from earlier versions of tests
baileythegreen Mar 28, 2022
cc59b98
Enhance comments
baileythegreen Mar 28, 2022
67ff5f9
Remove structures from `conftest.py` that are not being used after all
baileythegreen Mar 28, 2022
6c76bfb
Add note about missing `--dry-run` tests
baileythegreen Mar 28, 2022
40470a4
Fix typos
baileythegreen Mar 28, 2022
2ef493e
`sqldiff` is needed for `pyani versiondb`, but can't install from `co…
baileythegreen Mar 28, 2022
78d6f8a
Remove references to non-existent fixtures
baileythegreen Mar 28, 2022
7ddd894
Add `alembic` directories to package data for non-dev use cases
baileythegreen Mar 29, 2022
063f1e9
Update version of `sqlite` to 3.37.0 and abstract version into a vari…
baileythegreen Mar 31, 2022
9b7005e
Add explanations surrounding `alembic` tests and when they run/depend…
baileythegreen Mar 31, 2022
38d6e58
Updated config.yml
baileythegreen Mar 31, 2022
cefb577
Fix typo in code explanation
baileythegreen Mar 31, 2022
5ffa925
Merge branch 'alembic_378' of https://github.com/widdowquinn/pyani in…
baileythegreen Mar 31, 2022
6847534
Skip tests requiring `sqldiff` when it is not installed
baileythegreen Mar 31, 2022
d7ffd88
Merge branch 'master' into alembic_378
baileythegreen May 23, 2022
8d734ee
Merge branch 'master' of https://github.com/widdowquinn/pyani into al…
baileythegreen May 23, 2022
bc8fe40
Merge branch 'alembic_378' of https://github.com/widdowquinn/pyani in…
baileythegreen Jun 16, 2022
7b77064
Merge branch 'master' of https://github.com/widdowquinn/pyani into al…
baileythegreen Jun 21, 2022
93cc99f
Updated config.yml
baileythegreen Jun 21, 2022
582bdc4
Make changes to tests as discussed in Issue #405
baileythegreen Jul 29, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,21 @@ jobs:
echo 'export PATH=$PWD:$PATH' >> $BASH_ENV
source $BASH_ENV
fastANI -h
- run:
name: install sqlite3
command: |
version=version-3.37.0
wget https://github.com/sqlite/sqlite/archive/refs/tags/${version}.tar.gz
tar xzf ${version}.tar.gz ;# Unpack the source tree into "sqlite"
mkdir bld ;# Build will occur in a sibling directory
cd bld ;# Change to the build directory
../sqlite-${version}/configure ;# Run the configure script
make ;# Run the makefile.
make sqlite3.c ;# Build the "amalgamation" source file
make test ;# Run some tests (requires Tcl)
echo 'export PATH=~/repo/bld:$PATH' >> $BASH_ENV
source $BASH_ENV
echo $PATH


- run:
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Scratch directory for local testing
scratch/
alembic/version.txt
*/*add_third_column.py

# Mac-related dreck
.DS_Store
Expand Down Expand Up @@ -72,4 +74,4 @@ venv-*

# Extra documentation output
classes_pyani.pdf
packages_pyani.pdf
packages_pyani.pdf
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
setup_conda:
@conda install --file requirements-dev.txt --yes
@conda install --file requirements.txt --yes
@conda config --add channels blaze
@conda install --file requirements-thirdparty.txt --yes
@conda install --file requirements-fastani.txt --yes
@conda install --file requirements-pyqt-conda.txt --yes
Expand Down
99 changes: 99 additions & 0 deletions alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# A generic, single database configuration.

[alembic]
# path to migration scripts
script_location = %(here)s/alembic

# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s

# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
prepend_sys_path = .

# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the python-dateutil library that can be
# installed by adding `alembic[tz]` to the pip requirements
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =

# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false

# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false

# version location specification; This defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "version_path_separator"
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions

# version path separator; As mentioned above, this is the character used to split
# version_locations. Valid values are:
#
# version_path_separator = :
# version_path_separator = ;
# version_path_separator = space
version_path_separator = os # default: use os.pathsep

# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8

sqlalchemy.url = sqlite:///.pyani/pyanidb

[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples

# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks = black
# black.type = console_scripts
# black.entrypoint = black
# black.options = -l 79 REVISION_SCRIPT_FILENAME

# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
104 changes: 104 additions & 0 deletions alembic/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from logging.config import fileConfig

from sqlalchemy import engine_from_config
from sqlalchemy import pool

from alembic import context

import os

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

dbpath = os.environ.get("PYANI_DATABASE")
script_dir = os.environ.get("ALEMBIC_MIGRATIONS_DIR")
url = f"sqlite:///{dbpath}"
config.set_main_option("sqlalchemy.url", url)
config.set_main_option("script_location", "alembic")


# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = None

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.


def run_migrations_offline():
"""Run migrations in 'offline' mode.

This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.

Calls to context.execute() here emit the given string to the
script output.

"""

context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)

with context.begin_transaction():
context.run_migrations()


def run_migrations_online():
"""Run migrations in 'online' mode.

In this scenario we need to create an Engine
and associate a connection with the context.

"""

connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)

with connectable.connect() as connection:
context.configure(connection=connection, target_metadata=target_metadata)

with context.begin_transaction():
context.run_migrations()


if context.is_offline_mode():
# Track the current version of the database in a local file
# this value is used in the --dry-run option
version_file = os.path.join(
os.path.dirname(config.config_file_name), "alembic/version.txt"
)
if os.path.exists(version_file):
current_version = open(version_file).read().strip()
else:
current_version = None
context.configure(dialect_name="sqlite", starting_rev=current_version)

# Perform the dry run
run_migrations_offline()

# Write 'new' version to file
end_version = context.get_revision_argument()
if end_version and end_version != current_version:
open(version_file, "w").write(end_version)
elif end_version is None:
open(version_file, "w").write("base")
else:
run_migrations_online()
1 change: 1 addition & 0 deletions alembic/version.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
65538af5a5e1
123 changes: 123 additions & 0 deletions alembic/versions/92f7f6b1626e_add_fastani_columns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""add fastani columns

Revision ID: 92f7f6b1626e
Revises:
Create Date: 2022-02-07 22:57:35.779356

"""
from alembic import op
import sqlalchemy as sa
import sys

from sqlalchemy import (
Column,
# Table,
# MetaData,
# ForeignKey,
Integer,
# String,
Float,
# Boolean,
UniqueConstraint,
)

# revision identifiers, used by Alembic.
revision = "92f7f6b1626e"
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
# op.add_column("comparisons", sa.Column("kmersize", sa.Integer))
# op.add_column("comparisons", sa.Column("minmatch", sa.Float))
"""
comparisons = Table(
"comparisons",
meta,
Column("comparisons_id", Integer, primary_key=True),
Column("query_id", Integer, ForeignKey("genomes.genome_id"), nullable=False),
Column("subject_id", Integer, ForeignKey("genomes.genome_id"), nullable=False),
Column("aln_length", Integer),
Column("sim_errs", Integer),
Column("identity", Float),
Column("cov_query", Float),
Column("cov_subject", Float),
Column("program", String),
Column("version", String),
Column("fragsize", Integer),
Column("maxmatch", Boolean),
UniqueConstraint(
"query_id",
"subject_id",
"program",
"version",
"fragsize",
"maxmatch",
name="base_reqs",
),
)
"""
with op.batch_alter_table("comparisons") as batch_op:
# batch_op.add_column(sa.Column("kmersize", sa.Integer, default=None))
# batch_op.add_column(sa.Column("minmatch", sa.Float, default=None))
batch_op.drop_constraint("base_reqs")
batch_op.add_column(sa.Column("kmersize", sa.Integer, default=None))
batch_op.add_column(sa.Column("minmatch", sa.Float, default=None))
batch_op.create_unique_constraint(
"fastani_reqs",
[
"query_id",
"subject_id",
"program",
"version",
"fragsize",
"maxmatch",
"kmersize",
"minmatch",
],
)


def downgrade():
# op.drop_constraint("comparisons", 'kmersize')
# op.drop_column("comparisons", "kmersize")
"""
comparisons = Table(
"comparisons",
meta,
Column("comparisons_id", Integer, primary_key=True),
Column("query_id", Integer, ForeignKey("genomes.genome_id"), nullable=False),
Column("subject_id", Integer, ForeignKey("genomes.genome_id"), nullable=False),
Column("aln_length", Integer),
Column("sim_errs", Integer),
Column("identity", Float),
Column("cov_query", Float),
Column("cov_subject", Float),
Column("program", String),
Column("version", String),
Column("fragsize", Integer),
Column("maxmatch", Boolean),
Column("kmersize", Integer),
Column("minmatch", Float),
UniqueConstraint(
"query_id",
"subject_id",
"program",
"version",
"fragsize",
"maxmatch",
"kmersize",
"minmatch",
name="fastani_reqs",
),
)
"""
with op.batch_alter_table("comparisons") as batch_op:
batch_op.drop_column("kmersize")
batch_op.drop_column("minmatch")
batch_op.drop_constraint("fastani_reqs")
batch_op.create_unique_constraint(
"base_reqs",
["query_id", "subject_id", "program", "version", "fragsize", "maxmatch"],
)