PageRenderTime 42ms CodeModel.GetById 15ms app.highlight 24ms RepoModel.GetById 0ms app.codeStats 0ms

/bangkokhotel/lib/python2.5/site-packages/south/migration/base.py

https://bitbucket.org/luisrodriguez/bangkokhotel
Python | 439 lines | 332 code | 40 blank | 67 comment | 46 complexity | 62c69efc9694a55e0e6ae27a1840eecc MD5 | raw file
  1from collections import deque
  2import datetime
  3import os
  4import re
  5import sys
  6
  7from django.core.exceptions import ImproperlyConfigured
  8from django.db import models
  9from django.conf import settings
 10from django.utils import importlib
 11
 12from south import exceptions
 13from south.migration.utils import depends, dfs, flatten, get_app_label
 14from south.orm import FakeORM
 15from south.utils import memoize, ask_for_it_by_name, datetime_utils
 16from south.migration.utils import app_label_to_app_module
 17
 18
 19def all_migrations(applications=None):
 20    """
 21    Returns all Migrations for all `applications` that are migrated.
 22    """
 23    if applications is None:
 24        applications = models.get_apps()
 25    for model_module in applications:
 26        # The app they've passed is the models module - go up one level
 27        app_path = ".".join(model_module.__name__.split(".")[:-1])
 28        app = ask_for_it_by_name(app_path)
 29        try:
 30            yield Migrations(app)
 31        except exceptions.NoMigrations:
 32            pass
 33
 34
 35def application_to_app_label(application):
 36    "Works out the app label from either the app label, the app name, or the module"
 37    if isinstance(application, basestring):
 38        app_label = application.split('.')[-1]
 39    else:
 40        app_label = application.__name__.split('.')[-1]
 41    return app_label
 42
 43
 44class MigrationsMetaclass(type):
 45    
 46    """
 47    Metaclass which ensures there is only one instance of a Migrations for
 48    any given app.
 49    """
 50    
 51    def __init__(self, name, bases, dict):
 52        super(MigrationsMetaclass, self).__init__(name, bases, dict)
 53        self.instances = {}
 54    
 55    def __call__(self, application, **kwds):
 56        
 57        app_label = application_to_app_label(application)
 58        
 59        # If we don't already have an instance, make one
 60        if app_label not in self.instances:
 61            self.instances[app_label] = super(MigrationsMetaclass, self).__call__(app_label_to_app_module(app_label), **kwds)
 62        
 63        return self.instances[app_label]
 64
 65    def _clear_cache(self):
 66        "Clears the cache of Migration objects."
 67        self.instances = {}
 68
 69
 70class Migrations(list):
 71    """
 72    Holds a list of Migration objects for a particular app.
 73    """
 74    
 75    __metaclass__ = MigrationsMetaclass
 76    
 77    if getattr(settings, "SOUTH_USE_PYC", False):
 78        MIGRATION_FILENAME = re.compile(r'(?!__init__)' # Don't match __init__.py
 79                                        r'[0-9a-zA-Z_]*' # Don't match dotfiles, or names with dots/invalid chars in them
 80                                        r'(\.pyc?)?$')     # Match .py or .pyc files, or module dirs
 81    else:
 82        MIGRATION_FILENAME = re.compile(r'(?!__init__)' # Don't match __init__.py
 83                                        r'[0-9a-zA-Z_]*' # Don't match dotfiles, or names with dots/invalid chars in them
 84                                        r'(\.py)?$')       # Match only .py files, or module dirs
 85
 86    def __init__(self, application, force_creation=False, verbose_creation=True):
 87        "Constructor. Takes the module of the app, NOT its models (like get_app returns)"
 88        self._cache = {}
 89        self.set_application(application, force_creation, verbose_creation)
 90    
 91    def create_migrations_directory(self, verbose=True):
 92        "Given an application, ensures that the migrations directory is ready."
 93        migrations_dir = self.migrations_dir()
 94        # Make the directory if it's not already there
 95        if not os.path.isdir(migrations_dir):
 96            if verbose:
 97                print "Creating migrations directory at '%s'..." % migrations_dir
 98            os.mkdir(migrations_dir)
 99        # Same for __init__.py
