From ce9aea80b1bb48b1855295fa2ec99d08ca4ccde5 Mon Sep 17 00:00:00 2001
From: Nimisha Asthagiri <nasthagiri@edx.org>
Date: Thu, 3 Jul 2014 14:55:26 -0400
Subject: [PATCH] Move context_managers to base class, overridden by mixed.

---
 cms/djangoapps/contentstore/management/commands/clone_course.py     |  3 +--
 cms/djangoapps/contentstore/management/commands/migrate_to_split.py |  2 +-
 cms/djangoapps/contentstore/tests/test_clone_course.py              |  2 +-
 cms/djangoapps/contentstore/tests/test_contentstore.py              |  3 +--
 cms/djangoapps/contentstore/utils.py                                |  3 +--
 common/lib/xmodule/xmodule/modulestore/__init__.py                  | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 common/lib/xmodule/xmodule/modulestore/mixed.py                     | 68 ++++++++++++++++++++------------------------------------------------
 common/lib/xmodule/xmodule/modulestore/mongo/base.py                |  4 ++--
 common/lib/xmodule/xmodule/modulestore/xml_exporter.py              |  3 +--
 common/lib/xmodule/xmodule/modulestore/xml_importer.py              |  3 +--
 10 files changed, 75 insertions(+), 62 deletions(-)

diff --git a/cms/djangoapps/contentstore/management/commands/clone_course.py b/cms/djangoapps/contentstore/management/commands/clone_course.py
index be9b0bc..8f87e04 100644
--- a/cms/djangoapps/contentstore/management/commands/clone_course.py
+++ b/cms/djangoapps/contentstore/management/commands/clone_course.py
@@ -3,7 +3,6 @@ Script for cloning a course
 """
 from django.core.management.base import BaseCommand, CommandError
 from xmodule.modulestore.django import modulestore
-from xmodule.modulestore.mixed import store_bulk_write_operations_on_course
 from student.roles import CourseInstructorRole, CourseStaffRole
 from opaque_keys.edx.keys import CourseKey
 from opaque_keys import InvalidKeyError
@@ -38,7 +37,7 @@ class Command(BaseCommand):
 
         print("Cloning course {0} to {1}".format(source_course_id, dest_course_id))
 
-        with store_bulk_write_operations_on_course(mstore, dest_course_id):
+        with mstore.bulk_write_operations(dest_course_id):
             if mstore.clone_course(source_course_id, dest_course_id, None):
                 print("copying User permissions...")
                 # purposely avoids auth.add_user b/c it doesn't have a caller to authorize
diff --git a/cms/djangoapps/contentstore/management/commands/migrate_to_split.py b/cms/djangoapps/contentstore/management/commands/migrate_to_split.py
index 7b1845e..9d7b095 100644
--- a/cms/djangoapps/contentstore/management/commands/migrate_to_split.py
+++ b/cms/djangoapps/contentstore/management/commands/migrate_to_split.py
@@ -1,4 +1,4 @@
-# pylint: disable=W0212
+# pylint: disable=protected-access
 
 """
 Django management command to migrate a course from the old Mongo modulestore
diff --git a/cms/djangoapps/contentstore/tests/test_clone_course.py b/cms/djangoapps/contentstore/tests/test_clone_course.py
index ac6d582..04780e3 100644
--- a/cms/djangoapps/contentstore/tests/test_clone_course.py
+++ b/cms/djangoapps/contentstore/tests/test_clone_course.py
@@ -29,7 +29,7 @@ class CloneCourseTest(CourseTestCase):
         self.assertCoursesEqual(mongo_course1_id, mongo_course2_id)
 
         # 3. clone course (mongo -> split)
-        with self.store.set_default_store(ModuleStoreEnum.Type.split):
+        with self.store.default_store(ModuleStoreEnum.Type.split):
             split_course3_id = CourseLocator(
                 org="edx3", course="split3", run="2013_Fall", branch=ModuleStoreEnum.BranchName.draft
             )
diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py
index 795a372..260702b 100644
--- a/cms/djangoapps/contentstore/tests/test_contentstore.py
+++ b/cms/djangoapps/contentstore/tests/test_contentstore.py
@@ -26,7 +26,6 @@ from xmodule.contentstore.django import contentstore, _CONTENTSTORE
 from xmodule.contentstore.utils import restore_asset_from_trashcan, empty_asset_trashcan
 from xmodule.exceptions import NotFoundError, InvalidVersionError
 from xmodule.modulestore import ModuleStoreEnum
-from xmodule.modulestore.mixed import store_branch_setting
 from xmodule.modulestore.exceptions import ItemNotFoundError
 from xmodule.modulestore.inheritance import own_metadata
 from opaque_keys.edx.keys import UsageKey
@@ -873,7 +872,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
         mongo_store.collection.find = wrapper.find
 
         # set the branch to 'publish' in order to prevent extra lookups of draft versions
-        with store_branch_setting(mongo_store, ModuleStoreEnum.Branch.published_only):
+        with mongo_store.branch_setting(ModuleStoreEnum.Branch.published_only):
             course = mongo_store.get_course(course_id, depth=2)
 
             # make sure we haven't done too many round trips to DB
diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py
index ffdce78..7bd69b6 100644
--- a/cms/djangoapps/contentstore/utils.py
+++ b/cms/djangoapps/contentstore/utils.py
@@ -14,7 +14,6 @@ from xmodule.contentstore.content import StaticContent
 from xmodule.contentstore.django import contentstore
 from xmodule.modulestore import ModuleStoreEnum
 from xmodule.modulestore.django import modulestore
-from xmodule.modulestore.mixed import store_bulk_write_operations_on_course
 from xmodule.modulestore.exceptions import ItemNotFoundError
 from opaque_keys.edx.locations import SlashSeparatedCourseKey, Location
 from xmodule.modulestore.store_utilities import delete_course
@@ -37,7 +36,7 @@ def delete_course_and_groups(course_id, commit=False):
     module_store = modulestore()
     content_store = contentstore()
 
-    with store_bulk_write_operations_on_course(module_store, course_id):
+    with module_store.bulk_write_operations(course_id):
         if delete_course(module_store, content_store, course_id, commit):
 
             print 'removing User permissions from course....'
diff --git a/common/lib/xmodule/xmodule/modulestore/__init__.py b/common/lib/xmodule/xmodule/modulestore/__init__.py
index 29ffc39..8470e59 100644
--- a/common/lib/xmodule/xmodule/modulestore/__init__.py
+++ b/common/lib/xmodule/xmodule/modulestore/__init__.py
@@ -10,6 +10,7 @@ import datetime
 
 from collections import namedtuple, defaultdict
 import collections
+from contextlib import contextmanager
 
 from abc import ABCMeta, abstractmethod
 from xblock.plugin import default_select
@@ -447,6 +448,28 @@ class ModuleStoreReadBase(ModuleStoreRead):
         # default is to say yes by not raising an exception
         return {'default_impl': True}
 
