Commit e2ba1122 by Slater Committed by Slater-Victoroff

Proper abstraction of Module store base class implemented

Read/Write inheritance split up into separate base classes
parent 80c83f0b
......@@ -8,11 +8,14 @@ import re
from collections import namedtuple
from abc import ABCMeta, abstractmethod
from .exceptions import InvalidLocationError, InsufficientSpecificationError
from xmodule.errortracker import make_error_tracker
log = logging.getLogger('mitx.' + 'modulestore')
SPLIT_MONGO_MODULESTORE_TYPE = 'split'
MONGO_MODULESTORE_TYPE = 'mongo'
XML_MODULESTORE_TYPE = 'xml'
......@@ -254,17 +257,22 @@ class Location(_LocationBase):
return self._replace(**kwargs)
class ModuleStore(object):
class ModuleStoreRead(object):
"""
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):
"""
Returns True if location exists in this ModuleStore.
"""
raise NotImplementedError
pass
@abstractmethod
def get_item(self, location, depth=0):
"""
Returns an XModuleDescriptor instance for the item at location.
......@@ -282,15 +290,17 @@ class ModuleStore(object):
in the request. The depth is counted in the number of calls to
get_children() to cache. None indicates to cache all descendents
"""
raise NotImplementedError
pass
@abstractmethod
def get_instance(self, course_id, location, depth=0):
"""
Get an instance of this location, with policy for course_id applied.
TODO (vshnayder): this may want to live outside the modulestore eventually
"""
raise NotImplementedError
pass
@abstractmethod
def get_item_errors(self, location):
"""
Return a list of (msg, exception-or-None) errors that the modulestore
......@@ -301,8 +311,9 @@ class ModuleStore(object):
Raises the same exceptions as get_item if the location isn't found or
isn't fully specified.
"""
raise NotImplementedError
pass
@abstractmethod
def get_items(self, location, course_id=None, depth=0):
"""
Returns a list of XModuleDescriptor instances for the items
......@@ -316,8 +327,58 @@ class ModuleStore(object):
in the request. The depth is counted in the number of calls to
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):
"""
Set the data in the item specified by the location to
......@@ -326,8 +387,9 @@ class ModuleStore(object):
location: Something that can be passed to Location
data: A nested dictionary of problem data
"""
raise NotImplementedError
pass
@abstractmethod
def update_children(self, location, children):
"""
Set the children for the item specified by the location to
......@@ -336,8 +398,9 @@ class ModuleStore(object):
location: Something that can be passed to Location
children: A list of child item identifiers
"""
raise NotImplementedError
pass
@abstractmethod
def update_metadata(self, location, metadata):
"""
Set the metadata for the item specified by the location to
......@@ -346,58 +409,24 @@ class ModuleStore(object):
location: Something that can be passed to Location
metadata: A nested dictionary of module metadata
"""
raise NotImplementedError
pass
@abstractmethod
def delete_item(self, location):
"""
Delete an item from this modulestore
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):
'''
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):
class ModuleStoreReadBase(ModuleStoreRead):
'''
Implement interface functionality that can be shared.
'''
# pylint: disable=W0613
def __init__(
self,
doc_store_config=None, # ignore if passed up
......@@ -456,3 +485,10 @@ class ModuleStoreBase(ModuleStore):
if c.id == course_id:
return c
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
IMPORTANT: This modulestore only supports READONLY applications, e.g. LMS
"""
from . import ModuleStoreBase
from . import ModuleStoreWriteBase
from xmodule.modulestore.django import create_modulestore_instance
import logging
log = logging.getLogger(__name__)
class MixedModuleStore(ModuleStoreBase):
class MixedModuleStore(ModuleStoreWriteBase):
"""
ModuleStore that can be backed by either XML or Mongo
"""
......@@ -138,8 +138,11 @@ class MixedModuleStore(ModuleStoreBase):
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
Returns a type which identifies which modulestore is servicing the given course_id.
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)
......
......@@ -31,7 +31,7 @@ from xblock.runtime import DbModel
from xblock.exceptions import InvalidScopeError
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.inheritance import own_metadata, InheritanceMixin, inherit_metadata, InheritanceKeyValueStore
import re
......@@ -251,7 +251,7 @@ def metadata_cache_key(location):
return u"{0.org}/{0.course}".format(location)
class MongoModuleStore(ModuleStoreBase):
class MongoModuleStore(ModuleStoreWriteBase):
"""
A Mongodb backed ModuleStore
"""
......@@ -850,8 +850,11 @@ class MongoModuleStore(ModuleStoreBase):
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
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 MONGO_MODULESTORE_TYPE
......
......@@ -13,7 +13,7 @@ from xmodule.errortracker import null_error_tracker
from xmodule.x_module import XModuleDescriptor
from xmodule.modulestore.locator import BlockUsageLocator, DefinitionLocator, CourseLocator, VersionTree, LocalId
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 .definition_lazy_loader import DefinitionLazyLoader
......@@ -44,7 +44,7 @@ log = logging.getLogger(__name__)
#==============================================================================
class SplitMongoModuleStore(ModuleStoreBase):
class SplitMongoModuleStore(ModuleStoreWriteBase):
"""
A Mongodb backed ModuleStore supporting versions, inheritance,
and sharing.
......@@ -1455,3 +1455,13 @@ class SplitMongoModuleStore(ModuleStoreBase):
if 'category' in fields:
del fields['category']
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
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.search import path_to_location
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 = (
("i4x://edX/toy/video/Welcome",
("edX/toy/2012_Fall", "Overview", "Welcome", None)),
......
......@@ -13,7 +13,7 @@ from xblock.runtime import KeyValueStore
from xblock.exceptions import InvalidScopeError
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.draft import DraftModuleStore
from xmodule.modulestore.xml_importer import import_from_xml, perform_xlint
......@@ -122,7 +122,7 @@ class TestMongoModuleStore(object):
{'host': HOST, 'db': DB, 'collection': COLLECTION},
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):
'''Make sure the course objects loaded properly'''
......
......@@ -24,7 +24,7 @@ from xblock.core import XBlock
from xblock.fields import ScopeIds
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 .inheritance import compute_inherited_metadata
......@@ -292,7 +292,7 @@ class ParentTracker(object):
return list(self._parents[child])
class XMLModuleStore(ModuleStoreBase):
class XMLModuleStore(ModuleStoreReadBase):
"""
An XML backed ModuleStore
"""
......@@ -651,7 +651,10 @@ class XMLModuleStore(ModuleStoreBase):
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
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 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