Mercurial > kallithea
comparison rhodecode/lib/dbmigrate/migrate/versioning/repository.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 SQLAlchemy migrate repository management. | |
3 """ | |
4 import os | |
5 import shutil | |
6 import string | |
7 import logging | |
8 | |
9 from pkg_resources import resource_filename | |
10 from tempita import Template as TempitaTemplate | |
11 | |
12 from migrate import exceptions | |
13 from migrate.versioning import version, pathed, cfgparse | |
14 from migrate.versioning.template import Template | |
15 from migrate.versioning.config import * | |
16 | |
17 | |
18 log = logging.getLogger(__name__) | |
19 | |
20 class Changeset(dict): | |
21 """A collection of changes to be applied to a database. | |
22 | |
23 Changesets are bound to a repository and manage a set of | |
24 scripts from that repository. | |
25 | |
26 Behaves like a dict, for the most part. Keys are ordered based on step value. | |
27 """ | |
28 | |
29 def __init__(self, start, *changes, **k): | |
30 """ | |
31 Give a start version; step must be explicitly stated. | |
32 """ | |
33 self.step = k.pop('step', 1) | |
34 self.start = version.VerNum(start) | |
35 self.end = self.start | |
36 for change in changes: | |
37 self.add(change) | |
38 | |
39 def __iter__(self): | |
40 return iter(self.items()) | |
41 | |
42 def keys(self): | |
43 """ | |
44 In a series of upgrades x -> y, keys are version x. Sorted. | |
45 """ | |
46 ret = super(Changeset, self).keys() | |
47 # Reverse order if downgrading | |
48 ret.sort(reverse=(self.step < 1)) | |
49 return ret | |
50 | |
51 def values(self): | |
52 return [self[k] for k in self.keys()] | |
53 | |
54 def items(self): | |
55 return zip(self.keys(), self.values()) | |
56 | |
57 def add(self, change): | |
58 """Add new change to changeset""" | |
59 key = self.end | |
60 self.end += self.step | |
61 self[key] = change | |
62 | |
63 def run(self, *p, **k): | |
64 """Run the changeset scripts""" | |
65 for version, script in self: | |
66 script.run(*p, **k) | |
67 | |
68 | |
69 class Repository(pathed.Pathed): | |
70 """A project's change script repository""" | |
71 | |
72 _config = 'migrate.cfg' | |
73 _versions = 'versions' | |
74 | |
75 def __init__(self, path): | |
76 log.debug('Loading repository %s...' % path) | |
77 self.verify(path) | |
78 super(Repository, self).__init__(path) | |
79 self.config = cfgparse.Config(os.path.join(self.path, self._config)) | |
80 self.versions = version.Collection(os.path.join(self.path, | |
81 self._versions)) | |
82 log.debug('Repository %s loaded successfully' % path) | |
83 log.debug('Config: %r' % self.config.to_dict()) | |
84 | |
85 @classmethod | |
86 def verify(cls, path): | |
87 """ | |
88 Ensure the target path is a valid repository. | |
89 | |
90 :raises: :exc:`InvalidRepositoryError <migrate.exceptions.InvalidRepositoryError>` | |
91 """ | |
92 # Ensure the existence of required files | |
93 try: | |
94 cls.require_found(path) | |
95 cls.require_found(os.path.join(path, cls._config)) | |
96 cls.require_found(os.path.join(path, cls._versions)) | |
97 except exceptions.PathNotFoundError, e: | |
98 raise exceptions.InvalidRepositoryError(path) | |
99 | |
100 @classmethod | |
101 def prepare_config(cls, tmpl_dir, name, options=None): | |
102 """ | |
103 Prepare a project configuration file for a new project. | |
104 | |
105 :param tmpl_dir: Path to Repository template | |
106 :param config_file: Name of the config file in Repository template | |
107 :param name: Repository name | |
108 :type tmpl_dir: string | |
109 :type config_file: string | |
110 :type name: string | |
111 :returns: Populated config file | |
112 """ | |
113 if options is None: | |
114 options = {} | |
115 options.setdefault('version_table', 'migrate_version') | |
116 options.setdefault('repository_id', name) | |
117 options.setdefault('required_dbs', []) | |
118 | |
119 tmpl = open(os.path.join(tmpl_dir, cls._config)).read() | |
120 ret = TempitaTemplate(tmpl).substitute(options) | |
121 | |
122 # cleanup | |
123 del options['__template_name__'] | |
124 | |
125 return ret | |
126 | |
127 @classmethod | |
128 def create(cls, path, name, **opts): | |
129 """Create a repository at a specified path""" | |
130 cls.require_notfound(path) | |
131 theme = opts.pop('templates_theme', None) | |
132 t_path = opts.pop('templates_path', None) | |
133 | |
134 # Create repository | |
135 tmpl_dir = Template(t_path).get_repository(theme=theme) | |
136 shutil.copytree(tmpl_dir, path) | |
137 | |
138 # Edit config defaults | |
139 config_text = cls.prepare_config(tmpl_dir, name, options=opts) | |
140 fd = open(os.path.join(path, cls._config), 'w') | |
141 fd.write(config_text) | |
142 fd.close() | |
143 | |
144 opts['repository_name'] = name | |
145 | |
146 # Create a management script | |
147 manager = os.path.join(path, 'manage.py') | |
148 Repository.create_manage_file(manager, templates_theme=theme, | |
149 templates_path=t_path, **opts) | |
150 | |
151 return cls(path) | |
152 | |
153 def create_script(self, description, **k): | |
154 """API to :meth:`migrate.versioning.version.Collection.create_new_python_version`""" | |
155 self.versions.create_new_python_version(description, **k) | |
156 | |
157 def create_script_sql(self, database, **k): | |
158 """API to :meth:`migrate.versioning.version.Collection.create_new_sql_version`""" | |
159 self.versions.create_new_sql_version(database, **k) | |
160 | |
161 @property | |
162 def latest(self): | |
163 """API to :attr:`migrate.versioning.version.Collection.latest`""" | |
164 return self.versions.latest | |
165 | |
166 @property | |
167 def version_table(self): | |
168 """Returns version_table name specified in config""" | |
169 return self.config.get('db_settings', 'version_table') | |
170 | |
171 @property | |
172 def id(self): | |
173 """Returns repository id specified in config""" | |
174 return self.config.get('db_settings', 'repository_id') | |
175 | |
176 def version(self, *p, **k): | |
177 """API to :attr:`migrate.versioning.version.Collection.version`""" | |
178 return self.versions.version(*p, **k) | |
179 | |
180 @classmethod | |
181 def clear(cls): | |
182 # TODO: deletes repo | |
183 super(Repository, cls).clear() | |
184 version.Collection.clear() | |
185 | |
186 def changeset(self, database, start, end=None): | |
187 """Create a changeset to migrate this database from ver. start to end/latest. | |
188 | |
189 :param database: name of database to generate changeset | |
190 :param start: version to start at | |
191 :param end: version to end at (latest if None given) | |
192 :type database: string | |
193 :type start: int | |
194 :type end: int | |
195 :returns: :class:`Changeset instance <migration.versioning.repository.Changeset>` | |
196 """ | |
197 start = version.VerNum(start) | |
198 | |
199 if end is None: | |
200 end = self.latest | |
201 else: | |
202 end = version.VerNum(end) | |
203 | |
204 if start <= end: | |
205 step = 1 | |
206 range_mod = 1 | |
207 op = 'upgrade' | |
208 else: | |
209 step = -1 | |
210 range_mod = 0 | |
211 op = 'downgrade' | |
212 | |
213 versions = range(start + range_mod, end + range_mod, step) | |
214 changes = [self.version(v).script(database, op) for v in versions] | |
215 ret = Changeset(start, step=step, *changes) | |
216 return ret | |
217 | |
218 @classmethod | |
219 def create_manage_file(cls, file_, **opts): | |
220 """Create a project management script (manage.py) | |
221 | |
222 :param file_: Destination file to be written | |
223 :param opts: Options that are passed to :func:`migrate.versioning.shell.main` | |
224 """ | |
225 mng_file = Template(opts.pop('templates_path', None))\ | |
226 .get_manage(theme=opts.pop('templates_theme', None)) | |
227 | |
228 tmpl = open(mng_file).read() | |
229 fd = open(file_, 'w') | |
230 fd.write(TempitaTemplate(tmpl).substitute(opts)) | |
231 fd.close() |