Merge pull request #13667 from edx/jeskew/move_monkey_patch
Move monkey_patch to openedx.core.djangoapps.
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user