diff --git a/octavia/common/decorators.py b/octavia/common/decorators.py new file mode 100644 index 0000000000..93987e5349 --- /dev/null +++ b/octavia/common/decorators.py @@ -0,0 +1,54 @@ +# Copyright 2016 +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Decorators to provide backwards compatibility for V1 API.""" + + +def rename_kwargs(**renamed_kwargs): + """Renames a class's variables and maintains backwards compatibility. + + :param renamed_kwargs: mapping of old kwargs to new kwargs. For example, + to say a class has renamed variable foo to bar the decorator would + be used like: rename_kwargs(foo='bar') + + """ + + def wrap(cls): + def __getattr__(instance, name): + if name in renamed_kwargs: + return getattr(instance, renamed_kwargs[name]) + return getattr(instance, name) + + def __setattr__(instance, name, value): + if name in renamed_kwargs: + instance.__dict__[renamed_kwargs[name]] = value + else: + instance.__dict__[name] = value + + def wrapped_cls(*args, **kwargs): + # Handle cases of inner classes being called with self + # For example: self.TestClass(1, a=1) + if (args and hasattr(args[0], '__class__') and + hasattr(args[0].__class__, cls.__name__)): + args = args[1:] if args else tuple() + for old_kwarg, new_kwarg in renamed_kwargs.items(): + if old_kwarg in kwargs: + kwargs[new_kwarg] = kwargs[old_kwarg] + del kwargs[old_kwarg] + cls.__getattr__ = __getattr__ + cls.__setattr__ = __setattr__ + return cls(*args, **kwargs) + return wrapped_cls + return wrap \ No newline at end of file diff --git a/octavia/tests/unit/common/test_decorator.py b/octavia/tests/unit/common/test_decorator.py new file mode 100644 index 0000000000..072222f85e --- /dev/null +++ b/octavia/tests/unit/common/test_decorator.py @@ -0,0 +1,56 @@ +# Copyright 2016 +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +"""Test for the decorator which provides backward compatibility for V1 API.""" + +import octavia.common.decorators as dec +import octavia.tests.unit.base as base + + +class TestDecorator(base.TestCase): + + @dec.rename_kwargs(a='z') + class TestClass(object): + def __init__(self, x, z=None): + self.x = x + self.z = z + + @dec.rename_kwargs(a='z') + class TestClassDupe(object): + def __init__(self, x, z=None): + self.x = x + self.z = z + + def test_get(self): + obj = self.TestClass(1, a=3) + self.assertEqual(1, obj.x) + self.assertEqual(3, obj.z) + self.assertEqual(obj.z, obj.a) + + def test_set(self): + obj = self.TestClass(1, a=3) + obj.a = 5 + self.assertEqual(1, obj.x) + self.assertEqual(5, obj.z) + self.assertEqual(obj.z, obj.a) + + def test_similar_classes(self): + obj = self.TestClass(1, z=5) + obj2 = self.TestClassDupe(2, z=10) + self.assertEqual(5, obj.z) + self.assertEqual(10, obj2.z) + self.assertEqual(obj.z, obj.a) + self.assertEqual(obj2.z, obj2.a)