~ubuntu-branches/ubuntu/saucy/migrate/saucy-proposed

« back to all changes in this revision

Viewing changes to migrate/versioning/script/py.py

  • Committer: Bazaar Package Importer
  • Author(s): Jan Dittberner
  • Date: 2010-07-12 00:24:57 UTC
  • mfrom: (1.1.5 upstream) (2.1.8 sid)
  • Revision ID: james.westby@ubuntu.com-20100712002457-4j2fdmco4u9kqzm5
Upload to unstable.

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
# -*- coding: utf-8 -*-
3
3
 
4
4
import shutil
 
5
import warnings
 
6
import logging
5
7
from StringIO import StringIO
6
8
 
7
9
import migrate
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
 
15
 
 
16
 
 
17
log = logging.getLogger(__name__)
 
18
__all__ = ['PythonScript']
13
19
 
14
20
class PythonScript(base.BaseScript):
 
21
    """Base for Python scripts"""
15
22
 
16
23
    @classmethod
17
24
    def create(cls, path, **opts):
18
 
        """Create an empty migration script"""
 
25
        """Create an empty migration script at specified path
 
26
        
 
27
        :returns: :class:`PythonScript instance <migrate.versioning.script.py.PythonScript>`"""
19
28
        cls.require_notfound(path)
20
29
 
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.
24
 
        template_file = None
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)
27
32
 
 
33
        return cls(path)
 
34
 
28
35
    @classmethod
29
36
    def make_update_script_for_model(cls, engine, oldmodel,
30
37
                                     model, repository, **opts):
31
 
        """Create a migration script"""
32
 
        
33
 
        # Compute differences.
 
38
        """Create a migration script based on difference between two SA models.
 
39
        
 
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
 
49
        :rtype: string
 
50
        """
 
51
        
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)
 
56
 
38
57
        oldmodel = load_model(oldmodel)
39
58
        model = load_model(model)
 
59
 
 
60
        # Compute differences.
40
61
        diff = schemadiff.getDiffOfModelAgainstModel(
41
62
            oldmodel,
42
63
            model,
43
64
            engine,
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()
47
69
 
48
70
        # Store differences into file.
49
 
        template_file = None
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))
 
72
        f = open(src)
 
73
        contents = f.read()
 
74
        f.close()
 
75
 
 
76
        # generate source
 
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)
58
83
        return contents
59
84
 
60
85
    @classmethod
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
 
88
        
 
89
        :param path: Script location
 
90
        :type path: string
 
91
        :raises: :exc:`InvalidScriptError <migrate.versioning.exceptions.InvalidScriptError>`
 
92
        :returns: Python module
 
93
        """
63
94
        # Try to import and get the upgrade() func
64
 
        try:
65
 
            module=import_path(path)
66
 
        except:
67
 
            # If the script itself has errors, that's not our problem
68
 
            raise
 
95
        module = import_path(path)
69
96
        try:
70
97
            assert callable(module.upgrade)
71
98
        except Exception, e:
73
100
        return module
74
101
 
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>`
 
105
 
 
106
        :returns: SQL file
 
107
        """
78
108
        buf = StringIO()
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)
82
 
 
83
 
        self.run(engine, step)
84
 
 
85
 
        return buf.getvalue()
86
 
            
 
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
 
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
 
123
 
 
124
        :param engine: SQLAlchemy Engine
 
125
        :param step: Operation to run
 
126
        :type engine: string
 
127
        :type step: int
 
128
        """
90
129
        if step > 0:
91
130
            op = 'upgrade'
92
131
        elif step < 0:
93
132
            op = 'downgrade'
94
133
        else:
95
134
            raise exceptions.ScriptError("%d is not a valid step" % step)
 
135
 
96
136
        funcname = base.operations[op]
97
 
        
98
 
        migrate.migrate_engine = engine
99
 
        #migrate.run.migrate_engine = migrate.migrate_engine = engine
100
 
        func = self._func(funcname)
101
 
        func()
102
 
        migrate.migrate_engine = None
103
 
        #migrate.run.migrate_engine = migrate.migrate_engine = None
 
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)", exceptions.MigrateDeprecationWarning)
 
144
            raise
104
145
 
105
146
    @property
106
147
    def module(self):
107
 
        if not hasattr(self,'_module'):
 
148
        """Calls :meth:`migrate.versioning.script.py.verify_module`
 
149
        and returns it.
 
150
        """
 
151
        if not hasattr(self, '_module'):
108
152
            self._module = self.verify_module(self.path)
109
153
        return self._module
110
154
 
111
155
    def _func(self, funcname):
112
 
        fn = getattr(self.module, funcname, None)
113
 
        if not fn:
114
 
            msg = "The function %s is not defined in this script"
115
 
            raise exceptions.ScriptError(msg%funcname)
116
 
        return fn
 
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)