diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index a342557..7e54b34 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -11,3 +11,5 @@ Keep the list in alfabetical order please. * Devhouse Spindle {opensource@wearespindle.com} * Marco Vellinga {info@itstars.nl} + * Konrad Weihmann {kweihmann@outlook.com} + diff --git a/tests/test_decorator.py b/tests/test_decorator.py index 9028113..cfcff7b 100644 --- a/tests/test_decorator.py +++ b/tests/test_decorator.py @@ -1,7 +1,7 @@ from pytest import raises from versionary.decorators import versioned -from versionary.exceptions import DuplicateVersionException, InheritanceException, InvalidVersionException +from versionary.exceptions import DuplicateVersionException, InheritanceException, InvalidVersionException, NoApplicableVersion from .utils.mixed_members import MixedClass, mixed_func from .utils.versioned_members import MyClass, MyInheritanceClass, my_function, your_function @@ -24,6 +24,27 @@ def test_decorator_for_classes_without_number(): assert MyClass.v2().hello() == 4 +def test_decorator_for_classes_get_applicable_max(): + """ + Test the decorator for latest classes via get_applicable. + """ + assert MyClass.get_applicable()().hello() == 4 + assert MyClass.get_applicable(minver=2)().hello() == 4 + assert MyClass.get_applicable(maxver=1)().hello() == 3 + with raises(NoApplicableVersion): + MyClass.get_applicable(minver=100)().hello() + +def test_decorator_for_classes_get_applicable_min(): + """ + Test the decorator for earliest classes via get_applicable. + """ + assert MyClass.get_applicable(func=min)().hello() == 3 + assert MyClass.get_applicable(minver=2, func=min)().hello() == 4 + assert MyClass.get_applicable(maxver=1, func=min)().hello() == 3 + with raises(NoApplicableVersion): + MyClass.get_applicable(minver=100, func=min)().hello() + + def test_decorator_for_class_inheritance_without_number(): """ Test the decorator for classes that inherit without a number. diff --git a/tests/test_utils.py b/tests/test_utils.py index 15ae55e..2910e4b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -29,7 +29,7 @@ def test_create_proxy_class(): """ base_name = 'base_name' - proxy = create_proxy_class(base_name) + proxy = create_proxy_class(base_name, 123) message = 'Cannot call `base_name` directly, use versioned attributes instead (`base_name`.vX)' diff --git a/versionary/decorators.py b/versionary/decorators.py index 6cb0048..b29cd08 100644 --- a/versionary/decorators.py +++ b/versionary/decorators.py @@ -116,7 +116,7 @@ def wrap(member): validate_inheritance_for_class(member) # Get or create the proxy class to return. - proxy = version_members.get('proxy', create_proxy_class(name)) + proxy = version_members.get('proxy', create_proxy_class(name, module)) # Add new version method to this proxy class. setattr(proxy, '%s%s' % (PROXY_VERSION_TAG, version), staticmethod(member)) diff --git a/versionary/exceptions.py b/versionary/exceptions.py index 432b382..a19beab 100644 --- a/versionary/exceptions.py +++ b/versionary/exceptions.py @@ -41,3 +41,10 @@ class NotCallableException(Exception): Raised when trying to call the ProxyClass which shouldn't be called. """ pass + + +class NoApplicableVersion(Exception): + """ + Raised when no version matching the criteria was found. + """ + pass diff --git a/versionary/utils.py b/versionary/utils.py index f4dd61c..60b87c8 100644 --- a/versionary/utils.py +++ b/versionary/utils.py @@ -4,7 +4,7 @@ from six import with_metaclass -from .exceptions import InvalidVersionException, NotCallableException +from .exceptions import InvalidVersionException, NotCallableException, NoApplicableVersion CLASS, FUNCTION = 0, 1 CLASS_VERSION_TAG = 'V' @@ -37,7 +37,7 @@ def determine_member_type(member): return member_type -def create_proxy_class(base_name): +def create_proxy_class(base_name, mod): """ Function to create a proxy class to be able to set attributes to. This is used to make my_func.v1() or MyClass.v1() possible. @@ -83,7 +83,17 @@ def __getattr__(cls, name): raise AttributeError('%r has no attribute %r' % (cls._base_name, name)) + def get_applicable(cls, minver=0, maxver=10e9, func=max): + version_table = getattr(cls._modref, '__version_table__') + try: + best_version = func([x for x in version_table[cls._base_name] + ['members'].keys() if x <= maxver and x >= minver]) + return version_table[cls._base_name]['members'][best_version] + except ValueError: + raise NoApplicableVersion() + setattr(ProxyType, '_base_name', base_name) + setattr(ProxyType, '_modref', mod) class ProxyClass(with_metaclass(ProxyType)): """