Commit 108d5ccd authored by Gerion Entrup's avatar Gerion Entrup
Browse files

add database schema versioning

With this commit before the database is initialized the database schema
version is looked up and compared with the program version. The
possibility to migrate to a higher version is prepared.
parent 38aa2cd9
......@@ -18,18 +18,32 @@ from collector import Collector
from translator import Translator
from schedu import Schedu
version = "0.1-alpha"
schema_version = 1
def _init_database(dbfile):
def _init_database(dbfile, migrate=False):
"""Initialize the database. Currently only sqlite
Arguments:
dbfile -- the path of the database
"""
logger = logging.getLogger('main')
database = 'sqlite:///'
engine = create_engine(database + dbfile,
connect_args={'check_same_thread':False},
#echo = True,
poolclass=StaticPool)
db_version = model.get_version(engine)
if db_version > 0 and db_version < schema_version:
if migrate:
model.migrate(engine, db_version, schema_version)
else:
logger.error('Your database is too old for this version of the program. Please apply the "--migrate" flag to migrate the database to the new version. This process is not invertable.')
sys.exit(1)
if schema_version < db_version:
logger.error('Your database was created by a newer version of this program and is incompatible with this version. Please update your program.')
sys.exit(1)
model.init_database(engine)
session_factory = sessionmaker(bind=engine)
session = scoped_session(session_factory)
......@@ -37,11 +51,12 @@ def _init_database(dbfile):
#dirty workaroud for sqlite non concurrent mode in python
#remove it once python supports fully concurrency in sqlite
session = sqlitequeue.get_Session(session)
model.write_version(session, schema_version)
return session
def main(args):
"""Main method. Parses cmdline arguments and starts the program."""
version = "0.1-alpha"
parser = argparse.ArgumentParser(add_help=True,
description="Create a directory structure of music with" \
"symlinks based on musicbrainz data. Setting commandline options is only temporary. Change the config file to set option permanently.")
......@@ -53,6 +68,7 @@ def main(args):
help="change to loglevel to info")
parser.add_argument('--database', '-d', default="music.db", help="path of the database")
parser.add_argument('--logfile', '-l', help="log into a logfile")
parser.add_argument('--migrate', '-m', action="store_true", default=False, help="migrate to a newer database schema. This cannot be undone.")
# options from settings
for opt, value in settings.get_options():
......@@ -82,7 +98,7 @@ def main(args):
setattr(settings, opt, v)
logger.info("initializing database")
session = _init_database(arg.database)
session = _init_database(arg.database, migrate=arg.migrate)
logger.info("database initialized")
collector = Collector(session)
......
import sqlalchemy
from sqlalchemy import Column, Integer, String
from sqlalchemy.sql import text
from mbdata.utils import patch_model_schemas, NO_SCHEMAS
from mbdata.models import Base, Recording
from utils import session_scope
class BrainzFS(Base):
__tablename__ = 'brainzfs'
id = Column(Integer, primary_key=True)
schemaversion = Column(Integer, nullable=False)
programversion = Column(Integer, nullable=False)
def patch_recording():
......@@ -19,11 +22,39 @@ def patch_recording():
# store the mbid written in the file
setattr(Recording, 'fgid', Column(String, nullable=False))
def init_database(engine):
patch_model_schemas(NO_SCHEMAS)
patch_recording()
Base.metadata.create_all(engine)
def get_version(engine):
conn = engine.connect()
try:
res = conn.execute(text("SELECT schemaversion FROM brainzfs")).first()
if res == None:
res = 0
else:
res = res[0]
except sqlalchemy.exc.OperationalError:
res = 0
conn.close()
return res
def write_version(session, version):
with session_scope(session) as session:
res = session.query(BrainzFS).first()
if res != None:
res.schemaversion = version
else:
v = BrainzFS(schemaversion=version)
session.add(v)
def migrate(engine, db_version, schema_version):
print("migrate from ", db_version, "to", schema_version)
pass
......@@ -2,25 +2,7 @@ import logging
import sqlalchemy
from retrieval import fetcher
from contextlib import contextmanager
@contextmanager
def session_scope(Session):
"""Provide a transactional scope around a series of operations.
Arguments:
Session -- a SQLAlchemy session class (not an instance)
"""
session = Session()
try:
yield session
session.commit()
except:
session.rollback()
raise
finally:
session.close()
from utils import session_scope
class Retrieval():
......
......@@ -4,6 +4,25 @@ import pprint
from code import InteractiveConsole
from inspect import currentframe
from contextlib import contextmanager
@contextmanager
def session_scope(Session):
"""Provide a transactional scope around a series of operations.
Arguments:
Session -- a SQLAlchemy session class (not an instance)
"""
session = Session()
try:
yield session
session.commit()
except:
session.rollback()
raise
finally:
session.close()
def get_path(relative_path):
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment