2
2
# -*- coding: utf-8 -*-
5
7
from StringIO import StringIO
8
10
from migrate.versioning import exceptions, genmodel, schemadiff
9
from migrate.versioning.base import operations
10
from migrate.versioning.template import template
11
from migrate.versioning.config import operations
12
from migrate.versioning.template import Template
11
13
from migrate.versioning.script import base
12
from migrate.versioning.util import import_path, load_model, construct_engine
14
from migrate.versioning.util import import_path, load_model, with_engine
17
log = logging.getLogger(__name__)
18
__all__ = ['PythonScript']
14
20
class PythonScript(base.BaseScript):
21
"""Base for Python scripts"""
17
24
def create(cls, path, **opts):
18
"""Create an empty migration script"""
25
"""Create an empty migration script at specified path
27
:returns: :class:`PythonScript instance <migrate.versioning.script.py.PythonScript>`"""
19
28
cls.require_notfound(path)
21
# TODO: Use the default script template (defined in the template
22
# module) for now, but we might want to allow people to specify a
23
# different one later.
25
src = template.get_script(template_file)
30
src = Template(opts.pop('templates_path', None)).get_script(theme=opts.pop('templates_theme', None))
26
31
shutil.copy(src, path)
29
36
def make_update_script_for_model(cls, engine, oldmodel,
30
37
model, repository, **opts):
31
"""Create a migration script"""
33
# Compute differences.
38
"""Create a migration script based on difference between two SA models.
40
:param repository: path to migrate repository
41
:param oldmodel: dotted.module.name:SAClass or SAClass object
42
:param model: dotted.module.name:SAClass or SAClass object
43
:param engine: SQLAlchemy engine
44
:type repository: string or :class:`Repository instance <migrate.versioning.repository.Repository>`
45
:type oldmodel: string or Class
46
:type model: string or Class
47
:type engine: Engine instance
48
:returns: Upgrade / Downgrade script
34
52
if isinstance(repository, basestring):
35
53
# oh dear, an import cycle!
36
54
from migrate.versioning.repository import Repository
37
55
repository = Repository(repository)
38
57
oldmodel = load_model(oldmodel)
39
58
model = load_model(model)
60
# Compute differences.
40
61
diff = schemadiff.getDiffOfModelAgainstModel(
44
65
excludeTables=[repository.version_table])
66
# TODO: diff can be False (there is no difference?)
45
67
decls, upgradeCommands, downgradeCommands = \
46
68
genmodel.ModelGenerator(diff).toUpgradeDowngradePython()
48
70
# Store differences into file.
50
src = template.get_script(template_file)
51
contents = open(src).read()
52
search = 'def upgrade():'
71
src = Template(opts.pop('templates_path', None)).get_script(opts.pop('templates_theme', None))
77
search = 'def upgrade(migrate_engine):'
53
78
contents = contents.replace(search, '\n\n'.join((decls, search)), 1)
54
79
if upgradeCommands:
55
80
contents = contents.replace(' pass', upgradeCommands, 1)
61
def verify_module(cls,path):
62
"""Ensure this is a valid script, or raise InvalidScriptError"""
86
def verify_module(cls, path):
87
"""Ensure path is a valid script
89
:param path: Script location
91
:raises: :exc:`InvalidScriptError <migrate.versioning.exceptions.InvalidScriptError>`
92
:returns: Python module
63
94
# Try to import and get the upgrade() func
65
module=import_path(path)
67
# If the script itself has errors, that's not our problem
95
module = import_path(path)
70
97
assert callable(module.upgrade)
71
98
except Exception, e:
75
102
def preview_sql(self, url, step, **args):
76
"""Mock engine to store all executable calls in a string \
77
and execute the step"""
103
"""Mocks SQLAlchemy Engine to store all executed calls in a string
104
and runs :meth:`PythonScript.run <migrate.versioning.script.py.PythonScript.run>`
79
109
args['engine_arg_strategy'] = 'mock'
80
args['engine_arg_executor'] = lambda s, p='': buf.write(s + p)
81
engine = construct_engine(url, **args)
83
self.run(engine, step)
110
args['engine_arg_executor'] = lambda s, p = '': buf.write(str(s) + p)
113
def go(url, step, **kw):
114
engine = kw.pop('engine')
115
self.run(engine, step)
116
return buf.getvalue()
118
return go(url, step, **args)
87
120
def run(self, engine, step):
88
"""Core method of Script file. \
89
Exectues update() or downgrade() function"""
121
"""Core method of Script file.
122
Exectues :func:`update` or :func:`downgrade` functions
124
:param engine: SQLAlchemy Engine
125
:param step: Operation to run
95
134
raise exceptions.ScriptError("%d is not a valid step" % step)
96
136
funcname = base.operations[op]
98
migrate.migrate_engine = engine
99
#migrate.run.migrate_engine = migrate.migrate_engine = engine
100
func = self._func(funcname)
102
migrate.migrate_engine = None
103
#migrate.run.migrate_engine = migrate.migrate_engine = None
137
script_func = self._func(funcname)
142
warnings.warn("upgrade/downgrade functions must accept engine"
143
" parameter (since version > 0.5.4)", exceptions.MigrateDeprecationWarning)
106
147
def module(self):
107
if not hasattr(self,'_module'):
148
"""Calls :meth:`migrate.versioning.script.py.verify_module`
151
if not hasattr(self, '_module'):
108
152
self._module = self.verify_module(self.path)
109
153
return self._module
111
155
def _func(self, funcname):
112
fn = getattr(self.module, funcname, None)
114
msg = "The function %s is not defined in this script"
115
raise exceptions.ScriptError(msg%funcname)
156
if not hasattr(self.module, funcname):
157
msg = "Function '%s' is not defined in this script"
158
raise exceptions.ScriptError(msg % funcname)
159
return getattr(self.module, funcname)