Last active
January 25, 2016 15:51
-
-
Save vpodzime/4e35ff78286814a7d102 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
commit 04f7cc5d1a9d1e8f180394e90953c16255b48b24 | |
Author: Vratislav Podzimek <vpodzime@redhat.com> | |
Date: Mon Jan 25 16:26:19 2016 +0100 | |
Add a test for the to-be-used @type_specific decorator | |
When objects of various (sub)types need to be represented by a single class | |
(e.g. because their (sub)types can change in time), it's nice to implement the | |
(sub)type specific code in mixin classes the single resulting class inherits | |
from. With thatthere are two (major) options how to implement the methods of | |
such "ultimate" class: | |
1) Have gigantic ifs at the beginning of the methods making sure the right code | |
is executed for the particular object and its (sub)type. | |
2) Implement and use the @type_specific decorator that makes sure the method | |
of the right mixin class is called. | |
This adds an example implementation of 2) included in the test. A generic | |
@type_specific decorator would be even more complex and harder to use which is | |
why the decorator is tailored for the particular class and lives under its | |
namespace. | |
diff --git a/tests/devices_test/lvm_test.py b/tests/devices_test/lvm_test.py | |
index b153ce7..1715345 100644 | |
--- a/tests/devices_test/lvm_test.py | |
+++ b/tests/devices_test/lvm_test.py | |
@@ -370,3 +370,134 @@ class LVMDeviceTest(unittest.TestCase): | |
lv.target_size = orig_size | |
self.assertEqual(lv.target_size, orig_size) | |
self.assertEqual(lv.size, orig_size) | |
+ | |
+class TypeSpecificCallsTest(unittest.TestCase): | |
+ def test_type_specific_calls(self): | |
+ class A(object): | |
+ def __init__(self, a): | |
+ self._a = a | |
+ | |
+ @property | |
+ def is_a(self): | |
+ return self._a == "A" | |
+ | |
+ def say_hello(self): | |
+ return "Hello from A" | |
+ | |
+ @property | |
+ def greeting(self): | |
+ return self._greet or "Hi, this is A" | |
+ | |
+ @greeting.setter | |
+ def greeting(self, val): | |
+ self._greet = "Set by A: %s" % val | |
+ | |
+ class B(object): | |
+ def __init__(self, b): | |
+ self._b = b | |
+ | |
+ @property | |
+ def is_b(self): | |
+ return self._b == "B" | |
+ | |
+ def say_hello(self): | |
+ return "Hello from B" | |
+ | |
+ @property | |
+ def greeting(self): | |
+ return self._greet or "Hi, this is B" | |
+ | |
+ @greeting.setter | |
+ def greeting(self, val): | |
+ self._greet = "Set by B: %s" % val | |
+ | |
+ class C(A, B): | |
+ def __init__(self, a, b): | |
+ A.__init__(self, a) | |
+ B.__init__(self, b) | |
+ self._greet = None | |
+ | |
+ def _get_type_classes(self): | |
+ """Method to get type classes for this particular instance""" | |
+ ret = [] | |
+ if self.is_a: | |
+ ret.append(A) | |
+ if self.is_b: | |
+ ret.append(B) | |
+ return ret | |
+ | |
+ def _try_specific_call(self, method, *args, **kwargs): | |
+ """Try to call a type-specific method for this particular instance""" | |
+ clss = self._get_type_classes() | |
+ for cls in clss: | |
+ if hasattr(cls, method): | |
+ # found, get the specific property | |
+ if isinstance(vars(cls)[method], property): | |
+ if len(args) == 0 and len(kwargs.keys()) == 0: | |
+ # this is how you call the getter method of the property object | |
+ ret = getattr(cls, method).__get__(self) | |
+ else: | |
+ # this is how you call the setter method of the property object | |
+ ret = getattr(cls, method).__set__(self, *args, **kwargs) | |
+ else: | |
+ # or call the type-specific method | |
+ ret = getattr(cls, method)(self, *args, **kwargs) | |
+ return (True, ret) | |
+ # not found, let the caller know | |
+ return (False, None) | |
+ | |
+ # decorator | |
+ def type_specific(meth): | |
+ """Decorator that makes sure the type-specific code is executed if available""" | |
+ def decorated(self, *args, **kwargs): | |
+ found, ret = self._try_specific_call(meth.__name__, *args, **kwargs) | |
+ if found: | |
+ # nothing more to do here | |
+ return ret | |
+ else: | |
+ return meth(self, *args, **kwargs) | |
+ | |
+ return decorated | |
+ | |
+ @type_specific | |
+ def say_hello(self): | |
+ return "Hello from C" | |
+ | |
+ @property | |
+ @type_specific | |
+ def greeting(self): | |
+ return self._greet or "Hi, this is C" | |
+ | |
+ @greeting.setter | |
+ @type_specific | |
+ def greeting(self, val): | |
+ self._greet = val | |
+ | |
+ # a non-specific instance | |
+ c = C(a="x", b="y") | |
+ self.assertEqual(c.say_hello(), "Hello from C") | |
+ self.assertEqual(c.greeting, "Hi, this is C") | |
+ c.greeting = "Welcome" | |
+ self.assertEqual(c.greeting, "Welcome") | |
+ | |
+ # an A-specific instance | |
+ c = C(a="A", b="y") | |
+ self.assertEqual(c.say_hello(), "Hello from A") | |
+ self.assertEqual(c.greeting, "Hi, this is A") | |
+ c.greeting = "Welcome" | |
+ self.assertEqual(c.greeting, "Set by A: Welcome") | |
+ | |
+ # a B-specific instance | |
+ c = C(a="x", b="B") | |
+ self.assertEqual(c.say_hello(), "Hello from B") | |
+ self.assertEqual(c.greeting, "Hi, this is B") | |
+ c.greeting = "Welcome" | |
+ self.assertEqual(c.greeting, "Set by B: Welcome") | |
+ | |
+ # both A-B-specific instance | |
+ # A is listed first so it should win | |
+ c = C(a="A", b="B") | |
+ self.assertEqual(c.say_hello(), "Hello from A") | |
+ self.assertEqual(c.greeting, "Hi, this is A") | |
+ c.greeting = "Welcome" | |
+ self.assertEqual(c.greeting, "Set by A: Welcome") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment