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
"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
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
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:
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)
"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