Skip to content

Commit

Permalink
Ensure drivers loaded before failing
Browse files Browse the repository at this point in the history
so that deprecated are known
PLAT-132
  • Loading branch information
Don Mitchell committed Oct 9, 2014
1 parent af629a1 commit 6ade2a8
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 29 deletions.
53 changes: 25 additions & 28 deletions opaque_keys/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from functools import total_ordering

from stevedore.enabled import EnabledExtensionManager
from _collections import defaultdict


class InvalidKeyError(Exception):
Expand All @@ -35,9 +36,6 @@ def __new__(mcs, name, bases, attrs):
return super(OpaqueKeyMetaclass, mcs).__new__(mcs, name, bases, attrs)


PLUGIN_CACHE = {}


@total_ordering
class OpaqueKey(object):
"""
Expand Down Expand Up @@ -181,6 +179,8 @@ def from_string(cls, serialized):
raise InvalidKeyError(cls, serialized)

# pylint: disable=protected-access
# load drivers before checking for attr
cls._drivers()
try:
namespace, rest = cls._separate_namespace(serialized)
return cls.get_namespace_plugin(namespace)._from_string(rest)
Expand Down Expand Up @@ -220,37 +220,34 @@ def get_namespace_plugin(cls, namespace):
# The cache is stored per-calling-class, rather than per-KEY_TYPE,
# because we should raise InvalidKeyError if the namespace
# doesn't specify a subclass of cls
cache_key = (cls, namespace)
if cache_key not in PLUGIN_CACHE:
# Ensure all extensions are loaded. Extensions may modify the deprecated_fallback attribute of the class, so
# they must be loaded before processing any keys.
drivers = cls._drivers()

try:
PLUGIN_CACHE[cache_key] = drivers[namespace].plugin
except KeyError as key_error:
# Cache that the namespace doesn't correspond to a known plugin,
# so that we don't waste time checking every time we hit
# a particular unknown namespace (like i4x)
PLUGIN_CACHE[cache_key] = InvalidKeyError(cls, '{}:*'.format(namespace))

plugin = PLUGIN_CACHE[cache_key]
if isinstance(plugin, Exception):
raise plugin
else:
return plugin

# Ensure all extensions are loaded. Extensions may modify the deprecated_fallback attribute of the class, so
# they must be loaded before processing any keys.
drivers = cls._drivers()

try:
return drivers[namespace].plugin
except KeyError:
# Cache that the namespace doesn't correspond to a known plugin,
# so that we don't waste time checking every time we hit
# a particular unknown namespace (like i4x)
raise InvalidKeyError(cls, '{}:*'.format(namespace))

LOADED_DRIVERS = defaultdict() # If you change default, change test_default_deprecated

@classmethod
def _drivers(cls):
"""
Return a driver manager for all key classes that are
subclasses of `cls`.
"""
return EnabledExtensionManager(
cls.KEY_TYPE, # pylint: disable=no-member
check_func=lambda extension: issubclass(extension.plugin, cls),
invoke_on_load=False,
)
if cls not in cls.LOADED_DRIVERS:
cls.LOADED_DRIVERS[cls] = EnabledExtensionManager(
cls.KEY_TYPE, # pylint: disable=no-member
check_func=lambda extension: issubclass(extension.plugin, cls),
invoke_on_load=False,
)
return cls.LOADED_DRIVERS[cls]

@classmethod
def set_deprecated_fallback(cls, fallback):
Expand Down Expand Up @@ -294,7 +291,7 @@ def _checked_init(self, *args, **kwargs):

keyed_args.update(kwargs)

for key, value in keyed_args.viewitems():
for key in keyed_args.viewkeys():
if key not in self.KEY_FIELDS:
raise TypeError('__init__() got an unexpected argument {!r}'.format(key))

Expand Down
2 changes: 1 addition & 1 deletion opaque_keys/edx/locator.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import re
import warnings
from bson.son import SON
from abc import abstractmethod, abstractproperty
from abc import abstractproperty

from bson.objectid import ObjectId
from bson.errors import InvalidId
Expand Down
32 changes: 32 additions & 0 deletions opaque_keys/edx/tests/test_default_deprecated.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
Test that old keys deserialize just by importing opaque keys
"""
from unittest import TestCase
from opaque_keys.edx.keys import CourseKey, UsageKey


class TestDefault(TestCase):
"""
Check that clients which merely import CourseKey can deserialize the expected keys, etc
"""
def test_course_key(self):
"""
Test CourseKey
"""
CourseKey.LOADED_DRIVERS.clear()
key = CourseKey.from_string('org.id/course_id/run')
self.assertEqual(key.org, 'org.id')

key = CourseKey.from_string('course-v1:org.id+course_id+run')
self.assertEqual(key.org, 'org.id')

def test_usage_key(self):
"""
Test UsageKey
"""
UsageKey.LOADED_DRIVERS.clear()
key = UsageKey.from_string('i4x://org.id/course_id/category/block_id')
self.assertEqual(key.block_id, 'block_id')

key = UsageKey.from_string('block-v1:org.id+course_id+run+type@category+block@block_id')
self.assertEqual(key.block_id, 'block_id')

0 comments on commit 6ade2a8

Please sign in to comment.