git.fiddlerwoaroof.com
Browse code

added the registry class

Ed L authored on 26/09/2012 18:42:41
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,172 @@
1
+# Copyright (c) 2011 Edward Langley
2
+# All rights reserved.
3
+# 
4
+# Redistribution and use in source and binary forms, with or without
5
+# modification, are permitted provided that the following conditions
6
+# are met:
7
+# 
8
+# Redistributions of source code must retain the above copyright notice,
9
+# this list of conditions and the following disclaimer.
10
+# 
11
+# Redistributions in binary form must reproduce the above copyright
12
+# notice, this list of conditions and the following disclaimer in the
13
+# documentation and/or other materials provided with the distribution.
14
+# 
15
+# Neither the name of the project's author nor the names of its
16
+# contributors may be used to endorse or promote products derived from
17
+# this software without specific prior written permission.
18
+# 
19
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+import contextlib
32
+import functools
33
+
34
+class CallableGeneratorContextManager(contextlib.GeneratorContextManager):
35
+	'''A subclass of :py:class:`contextlib.GeneratorContextManager` which can be called
36
+
37
+	Basically, this exists to avoid unecessary hassles with object methods which need special treatment'''
38
+	def __call__(self, *a, **kw):
39
+		'''Enter self's context and call the object returned by __enter__'''
40
+		with self as obj:
41
+			return obj(*a, **kw)
42
+
43
+#NOTE: (ed) this is ripped out of contextlib but I've replaced contextlib.GeneratorContextManager with the previous class
44
+def contextmanager(func):
45
+	'''see documentation for :py:class:`contextlib.GeneratorContextManager`'''
46
+	@functools.wraps(func)
47
+	def helper(*args, **kwds):
48
+		return CallableGeneratorContextManager(func(*args, **kwds))
49
+		# ^^^ This is the only line diferent from contextlib.contextmanager
50
+	return helper
51
+
52
+
53
+class RegisteredObj(object):
54
+	'''Interface definition for :py:attr:`Registry.child_class`'''
55
+	def __init__(self, name, *args):
56
+		self.name = name
57
+
58
+	def update(self, other):
59
+		'''override me'''
60
+		pass
61
+
62
+def make_ctxt_manager(name):
63
+	'''create a factory method for making and registering objects suitable to be registered in
64
+	the Registry.
65
+
66
+	:param str name: The name of the factory method
67
+	:return: A contextmanager
68
+	'''
69
+	@contextmanager
70
+	def _inner(self, name, *args, **kwargs):
71
+		obj = self.registry.get(name) if name in self.registry else self.child_class(name, *args, **kwargs)
72
+		yield obj
73
+		if obj.name not in self.registry:
74
+			self.register(obj)
75
+
76
+	_inner.__name__ = name
77
+	return _inner
78
+
79
+import threading
80
+class Registry(object):
81
+	#: The kind of object to be stored in the registry
82
+	#: must, as a minimum implement the :py:class:`RegisteredObj` interface
83
+	child_class = None
84
+
85
+	def __setitem__(self, name, value):
86
+		with self._lock:
87
+			self.registry[name] = value
88
+
89
+	def __getitem__(self, name):
90
+		with self._lock:
91
+			return self.registry[name]
92
+
93
+	def __delitem__(self, name):
94
+		with self._lock:
95
+			del self.registry[name]
96
+
97
+	@classmethod
98
+	def reset(cls):
99
+		with cls._lock:
100
+			cls.registry.clear()
101
+
102
+	@classmethod
103
+	def get(cls, name, default=None):
104
+		with cls._lock:
105
+			return cls.registry.get(name, default)
106
+
107
+	_init_lock = threading.RLock()
108
+	@staticmethod
109
+	def setup(cls):
110
+		'''Decorate the registry with this, this sets the 'registry' attribute to an
111
+		empty dictionary and a factory function for creating child objects.
112
+
113
+		The name of the factory function is that of the class the registry registers,
114
+		but lowercase'''
115
+
116
+		with cls._init_lock:
117
+			factory_name = cls.child_class.__name__.lower()
118
+			setattr(cls, factory_name, make_ctxt_manager(factory_name))
119
+			cls.registry = {}
120
+			cls._lock = threading.RLock()
121
+			return cls
122
+
123
+	def register(self, obj):
124
+		with self._lock:
125
+			old_obj = self.registry.get(obj.name)
126
+			result = obj
127
+
128
+			if old_obj is not None:
129
+				old_obj.update(obj)
130
+				result = old_obj
131
+			else:
132
+				self.registry[obj.name] = obj
133
+
134
+		return result
135
+
136
+### DEMO, read this for a practical example of how the above works:
137
+
138
+class DataObj(RegisteredObj):
139
+	def __init__(self, name, a):
140
+		RegisteredObj.__init__(self, name)
141
+		self.a = a
142
+		self.b = None
143
+		self.c = None
144
+
145
+@Registry.setup
146
+class DataObjRegistry(Registry):
147
+	child_class = DataObj
148
+
149
+if __name__ == '__main__':
150
+	#Demo code
151
+
152
+	registry = DataObjRegistry()
153
+	with registry.dataobj('first_obj', 1) as obj1:
154
+		obj1.b = 2
155
+
156
+	with registry.dataobj('second_obj', 2) as obj2:
157
+		obj2.b = 3
158
+		obj2.c = 4
159
+
160
+	with registry.dataobj('third_obj', 3) as obj3:
161
+		obj1.b = 4
162
+
163
+	import random
164
+	def shuffled(lis):
165
+	lis = lis[:]
166
+	random.shuffle(lis)
167
+	return iter(lis)
168
+
169
+
170
+	for obj_name in shuffled(['first_obj', 'second_obj', 'third_obj']):
171
+		with registry.dataobj(obj_name) as obj:
172
+			print '%s - a: %s, b: %s, c: %s' % (obj.name, obj.a, obj.b, obj.c)