diff options
Diffstat (limited to 'src/yum/providers/util/singletonmixin.py')
-rw-r--r-- | src/yum/providers/util/singletonmixin.py | 508 |
1 files changed, 508 insertions, 0 deletions
diff --git a/src/yum/providers/util/singletonmixin.py b/src/yum/providers/util/singletonmixin.py new file mode 100644 index 0000000..8051695 --- /dev/null +++ b/src/yum/providers/util/singletonmixin.py @@ -0,0 +1,508 @@ +""" +A Python Singleton mixin class that makes use of some of the ideas +found at http://c2.com/cgi/wiki?PythonSingleton. Just inherit +from it and you have a singleton. No code is required in +subclasses to create singleton behavior -- inheritance from +Singleton is all that is needed. + +Singleton creation is threadsafe. + +USAGE: + +Just inherit from Singleton. If you need a constructor, include +an __init__() method in your class as you usually would. However, +if your class is S, you instantiate the singleton using S.getInstance() +instead of S(). Repeated calls to S.getInstance() return the +originally-created instance. + +For example: + +class S(Singleton): + + def __init__(self, a, b=1): + pass + +S1 = S.getInstance(1, b=3) + + +Most of the time, that's all you need to know. However, there are some +other useful behaviors. Read on for a full description: + +1) Getting the singleton: + + S.getInstance() + +returns the instance of S. If none exists, it is created. + +2) The usual idiom to construct an instance by calling the class, i.e. + + S() + +is disabled for the sake of clarity. + +For one thing, the S() syntax means instantiation, but getInstance() +usually does not cause instantiation. So the S() syntax would +be misleading. + +Because of that, if S() were allowed, a programmer who didn't +happen to notice the inheritance from Singleton (or who +wasn't fully aware of what a Singleton pattern +does) might think he was creating a new instance, +which could lead to very unexpected behavior. + +So, overall, it is felt that it is better to make things clearer +by requiring the call of a class method that is defined in +Singleton. An attempt to instantiate via S() will result +in a SingletonException being raised. + +3) Use __S.__init__() for instantiation processing, +since S.getInstance() runs S.__init__(), passing it the args it has received. + +If no data needs to be passed in at instantiation time, you don't need S.__init__(). + +4) If S.__init__(.) requires parameters, include them ONLY in the +first call to S.getInstance(). If subsequent calls have arguments, +a SingletonException is raised by default. + +If you find it more convenient for subsequent calls to be allowed to +have arguments, but for those argumentsto be ignored, just include +'ignoreSubsequent = True' in your class definition, i.e.: + + class S(Singleton): + + ignoreSubsequent = True + + def __init__(self, a, b=1): + pass + +5) For testing, it is sometimes convenient for all existing singleton +instances to be forgotten, so that new instantiations can occur. For that +reason, a forgetAllSingletons() function is included. Just call + + forgetAllSingletons() + +and it is as if no earlier instantiations have occurred. + +6) As an implementation detail, classes that inherit +from Singleton may not have their own __new__ +methods. To make sure this requirement is followed, +an exception is raised if a Singleton subclass includ +es __new__. This happens at subclass instantiation +time (by means of the MetaSingleton metaclass. + + +By Gary Robinson, grobinson@flyfi.com. No rights reserved -- +placed in the public domain -- which is only reasonable considering +how much it owes to other people's code and ideas which are in the +public domain. The idea of using a metaclass came from +a comment on Gary's blog (see +http://www.garyrobinson.net/2004/03/python_singleto.html#comments). +Other improvements came from comments and email from other +people who saw it online. (See the blog post and comments +for further credits.) + +Not guaranteed to be fit for any particular purpose. Use at your +own risk. +""" + +import threading + +class SingletonException(Exception): + pass + +_stSingletons = set() +_lockForSingletons = threading.RLock() +_lockForSingletonCreation = threading.RLock() # Ensure only one instance of each Singleton + # class is created. This is not bound to the + # individual Singleton class since we need to + # ensure that there is only one mutex for each + # Singleton class, which would require having + # a lock when setting up the Singleton class, + # which is what this is anyway. So, when any + # Singleton is created, we lock this lock and + # then we don't need to lock it again for that + # class. + +def _createSingletonInstance(cls, lstArgs, dctKwArgs): + _lockForSingletonCreation.acquire() + try: + if cls._isInstantiated(): # some other thread got here first + return + + instance = cls.__new__(cls) + try: + instance.__init__(*lstArgs, **dctKwArgs) + except TypeError, e: + if e.message.find('__init__() takes') != -1: + raise SingletonException, 'If the singleton requires __init__ args, supply them on first call to getInstance().' + else: + raise + cls.cInstance = instance + _addSingleton(cls) + finally: + _lockForSingletonCreation.release() + +def _addSingleton(cls): + _lockForSingletons.acquire() + try: + assert cls not in _stSingletons + _stSingletons.add(cls) + finally: + _lockForSingletons.release() + +def _removeSingleton(cls): + _lockForSingletons.acquire() + try: + if cls in _stSingletons: + _stSingletons.remove(cls) + finally: + _lockForSingletons.release() + +def forgetAllSingletons(): + '''This is useful in tests, since it is hard to know which singletons need to be cleared to make a test work.''' + _lockForSingletons.acquire() + try: + for cls in _stSingletons.copy(): + cls._forgetClassInstanceReferenceForTesting() + + # Might have created some Singletons in the process of tearing down. + # Try one more time - there should be a limit to this. + iNumSingletons = len(_stSingletons) + if len(_stSingletons) > 0: + for cls in _stSingletons.copy(): + cls._forgetClassInstanceReferenceForTesting() + iNumSingletons -= 1 + assert iNumSingletons == len(_stSingletons), 'Added a singleton while destroying ' + str(cls) + assert len(_stSingletons) == 0, _stSingletons + finally: + _lockForSingletons.release() + +class MetaSingleton(type): + def __new__(metaclass, strName, tupBases, dct): + if dct.has_key('__new__'): + raise SingletonException, 'Can not override __new__ in a Singleton' + return super(MetaSingleton, metaclass).__new__(metaclass, strName, tupBases, dct) + + def __call__(cls, *lstArgs, **dictArgs): + raise SingletonException, 'Singletons may only be instantiated through getInstance()' + +class Singleton(object): + __metaclass__ = MetaSingleton + + def getInstance(cls, *lstArgs, **dctKwArgs): + """ + Call this to instantiate an instance or retrieve the existing instance. + If the singleton requires args to be instantiated, include them the first + time you call getInstance. + """ + if cls._isInstantiated(): + if (lstArgs or dctKwArgs) and not hasattr(cls, 'ignoreSubsequent'): + raise SingletonException, 'Singleton already instantiated, but getInstance() called with args.' + else: + _createSingletonInstance(cls, lstArgs, dctKwArgs) + + return cls.cInstance + getInstance = classmethod(getInstance) + + def _isInstantiated(cls): + # Don't use hasattr(cls, 'cInstance'), because that screws things up if there is a singleton that + # extends another singleton. hasattr looks in the base class if it doesn't find in subclass. + return 'cInstance' in cls.__dict__ + _isInstantiated = classmethod(_isInstantiated) + + # This can be handy for public use also + isInstantiated = _isInstantiated + + def _forgetClassInstanceReferenceForTesting(cls): + """ + This is designed for convenience in testing -- sometimes you + want to get rid of a singleton during test code to see what + happens when you call getInstance() under a new situation. + + To really delete the object, all external references to it + also need to be deleted. + """ + try: + if hasattr(cls.cInstance, '_prepareToForgetSingleton'): + # tell instance to release anything it might be holding onto. + cls.cInstance._prepareToForgetSingleton() + del cls.cInstance + _removeSingleton(cls) + except AttributeError: + # run up the chain of base classes until we find the one that has the instance + # and then delete it there + for baseClass in cls.__bases__: + if issubclass(baseClass, Singleton): + baseClass._forgetClassInstanceReferenceForTesting() + _forgetClassInstanceReferenceForTesting = classmethod(_forgetClassInstanceReferenceForTesting) + + +if __name__ == '__main__': + + import unittest + import time + + class singletonmixin_Public_TestCase(unittest.TestCase): + def testReturnsSameObject(self): + """ + Demonstrates normal use -- just call getInstance and it returns a singleton instance + """ + + class A(Singleton): + def __init__(self): + super(A, self).__init__() + + a1 = A.getInstance() + a2 = A.getInstance() + self.assertEquals(id(a1), id(a2)) + + def testInstantiateWithMultiArgConstructor(self): + """ + If the singleton needs args to construct, include them in the first + call to get instances. + """ + + class B(Singleton): + + def __init__(self, arg1, arg2): + super(B, self).__init__() + self.arg1 = arg1 + self.arg2 = arg2 + + b1 = B.getInstance('arg1 value', 'arg2 value') + b2 = B.getInstance() + self.assertEquals(b1.arg1, 'arg1 value') + self.assertEquals(b1.arg2, 'arg2 value') + self.assertEquals(id(b1), id(b2)) + + def testInstantiateWithKeywordArg(self): + + class B(Singleton): + + def __init__(self, arg1=5): + super(B, self).__init__() + self.arg1 = arg1 + + b1 = B.getInstance('arg1 value') + b2 = B.getInstance() + self.assertEquals(b1.arg1, 'arg1 value') + self.assertEquals(id(b1), id(b2)) + + def testTryToInstantiateWithoutNeededArgs(self): + + class B(Singleton): + + def __init__(self, arg1, arg2): + super(B, self).__init__() + self.arg1 = arg1 + self.arg2 = arg2 + + self.assertRaises(SingletonException, B.getInstance) + + def testPassTypeErrorIfAllArgsThere(self): + """ + Make sure the test for capturing missing args doesn't interfere with a normal TypeError. + """ + class B(Singleton): + + def __init__(self, arg1, arg2): + super(B, self).__init__() + self.arg1 = arg1 + self.arg2 = arg2 + raise TypeError, 'some type error' + + self.assertRaises(TypeError, B.getInstance, 1, 2) + + def testTryToInstantiateWithoutGetInstance(self): + """ + Demonstrates that singletons can ONLY be instantiated through + getInstance, as long as they call Singleton.__init__ during construction. + + If this check is not required, you don't need to call Singleton.__init__(). + """ + + class A(Singleton): + def __init__(self): + super(A, self).__init__() + + self.assertRaises(SingletonException, A) + + def testDontAllowNew(self): + + def instantiatedAnIllegalClass(): + class A(Singleton): + def __init__(self): + super(A, self).__init__() + + def __new__(metaclass, strName, tupBases, dct): + return super(MetaSingleton, metaclass).__new__(metaclass, strName, tupBases, dct) + + self.assertRaises(SingletonException, instantiatedAnIllegalClass) + + + def testDontAllowArgsAfterConstruction(self): + class B(Singleton): + + def __init__(self, arg1, arg2): + super(B, self).__init__() + self.arg1 = arg1 + self.arg2 = arg2 + + B.getInstance('arg1 value', 'arg2 value') + self.assertRaises(SingletonException, B, 'arg1 value', 'arg2 value') + + def test_forgetClassInstanceReferenceForTesting(self): + class A(Singleton): + def __init__(self): + super(A, self).__init__() + class B(A): + def __init__(self): + super(B, self).__init__() + + # check that changing the class after forgetting the instance produces + # an instance of the new class + a = A.getInstance() + assert a.__class__.__name__ == 'A' + A._forgetClassInstanceReferenceForTesting() + b = B.getInstance() + assert b.__class__.__name__ == 'B' + + # check that invoking the 'forget' on a subclass still deletes the instance + B._forgetClassInstanceReferenceForTesting() + a = A.getInstance() + B._forgetClassInstanceReferenceForTesting() + b = B.getInstance() + assert b.__class__.__name__ == 'B' + + def test_forgetAllSingletons(self): + # Should work if there are no singletons + forgetAllSingletons() + + class A(Singleton): + ciInitCount = 0 + def __init__(self): + super(A, self).__init__() + A.ciInitCount += 1 + + A.getInstance() + self.assertEqual(A.ciInitCount, 1) + + A.getInstance() + self.assertEqual(A.ciInitCount, 1) + + forgetAllSingletons() + A.getInstance() + self.assertEqual(A.ciInitCount, 2) + + def test_threadedCreation(self): + # Check that only one Singleton is created even if multiple + # threads try at the same time. If fails, would see assert in _addSingleton + class Test_Singleton(Singleton): + def __init__(self): + super(Test_Singleton, self).__init__() + + class Test_SingletonThread(threading.Thread): + def __init__(self, fTargetTime): + super(Test_SingletonThread, self).__init__() + self._fTargetTime = fTargetTime + self._eException = None + + def run(self): + try: + fSleepTime = self._fTargetTime - time.time() + if fSleepTime > 0: + time.sleep(fSleepTime) + Test_Singleton.getInstance() + except Exception, e: + self._eException = e + + fTargetTime = time.time() + 0.1 + lstThreads = [] + for _ in xrange(100): + t = Test_SingletonThread(fTargetTime) + t.start() + lstThreads.append(t) + eException = None + for t in lstThreads: + t.join() + if t._eException and not eException: + eException = t._eException + if eException: + raise eException + + def testNoInit(self): + """ + Demonstrates use with a class not defining __init__ + """ + + class A(Singleton): + pass + + #INTENTIONALLY UNDEFINED: + #def __init__(self): + # super(A, self).__init__() + + A.getInstance() #Make sure no exception is raised + + def testMultipleGetInstancesWithArgs(self): + + class A(Singleton): + + ignoreSubsequent = True + + def __init__(self, a, b=1): + pass + + a1 = A.getInstance(1) + a2 = A.getInstance(2) # ignores the second call because of ignoreSubsequent + + class B(Singleton): + + def __init__(self, a, b=1): + pass + + b1 = B.getInstance(1) + self.assertRaises(SingletonException, B.getInstance, 2) # No ignoreSubsequent included + + class C(Singleton): + + def __init__(self, a=1): + pass + + c1 = C.getInstance(a=1) + self.assertRaises(SingletonException, C.getInstance, a=2) # No ignoreSubsequent included + + def testInheritance(self): + """ + It's sometimes said that you can't subclass a singleton (see, for instance, + http://steve.yegge.googlepages.com/singleton-considered-stupid point e). This + test shows that at least rudimentary subclassing works fine for us. + """ + + class A(Singleton): + + def setX(self, x): + self.x = x + + def setZ(self, z): + raise NotImplementedError + + class B(A): + + def setX(self, x): + self.x = -x + + def setY(self, y): + self.y = y + + a = A.getInstance() + a.setX(5) + b = B.getInstance() + b.setX(5) + b.setY(50) + self.assertEqual((a.x, b.x, b.y), (5, -5, 50)) + self.assertRaises(AttributeError, eval, 'a.setY', {}, locals()) + self.assertRaises(NotImplementedError, b.setZ, 500) + + unittest.main() + |