From d4aeb5e9b6c40b75910adecec8e242cbc9eb6e74 Mon Sep 17 00:00:00 2001 From: Konrad Weihmann Date: Thu, 30 Mar 2023 10:15:51 +0200 Subject: [PATCH] versionary: add get_applicable method which adds the possibility to select a version based on passed minium and maximum versions requirements, so we do not need to know what versions are actually implemented and can select the highest available version within the given boundaries. The earliest implementation can be picked by choosing func=min. If no version was found a new exception NoApplicableVersion is raised Signed-off-by: Konrad Weihmann --- CONTRIBUTORS.md | 2 ++ tests/test_decorator.py | 23 ++++++++++++++++++++++- tests/test_utils.py | 2 +- versionary/decorators.py | 2 +- versionary/exceptions.py | 7 +++++++ versionary/utils.py | 14 ++++++++++++-- 6 files changed, 45 insertions(+), 5 deletions(-) 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)): """