100        init_path = os.path.join(migrations_dir, "__init__.py")
101        if not os.path.isfile(init_path):
102            # Touch the init py file
103            if verbose:
104                print "Creating __init__.py in '%s'..." % migrations_dir
105            open(init_path, "w").close()
106    
107    def migrations_dir(self):
108        """
109        Returns the full path of the migrations directory.
110        If it doesn't exist yet, returns where it would exist, based on the
111        app's migrations module (defaults to app.migrations)
112        """
113        module_path = self.migrations_module()
114        try:
115            module = importlib.import_module(module_path)
116        except ImportError:
117            # There's no migrations module made yet; guess!
118            try:
119                parent = importlib.import_module(".".join(module_path.split(".")[:-1]))
120            except ImportError:
121                # The parent doesn't even exist, that's an issue.
122                raise exceptions.InvalidMigrationModule(
123                    application = self.application.__name__,
124                    module = module_path,
125                )
126            else:
127                # Good guess.
128                return os.path.join(os.path.dirname(parent.__file__), module_path.split(".")[-1])
129        else:
130            # Get directory directly
131            return os.path.dirname(module.__file__)
132    
133    def migrations_module(self):
134        "Returns the module name of the migrations module for this"
135        app_label = application_to_app_label(self.application)
136        if hasattr(settings, "SOUTH_MIGRATION_MODULES"):
137            if app_label in settings.SOUTH_MIGRATION_MODULES:
138                # There's an override.
139                return settings.SOUTH_MIGRATION_MODULES[app_label]
140        return self._application.__name__ + '.migrations'
141
142    def get_application(self):
143        return self._application
144
145    def set_application(self, application, force_creation=False, verbose_creation=True):
146        """
147        Called when the application for this Migrations is set.
148        Imports the migrations module object, and throws a paddy if it can't.
149        """
150        self._application = application
151        if not hasattr(application, 'migrations'):
152            try:
153                module = importlib.import_module(self.migrations_module())
154                self._migrations = application.migrations = module
155            except ImportError:
156                if force_creation:
157                    self.create_migrations_directory(verbose_creation)
158                    module = importlib.import_module(self.migrations_module())
159                    self._migrations = application.migrations = module
160                else:
161                    raise exceptions.NoMigrations(application)
162        self._load_migrations_module(application.migrations)
163
164    application = property(get_application, set_application)
165
166    def _load_migrations_module(self, module):
167        self._migrations = module
168        filenames = []
169        dirname = self.migrations_dir()
170        for f in os.listdir(dirname):
171            if self.MIGRATION_FILENAME.match(os.path.basename(f)):
172                full_path = os.path.join(dirname, f)
173                # If it's a .pyc file, only append if the .py isn't already around
174                if f.endswith(".pyc") and (os.path.isfile(full_path[:-1])):
175                    continue
176                # If it's a module directory, only append if it contains __init__.py[c].
177                if os.path.isdir(full_path):
178                    if not (os.path.isfile(os.path.join(full_path, "__init__.py")) or \
179                      (getattr(settings, "SOUTH_USE_PYC", False) and \
180                      os.path.isfile(os.path.join(full_path, "__init__.pyc")))):
181                        continue
182                filenames.append(f)
183        filenames.sort()
184        self.extend(self.migration(f) for f in filenames)
185
186    def migration(self, filename):
187        name = Migration.strip_filename(filename)
188        if name not in self._cache:
189            self._cache[name] = Migration(self, name)
190        return self._cache[name]
191
192    def __getitem__(self, value):
193        if isinstance(value, basestring):
194            return self.migration(value)
195        return super(Migrations, self).__getitem__(value)
196
197    def _guess_migration(self, prefix):
198        prefix = Migration.strip_filename(prefix)
199        matches = [m for m in self if m.name().startswith(prefix)]
200        if len(matches) == 1:
201            return matches[0]
202        elif len(matches) > 1:
203            raise exceptions.MultiplePrefixMatches(prefix, matches)
204        else:
205            raise exceptions.UnknownMigration(prefix, None)
206
207    def guess_migration(self, target_name):
208        if target_name == 'zero' or not self:
209            return
210        elif target_name is None:
211            return self[-1]
212        else:
213            return self._guess_migration(prefix=target_name)
214    
215    def app_label(self):
216        return self._application.__name__.split('.')[-1]
217
218    def full_name(self):
219        return self._migrations.__name__
220
221    @classmethod
222    def calculate_dependencies(cls, force=False):
223        "Goes through all the migrations, and works out the dependencies."
224        if getattr(cls, "_dependencies_done", False) and not force:
225            return
226        for migrations in all_migrations():
227            for migration in migrations:
228                migration.calculate_dependencies()
229        cls._dependencies_done = True
230    
231    @staticmethod
232    def invalidate_all_modules():
233        "Goes through all the migrations, and invalidates all cached modules."
234        for migrations in all_migrations():
235            for migration in migrations:
236                migration.invalidate_module()
237    
238    def next_filename(self, name):
239        "Returns the fully-formatted filename of what a new migration 'name' would be"
240        highest_number = 0
241        for migration in self:
242            try:
243                number = int(migration.name().split("_")[0])
244                highest_number = max(highest_number, number)
245            except ValueError:
246                pass
247        # Work out the new filename
248        return "%04i_%s.py" % (
249            highest_number + 1,
250            name,
251        )
252
253
254class Migration(object):
255    
256    """
257    Class which represents a particular migration file on-disk.
258    """
259    
260    def __init__(self, migrations, filename):
261        """
262        Returns the migration class implied by 'filename'.
263        """
264        self.migrations = migrations
265        self.filename = filename
266        self.dependencies = set()
267        self.dependents = set()
268
269    def __str__(self):
270        return self.app_label() + ':' + self.name()
271
272    def __repr__(self):
273        return u'<Migration: %s>' % unicode(self)
274
275    def __eq__(self, other):
276        return self.app_label() == other.app_label() and self.name() == other.name()
277
278    def __hash__(self):
279        return hash(str(self))
280
281    def app_label(self):
282        return self.migrations.app_label()
283
284    @staticmethod
285    def strip_filename(filename):
286        return os.path.splitext(os.path.basename(filename))[0]
287
288    def name(self):
289        return self.strip_filename(os.path.basename(self.filename))
290
291    def full_name(self):
292        return self.migrations.full_name() + '.' + self.name()
293
294    def migration(self):
295        "Tries to load the actual migration module"
296        full_name = self.full_name()
297        try:
298            migration = sys.modules[full_name]
299        except KeyError:
300            try:
301                migration = __import__(full_name, {}, {}, ['Migration'])
302            except ImportError, e:
303                raise exceptions.UnknownMigration(self, sys.exc_info())
304            except Exception, e:
305                raise exceptions.BrokenMigration(self, sys.exc_info())
306        # Override some imports
307        migration._ = lambda x: x  # Fake i18n
308        migration.datetime = datetime_utils
309        return migration
310    migration = memoize(migration)
311
312    def migration_class(self):
313        "Returns the Migration class from the module"
314        return self.migration().Migration
315
316    def migration_instance(self):
317        "Instantiates the migration_class"
318        return self.migration_class()()
319    migration_instance = memoize(migration_instance)
320
321    def previous(self):
322        "Returns the migration that comes before this one in the sequence."
323        index = self.migrations.index(self) - 1
324        if index < 0:
325            return None
326        return self.migrations[index]
327    previous = memoize(previous)
328
329    def next(self):
330        "Returns the migration that comes after this one in the sequence."
331        index = self.migrations.index(self) + 1
332        if index >= len(self.migrations):
333            return None
334        return self.migrations[index]
335    next = memoize(next)
336    
337    def _get_dependency_objects(self, attrname):
338        """
339        Given the name of an attribute (depends_on or needed_by), either yields
340        a list of migration objects representing it, or errors out.
341        """
342        for app, name in getattr(self.migration_class(), attrname, []):
343            try:
344                migrations = Migrations(app)
345            except ImproperlyConfigured:
346                raise exceptions.DependsOnUnmigratedApplication(self, app)
347            migration = migrations.migration(name)
348            try:
349                migration.migration()
350            except exceptions.UnknownMigration:
351                raise exceptions.DependsOnUnknownMigration(self, migration)
352            if migration.is_before(self) == False:
353                raise exceptions.DependsOnHigherMigration(self, migration)
354            yield migration
355    
356    def calculate_dependencies(self):
357        """
358        Loads dependency info for this migration, and stores it in itself
359        and any other relevant migrations.
360        """
361        # Normal deps first
362        for migration in self._get_dependency_objects("depends_on"):
363            self.dependencies.add(migration)
364            migration.dependents.add(self)
365        # And reverse deps
366        for migration in self._get_dependency_objects("needed_by"):
367            self.dependents.add(migration)
368            migration.dependencies.add(self)
369        # And implicit ordering deps
370        previous = self.previous()
371        if previous:
372            self.dependencies.add(previous)
373            previous.dependents.add(self)
374    
375    def invalidate_module(self):
376        """
377        Removes the cached version of this migration's module import, so we
378        have to re-import it. Used when south.db.db changes.
379        """
380        reload(self.migration())
381        self.migration._invalidate()
382
383    def forwards(self):
384        return self.migration_instance().forwards
385
386    def backwards(self):
387        return self.migration_instance().backwards
388
389    def forwards_plan(self):
390        """
391        Returns a list of Migration objects to be applied, in order.
392
393        This list includes `self`, which will be applied last.
394        """
395        return depends(self, lambda x: x.dependencies)
396
397    def _backwards_plan(self):
398        return depends(self, lambda x: x.dependents)
399
400    def backwards_plan(self):
401        """
402        Returns a list of Migration objects to be unapplied, in order.
403
404        This list includes `self`, which will be unapplied last.
405        """
406        return list(self._backwards_plan())
407
408    def is_before(self, other):
409        if self.migrations == other.migrations:
410            if self.filename < other.filename:
411                return True
412            return False
413
414    def is_after(self, other):
415        if self.migrations == other.migrations:
416            if self.filename > other.filename:
417                return True
418            return False
419
420    def prev_orm(self):
421        if getattr(self.migration_class(), 'symmetrical', False):
422            return self.orm()
423        previous = self.previous()
424        if previous is None:
425            # First migration? The 'previous ORM' is empty.
426            return FakeORM(None, self.app_label())
427        return previous.orm()
428    prev_orm = memoize(prev_orm)
429
430    def orm(self):
431        return FakeORM(self.migration_class(), self.app_label())
432    orm = memoize(orm)
433
434    def no_dry_run(self):
435        migration_class = self.migration_class()
436        try:
437            return migration_class.no_dry_run
438        except AttributeError:
439            return False