Mercurial > kallithea
comparison rhodecode/lib/dbmigrate/migrate/versioning/api.py @ 833:9753e0907827 beta
added dbmigrate package, added model changes
moved out upgrade db command to that package
author | Marcin Kuzminski <marcin@python-works.com> |
---|---|
date | Sat, 11 Dec 2010 01:54:12 +0100 |
parents | |
children | 08d2dcd71666 |
comparison
equal
deleted
inserted
replaced
832:634596f81cfd | 833:9753e0907827 |
---|---|
1 """ | |
2 This module provides an external API to the versioning system. | |
3 | |
4 .. versionchanged:: 0.6.0 | |
5 :func:`migrate.versioning.api.test` and schema diff functions | |
6 changed order of positional arguments so all accept `url` and `repository` | |
7 as first arguments. | |
8 | |
9 .. versionchanged:: 0.5.4 | |
10 ``--preview_sql`` displays source file when using SQL scripts. | |
11 If Python script is used, it runs the action with mocked engine and | |
12 returns captured SQL statements. | |
13 | |
14 .. versionchanged:: 0.5.4 | |
15 Deprecated ``--echo`` parameter in favour of new | |
16 :func:`migrate.versioning.util.construct_engine` behavior. | |
17 """ | |
18 | |
19 # Dear migrate developers, | |
20 # | |
21 # please do not comment this module using sphinx syntax because its | |
22 # docstrings are presented as user help and most users cannot | |
23 # interpret sphinx annotated ReStructuredText. | |
24 # | |
25 # Thanks, | |
26 # Jan Dittberner | |
27 | |
28 import sys | |
29 import inspect | |
30 import logging | |
31 | |
32 from migrate import exceptions | |
33 from migrate.versioning import (repository, schema, version, | |
34 script as script_) # command name conflict | |
35 from migrate.versioning.util import catch_known_errors, with_engine | |
36 | |
37 | |
38 log = logging.getLogger(__name__) | |
39 command_desc = { | |
40 'help': 'displays help on a given command', | |
41 'create': 'create an empty repository at the specified path', | |
42 'script': 'create an empty change Python script', | |
43 'script_sql': 'create empty change SQL scripts for given database', | |
44 'version': 'display the latest version available in a repository', | |
45 'db_version': 'show the current version of the repository under version control', | |
46 'source': 'display the Python code for a particular version in this repository', | |
47 'version_control': 'mark a database as under this repository\'s version control', | |
48 'upgrade': 'upgrade a database to a later version', | |
49 'downgrade': 'downgrade a database to an earlier version', | |
50 'drop_version_control': 'removes version control from a database', | |
51 'manage': 'creates a Python script that runs Migrate with a set of default values', | |
52 'test': 'performs the upgrade and downgrade command on the given database', | |
53 'compare_model_to_db': 'compare MetaData against the current database state', | |
54 'create_model': 'dump the current database as a Python model to stdout', | |
55 'make_update_script_for_model': 'create a script changing the old MetaData to the new (current) MetaData', | |
56 'update_db_from_model': 'modify the database to match the structure of the current MetaData', | |
57 } | |
58 __all__ = command_desc.keys() | |
59 | |
60 Repository = repository.Repository | |
61 ControlledSchema = schema.ControlledSchema | |
62 VerNum = version.VerNum | |
63 PythonScript = script_.PythonScript | |
64 SqlScript = script_.SqlScript | |
65 | |
66 | |
67 # deprecated | |
68 def help(cmd=None, **opts): | |
69 """%prog help COMMAND | |
70 | |
71 Displays help on a given command. | |
72 """ | |
73 if cmd is None: | |
74 raise exceptions.UsageError(None) | |
75 try: | |
76 func = globals()[cmd] | |
77 except: | |
78 raise exceptions.UsageError( | |
79 "'%s' isn't a valid command. Try 'help COMMAND'" % cmd) | |
80 ret = func.__doc__ | |
81 if sys.argv[0]: | |
82 ret = ret.replace('%prog', sys.argv[0]) | |
83 return ret | |
84 | |
85 @catch_known_errors | |
86 def create(repository, name, **opts): | |
87 """%prog create REPOSITORY_PATH NAME [--table=TABLE] | |
88 | |
89 Create an empty repository at the specified path. | |
90 | |
91 You can specify the version_table to be used; by default, it is | |
92 'migrate_version'. This table is created in all version-controlled | |
93 databases. | |
94 """ | |
95 repo_path = Repository.create(repository, name, **opts) | |
96 | |
97 | |
98 @catch_known_errors | |
99 def script(description, repository, **opts): | |
100 """%prog script DESCRIPTION REPOSITORY_PATH | |
101 | |
102 Create an empty change script using the next unused version number | |
103 appended with the given description. | |
104 | |
105 For instance, manage.py script "Add initial tables" creates: | |
106 repository/versions/001_Add_initial_tables.py | |
107 """ | |
108 repo = Repository(repository) | |
109 repo.create_script(description, **opts) | |
110 | |
111 | |
112 @catch_known_errors | |
113 def script_sql(database, repository, **opts): | |
114 """%prog script_sql DATABASE REPOSITORY_PATH | |
115 | |
116 Create empty change SQL scripts for given DATABASE, where DATABASE | |
117 is either specific ('postgres', 'mysql', 'oracle', 'sqlite', etc.) | |
118 or generic ('default'). | |
119 | |
120 For instance, manage.py script_sql postgres creates: | |
121 repository/versions/001_postgres_upgrade.sql and | |
122 repository/versions/001_postgres_postgres.sql | |
123 """ | |
124 repo = Repository(repository) | |
125 repo.create_script_sql(database, **opts) | |
126 | |
127 | |
128 def version(repository, **opts): | |
129 """%prog version REPOSITORY_PATH | |
130 | |
131 Display the latest version available in a repository. | |
132 """ | |
133 repo = Repository(repository) | |
134 return repo.latest | |
135 | |
136 | |
137 @with_engine | |
138 def db_version(url, repository, **opts): | |
139 """%prog db_version URL REPOSITORY_PATH | |
140 | |
141 Show the current version of the repository with the given | |
142 connection string, under version control of the specified | |
143 repository. | |
144 | |
145 The url should be any valid SQLAlchemy connection string. | |
146 """ | |
147 engine = opts.pop('engine') | |
148 schema = ControlledSchema(engine, repository) | |
149 return schema.version | |
150 | |
151 | |
152 def source(version, dest=None, repository=None, **opts): | |
153 """%prog source VERSION [DESTINATION] --repository=REPOSITORY_PATH | |
154 | |
155 Display the Python code for a particular version in this | |
156 repository. Save it to the file at DESTINATION or, if omitted, | |
157 send to stdout. | |
158 """ | |
159 if repository is None: | |
160 raise exceptions.UsageError("A repository must be specified") | |
161 repo = Repository(repository) | |
162 ret = repo.version(version).script().source() | |
163 if dest is not None: | |
164 dest = open(dest, 'w') | |
165 dest.write(ret) | |
166 dest.close() | |
167 ret = None | |
168 return ret | |
169 | |
170 | |
171 def upgrade(url, repository, version=None, **opts): | |
172 """%prog upgrade URL REPOSITORY_PATH [VERSION] [--preview_py|--preview_sql] | |
173 | |
174 Upgrade a database to a later version. | |
175 | |
176 This runs the upgrade() function defined in your change scripts. | |
177 | |
178 By default, the database is updated to the latest available | |
179 version. You may specify a version instead, if you wish. | |
180 | |
181 You may preview the Python or SQL code to be executed, rather than | |
182 actually executing it, using the appropriate 'preview' option. | |
183 """ | |
184 err = "Cannot upgrade a database of version %s to version %s. "\ | |
185 "Try 'downgrade' instead." | |
186 return _migrate(url, repository, version, upgrade=True, err=err, **opts) | |
187 | |
188 | |
189 def downgrade(url, repository, version, **opts): | |
190 """%prog downgrade URL REPOSITORY_PATH VERSION [--preview_py|--preview_sql] | |
191 | |
192 Downgrade a database to an earlier version. | |
193 | |
194 This is the reverse of upgrade; this runs the downgrade() function | |
195 defined in your change scripts. | |
196 | |
197 You may preview the Python or SQL code to be executed, rather than | |
198 actually executing it, using the appropriate 'preview' option. | |
199 """ | |
200 err = "Cannot downgrade a database of version %s to version %s. "\ | |
201 "Try 'upgrade' instead." | |
202 return _migrate(url, repository, version, upgrade=False, err=err, **opts) | |
203 | |
204 @with_engine | |
205 def test(url, repository, **opts): | |
206 """%prog test URL REPOSITORY_PATH [VERSION] | |
207 | |
208 Performs the upgrade and downgrade option on the given | |
209 database. This is not a real test and may leave the database in a | |
210 bad state. You should therefore better run the test on a copy of | |
211 your database. | |
212 """ | |
213 engine = opts.pop('engine') | |
214 repos = Repository(repository) | |
215 script = repos.version(None).script() | |
216 | |
217 # Upgrade | |
218 log.info("Upgrading...") | |
219 script.run(engine, 1) | |
220 log.info("done") | |
221 | |
222 log.info("Downgrading...") | |
223 script.run(engine, -1) | |
224 log.info("done") | |
225 log.info("Success") | |
226 | |
227 | |
228 @with_engine | |
229 def version_control(url, repository, version=None, **opts): | |
230 """%prog version_control URL REPOSITORY_PATH [VERSION] | |
231 | |
232 Mark a database as under this repository's version control. | |
233 | |
234 Once a database is under version control, schema changes should | |
235 only be done via change scripts in this repository. | |
236 | |
237 This creates the table version_table in the database. | |
238 | |
239 The url should be any valid SQLAlchemy connection string. | |
240 | |
241 By default, the database begins at version 0 and is assumed to be | |
242 empty. If the database is not empty, you may specify a version at | |
243 which to begin instead. No attempt is made to verify this | |
244 version's correctness - the database schema is expected to be | |
245 identical to what it would be if the database were created from | |
246 scratch. | |
247 """ | |
248 engine = opts.pop('engine') | |
249 ControlledSchema.create(engine, repository, version) | |
250 | |
251 | |
252 @with_engine | |
253 def drop_version_control(url, repository, **opts): | |
254 """%prog drop_version_control URL REPOSITORY_PATH | |
255 | |
256 Removes version control from a database. | |
257 """ | |
258 engine = opts.pop('engine') | |
259 schema = ControlledSchema(engine, repository) | |
260 schema.drop() | |
261 | |
262 | |
263 def manage(file, **opts): | |
264 """%prog manage FILENAME [VARIABLES...] | |
265 | |
266 Creates a script that runs Migrate with a set of default values. | |
267 | |
268 For example:: | |
269 | |
270 %prog manage manage.py --repository=/path/to/repository \ | |
271 --url=sqlite:///project.db | |
272 | |
273 would create the script manage.py. The following two commands | |
274 would then have exactly the same results:: | |
275 | |
276 python manage.py version | |
277 %prog version --repository=/path/to/repository | |
278 """ | |
279 Repository.create_manage_file(file, **opts) | |
280 | |
281 | |
282 @with_engine | |
283 def compare_model_to_db(url, repository, model, **opts): | |
284 """%prog compare_model_to_db URL REPOSITORY_PATH MODEL | |
285 | |
286 Compare the current model (assumed to be a module level variable | |
287 of type sqlalchemy.MetaData) against the current database. | |
288 | |
289 NOTE: This is EXPERIMENTAL. | |
290 """ # TODO: get rid of EXPERIMENTAL label | |
291 engine = opts.pop('engine') | |
292 return ControlledSchema.compare_model_to_db(engine, model, repository) | |
293 | |
294 | |
295 @with_engine | |
296 def create_model(url, repository, **opts): | |
297 """%prog create_model URL REPOSITORY_PATH [DECLERATIVE=True] | |
298 | |
299 Dump the current database as a Python model to stdout. | |
300 | |
301 NOTE: This is EXPERIMENTAL. | |
302 """ # TODO: get rid of EXPERIMENTAL label | |
303 engine = opts.pop('engine') | |
304 declarative = opts.get('declarative', False) | |
305 return ControlledSchema.create_model(engine, repository, declarative) | |
306 | |
307 | |
308 @catch_known_errors | |
309 @with_engine | |
310 def make_update_script_for_model(url, repository, oldmodel, model, **opts): | |
311 """%prog make_update_script_for_model URL OLDMODEL MODEL REPOSITORY_PATH | |
312 | |
313 Create a script changing the old Python model to the new (current) | |
314 Python model, sending to stdout. | |
315 | |
316 NOTE: This is EXPERIMENTAL. | |
317 """ # TODO: get rid of EXPERIMENTAL label | |
318 engine = opts.pop('engine') | |
319 return PythonScript.make_update_script_for_model( | |
320 engine, oldmodel, model, repository, **opts) | |
321 | |
322 | |
323 @with_engine | |
324 def update_db_from_model(url, repository, model, **opts): | |
325 """%prog update_db_from_model URL REPOSITORY_PATH MODEL | |
326 | |
327 Modify the database to match the structure of the current Python | |
328 model. This also sets the db_version number to the latest in the | |
329 repository. | |
330 | |
331 NOTE: This is EXPERIMENTAL. | |
332 """ # TODO: get rid of EXPERIMENTAL label | |
333 engine = opts.pop('engine') | |
334 schema = ControlledSchema(engine, repository) | |
335 schema.update_db_from_model(model) | |
336 | |
337 @with_engine | |
338 def _migrate(url, repository, version, upgrade, err, **opts): | |
339 engine = opts.pop('engine') | |
340 url = str(engine.url) | |
341 schema = ControlledSchema(engine, repository) | |
342 version = _migrate_version(schema, version, upgrade, err) | |
343 | |
344 changeset = schema.changeset(version) | |
345 for ver, change in changeset: | |
346 nextver = ver + changeset.step | |
347 log.info('%s -> %s... ', ver, nextver) | |
348 | |
349 if opts.get('preview_sql'): | |
350 if isinstance(change, PythonScript): | |
351 log.info(change.preview_sql(url, changeset.step, **opts)) | |
352 elif isinstance(change, SqlScript): | |
353 log.info(change.source()) | |
354 | |
355 elif opts.get('preview_py'): | |
356 if not isinstance(change, PythonScript): | |
357 raise exceptions.UsageError("Python source can be only displayed" | |
358 " for python migration files") | |
359 source_ver = max(ver, nextver) | |
360 module = schema.repository.version(source_ver).script().module | |
361 funcname = upgrade and "upgrade" or "downgrade" | |
362 func = getattr(module, funcname) | |
363 log.info(inspect.getsource(func)) | |
364 else: | |
365 schema.runchange(ver, change, changeset.step) | |
366 log.info('done') | |
367 | |
368 | |
369 def _migrate_version(schema, version, upgrade, err): | |
370 if version is None: | |
371 return version | |
372 # Version is specified: ensure we're upgrading in the right direction | |
373 # (current version < target version for upgrading; reverse for down) | |
374 version = VerNum(version) | |
375 cur = schema.version | |
376 if upgrade is not None: | |
377 if upgrade: | |
378 direction = cur <= version | |
379 else: | |
380 direction = cur >= version | |
381 if not direction: | |
382 raise exceptions.KnownError(err % (cur, version)) | |
383 return version |