Example #1
0
class Migrations(object):
    """
    Migrations

    Handle all migrations during application start.
    """

    logger = None
    database = None

    def __init__(self, config):
        unmanic_logging = unlogger.UnmanicLogger.__call__()
        self.logger = unmanic_logging.get_logger(__class__.__name__)

        # Based on configuration, select database to connect to.
        if config['TYPE'] == 'SQLITE':
            # Create SQLite directory if not exists
            db_file_directory = os.path.dirname(config['FILE'])
            if not os.path.exists(db_file_directory):
                os.makedirs(db_file_directory)
            self.database = SqliteDatabase(config['FILE'])

            self.router = Router(database=self.database,
                                 migrate_table='migratehistory_{}'.format(
                                     config.get('MIGRATIONS_HISTORY_VERSION')),
                                 migrate_dir=config.get('MIGRATIONS_DIR'),
                                 logger=self.logger)

    def __log(self, message, level='info'):
        if self.logger:
            getattr(self.logger, level)(message)
        else:
            print(message)

    def __run_all_migrations(self):
        """
        Run all new migrations.
        Migrations that have already been run will be ignored.

        :return:
        """
        self.router.run()

    def update_schema(self):
        """
        Updates the Unmanic database schema.

        Newly added tables/models and columns/fields will be automatically generated by this function.
        This way we do not need to create a migration script unless we:
            - rename a column/field
            - delete a column/field
            - delete a table/model

        :return:
        """
        # Fetch all model classes
        all_models = []
        all_base_models = []
        for model in list_all_models():
            imported_model = getattr(
                importlib.import_module("unmanic.libs.unmodels"), model)
            if inspect.isclass(imported_model) and issubclass(
                    imported_model, BaseModel):
                # Add this model to both the 'all_models' list and our list of base models
                all_models.append(imported_model)
                all_base_models.append(imported_model)
            elif inspect.isclass(imported_model) and issubclass(
                    imported_model, Model):
                # If the model is not one of the base models, it is an in-build model from peewee.
                # For, this list of models we will not run a migration, but we will still ensure that the
                #   table is created in the DB
                all_models.append(imported_model)
                pass

        # Start by creating all models
        self.__log("Initialising database tables")
        try:
            with self.database.transaction():
                for model in all_models:
                    self.router.migrator.create_table(model)
                self.router.migrator.run()
        except Exception:
            self.database.rollback()
            self.__log("Initialising tables failed", level='exception')
            raise

        # Migrations will only be used for removing obsolete columns
        self.__run_all_migrations()

        # Newly added fields can be auto added with this function... no need for a migration script
        # Ensure all files are also present for each of the model classes
        self.__log("Updating database fields")
        for model in all_base_models:
            # Fetch all peewee fields for the model class
            # https://stackoverflow.com/questions/22573558/peewee-determining-meta-data-about-model-at-run-time
            fields = model._meta.fields
            # loop over the fields and ensure each on exists in the table
            field_keys = [f for f in fields]
            for fk in field_keys:
                field = fields.get(fk)
                if isinstance(field, Field):
                    if not any(f for f in self.database.get_columns(
                            model._meta.name) if f.name == field.name):
                        # Field does not exist in DB table
                        self.__log("Adding missing column")
                        try:
                            with self.database.transaction():
                                self.router.migrator.add_columns(
                                    model, **{field.name: field})
                                self.router.migrator.run()
                        except Exception:
                            self.database.rollback()
                            self.__log("Update failed", level='exception')
                            raise