Merge pull request #13667 from edx/jeskew/move_monkey_patch

Move monkey_patch to openedx.core.djangoapps.
This commit is contained in:
John Eskew
2016-10-06 14:32:35 -04:00
committed by GitHub
6 changed files with 3 additions and 3 deletions

View File

@@ -1,80 +0,0 @@
"""
Monkey-patch the edX platform
Here be dragons (and simians!)
* USE WITH CAUTION *
No, but seriously, you probably never really want to make changes here.
This module contains methods to monkey-patch [0] the edx-platform.
Patches are to be applied as early as possible in the callstack
(currently lms/startup.py and cms/startup.py). Consequently, changes
made here will affect the entire platform.
That said, if you've decided you really need to monkey-patch the
platform (and you've convinced enough people that this is best
solution), kindly follow these guidelines:
- Reference django_18_upgrade.py for a sample implementation.
- Name your module by replacing periods with underscores for the
module to be patched:
- patching 'django.utils.translation'
becomes 'django_utils_translation'
- patching 'your.module'
becomes 'your_module'
- Implement argumentless function wrappers in
monkey_patch.your_module for the following:
- is_patched
- patch
- unpatch
- Add the following code where needed (typically cms/startup.py and
lms/startup.py):
```
from monkey_patch import your_module
your_module.patch()
```
- Write tests! All code should be tested anyway, but with code that
patches the platform runtime, we must be extra sure there are no
unintended consequences.
[0] http://en.wikipedia.org/wiki/Monkey_patch
"""
# Use this key to store a reference to the unpatched copy
__BACKUP_ATTRIBUTE_NAME = '__monkey_patch'
def is_patched(module, attribute_name):
"""
Check if an attribute has been monkey-patched
"""
attribute = getattr(module, attribute_name)
return hasattr(attribute, __BACKUP_ATTRIBUTE_NAME)
def patch(module, attribute_name, attribute_replacement):
"""
Monkey-patch an attribute
A backup of the original attribute is preserved in the patched
attribute (see: __BACKUP_ATTRIBUTE_NAME).
"""
attribute = getattr(module, attribute_name)
setattr(attribute_replacement, __BACKUP_ATTRIBUTE_NAME, attribute)
setattr(module, attribute_name, attribute_replacement)
return is_patched(module, attribute_name)
def unpatch(module, attribute_name):
"""
Un-monkey-patch an attribute
Restore a backup of the original attribute from the patched
attribute, iff it exists (see: __BACKUP_ATTRIBUTE_NAME).
Return boolean whether or not the attribute could be unpatched
"""
was_patched = False
attribute = getattr(module, attribute_name)
if hasattr(attribute, __BACKUP_ATTRIBUTE_NAME):
attribute_old = getattr(attribute, __BACKUP_ATTRIBUTE_NAME)
setattr(module, attribute_name, attribute_old)
was_patched = True
return was_patched

View File

@@ -1,39 +0,0 @@
"""
Monkey patch implementation of the following _expire_cache performance improvement:
https://github.com/django/django/commit/7628f87e2b1ab4b8a881f06c8973be4c368aaa3d
Remove once we upgrade to a version of django which includes this fix natively!
NOTE: This is on django's master branch but is NOT currently part of any django 1.8 or 1.9 release.
"""
from django.db.models.options import Options
def patch():
"""
Monkey-patch the Options class.
"""
def _expire_cache(self, forward=True, reverse=True):
# pylint: disable=missing-docstring
# This method is usually called by apps.cache_clear(), when the
# registry is finalized, or when a new field is added.
if forward:
for cache_key in self.FORWARD_PROPERTIES:
if cache_key in self.__dict__:
delattr(self, cache_key)
if reverse and not self.abstract:
for cache_key in self.REVERSE_PROPERTIES:
if cache_key in self.__dict__:
delattr(self, cache_key)
self._get_fields_cache = {} # pylint: disable=protected-access
# Patch constants as a set instead of a list.
Options.FORWARD_PROPERTIES = {'fields', 'many_to_many', 'concrete_fields',
'local_concrete_fields', '_forward_fields_map'}
Options.REVERSE_PROPERTIES = {'related_objects', 'fields_map', '_relation_tree'}
# Patch the expire_cache method to utilize constant's new set data structure.
Options._expire_cache = _expire_cache # pylint: disable=protected-access

View File

@@ -1,38 +0,0 @@
"""
Monkey patch implementation for a python_social_auth Django ORM method that is not Django 1.8-compatible.
Remove once the module fully supports Django 1.8!
"""
from django.db import transaction
from social.storage.django_orm import DjangoUserMixin
from social.apps.django_app.default.models import (
UserSocialAuth, Nonce, Association, Code
)
def patch():
"""
Monkey-patch the DjangoUserMixin class.
"""
def create_social_auth_wrapper(wrapped_func):
# pylint: disable=missing-docstring
wrapped_func = wrapped_func.__func__
def _create_social_auth(*args, **kwargs):
# The entire reason for this monkey-patch is to wrap the create_social_auth call
# in an atomic transaction. The call can sometime raise an IntegrityError, which is
# caught and dealt with by python_social_auth - but not inside of an atomic transaction.
# In Django 1.8, unless the exception is raised in an atomic transaction, the transaction
# becomes unusable after the IntegrityError exception is raised.
with transaction.atomic():
return wrapped_func(*args, **kwargs)
return classmethod(_create_social_auth)
DjangoUserMixin.create_social_auth = create_social_auth_wrapper(DjangoUserMixin.create_social_auth)
# Monkey-patch some social auth models' Meta class to squelch Django19 warnings.
# pylint: disable=protected-access
UserSocialAuth._meta.app_label = "default"
Nonce._meta.app_label = "default"
Association._meta.app_label = "default"
Code._meta.app_label = "default"