Mercurial > kallithea
comparison rhodecode/lib/dbmigrate/migrate/versioning/genmodel.py @ 1442:7f31de1584c6 beta
update migrations for 1.2
author | Marcin Kuzminski <marcin@python-works.com> |
---|---|
date | Sun, 14 Aug 2011 23:51:21 +0300 |
parents | 6832ef664673 |
children | cf51bbfb120e |
comparison
equal
deleted
inserted
replaced
1441:b596a0e63466 | 1442:7f31de1584c6 |
---|---|
1 """ | 1 """ |
2 Code to generate a Python model from a database or differences | 2 Code to generate a Python model from a database or differences |
3 between a model and database. | 3 between a model and database. |
4 | 4 |
5 Some of this is borrowed heavily from the AutoCode project at: | 5 Some of this is borrowed heavily from the AutoCode project at: |
6 http://code.google.com/p/sqlautocode/ | 6 http://code.google.com/p/sqlautocode/ |
7 """ | 7 """ |
8 | 8 |
9 import sys | 9 import sys |
10 import logging | 10 import logging |
11 | 11 |
12 import sqlalchemy | 12 import sqlalchemy |
13 | 13 |
14 from rhodecode.lib.dbmigrate import migrate | 14 from rhodecode.lib.dbmigrate import migrate |
15 from rhodecode.lib.dbmigrate.migrate import changeset | 15 from rhodecode.lib.dbmigrate.migrate import changeset |
16 | |
16 | 17 |
17 log = logging.getLogger(__name__) | 18 log = logging.getLogger(__name__) |
18 HEADER = """ | 19 HEADER = """ |
19 ## File autogenerated by genmodel.py | 20 ## File autogenerated by genmodel.py |
20 | 21 |
31 Base = declarative.declarative_base() | 32 Base = declarative.declarative_base() |
32 """ | 33 """ |
33 | 34 |
34 | 35 |
35 class ModelGenerator(object): | 36 class ModelGenerator(object): |
37 """Various transformations from an A, B diff. | |
38 | |
39 In the implementation, A tends to be called the model and B | |
40 the database (although this is not true of all diffs). | |
41 The diff is directionless, but transformations apply the diff | |
42 in a particular direction, described in the method name. | |
43 """ | |
36 | 44 |
37 def __init__(self, diff, engine, declarative=False): | 45 def __init__(self, diff, engine, declarative=False): |
38 self.diff = diff | 46 self.diff = diff |
39 self.engine = engine | 47 self.engine = engine |
40 self.declarative = declarative | 48 self.declarative = declarative |
56 # default value for the sequence, but let's not show | 64 # default value for the sequence, but let's not show |
57 # that. | 65 # that. |
58 pass | 66 pass |
59 else: | 67 else: |
60 kwarg.append('default') | 68 kwarg.append('default') |
61 ks = ', '.join('%s=%r' % (k, getattr(col, k)) for k in kwarg) | 69 args = ['%s=%r' % (k, getattr(col, k)) for k in kwarg] |
62 | 70 |
63 # crs: not sure if this is good idea, but it gets rid of extra | 71 # crs: not sure if this is good idea, but it gets rid of extra |
64 # u'' | 72 # u'' |
65 name = col.name.encode('utf8') | 73 name = col.name.encode('utf8') |
66 | 74 |
70 not cls.__name__.isupper(): | 78 not cls.__name__.isupper(): |
71 if cls is not type_.__class__: | 79 if cls is not type_.__class__: |
72 type_ = cls() | 80 type_ = cls() |
73 break | 81 break |
74 | 82 |
83 type_repr = repr(type_) | |
84 if type_repr.endswith('()'): | |
85 type_repr = type_repr[:-2] | |
86 | |
87 constraints = [repr(cn) for cn in col.constraints] | |
88 | |
75 data = { | 89 data = { |
76 'name': name, | 90 'name': name, |
77 'type': type_, | 91 'commonStuff': ', '.join([type_repr] + constraints + args), |
78 'constraints': ', '.join([repr(cn) for cn in col.constraints]), | 92 } |
79 'args': ks and ks or ''} | 93 |
80 | |
81 if data['constraints']: | |
82 if data['args']: | |
83 data['args'] = ',' + data['args'] | |
84 | |
85 if data['constraints'] or data['args']: | |
86 data['maybeComma'] = ',' | |
87 else: | |
88 data['maybeComma'] = '' | |
89 | |
90 commonStuff = """ %(maybeComma)s %(constraints)s %(args)s)""" % data | |
91 commonStuff = commonStuff.strip() | |
92 data['commonStuff'] = commonStuff | |
93 if self.declarative: | 94 if self.declarative: |
94 return """%(name)s = Column(%(type)r%(commonStuff)s""" % data | 95 return """%(name)s = Column(%(commonStuff)s)""" % data |
95 else: | 96 else: |
96 return """Column(%(name)r, %(type)r%(commonStuff)s""" % data | 97 return """Column(%(name)r, %(commonStuff)s)""" % data |
97 | 98 |
98 def getTableDefn(self, table): | 99 def _getTableDefn(self, table, metaName='meta'): |
99 out = [] | 100 out = [] |
100 tableName = table.name | 101 tableName = table.name |
101 if self.declarative: | 102 if self.declarative: |
102 out.append("class %(table)s(Base):" % {'table': tableName}) | 103 out.append("class %(table)s(Base):" % {'table': tableName}) |
103 out.append(" __tablename__ = '%(table)s'" % {'table': tableName}) | 104 out.append(" __tablename__ = '%(table)s'\n" % |
105 {'table': tableName}) | |
104 for col in table.columns: | 106 for col in table.columns: |
105 out.append(" %s" % self.column_repr(col)) | 107 out.append(" %s" % self.column_repr(col)) |
106 else: | 108 out.append('\n') |
107 out.append("%(table)s = Table('%(table)s', meta," % \ | 109 else: |
108 {'table': tableName}) | 110 out.append("%(table)s = Table('%(table)s', %(meta)s," % |
111 {'table': tableName, 'meta': metaName}) | |
109 for col in table.columns: | 112 for col in table.columns: |
110 out.append(" %s," % self.column_repr(col)) | 113 out.append(" %s," % self.column_repr(col)) |
111 out.append(")") | 114 out.append(")\n") |
112 return out | 115 return out |
113 | 116 |
114 def _get_tables(self,missingA=False,missingB=False,modified=False): | 117 def _get_tables(self,missingA=False,missingB=False,modified=False): |
115 to_process = [] | 118 to_process = [] |
116 for bool_,names,metadata in ( | 119 for bool_,names,metadata in ( |
120 ): | 123 ): |
121 if bool_: | 124 if bool_: |
122 for name in names: | 125 for name in names: |
123 yield metadata.tables.get(name) | 126 yield metadata.tables.get(name) |
124 | 127 |
125 def toPython(self): | 128 def genBDefinition(self): |
126 """Assume database is current and model is empty.""" | 129 """Generates the source code for a definition of B. |
130 | |
131 Assumes a diff where A is empty. | |
132 | |
133 Was: toPython. Assume database (B) is current and model (A) is empty. | |
134 """ | |
135 | |
127 out = [] | 136 out = [] |
128 if self.declarative: | 137 if self.declarative: |
129 out.append(DECLARATIVE_HEADER) | 138 out.append(DECLARATIVE_HEADER) |
130 else: | 139 else: |
131 out.append(HEADER) | 140 out.append(HEADER) |
132 out.append("") | 141 out.append("") |
133 for table in self._get_tables(missingA=True): | 142 for table in self._get_tables(missingA=True): |
134 out.extend(self.getTableDefn(table)) | 143 out.extend(self._getTableDefn(table)) |
135 out.append("") | |
136 return '\n'.join(out) | 144 return '\n'.join(out) |
137 | 145 |
138 def toUpgradeDowngradePython(self, indent=' '): | 146 def genB2AMigration(self, indent=' '): |
139 ''' Assume model is most current and database is out-of-date. ''' | 147 '''Generate a migration from B to A. |
140 decls = ['from rhodecode.lib.dbmigrate.migrate.changeset import schema', | 148 |
141 'meta = MetaData()'] | 149 Was: toUpgradeDowngradePython |
142 for table in self._get_tables( | 150 Assume model (A) is most current and database (B) is out-of-date. |
143 missingA=True,missingB=True,modified=True | 151 ''' |
144 ): | 152 |
145 decls.extend(self.getTableDefn(table)) | 153 decls = ['from migrate.changeset import schema', |
146 | 154 'pre_meta = MetaData()', |
147 upgradeCommands, downgradeCommands = [], [] | 155 'post_meta = MetaData()', |
148 for tableName in self.diff.tables_missing_from_A: | 156 ] |
149 upgradeCommands.append("%(table)s.drop()" % {'table': tableName}) | 157 upgradeCommands = ['pre_meta.bind = migrate_engine', |
150 downgradeCommands.append("%(table)s.create()" % \ | 158 'post_meta.bind = migrate_engine'] |
151 {'table': tableName}) | 159 downgradeCommands = list(upgradeCommands) |
152 for tableName in self.diff.tables_missing_from_B: | 160 |
153 upgradeCommands.append("%(table)s.create()" % {'table': tableName}) | 161 for tn in self.diff.tables_missing_from_A: |
154 downgradeCommands.append("%(table)s.drop()" % {'table': tableName}) | 162 pre_table = self.diff.metadataB.tables[tn] |
155 | 163 decls.extend(self._getTableDefn(pre_table, metaName='pre_meta')) |
156 for tableName in self.diff.tables_different: | 164 upgradeCommands.append( |
157 dbTable = self.diff.metadataB.tables[tableName] | 165 "pre_meta.tables[%(table)r].drop()" % {'table': tn}) |
158 missingInDatabase, missingInModel, diffDecl = \ | 166 downgradeCommands.append( |
159 self.diff.colDiffs[tableName] | 167 "pre_meta.tables[%(table)r].create()" % {'table': tn}) |
160 for col in missingInDatabase: | 168 |
161 upgradeCommands.append('%s.columns[%r].create()' % ( | 169 for tn in self.diff.tables_missing_from_B: |
162 modelTable, col.name)) | 170 post_table = self.diff.metadataA.tables[tn] |
163 downgradeCommands.append('%s.columns[%r].drop()' % ( | 171 decls.extend(self._getTableDefn(post_table, metaName='post_meta')) |
164 modelTable, col.name)) | 172 upgradeCommands.append( |
165 for col in missingInModel: | 173 "post_meta.tables[%(table)r].create()" % {'table': tn}) |
166 upgradeCommands.append('%s.columns[%r].drop()' % ( | 174 downgradeCommands.append( |
167 modelTable, col.name)) | 175 "post_meta.tables[%(table)r].drop()" % {'table': tn}) |
168 downgradeCommands.append('%s.columns[%r].create()' % ( | 176 |
169 modelTable, col.name)) | 177 for (tn, td) in self.diff.tables_different.iteritems(): |
170 for modelCol, databaseCol, modelDecl, databaseDecl in diffDecl: | 178 if td.columns_missing_from_A or td.columns_different: |
179 pre_table = self.diff.metadataB.tables[tn] | |
180 decls.extend(self._getTableDefn( | |
181 pre_table, metaName='pre_meta')) | |
182 if td.columns_missing_from_B or td.columns_different: | |
183 post_table = self.diff.metadataA.tables[tn] | |
184 decls.extend(self._getTableDefn( | |
185 post_table, metaName='post_meta')) | |
186 | |
187 for col in td.columns_missing_from_A: | |
188 upgradeCommands.append( | |
189 'pre_meta.tables[%r].columns[%r].drop()' % (tn, col)) | |
190 downgradeCommands.append( | |
191 'pre_meta.tables[%r].columns[%r].create()' % (tn, col)) | |
192 for col in td.columns_missing_from_B: | |
193 upgradeCommands.append( | |
194 'post_meta.tables[%r].columns[%r].create()' % (tn, col)) | |
195 downgradeCommands.append( | |
196 'post_meta.tables[%r].columns[%r].drop()' % (tn, col)) | |
197 for modelCol, databaseCol, modelDecl, databaseDecl in td.columns_different: | |
171 upgradeCommands.append( | 198 upgradeCommands.append( |
172 'assert False, "Can\'t alter columns: %s:%s=>%s"' % ( | 199 'assert False, "Can\'t alter columns: %s:%s=>%s"' % ( |
173 modelTable, modelCol.name, databaseCol.name)) | 200 tn, modelCol.name, databaseCol.name)) |
174 downgradeCommands.append( | 201 downgradeCommands.append( |
175 'assert False, "Can\'t alter columns: %s:%s=>%s"' % ( | 202 'assert False, "Can\'t alter columns: %s:%s=>%s"' % ( |
176 modelTable, modelCol.name, databaseCol.name)) | 203 tn, modelCol.name, databaseCol.name)) |
177 pre_command = ' meta.bind = migrate_engine' | |
178 | 204 |
179 return ( | 205 return ( |
180 '\n'.join(decls), | 206 '\n'.join(decls), |
181 '\n'.join([pre_command] + ['%s%s' % (indent, line) for line in upgradeCommands]), | 207 '\n'.join('%s%s' % (indent, line) for line in upgradeCommands), |
182 '\n'.join([pre_command] + ['%s%s' % (indent, line) for line in downgradeCommands])) | 208 '\n'.join('%s%s' % (indent, line) for line in downgradeCommands)) |
183 | 209 |
184 def _db_can_handle_this_change(self,td): | 210 def _db_can_handle_this_change(self,td): |
211 """Check if the database can handle going from B to A.""" | |
212 | |
185 if (td.columns_missing_from_B | 213 if (td.columns_missing_from_B |
186 and not td.columns_missing_from_A | 214 and not td.columns_missing_from_A |
187 and not td.columns_different): | 215 and not td.columns_different): |
188 # Even sqlite can handle this. | 216 # Even sqlite can handle column additions. |
189 return True | 217 return True |
190 else: | 218 else: |
191 return not self.engine.url.drivername.startswith('sqlite') | 219 return not self.engine.url.drivername.startswith('sqlite') |
192 | 220 |
193 def applyModel(self): | 221 def runB2A(self): |
194 """Apply model to current database.""" | 222 """Goes from B to A. |
223 | |
224 Was: applyModel. Apply model (A) to current database (B). | |
225 """ | |
195 | 226 |
196 meta = sqlalchemy.MetaData(self.engine) | 227 meta = sqlalchemy.MetaData(self.engine) |
197 | 228 |
198 for table in self._get_tables(missingA=True): | 229 for table in self._get_tables(missingA=True): |
199 table = table.tometadata(meta) | 230 table = table.tometadata(meta) |
249 connection.execute('DROP TABLE %s' % tempName) | 280 connection.execute('DROP TABLE %s' % tempName) |
250 trans.commit() | 281 trans.commit() |
251 except: | 282 except: |
252 trans.rollback() | 283 trans.rollback() |
253 raise | 284 raise |
285 |