Package Camelot :: Package camelot :: Package core :: Module backup
[frames] | no frames]

Source Code for Module Camelot.camelot.core.backup

  1  import sqlalchemy 
  2  from camelot.core.utils import ugettext as _ 
  3   
4 -class BackupMechanism(object):
5 """Create a backup of the current database to an sqlite database stored in 6 filename. 7 8 The backupmechanism always considers the schema of the backed up database 9 as the master, and never that of the backup. This means that when a backup 10 is made, the backup file is first removed, and then filled with the tables 11 from the the database to backup. When a restore is done, the schema of the 12 database is not touched, but the tables are emptied and the data from the 13 backup is copied into the existing schema. 14 """ 15
16 - def __init__(self, filename):
17 """Backup and restore to a local file using it as an sqlite database 18 """ 19 self._filename = filename
20
21 - def backup_table_filter(self, from_table):
22 """ 23 Method used to filter which tables should be backed up, overwrite this method 24 for taking into account specific schema issues. 25 26 :from_table: the table that is considered for backup 27 :return: True when the table should be backed up 28 """ 29 return True
30
31 - def restore_table_filter(self, from_table):
32 """ 33 Method used to filter which tables should be restored, overwrite this method 34 for taking into account specific schema issues. restore_table_filter is different 35 from backup_table_filter, since we might want to store data in the backup that 36 should not be restored, like schema version information. 37 38 :from_table: the table that is considered for backup 39 :return: True when the table should be restored 40 """ 41 return True
42
43 - def prepare_schema_for_restore(self, from_engine, to_engine):
44 """This method will be called before the actual restore starts. It allows bringing 45 the schema at the same revision as the backed up data. 46 """ 47 pass
48
49 - def update_schema_after_restore(self, from_engine, to_engine):
50 """This method will be called after the restore has been done. It allows bringing 51 the schema at the revision the application expects. 52 """ 53 pass
54
55 - def backup(self):
56 """Generator function that yields tuples : 57 (numer_of_steps_completed, total_number_of_steps, description_of_current_step) 58 while performing a backup. 59 """ 60 import os 61 from sqlalchemy import create_engine, MetaData 62 import settings 63 64 yield (0, 0, _('Analyzing database structure')) 65 from_engine = settings.ENGINE() 66 from_meta_data = MetaData() 67 from_meta_data.bind = from_engine 68 from_meta_data.reflect() 69 70 yield (0, 0, _('Preparing backup file')) 71 if os.path.exists(self._filename): 72 os.remove(self._filename) 73 to_engine = create_engine('sqlite:///%s'%self._filename) 74 to_meta_data = MetaData() 75 to_meta_data.bind = to_engine 76 # 77 # Only copy tables, to prevent issues with indices and constraints 78 # 79 from_and_to_tables = [] 80 for from_table in from_meta_data.sorted_tables: 81 if self.backup_table_filter(from_table): 82 to_table = from_table.tometadata(to_meta_data) 83 to_table.create(to_engine) 84 from_and_to_tables.append((from_table, to_table)) 85 86 number_of_tables = len(from_and_to_tables) 87 for i,(from_table, to_table) in enumerate(from_and_to_tables): 88 yield (i, number_of_tables, _('Copy data of table %s')%from_table.name) 89 self.copy_table_data(from_table, to_table) 90 yield (number_of_tables, number_of_tables, _('Backup completed'))
91
92 - def restore(self):
93 """Generator function that yields tuples : 94 (numer_of_steps_completed, total_number_of_steps, description_of_current_step) 95 while performing a restore. 96 """ 97 import os 98 from sqlalchemy import create_engine, MetaData 99 import settings 100 101 yield (0, 0, _('Open backup file')) 102 if not os.path.exists(self._filename): 103 raise Exception('Backup file does not exist') 104 from_engine = create_engine('sqlite:///%s'%self._filename) 105 106 yield (0, 0, _('Prepare database for restore')) 107 to_engine = settings.ENGINE() 108 self.prepare_schema_for_restore(from_engine, to_engine) 109 110 yield (0, 0, _('Analyzing backup structure')) 111 from_meta_data = MetaData() 112 from_meta_data.bind = from_engine 113 from_meta_data.reflect() 114 115 yield (0, 0, _('Analyzing database structure')) 116 to_meta_data = MetaData() 117 to_meta_data.bind = to_engine 118 to_meta_data.reflect() 119 to_tables = list(table for table in to_meta_data.sorted_tables if self.restore_table_filter(table)) 120 number_of_tables = len(to_tables) 121 122 for i,to_table in enumerate(reversed(to_tables)): 123 yield (i, number_of_tables*2, _('Delete data from table %s')%to_table.name) 124 self.delete_table_data(to_table) 125 126 for i,to_table in enumerate(to_tables): 127 if to_table.name in from_meta_data.tables: 128 yield (number_of_tables+i, number_of_tables*2, _('Copy data from table %s')%to_table.name) 129 self.copy_table_data(from_meta_data.tables[to_table.name], to_table) 130 131 yield (1, 1, _('Update schema after restore')) 132 self.update_schema_after_restore(from_engine, to_engine) 133 yield (1, 1, _('Restore completed'))
134
135 - def delete_table_data(self, to_table):
136 """This method might be subclassed to turn off/on foreign key checks""" 137 to_connection = to_table.bind.connect() 138 to_connection.execute(to_table.delete()) 139 to_connection.close()
140
141 - def copy_table_data(self, from_table, to_table):
142 from_connection = from_table.bind.connect() 143 to_connection = to_table.bind.connect() 144 query = sqlalchemy.select([from_table]) 145 for row in from_connection.execute(query).fetchall(): 146 data = dict((key, getattr(row, key)) for key in row.keys()) 147 to_connection.execute(to_table.insert(values=data)) 148 from_connection.close() 149 to_connection.close()
150