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