Commit e3cef4e2 by Slater-Victoroff

Merge pull request #1545 from edx/slater/mixed_mongo_abstract

Proper abstraction of Module store base class implemented
parents d41a9745 e2ba1122
...@@ -8,11 +8,14 @@ import re ...@@ -8,11 +8,14 @@ import re
from collections import namedtuple from collections import namedtuple
from abc import ABCMeta, abstractmethod
from .exceptions import InvalidLocationError, InsufficientSpecificationError from .exceptions import InvalidLocationError, InsufficientSpecificationError
from xmodule.errortracker import make_error_tracker from xmodule.errortracker import make_error_tracker
log = logging.getLogger('mitx.' + 'modulestore') log = logging.getLogger('mitx.' + 'modulestore')
SPLIT_MONGO_MODULESTORE_TYPE = 'split'
MONGO_MODULESTORE_TYPE = 'mongo' MONGO_MODULESTORE_TYPE = 'mongo'
XML_MODULESTORE_TYPE = 'xml' XML_MODULESTORE_TYPE = 'xml'
...@@ -254,17 +257,22 @@ class Location(_LocationBase): ...@@ -254,17 +257,22 @@ class Location(_LocationBase):
return self._replace(**kwargs) return self._replace(**kwargs)
class ModuleStore(object): class ModuleStoreRead(object):
""" """
An abstract interface for a database backend that stores XModuleDescriptor An abstract interface for a database backend that stores XModuleDescriptor
instances instances and extends read-only functionality
""" """
__metaclass__ = ABCMeta
@abstractmethod
def has_item(self, course_id, location): def has_item(self, course_id, location):
""" """
Returns True if location exists in this ModuleStore. Returns True if location exists in this ModuleStore.
""" """
raise NotImplementedError pass
@abstractmethod
def get_item(self, location, depth=0): def get_item(self, location, depth=0):
""" """
Returns an XModuleDescriptor instance for the item at location. Returns an XModuleDescriptor instance for the item at location.
...@@ -282,15 +290,17 @@ class ModuleStore(object): ...@@ -282,15 +290,17 @@ class ModuleStore(object):
in the request. The depth is counted in the number of calls to in the request. The depth is counted in the number of calls to
get_children() to cache. None indicates to cache all descendents get_children() to cache. None indicates to cache all descendents
""" """
raise NotImplementedError pass
@abstractmethod
def get_instance(self, course_id, location, depth=0): def get_instance(self, course_id, location, depth=0):
""" """
Get an instance of this location, with policy for course_id applied. Get an instance of this location, with policy for course_id applied.
TODO (vshnayder): this may want to live outside the modulestore eventually TODO (vshnayder): this may want to live outside the modulestore eventually
""" """
raise NotImplementedError pass
@abstractmethod
def get_item_errors(self, location): def get_item_errors(self, location):
""" """
Return a list of (msg, exception-or-None) errors that the modulestore Return a list of (msg, exception-or-None) errors that the modulestore
...@@ -301,8 +311,9 @@ class ModuleStore(object): ...@@ -301,8 +311,9 @@ class ModuleStore(object):
Raises the same exceptions as get_item if the location isn't found or Raises the same exceptions as get_item if the location isn't found or
isn't fully specified. isn't fully specified.
""" """
raise NotImplementedError pass
@abstractmethod
def get_items(self, location, course_id=None, depth=0): def get_items(self, location, course_id=None, depth=0):
""" """
Returns a list of XModuleDescriptor instances for the items Returns a list of XModuleDescriptor instances for the items
...@@ -316,8 +327,58 @@ class ModuleStore(object): ...@@ -316,8 +327,58 @@ class ModuleStore(object):
in the request. The depth is counted in the number of calls to in the request. The depth is counted in the number of calls to
get_children() to cache. None indicates to cache all descendents get_children() to cache. None indicates to cache all descendents
""" """
raise NotImplementedError pass
@abstractmethod
def get_courses(self):
'''
Returns a list containing the top level XModuleDescriptors of the courses
in this modulestore.
'''
pass
@abstractmethod
def get_course(self, course_id):
'''
Look for a specific course id. Returns the course descriptor, or None if not found.
'''
pass
@abstractmethod
def get_parent_locations(self, location, course_id):
'''Find all locations that are the parents of this location in this
course. Needed for path_to_location().
returns an iterable of things that can be passed to Location.
'''
pass
@abstractmethod
def get_errored_courses(self):
"""
Return a dictionary of course_dir -> [(msg, exception_str)], for each
course_dir where course loading failed.
"""
pass
@abstractmethod
def get_modulestore_type(self, course_id):
"""
Returns a type which identifies which modulestore is servicing the given
course_id. The return can be either "xml" (for XML based courses) or "mongo" for MongoDB backed courses
"""
pass
class ModuleStoreWrite(ModuleStoreRead):
"""
An abstract interface for a database backend that stores XModuleDescriptor
instances and extends both read and write functionality
"""
__metaclass__ = ABCMeta
@abstractmethod
def update_item(self, location, data, allow_not_found=False): def update_item(self, location, data, allow_not_found=False):
""" """
Set the data in the item specified by the location to Set the data in the item specified by the location to
...@@ -326,8 +387,9 @@ class ModuleStore(object): ...@@ -326,8 +387,9 @@ class ModuleStore(object):
location: Something that can be passed to Location location: Something that can be passed to Location
data: A nested dictionary of problem data data: A nested dictionary of problem data
""" """
raise NotImplementedError pass
@abstractmethod
def update_children(self, location, children): def update_children(self, location, children):
""" """
Set the children for the item specified by the location to Set the children for the item specified by the location to
...@@ -336,8 +398,9 @@ class ModuleStore(object): ...@@ -336,8 +398,9 @@ class ModuleStore(object):
location: Something that can be passed to Location location: Something that can be passed to Location
children: A list of child item identifiers children: A list of child item identifiers
""" """
raise NotImplementedError pass
@abstractmethod
def update_metadata(self, location, metadata): def update_metadata(self, location, metadata):
""" """
Set the metadata for the item specified by the location to Set the metadata for the item specified by the location to
...@@ -346,58 +409,24 @@ class ModuleStore(object): ...@@ -346,58 +409,24 @@ class ModuleStore(object):
location: Something that can be passed to Location location: Something that can be passed to Location
metadata: A nested dictionary of module metadata metadata: A nested dictionary of module metadata
""" """
raise NotImplementedError pass
@abstractmethod
def delete_item(self, location): def delete_item(self, location):
""" """
Delete an item from this modulestore Delete an item from this modulestore
location: Something that can be passed to Location location: Something that can be passed to Location
""" """
raise NotImplementedError pass
def get_courses(self):
'''
Returns a list containing the top level XModuleDescriptors of the courses
in this modulestore.
'''
course_filter = Location("i4x", category="course")
return self.get_items(course_filter)
def get_course(self, course_id): class ModuleStoreReadBase(ModuleStoreRead):
'''
Look for a specific course id. Returns the course descriptor, or None if not found.
'''
raise NotImplementedError
def get_parent_locations(self, location, course_id):
'''Find all locations that are the parents of this location in this
course. Needed for path_to_location().
returns an iterable of things that can be passed to Location.
'''
raise NotImplementedError
def get_errored_courses(self):
"""
Return a dictionary of course_dir -> [(msg, exception_str)], for each
course_dir where course loading failed.
"""
raise NotImplementedError
def get_modulestore_type(self, course_id):
"""
Returns a type which identifies which modulestore is servicing the given
course_id. The return can be either "xml" (for XML based courses) or "mongo" for MongoDB backed courses
"""
raise NotImplementedError
class ModuleStoreBase(ModuleStore):
''' '''
Implement interface functionality that can be shared. Implement interface functionality that can be shared.
''' '''
# pylint: disable=W0613 # pylint: disable=W0613
def __init__( def __init__(
self, self,
doc_store_config=None, # ignore if passed up doc_store_config=None, # ignore if passed up
...@@ -456,3 +485,10 @@ class ModuleStoreBase(ModuleStore): ...@@ -456,3 +485,10 @@ class ModuleStoreBase(ModuleStore):
if c.id == course_id: if c.id == course_id:
return c return c
return None return None
class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite):
'''
Implement interface functionality that can be shared.
'''
pass
...@@ -6,14 +6,14 @@ In this way, courses can be served up both - say - XMLModuleStore or MongoModule ...@@ -6,14 +6,14 @@ In this way, courses can be served up both - say - XMLModuleStore or MongoModule
IMPORTANT: This modulestore only supports READONLY applications, e.g. LMS IMPORTANT: This modulestore only supports READONLY applications, e.g. LMS
""" """
from . import ModuleStoreBase from . import ModuleStoreWriteBase
from xmodule.modulestore.django import create_modulestore_instance from xmodule.modulestore.django import create_modulestore_instance
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class MixedModuleStore(ModuleStoreBase): class MixedModuleStore(ModuleStoreWriteBase):
""" """
ModuleStore that can be backed by either XML or Mongo ModuleStore that can be backed by either XML or Mongo
""" """
...@@ -138,8 +138,11 @@ class MixedModuleStore(ModuleStoreBase): ...@@ -138,8 +138,11 @@ class MixedModuleStore(ModuleStoreBase):
def get_modulestore_type(self, course_id): def get_modulestore_type(self, course_id):
""" """
Returns a type which identifies which modulestore is servicing the given Returns a type which identifies which modulestore is servicing the given course_id.
course_id. The return can be either "xml" (for XML based courses) or "mongo" for MongoDB backed courses The return can be one of:
"xml" (for XML based courses),
"mongo" for old-style MongoDB backed courses,
"split" for new-style split MongoDB backed courses.
""" """
return self._get_modulestore_for_courseid(course_id).get_modulestore_type(course_id) return self._get_modulestore_for_courseid(course_id).get_modulestore_type(course_id)
......
...@@ -31,7 +31,7 @@ from xblock.runtime import DbModel ...@@ -31,7 +31,7 @@ from xblock.runtime import DbModel
from xblock.exceptions import InvalidScopeError from xblock.exceptions import InvalidScopeError
from xblock.fields import Scope, ScopeIds from xblock.fields import Scope, ScopeIds
from xmodule.modulestore import ModuleStoreBase, Location, MONGO_MODULESTORE_TYPE from xmodule.modulestore import ModuleStoreWriteBase, Location, MONGO_MODULESTORE_TYPE
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.inheritance import own_metadata, InheritanceMixin, inherit_metadata, InheritanceKeyValueStore from xmodule.modulestore.inheritance import own_metadata, InheritanceMixin, inherit_metadata, InheritanceKeyValueStore
import re import re
...@@ -251,7 +251,7 @@ def metadata_cache_key(location): ...@@ -251,7 +251,7 @@ def metadata_cache_key(location):
return u"{0.org}/{0.course}".format(location) return u"{0.org}/{0.course}".format(location)
class MongoModuleStore(ModuleStoreBase): class MongoModuleStore(ModuleStoreWriteBase):
""" """
A Mongodb backed ModuleStore A Mongodb backed ModuleStore
""" """
...@@ -850,8 +850,11 @@ class MongoModuleStore(ModuleStoreBase): ...@@ -850,8 +850,11 @@ class MongoModuleStore(ModuleStoreBase):
def get_modulestore_type(self, course_id): def get_modulestore_type(self, course_id):
""" """
Returns a type which identifies which modulestore is servicing the given Returns an enumeration-like type reflecting the type of this modulestore
course_id. The return can be either "xml" (for XML based courses) or "mongo" for MongoDB backed courses The return can be one of:
"xml" (for XML based courses),
"mongo" for old-style MongoDB backed courses,
"split" for new-style split MongoDB backed courses.
""" """
return MONGO_MODULESTORE_TYPE return MONGO_MODULESTORE_TYPE
......
...@@ -13,7 +13,7 @@ from xmodule.errortracker import null_error_tracker ...@@ -13,7 +13,7 @@ from xmodule.errortracker import null_error_tracker
from xmodule.x_module import XModuleDescriptor from xmodule.x_module import XModuleDescriptor
from xmodule.modulestore.locator import BlockUsageLocator, DefinitionLocator, CourseLocator, VersionTree, LocalId from xmodule.modulestore.locator import BlockUsageLocator, DefinitionLocator, CourseLocator, VersionTree, LocalId
from xmodule.modulestore.exceptions import InsufficientSpecificationError, VersionConflictError, DuplicateItemError from xmodule.modulestore.exceptions import InsufficientSpecificationError, VersionConflictError, DuplicateItemError
from xmodule.modulestore import inheritance, ModuleStoreBase, Location from xmodule.modulestore import inheritance, ModuleStoreWriteBase, Location, SPLIT_MONGO_MODULESTORE_TYPE
from ..exceptions import ItemNotFoundError from ..exceptions import ItemNotFoundError
from .definition_lazy_loader import DefinitionLazyLoader from .definition_lazy_loader import DefinitionLazyLoader
...@@ -44,7 +44,7 @@ log = logging.getLogger(__name__) ...@@ -44,7 +44,7 @@ log = logging.getLogger(__name__)
#============================================================================== #==============================================================================
class SplitMongoModuleStore(ModuleStoreBase): class SplitMongoModuleStore(ModuleStoreWriteBase):
""" """
A Mongodb backed ModuleStore supporting versions, inheritance, A Mongodb backed ModuleStore supporting versions, inheritance,
and sharing. and sharing.
...@@ -1455,3 +1455,13 @@ class SplitMongoModuleStore(ModuleStoreBase): ...@@ -1455,3 +1455,13 @@ class SplitMongoModuleStore(ModuleStoreBase):
if 'category' in fields: if 'category' in fields:
del fields['category'] del fields['category']
return fields return fields
def get_modulestore_type(self, course_id):
"""
Returns an enumeration-like type reflecting the type of this modulestore
The return can be one of:
"xml" (for XML based courses),
"mongo" for old-style MongoDB backed courses,
"split" for new-style split MongoDB backed courses.
"""
return SPLIT_MONGO_MODULESTORE_TYPE
"""
Simple test to ensure that modulestore base classes remain abstract
"""
from unittest import TestCase
from xmodule.modulestore import ModuleStoreRead, ModuleStoreWrite
class AbstractionTest(TestCase):
"""
Tests that the ModuleStore objects are properly abstracted
"""
def test_cant_instantiate_abstract_class(self):
self.assertRaises(TypeError, ModuleStoreRead) # Cannot be instantiated due to explicit abstraction
self.assertRaises(TypeError, ModuleStoreWrite)
...@@ -3,10 +3,11 @@ from nose.tools import assert_equals, assert_raises # pylint: disable=E0611 ...@@ -3,10 +3,11 @@ from nose.tools import assert_equals, assert_raises # pylint: disable=E0611
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.search import path_to_location from xmodule.modulestore.search import path_to_location
def check_path_to_location(modulestore): def check_path_to_location(modulestore):
'''Make sure that path_to_location works: should be passed a modulestore """
with the toy and simple courses loaded.''' Make sure that path_to_location works: should be passed a modulestore
with the toy and simple courses loaded.
"""
should_work = ( should_work = (
("i4x://edX/toy/video/Welcome", ("i4x://edX/toy/video/Welcome",
("edX/toy/2012_Fall", "Overview", "Welcome", None)), ("edX/toy/2012_Fall", "Overview", "Welcome", None)),
......
...@@ -13,7 +13,7 @@ from xblock.runtime import KeyValueStore ...@@ -13,7 +13,7 @@ from xblock.runtime import KeyValueStore
from xblock.exceptions import InvalidScopeError from xblock.exceptions import InvalidScopeError
from xmodule.tests import DATA_DIR from xmodule.tests import DATA_DIR
from xmodule.modulestore import Location from xmodule.modulestore import Location, MONGO_MODULESTORE_TYPE
from xmodule.modulestore.mongo import MongoModuleStore, MongoKeyValueStore from xmodule.modulestore.mongo import MongoModuleStore, MongoKeyValueStore
from xmodule.modulestore.draft import DraftModuleStore from xmodule.modulestore.draft import DraftModuleStore
from xmodule.modulestore.xml_importer import import_from_xml, perform_xlint from xmodule.modulestore.xml_importer import import_from_xml, perform_xlint
...@@ -122,7 +122,7 @@ class TestMongoModuleStore(object): ...@@ -122,7 +122,7 @@ class TestMongoModuleStore(object):
{'host': HOST, 'db': DB, 'collection': COLLECTION}, {'host': HOST, 'db': DB, 'collection': COLLECTION},
FS_ROOT, RENDER_TEMPLATE, default_class=DEFAULT_CLASS FS_ROOT, RENDER_TEMPLATE, default_class=DEFAULT_CLASS
) )
assert_equals(store.get_modulestore_type('foo/bar/baz'), 'mongo') assert_equals(store.get_modulestore_type('foo/bar/baz'), MONGO_MODULESTORE_TYPE)
def test_get_courses(self): def test_get_courses(self):
'''Make sure the course objects loaded properly''' '''Make sure the course objects loaded properly'''
......
...@@ -24,7 +24,7 @@ from xblock.core import XBlock ...@@ -24,7 +24,7 @@ from xblock.core import XBlock
from xblock.fields import ScopeIds from xblock.fields import ScopeIds
from xblock.field_data import DictFieldData from xblock.field_data import DictFieldData
from . import ModuleStoreBase, Location, XML_MODULESTORE_TYPE from . import ModuleStoreReadBase, Location, XML_MODULESTORE_TYPE
from .exceptions import ItemNotFoundError from .exceptions import ItemNotFoundError
from .inheritance import compute_inherited_metadata from .inheritance import compute_inherited_metadata
...@@ -292,7 +292,7 @@ class ParentTracker(object): ...@@ -292,7 +292,7 @@ class ParentTracker(object):
return list(self._parents[child]) return list(self._parents[child])
class XMLModuleStore(ModuleStoreBase): class XMLModuleStore(ModuleStoreReadBase):
""" """
An XML backed ModuleStore An XML backed ModuleStore
""" """
...@@ -651,7 +651,10 @@ class XMLModuleStore(ModuleStoreBase): ...@@ -651,7 +651,10 @@ class XMLModuleStore(ModuleStoreBase):
def get_modulestore_type(self, course_id): def get_modulestore_type(self, course_id):
""" """
Returns a type which identifies which modulestore is servicing the given Returns an enumeration-like type reflecting the type of this modulestore
course_id. The return can be either "xml" (for XML based courses) or "mongo" for MongoDB backed courses The return can be one of:
"xml" (for XML based courses),
"mongo" for old-style MongoDB backed courses,
"split" for new-style split MongoDB backed courses.
""" """
return XML_MODULESTORE_TYPE return XML_MODULESTORE_TYPE
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment