Пример #1
0
        def final_decorator(external_replacement_function):
            # Activate the patch now
            actual_final_replacement = curry(wrapper_function,
                external_replacement_function, original_function)

            if memoize_the_replacement:
                from south.utils import memoize
                actual_final_replacement = memoize(actual_final_replacement)

            setattr(class_or_instance, method_name, actual_final_replacement)

            # Note: by now, class_or_instance is the original function, not the
            # class or module that it's a member of.
            if isinstance(class_or_instance, cached_property):
                # Rename so that cached_property's assignment to
                # instance.__dict__[self.func.__name__] does actually replace
                # the cached_property object with the result of the function
                # call, and the property is actually cached.
                actual_final_replacement.__name__ = original_function.__name__

            # It's not useful to return the same wrapper, because
            # that would replace the external_replacement_function with
            # a decorated version, which would stop it from being used
            # to replace multiple methods. So we return the
            # external_replacement_function as it originally was, leaving
            # it unchanged in its definition.
            return external_replacement_function
Пример #2
0
        "Tries to load the actual migration module"
        full_name = self.full_name()
        try:
            migration = sys.modules[full_name]
        except KeyError:
            try:
                migration = __import__(full_name, {}, {}, ['Migration'])
            except ImportError, e:
                raise exceptions.UnknownMigration(self, sys.exc_info())
            except Exception, e:
                raise exceptions.BrokenMigration(self, sys.exc_info())
        # Override some imports
        migration._ = lambda x: x  # Fake i18n
        migration.datetime = datetime
        return migration
    migration = memoize(migration)

    def migration_class(self):
        "Returns the Migration class from the module"
        return self.migration().Migration

    def migration_instance(self):
        "Instantiates the migration_class"
        return self.migration_class()()
    migration_instance = memoize(migration_instance)

    def previous(self):
        "Returns the migration that comes before this one in the sequence."
        index = self.migrations.index(self) - 1
        if index < 0:
            return None
