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)
|