Skip to content

Instantly share code, notes, and snippets.

@vpodzime
Last active January 25, 2016 15:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vpodzime/4e35ff78286814a7d102 to your computer and use it in GitHub Desktop.
Save vpodzime/4e35ff78286814a7d102 to your computer and use it in GitHub Desktop.
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