import abc from playhouse.migrate import (migrate, MySQLMigrator) class Migrator(object): """ Migration interface """ __metaclass__ = abc.ABCMeta connection = db_connection.connection # db_connection is a Borg instance migrator = MySQLMigrator(db_connection.connection) @abc.abstractproperty def migrations(self): """ List of the migrations dictionaries :param self: class instance :return: list """ return [ {'statement': 1 != 2, 'migration': ['list', 'of', 'migration', 'options'], 'migration_kwargs': {}, 'pre_migrations': list(), 'post_migrations': list()} ] # Just an example def migrate(self): """ Run migrations """ for migration in self.migrations: if migration['statement']: # Run scripts before the migration pre_migrations = migration.get('pre_migrations', list()) for pre_m in pre_migrations: pre_m() # Migrate with db_connection.connection.transaction(): migration_kwargs = migration.get('migration_kwargs', {}) migrate(*migration['migration'], **migration_kwargs) # Run scripts after the migration post_migrations = migration.get('post_migrations', list()) for post_m in post_migrations: post_m()
import sys import re def get_migration_modules(packages=[]): """ Get python modules with migrations :param packages: iterable - list or tuple with packages names for the searching :return: list - ('module.path', 'module_name') """ # List of the modules to migrate migration_modules = list() for pack in packages: migration_module = __import__(pack, globals(), locals(), fromlist=[str('migrations')]) try: # Check, that imported object is module if inspect.ismodule(migration_module.migrations): # Find submodules inside the module for importer, modname, ispkg in pkgutil.iter_modules(migration_module.migrations.__path__): if re.match(r'^\d{3,}_migration_[\d\w_]+$', modname) and not ispkg: migration_modules.append((migration_module.migrations.__name__, modname)) # Unregister module sys.modules.pop(migration_module.__name__) except AttributeError: pass return migration_modules def get_migration_classes(migration_modules): """ Get list of the migration classes :type migration_modules: iterable :param migration_modules: array with a migration modules :return: list """ migration_classes = list() for mig_mod, m in migration_modules: mig = __import__(mig_mod, globals(), locals(), fromlist=[m]) try: target_module = mig.__getattribute__(m) # Check, that imported object is module if inspect.ismodule(target_module): for name, obj in inspect.getmembers(target_module): # Get all containing elements if inspect.isclass(obj) and issubclass(obj, Migrator) and obj != Migrator: # Save this elements migration_classes.append(obj) # Remove imported module from the stack sys.modules.pop(mig.__name__) except AttributeError: pass return migration_classes
# Get modules with migrations m_mods = get_migration_modules(packages=['package_1', 'package_2', 'package_3']) # Get migration classes m_classes = get_migration_classes(m_mods) # Execute migrations for m_class in m_classes: mig = m_class() mig.migrate()
from controllers.migrator import Migrator from package_1.models import FirstModel class AddNewFields(Migrator): """ Append new fields to the FirstModel """ table_name = FirstModel._meta.db_table # Get name of the table for target model def __field_not_exists(self): """ Check, that new field does not exists :return: bool """ q = 'SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_NAME = \'{0}\' AND COLUMN_NAME = \'my_new_field\''.format(self.table_name) cursor = self.connection.execute_sql(q) result = int(cursor.fetchone()[0]) return result == 0 @property def migrations(self): return [ # add my_new_field column { 'statement': self.__field_not_exists(), 'migration': [self.migrator.add_column(self.table_name, 'my_new_field', FirstModel.my_new_field)], } ]
@property def migrations(self): # Modify NULL field my_new_field = FirstModel.my_new_field my_new_field.default = 'Rewrite me' return [ # add my_new_field column { 'statement': self.__field_not_exists(), 'migration': [self.migrator.add_column(self.table_name, 'my_new_field', FirstModel.my_new_field)], } ]
Source: https://habr.com/ru/post/262697/