+    @contextmanager
+    def default_store(self, store_type):
+        """
+        A context manager for temporarily changing the default store
+        """
+        if self.get_modulestore_type(None) != store_type:
+            raise ValueError(u"Cannot set default store to type {}".format(store_type))
+        yield
+
+    @contextmanager
+    def branch_setting(self, branch_setting, course_id=None):
+        """
+        A context manager for temporarily setting a store's branch value
+        """
+        previous_branch_setting_func = getattr(self, 'branch_setting_func', None)
+        try:
+            self.branch_setting_func = lambda: branch_setting
+            yield
+        finally:
+            self.branch_setting_func = previous_branch_setting_func
+
+
 class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite):
     '''
     Implement interface functionality that can be shared.
@@ -530,6 +553,29 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite):
         super(ModuleStoreWriteBase, self).clone_course(source_course_id, dest_course_id, user_id)
         return dest_course_id
 
+    @contextmanager
+    def bulk_write_operations(self, course_id):
+        """
+        A context manager for notifying the store of bulk write events.
+
+        In the case of Mongo, it temporarily disables refreshing the metadata inheritance tree
+        until the bulk operation is completed.
+        """
+        # TODO
+        # Make this multi-process-safe if future operations need it.
+        # Right now, only Import Course, Clone Course, and Delete Course use this, so
+        # it's ok if the cached metadata in the memcache is invalid when another
+        # request comes in for the same course.
+        try:
+            if hasattr(self, '_begin_bulk_write_operation'):
+                self._begin_bulk_write_operation(course_id)
+            yield
+        finally:
+            # check for the begin method here,
+            # since it's an error if an end method is not defined when a begin method is
+            if hasattr(self, '_begin_bulk_write_operation'):
+                self._end_bulk_write_operation(course_id)
+
 
 def only_xmodules(identifier, entry_points):
     """Only use entry_points that are supplied by the xmodule package"""
diff --git a/common/lib/xmodule/xmodule/modulestore/mixed.py b/common/lib/xmodule/xmodule/modulestore/mixed.py
index f1efa8e..09a364e 100644
--- a/common/lib/xmodule/xmodule/modulestore/mixed.py
+++ b/common/lib/xmodule/xmodule/modulestore/mixed.py
@@ -492,9 +492,9 @@ class MixedModuleStore(ModuleStoreWriteBase):
             raise NotImplementedError(u"Cannot call {} on store {}".format(method, store))
 
     @contextmanager
-    def set_default_store(self, store_type):
+    def default_store(self, store_type):
         """
-        A context manager for temporarily changing the default store in the Mixed modulestore
+        A context manager for temporarily changing the default store in the Mixed modulestore to the given store type
         """
         previous_store_list = self.modulestores
         found = False
@@ -509,50 +509,22 @@ class MixedModuleStore(ModuleStoreWriteBase):
         finally:
             self.modulestores = previous_store_list
 
+    @contextmanager
+    def branch_setting(self, branch_setting, course_id=None):
+        """
+        A context manager for temporarily setting the branch value for the given course' store
+        to the given branch_setting.  If course_id is None, the default store is used.
+        """
+        store = self._get_modulestore_for_courseid(course_id)
+        with store.branch_setting(branch_setting, course_id):
+            yield
 
-@contextmanager
-def store_branch_setting(store, branch_setting):
-    """
-    A context manager for temporarily setting a store's branch value
-
-    Note: to be effective, the store must be a direct pointer to the underlying store;
-        not the intermediary Mixed store.
-    """
-    assert not isinstance(store, MixedModuleStore)
-
-    try:
-        previous_branch_setting_func = store.branch_setting_func
-        store.branch_setting_func = lambda: branch_setting
-        yield
-    finally:
-        store.branch_setting_func = previous_branch_setting_func
-
-
-@contextmanager
-def store_bulk_write_operations_on_course(store, course_id):
-    """
-    A context manager for notifying the store of bulk write events.
-
-    In the case of Mongo, it temporarily disables refreshing the metadata inheritance tree
-    until the bulk operation is completed.
-
-    The store can be either the Mixed modulestore or a direct pointer to the underlying store.
-    """
-
-    # TODO
-    # Make this multi-process-safe if future operations need it.
-    # Right now, only Import Course, Clone Course, and Delete Course use this, so
-    # it's ok if the cached metadata in the memcache is invalid when another
-    # request comes in for the same course.
-
-    # if the caller passed in the mixed modulestore, get a direct pointer to the underlying store
-    if hasattr(store, '_get_modulestore_by_course_id'):
-        store = store._get_modulestore_by_course_id(course_id)
-
-    try:
-        if hasattr(store, 'begin_bulk_write_operation_on_course'):
-            store.begin_bulk_write_operation_on_course(course_id)
-        yield
-    finally:
-        if hasattr(store, 'begin_bulk_write_operation_on_course'):
-            store.end_bulk_write_operation_on_course(course_id)
+    @contextmanager
+    def bulk_write_operations(self, course_id):
+        """
+        A context manager for notifying the store of bulk write events.
+        If course_id is None, the default store is used.
+        """
+        store = self._get_modulestore_for_courseid(course_id)
+        with store.bulk_write_operations(course_id):
+            yield
diff --git a/common/lib/xmodule/xmodule/modulestore/mongo/base.py b/common/lib/xmodule/xmodule/modulestore/mongo/base.py
index 32311b9..2f71e83 100644
--- a/common/lib/xmodule/xmodule/modulestore/mongo/base.py
+++ b/common/lib/xmodule/xmodule/modulestore/mongo/base.py
@@ -390,13 +390,13 @@ class MongoModuleStore(ModuleStoreWriteBase):
         self.ignore_write_events_on_courses = set()
         self._course_run_cache = {}
 
-    def begin_bulk_write_operation_on_course(self, course_id):
+    def _begin_bulk_write_operation(self, course_id):
         """
         Prevent updating the meta-data inheritance cache for the given course
         """
         self.ignore_write_events_on_courses.add(course_id)
 
-    def end_bulk_write_operation_on_course(self, course_id):
+    def _end_bulk_write_operation(self, course_id):
         """
         Restart updating the meta-data inheritance cache for the given course.
         Refresh the meta-data inheritance cache now since it was temporarily disabled.
diff --git a/common/lib/xmodule/xmodule/modulestore/xml_exporter.py b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py
index 0633642..4a17704 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml_exporter.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py
@@ -9,7 +9,6 @@ from xmodule.contentstore.content import StaticContent
 from xmodule.exceptions import NotFoundError
 from xmodule.modulestore import EdxJSONEncoder, ModuleStoreEnum
 from xmodule.modulestore.inheritance import own_metadata
-from xmodule.modulestore.mixed import store_branch_setting
 from fs.osfs import OSFS
 from json import dumps
 import json
@@ -45,7 +44,7 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir):
     root = lxml.etree.Element('unknown')
 
     # export only the published content
-    with store_branch_setting(course.runtime.modulestore, ModuleStoreEnum.Branch.published_only):
+    with modulestore.branch_setting(ModuleStoreEnum.Branch.published_only, course_key):
         course.add_xml_to_node(root)
 
     with export_fs.open('course.xml', 'w') as course_xml:
diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
index c14999b..d1d4636 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
@@ -10,7 +10,6 @@ from xmodule.x_module import XModuleDescriptor
 from opaque_keys.edx.keys import UsageKey
 from xblock.fields import Scope, Reference, ReferenceList, ReferenceValueDict
 from xmodule.contentstore.content import StaticContent
-from xmodule.modulestore.mixed import store_bulk_write_operations_on_course
 from .inheritance import own_metadata
 from xmodule.errortracker import make_error_tracker
 from .store_utilities import rewrite_nonportable_content_links
@@ -179,7 +178,7 @@ def import_from_xml(
                 )
                 continue
 
-        with store_bulk_write_operations_on_course(store, dest_course_id):
+        with store.bulk_write_operations(dest_course_id):
             course_data_path = None
 
             if verbose:
--
libgit2 0.26.0