Пример #3
0
class Migration(object):
    """
    Class which represents a particular migration file on-disk.
    """
    def __init__(self, migrations, filename):
        """
        Returns the migration class implied by 'filename'.
        """
        self.migrations = migrations
        self.filename = filename
        self.dependencies = set()
        self.dependents = set()

    def __str__(self):
        return self.app_label() + ':' + self.name()

    def __repr__(self):
        return '<Migration: %s>' % str(self)

    def __eq__(self, other):
        return self.app_label() == other.app_label() and self.name(
        ) == other.name()

    def __hash__(self):
        return hash(str(self))

    def app_label(self):
        return self.migrations.app_label()

    @staticmethod
    def strip_filename(filename):
        return os.path.splitext(os.path.basename(filename))[0]

    def name(self):
        return self.strip_filename(os.path.basename(self.filename))

    def full_name(self):
        return self.migrations.full_name() + '.' + self.name()

    def migration(self):
        "Tries to load the actual migration module"
        full_name = self.full_name()
        try:
            migration = sys.modules[full_name]
        except KeyError:
            try:
                migration = __import__(full_name, {}, {}, ['Migration'])
            except ImportError as e:
                raise exceptions.UnknownMigration(self, sys.exc_info())
            except Exception as e:
                raise exceptions.BrokenMigration(self, sys.exc_info())
        # Override some imports
        migration._ = lambda x: x  # Fake i18n
        migration.datetime = datetime_utils
        return migration

    migration = memoize(migration)

    def migration_class(self):
        "Returns the Migration class from the module"
        return self.migration().Migration

    def migration_instance(self):
        "Instantiates the migration_class"
        return self.migration_class()()

    migration_instance = memoize(migration_instance)

    def previous(self):
        "Returns the migration that comes before this one in the sequence."
        index = self.migrations.index(self) - 1
        if index < 0:
            return None
        return self.migrations[index]

    previous = memoize(previous)

    def next(self):
        "Returns the migration that comes after this one in the sequence."
        index = self.migrations.index(self) + 1
        if index >= len(self.migrations):
            return None
        return self.migrations[index]

    next = memoize(next)

    def _get_dependency_objects(self, attrname):
        """
        Given the name of an attribute (depends_on or needed_by), either yields
        a list of migration objects representing it, or errors out.
        """
        for app, name in getattr(self.migration_class(), attrname, []):
            try:
                migrations = Migrations(app)
            except ImproperlyConfigured:
                raise exceptions.DependsOnUnmigratedApplication(self, app)
            migration = migrations.migration(name)
            try:
                migration.migration()
            except exceptions.UnknownMigration:
                raise exceptions.DependsOnUnknownMigration(self, migration)
            if migration.is_before(self) == False:
                raise exceptions.DependsOnHigherMigration(self, migration)
            yield migration

    def calculate_dependencies(self):
        """
        Loads dependency info for this migration, and stores it in itself
        and any other relevant migrations.
        """
        # Normal deps first
        for migration in self._get_dependency_objects("depends_on"):
            self.dependencies.add(migration)
            migration.dependents.add(self)
        # And reverse deps
        for migration in self._get_dependency_objects("needed_by"):
            self.dependents.add(migration)
            migration.dependencies.add(self)
        # And implicit ordering deps
        previous = self.previous()
        if previous:
            self.dependencies.add(previous)
            previous.dependents.add(self)

    def invalidate_module(self):
        """
        Removes the cached version of this migration's module import, so we
        have to re-import it. Used when south.db.db changes.
        """
        reload(self.migration())
        self.migration._invalidate()

    def forwards(self):
        return self.migration_instance().forwards

    def backwards(self):
        return self.migration_instance().backwards

    def forwards_plan(self):
        """
        Returns a list of Migration objects to be applied, in order.

        This list includes `self`, which will be applied last.
        """
        return depends(self, lambda x: x.dependencies)

    def _backwards_plan(self):
        return depends(self, lambda x: x.dependents)

    def backwards_plan(self):
        """
        Returns a list of Migration objects to be unapplied, in order.

        This list includes `self`, which will be unapplied last.
        """
        return list(self._backwards_plan())

    def is_before(self, other):
        if self.migrations == other.migrations:
            if self.filename < other.filename:
                return True
            return False

    def is_after(self, other):
        if self.migrations == other.migrations:
            if self.filename > other.filename:
                return True
            return False

    def prev_orm(self):
        if getattr(self.migration_class(), 'symmetrical', False):
            return self.orm()
        previous = self.previous()
        if previous is None:
            # First migration? The 'previous ORM' is empty.
            return FakeORM(None, self.app_label())
        return previous.orm()

    prev_orm = memoize(prev_orm)

    def orm(self):
        return FakeORM(self.migration_class(), self.app_label())

    orm = memoize(orm)

    def no_dry_run(self):
        migration_class = self.migration_class()
        try:
            return migration_class.no_dry_run
        except AttributeError:
            return False
Пример #4
0
        full_name = self.full_name()
        try:
            migration = sys.modules[full_name]
        except KeyError:
            try:
                migration = __import__(full_name, '', '', ['Migration'])
            except ImportError, e:
                raise exceptions.UnknownMigration(self, sys.exc_info())
            except Exception, e:
                raise exceptions.BrokenMigration(self, sys.exc_info())
        # Override some imports
        migration._ = lambda x: x  # Fake i18n
        migration.datetime = datetime
        return migration

    migration = memoize(migration)

    def migration_class(self):
        "Returns the Migration class from the module"
        return self.migration().Migration

    def migration_instance(self):
        "Instantiates the migration_class"
        return self.migration_class()()

    migration_instance = memoize(migration_instance)

    def previous(self):
        "Returns the migration that comes before this one in the sequence."
        index = self.migrations.index(self) - 1
        if index < 0:
Пример #5
0
        "Tries to load the actual migration module"
        full_name = self.full_name()
        try:
            migration = sys.modules[full_name]
        except KeyError:
            try:
                migration = __import__(full_name, {}, {}, ['Migration'])
            except ImportError, e:
                raise exceptions.UnknownMigration(self, sys.exc_info())
            except Exception, e:
                raise exceptions.BrokenMigration(self, sys.exc_info())
        # Override some imports
        migration._ = lambda x: x  # Fake i18n
        migration.datetime = datetime
        return migration
    migration = memoize(migration)

    def migration_class(self):
        "Returns the Migration class from the module"
        return self.migration().Migration

    def migration_instance(self):
        "Instantiates the migration_class"
        return self.migration_class()()
    migration_instance = memoize(migration_instance)

    def previous(self):
        "Returns the migration that comes before this one in the sequence."
        index = self.migrations.index(self) - 1
        if index < 0:
            return None
Пример #6
0
def get_decorator_or_context_object(class_or_instance, method_name,
    wrapper_function, external_replacement_function=None):
    """
    This is really confusing, but helps reduce code duplication. You have
    been warned: be prepared for head-spinning.
    
    A number of monkeypatch helper functions (before, after, patch...)
    do the same thing, so it's abstracted out here: they behave differently
    depending on whether external_replacement_function is None or not.
    
    If external_replacement_function is None, the monkeypatch helper is
    being used as a decorator for an external replacement function (the
    actual monkey patch code) which is not yet known, so the helper returns
    a no-argument decorator which then decorates the monkey patch
    (the final_decorator). For example:
    
        @after(MyClass, 'do_something')
        def after_MyClass_do_something_add_monkeys(*args, **kwargs):
            monkeys++
    
    If external_replacement_function is provided, the monkeypatch helper is
    being used as a procedure, in one of two ways:
    
    * to permanently replace a function or method with a monkeypatched
      version that invokes the external_replacement_function in some way
      (before, after or around the original_function);
      
    * or as a context object for a "with" statement, which undoes the
      patching when it finishes.
      
    We handle that by always returning a context object (a TemporaryPatcher)
    and if it's discarded (procedural style) then the patch is never undone
    and is permanent. In the context of a "with" statement, the
    TemporaryPatcher's __exit__ method is called by Python after the "with"
    statement's block exits, and it undoes the patch.
    
    An example of procedural use: 
    
        def after_MyClass_do_something_add_monkeys(*args, **kwargs):
            monkeys++
        after(MyClass, 'do_something', after_MyClass_do_something_add_monkeys)
        
    An example of use in a "with" statement (temporary patching):
        
        with after(MyClass, 'do_something', after_MyClass_do_something_add_monkeys):
            MyClass().do_something()

    The monkeypatch helpers use this function (get_decorator_or_context_object)
    to decorate their own wrapper_function, which encapsulates what's unique
    about them: in what order, and with what arguments, they run the
    external_replacement_function and the original_function.
    
    The wrapper function is curried to receive two additional parameters, and
    patched over the target class method or module function. The additional
    parameters, which go before the arguments that the original_function is
    called with, are: (1) the undecorated external_replacement_function; and
    (2) the original_function, that was replaced by the monkey patch.
    """

    # http://gnosis.cx/publish/programming/metaclass_2.txt
    def get_obj_mro(obj):
        if isinstance(obj, type):
            return obj.mro()
        else:
            return obj.__class__.mro()
        
    # http://stackoverflow.com/a/3681323/648162
    def get_dict_attr(obj, attr):
        for c in [obj] + get_obj_mro(obj):
            if attr in c.__dict__:
                return c.__dict__[attr]
        raise AttributeError("No attribute called %s found in class of %s "
            "or any superclass" % (attr, obj))

    original_function = get_dict_attr(class_or_instance, method_name)

    # if original_function is a @cached_property, then trying to read it
    # will result in a call to __get__ which will execute the function
    # to cache the property, which is not what we want at all! To fix that,
    # we check for things that look like @cached_property and patch their
    # 'func' attribute instead

    from django.utils.functional import cached_property
    if isinstance(original_function, cached_property):
        class_or_instance = original_function
        original_function = original_function.func
        method_name = 'func'

    # If it looks like it was memoized by South, then we can't access the real
    # original function as it's hidden by a closure, so we just memoize the
    # replacement instead.
    memoize_the_replacement = False
    if hasattr(original_function, '__name__') and \
        hasattr(original_function, '_invalidate'):

        memoize_the_replacement = True
   
    if external_replacement_function is None:
        # The monkeypatch function (not this one) is being used as an
        # unbound decorator. In this case, we don't actually know what
        # external function we're wrapping until the decorator is called,
        # after the monkeypatch function returns it.
        # 
        # So we (raw_decorator) return a function (the bound decorator),
        # which takes the external replacement function as its only
        # argument, and replaces the original with it, permanently.
        #
        # The monkeypatch function returns this bound decorator to its
        # caller, where it's applied to the external replacement function.

        def final_decorator(external_replacement_function):
            # Activate the patch now
            actual_final_replacement = curry(wrapper_function,
                external_replacement_function, original_function)

            if memoize_the_replacement:
                from south.utils import memoize
                actual_final_replacement = memoize(actual_final_replacement)

            setattr(class_or_instance, method_name, actual_final_replacement)

            # Note: by now, class_or_instance is the original function, not the
            # class or module that it's a member of.
            if isinstance(class_or_instance, cached_property):
                # Rename so that cached_property's assignment to
                # instance.__dict__[self.func.__name__] does actually replace
                # the cached_property object with the result of the function
                # call, and the property is actually cached.
                actual_final_replacement.__name__ = original_function.__name__

            # It's not useful to return the same wrapper, because
            # that would replace the external_replacement_function with
            # a decorated version, which would stop it from being used
            # to replace multiple methods. So we return the
            # external_replacement_function as it originally was, leaving
            # it unchanged in its definition.
            return external_replacement_function
       
        return final_decorator
    else:
        # Being used as a context object or procedural call.
        # The monkeypatch function returns this TemporaryPatcher to its
        # caller, where it's used as a context object, or discarded.
        # import pdb; pdb.set_trace()

        if memoize_the_replacement:
            from south.utils import memoize
            external_replacement_function = memoize(external_replacement_function)

        # If the replacement is a callable, then curry it so that it receives
        # original_function as its first argument.
        if hasattr(external_replacement_function, '__call__'):
            return TemporaryPatcher(class_or_instance, method_name,
                curry(wrapper_function, external_replacement_function,
                    original_function))
        else:
            # Otherwise, it's a plain value, which will never be called and
            # has no way to use an original function if it bit it in the ass.
            return TemporaryPatcher(class_or_instance, method_name,
                external_replacement_function)
Пример #7
0
        "Tries to load the actual migration module"
        full_name = self.full_name()
        try:
            migration = sys.modules[full_name]
        except KeyError:
            try:
                migration = __import__(full_name, '', '', ['Migration'])
            except ImportError, e:
                raise exceptions.UnknownMigration(self, sys.exc_info())
            except Exception, e:
                raise exceptions.BrokenMigration(self, sys.exc_info())
        # Override some imports
        migration._ = lambda x: x  # Fake i18n
        migration.datetime = datetime
        return migration
    migration = memoize(migration)

    def migration_class(self):
        "Returns the Migration class from the module"
        return self.migration().Migration

    def migration_instance(self):
        "Instantiates the migration_class"
        return self.migration_class()()
    migration_instance = memoize(migration_instance)

    def previous(self):
        "Returns the migration that comes before this one in the sequence."
        index = self.migrations.index(self) - 1
        if index < 0:
            return None