comparison rhodecode/lib/dbmigrate/migrate/versioning/script/py.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 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 import shutil
5 import warnings
6 import logging
7 from StringIO import StringIO
8
9 import migrate
10 from migrate.versioning import genmodel, schemadiff
11 from migrate.versioning.config import operations
12 from migrate.versioning.template import Template
13 from migrate.versioning.script import base
14 from migrate.versioning.util import import_path, load_model, with_engine
15 from migrate.exceptions import MigrateDeprecationWarning, InvalidScriptError, ScriptError
16
17 log = logging.getLogger(__name__)
18 __all__ = ['PythonScript']
19
20
21 class PythonScript(base.BaseScript):
22 """Base for Python scripts"""
23
24 @classmethod
25 def create(cls, path, **opts):
26 """Create an empty migration script at specified path
27
28 :returns: :class:`PythonScript instance <migrate.versioning.script.py.PythonScript>`"""
29 cls.require_notfound(path)
30
31 src = Template(opts.pop('templates_path', None)).get_script(theme=opts.pop('templates_theme', None))
32 shutil.copy(src, path)
33
34 return cls(path)
35
36 @classmethod
37 def make_update_script_for_model(cls, engine, oldmodel,
38 model, repository, **opts):
39 """Create a migration script based on difference between two SA models.
40
41 :param repository: path to migrate repository
42 :param oldmodel: dotted.module.name:SAClass or SAClass object
43 :param model: dotted.module.name:SAClass or SAClass object
44 :param engine: SQLAlchemy engine
45 :type repository: string or :class:`Repository instance <migrate.versioning.repository.Repository>`
46 :type oldmodel: string or Class
47 :type model: string or Class
48 :type engine: Engine instance
49 :returns: Upgrade / Downgrade script
50 :rtype: string
51 """
52
53 if isinstance(repository, basestring):
54 # oh dear, an import cycle!
55 from migrate.versioning.repository import Repository
56 repository = Repository(repository)
57
58 oldmodel = load_model(oldmodel)
59 model = load_model(model)
60
61 # Compute differences.
62 diff = schemadiff.getDiffOfModelAgainstModel(
63 oldmodel,
64 model,
65 excludeTables=[repository.version_table])
66 # TODO: diff can be False (there is no difference?)
67 decls, upgradeCommands, downgradeCommands = \
68 genmodel.ModelGenerator(diff,engine).toUpgradeDowngradePython()
69
70 # Store differences into file.
71 src = Template(opts.pop('templates_path', None)).get_script(opts.pop('templates_theme', None))
72 f = open(src)
73 contents = f.read()
74 f.close()
75
76 # generate source
77 search = 'def upgrade(migrate_engine):'
78 contents = contents.replace(search, '\n\n'.join((decls, search)), 1)
79 if upgradeCommands:
80 contents = contents.replace(' pass', upgradeCommands, 1)
81 if downgradeCommands:
82 contents = contents.replace(' pass', downgradeCommands, 1)
83 return contents
84
85 @classmethod
86 def verify_module(cls, path):
87 """Ensure path is a valid script
88
89 :param path: Script location
90 :type path: string
91 :raises: :exc:`InvalidScriptError <migrate.exceptions.InvalidScriptError>`
92 :returns: Python module
93 """
94 # Try to import and get the upgrade() func
95 module = import_path(path)
96 try:
97 assert callable(module.upgrade)
98 except Exception, e:
99 raise InvalidScriptError(path + ': %s' % str(e))
100 return module
101
102 def preview_sql(self, url, step, **args):
103 """Mocks SQLAlchemy Engine to store all executed calls in a string
104 and runs :meth:`PythonScript.run <migrate.versioning.script.py.PythonScript.run>`
105
106 :returns: SQL file
107 """
108 buf = StringIO()
109 args['engine_arg_strategy'] = 'mock'
110 args['engine_arg_executor'] = lambda s, p = '': buf.write(str(s) + p)
111
112 @with_engine
113 def go(url, step, **kw):
114 engine = kw.pop('engine')
115 self.run(engine, step)
116 return buf.getvalue()
117
118 return go(url, step, **args)
119
120 def run(self, engine, step):
121 """Core method of Script file.
122 Exectues :func:`update` or :func:`downgrade` functions
123
124 :param engine: SQLAlchemy Engine
125 :param step: Operation to run
126 :type engine: string
127 :type step: int
128 """
129 if step > 0:
130 op = 'upgrade'
131 elif step < 0:
132 op = 'downgrade'
133 else:
134 raise ScriptError("%d is not a valid step" % step)
135
136 funcname = base.operations[op]
137 script_func = self._func(funcname)
138
139 try:
140 script_func(engine)
141 except TypeError:
142 warnings.warn("upgrade/downgrade functions must accept engine"
143 " parameter (since version > 0.5.4)", MigrateDeprecationWarning)
144 raise
145
146 @property
147 def module(self):
148 """Calls :meth:`migrate.versioning.script.py.verify_module`
149 and returns it.
150 """
151 if not hasattr(self, '_module'):
152 self._module = self.verify_module(self.path)
153 return self._module
154
155 def _func(self, funcname):
156 if not hasattr(self.module, funcname):
157 msg = "Function '%s' is not defined in this script"
158 raise ScriptError(msg % funcname)
159 return getattr(self.module, funcname)