git.fiddlerwoaroof.com
registry.py
a3fd722b
 # Copyright (c) 2011 Edward Langley
 # All rights reserved.
 # 
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
 # are met:
 # 
 # Redistributions of source code must retain the above copyright notice,
 # this list of conditions and the following disclaimer.
 # 
 # Redistributions in binary form must reproduce the above copyright
 # notice, this list of conditions and the following disclaimer in the
 # documentation and/or other materials provided with the distribution.
 # 
 # Neither the name of the project's author nor the names of its
 # contributors may be used to endorse or promote products derived from
 # this software without specific prior written permission.
 # 
 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
60c2b9f5
 import contextlib
 import functools
 
 class CallableGeneratorContextManager(contextlib.GeneratorContextManager):
7e90196f
 	'''A subclass of :py:class:`contextlib.GeneratorContextManager` which can be called
 
 	Basically, this exists to avoid unecessary hassles with object methods which need special treatment'''
60c2b9f5
 	def __call__(self, *a, **kw):
7e90196f
 		'''Enter self's context and call the object returned by __enter__'''
60c2b9f5
 		with self as obj:
 			return obj(*a, **kw)
 
7e90196f
 #NOTE: (ed) this is ripped out of contextlib but I've replaced contextlib.GeneratorContextManager with the previous class
 def contextmanager(func):
 	'''see documentation for :py:class:`contextlib.GeneratorContextManager`'''
 	@functools.wraps(func)
 	def helper(*args, **kwds):
 		return CallableGeneratorContextManager(func(*args, **kwds))
 		# ^^^ This is the only line diferent from contextlib.contextmanager
 	return helper
60c2b9f5
 
 
 class RegisteredObj(object):
7e90196f
 	'''Interface definition for :py:attr:`Registry.child_class`'''
60c2b9f5
 	def __init__(self, name, *args):
 		self.name = name
 
 	def update(self, other):
7e90196f
 		'''override me'''
60c2b9f5
 		pass
 
 def make_ctxt_manager(name):
7e90196f
 	'''create a factory method for making and registering objects suitable to be registered in
 	the Registry.
 
 	:param str name: The name of the factory method
 	:return: A contextmanager
 	'''
60c2b9f5
 	@contextmanager
7e90196f
 	def _inner(self, name, *args, **kwargs):
 		obj = self.registry.get(name) if name in self.registry else self.child_class(name, *args, **kwargs)
60c2b9f5
 		yield obj
 		if obj.name not in self.registry:
 			self.register(obj)
 
 	_inner.__name__ = name
 	return _inner
 
 import threading
 class Registry(object):
7e90196f
 	#: The kind of object to be stored in the registry
 	#: must, as a minimum implement the :py:class:`RegisteredObj` interface
60c2b9f5
 	child_class = None
 
 	def __setitem__(self, name, value):
 		with self._lock:
 			self.registry[name] = value
 
 	def __getitem__(self, name):
 		with self._lock:
 			return self.registry[name]
 
 	def __delitem__(self, name):
 		with self._lock:
 			del self.registry[name]
 
 	@classmethod
 	def reset(cls):
 		with cls._lock:
 			cls.registry.clear()
 
 	@classmethod
 	def get(cls, name, default=None):
 		with cls._lock:
 			return cls.registry.get(name, default)
 
 	_init_lock = threading.RLock()
 	@staticmethod
 	def setup(cls):
7e90196f
 		'''Decorate the registry with this, this sets the 'registry' attribute to an
 		empty dictionary and a factory function for creating child objects.
 
 		The name of the factory function is that of the class the registry registers,
 		but lowercase'''
 
60c2b9f5
 		with cls._init_lock:
 			factory_name = cls.child_class.__name__.lower()
 			setattr(cls, factory_name, make_ctxt_manager(factory_name))
 			cls.registry = {}
 			cls._lock = threading.RLock()
 			return cls
 
 	def register(self, obj):
 		with self._lock:
 			old_obj = self.registry.get(obj.name)
 			result = obj
 
 			if old_obj is not None:
 				old_obj.update(obj)
 				result = old_obj
 			else:
 				self.registry[obj.name] = obj
 
 		return result
 
7e90196f
 ### DEMO, read this for a practical example of how the above works:
 
60c2b9f5
 class DataObj(RegisteredObj):
 	def __init__(self, name, a):
 		RegisteredObj.__init__(self, name)
 		self.a = a
 		self.b = None
 		self.c = None
 
 @Registry.setup
 class DataObjRegistry(Registry):
 	child_class = DataObj
 
 if __name__ == '__main__':
 	#Demo code
 
 	registry = DataObjRegistry()
 	with registry.dataobj('first_obj', 1) as obj1:
 		obj1.b = 2
 
 	with registry.dataobj('second_obj', 2) as obj2:
 		obj2.b = 3
 		obj2.c = 4
 
 	with registry.dataobj('third_obj', 3) as obj3:
 		obj1.b = 4
 
 	import random
 	def shuffled(lis):
ddb6cde6
 	lis = lis[:]
 	random.shuffle(lis)
 	return iter(lis)
60c2b9f5
 
 
 	for obj_name in shuffled(['first_obj', 'second_obj', 'third_obj']):
 		with registry.dataobj(obj_name) as obj:
 			print '%s - a: %s, b: %s, c: %s' % (obj.name, obj.a, obj.b, obj.c)