Home | History | Annotate | Download | only in axl
      1 """
      2 A Python Singleton mixin class that makes use of some of the ideas
      3 found at http://c2.com/cgi/wiki?PythonSingleton. Just inherit
      4 from it and you have a singleton. No code is required in
      5 subclasses to create singleton behavior -- inheritance from 
      6 Singleton is all that is needed.
      7 
      8 Assume S is a class that inherits from Singleton. Useful behaviors
      9 are:
     10 
     11 1) Getting the singleton:
     12 
     13     S.getInstance() 
     14     
     15 returns the instance of S. If none exists, it is created. 
     16 
     17 2) The usual idiom to construct an instance by calling the class, i.e.
     18 
     19     S()
     20     
     21 is disabled for the sake of clarity. If it were allowed, a programmer
     22 who didn't happen  notice the inheritance from Singleton might think he
     23 was creating a new instance. So it is felt that it is better to
     24 make that clearer by requiring the call of a class method that is defined in
     25 Singleton. An attempt to instantiate via S() will restult in an SingletonException
     26 being raised.
     27 
     28 3) If S.__init__(.) requires parameters, include them in the
     29 first call to S.getInstance(.). If subsequent calls have parameters,
     30 a SingletonException is raised.
     31 
     32 4) As an implementation detail, classes that inherit 
     33 from Singleton may not have their own __new__
     34 methods. To make sure this requirement is followed, 
     35 an exception is raised if a Singleton subclass includ
     36 es __new__. This happens at subclass instantiation
     37 time (by means of the MetaSingleton metaclass.
     38 
     39 By Gary Robinson, grobinson (at] transpose.com. No rights reserved -- 
     40 placed in the public domain -- which is only reasonable considering
     41 how much it owes to other people's version which are in the
     42 public domain. The idea of using a metaclass came from 
     43 a  comment on Gary's blog (see 
     44 http://www.garyrobinson.net/2004/03/python_singleto.html#comments). 
     45 Not guaranteed to be fit for any particular purpose. 
     46 """
     47 
     48 class SingletonException(Exception):
     49     pass
     50 
     51 class MetaSingleton(type):
     52     def __new__(metaclass, strName, tupBases, dict):
     53         if '__new__' in dict:
     54             raise SingletonException, 'Can not override __new__ in a Singleton'
     55         return super(MetaSingleton,metaclass).__new__(metaclass, strName, tupBases, dict)
     56         
     57     def __call__(cls, *lstArgs, **dictArgs):
     58         raise SingletonException, 'Singletons may only be instantiated through getInstance()'
     59         
     60 class Singleton(object):
     61     __metaclass__ = MetaSingleton
     62     
     63     def getInstance(cls, *lstArgs):
     64         """
     65         Call this to instantiate an instance or retrieve the existing instance.
     66         If the singleton requires args to be instantiated, include them the first
     67         time you call getInstance.        
     68         """
     69         if cls._isInstantiated():
     70             if len(lstArgs) != 0:
     71                 raise SingletonException, 'If no supplied args, singleton must already be instantiated, or __init__ must require no args'
     72         else:
     73             if len(lstArgs) != cls._getConstructionArgCountNotCountingSelf():
     74                 raise SingletonException, 'If the singleton requires __init__ args, supply them on first instantiation'
     75             instance = cls.__new__(cls)
     76             instance.__init__(*lstArgs)
     77             cls.cInstance = instance
     78         return cls.cInstance
     79     getInstance = classmethod(getInstance)
     80     
     81     def _isInstantiated(cls):
     82         return hasattr(cls, 'cInstance')
     83     _isInstantiated = classmethod(_isInstantiated)
     84 
     85     def _getConstructionArgCountNotCountingSelf(cls):
     86         return cls.__init__.im_func.func_code.co_argcount - 1
     87     _getConstructionArgCountNotCountingSelf = classmethod(_getConstructionArgCountNotCountingSelf)
     88 
     89     def _forgetClassInstanceReferenceForTesting(cls):
     90         """
     91         This is designed for convenience in testing -- sometimes you 
     92         want to get rid of a singleton during test code to see what
     93         happens when you call getInstance() under a new situation.
     94         
     95         To really delete the object, all external references to it
     96         also need to be deleted.
     97         """
     98         try:
     99             delattr(cls,'cInstance')
    100         except AttributeError:
    101             # run up the chain of base classes until we find the one that has the instance
    102             # and then delete it there
    103             for baseClass in cls.__bases__: 
    104                 if issubclass(baseClass, Singleton):
    105                     baseClass._forgetClassInstanceReferenceForTesting()
    106     _forgetClassInstanceReferenceForTesting = classmethod(_forgetClassInstanceReferenceForTesting)
    107 
    108 
    109 if __name__ == '__main__':
    110     import unittest
    111     
    112     class PublicInterfaceTest(unittest.TestCase):
    113         def testReturnsSameObject(self):
    114             """
    115             Demonstrates normal use -- just call getInstance and it returns a singleton instance
    116             """
    117         
    118             class A(Singleton): 
    119                 def __init__(self):
    120                     super(A, self).__init__()
    121                     
    122             a1 = A.getInstance()
    123             a2 = A.getInstance()
    124             self.assertEquals(id(a1), id(a2))
    125             
    126         def testInstantiateWithMultiArgConstructor(self):
    127             """
    128             If the singleton needs args to construct, include them in the first
    129             call to get instances.
    130             """
    131                     
    132             class B(Singleton): 
    133                     
    134                 def __init__(self, arg1, arg2):
    135                     super(B, self).__init__()
    136                     self.arg1 = arg1
    137                     self.arg2 = arg2
    138 
    139             b1 = B.getInstance('arg1 value', 'arg2 value')
    140             b2 = B.getInstance()
    141             self.assertEquals(b1.arg1, 'arg1 value')
    142             self.assertEquals(b1.arg2, 'arg2 value')
    143             self.assertEquals(id(b1), id(b2))
    144             
    145             
    146         def testTryToInstantiateWithoutNeededArgs(self):
    147             
    148             class B(Singleton): 
    149                     
    150                 def __init__(self, arg1, arg2):
    151                     super(B, self).__init__()
    152                     self.arg1 = arg1
    153                     self.arg2 = arg2
    154 
    155             self.assertRaises(SingletonException, B.getInstance)
    156             
    157         def testTryToInstantiateWithoutGetInstance(self):
    158             """
    159             Demonstrates that singletons can ONLY be instantiated through
    160             getInstance, as long as they call Singleton.__init__ during construction.
    161             
    162             If this check is not required, you don't need to call Singleton.__init__().
    163             """
    164 
    165             class A(Singleton): 
    166                 def __init__(self):
    167                     super(A, self).__init__()
    168                     
    169             self.assertRaises(SingletonException, A)
    170             
    171         def testDontAllowNew(self):
    172         
    173             def instantiatedAnIllegalClass():
    174                 class A(Singleton): 
    175                     def __init__(self):
    176                         super(A, self).__init__()
    177                         
    178                     def __new__(metaclass, strName, tupBases, dict):
    179                         return super(MetaSingleton,metaclass).__new__(metaclass, strName, tupBases, dict)
    180                                         
    181             self.assertRaises(SingletonException, instantiatedAnIllegalClass)
    182         
    183         
    184         def testDontAllowArgsAfterConstruction(self):
    185             class B(Singleton): 
    186                     
    187                 def __init__(self, arg1, arg2):
    188                     super(B, self).__init__()
    189                     self.arg1 = arg1
    190                     self.arg2 = arg2
    191 
    192             b1 = B.getInstance('arg1 value', 'arg2 value')
    193             self.assertRaises(SingletonException, B, 'arg1 value', 'arg2 value')
    194             
    195         def test_forgetClassInstanceReferenceForTesting(self):
    196             class A(Singleton): 
    197                 def __init__(self):
    198                     super(A, self).__init__()
    199             class B(A): 
    200                 def __init__(self):
    201                     super(B, self).__init__()
    202                     
    203             # check that changing the class after forgetting the instance produces
    204             # an instance of the new class
    205             a = A.getInstance()
    206             assert a.__class__.__name__ == 'A'
    207             A._forgetClassInstanceReferenceForTesting()
    208             b = B.getInstance()
    209             assert b.__class__.__name__ == 'B'
    210             
    211             # check that invoking the 'forget' on a subclass still deletes the instance
    212             B._forgetClassInstanceReferenceForTesting()
    213             a = A.getInstance()
    214             B._forgetClassInstanceReferenceForTesting()
    215             b = B.getInstance()
    216             assert b.__class__.__name__ == 'B'
    217 
    218     unittest.main()
    219 
    220     
    221