1""" 2classresolver.py 3 2 February 2004, Ian Bicking <ianb@colorstudy.com> 4 5Resolves strings to classes, and runs callbacks when referenced 6classes are created. 7 8Classes are referred to only by name, not by module. So that 9identically-named classes can coexist, classes are put into individual 10registries, which are keyed on strings (names). These registries are 11created on demand. 12 13Use like:: 14 15 >>> import classregistry 16 >>> registry = classregistry.registry('MyModules') 17 >>> def afterMyClassExists(cls): 18 ... print 'Class finally exists:', cls 19 >>> registry.addClassCallback('MyClass', afterMyClassExists) 20 >>> class MyClass: 21 ... pass 22 >>> registry.addClass(MyClass) 23 Class finally exists: MyClass 24 25""" 26 27class ClassRegistry(object): 28 """ 29 We'll be dealing with classes that reference each other, so 30 class C1 may reference C2 (in a join), while C2 references 31 C1 right back. Since classes are created in an order, there 32 will be a point when C1 exists but C2 doesn't. So we deal 33 with classes by name, and after each class is created we 34 try to fix up any references by replacing the names with 35 actual classes. 36 37 Here we keep a dictionaries of class names to classes -- note 38 that the classes might be spread among different modules, so 39 since we pile them together names need to be globally unique, 40 to just module unique. 41 Like needSet below, the container dictionary is keyed by the 42 class registry. 43 """ 44 45 def __init__(self, name): 46 self.name = name 47 self.classes = {} 48 self.callbacks = {} 49 self.genericCallbacks = [] 50 51 def addClassCallback(self, className, callback, *args, **kw): 52 """ 53 Whenever a name is substituted for the class, you can register 54 a callback that will be called when the needed class is 55 created. If it's already been created, the callback will be 56 called immediately. 57 """ 58 if className in self.classes: 59 callback(self.classes[className], *args, **kw) 60 else: 61 self.callbacks.setdefault(className, []).append((callback, args, kw)) 62 63 def addCallback(self, callback, *args, **kw): 64 """ 65 This callback is called for all classes, not just specific 66 ones (like addClassCallback). 67 """ 68 self.genericCallbacks.append((callback, args, kw)) 69 for cls in self.classes.values(): 70 callback(cls, *args, **kw) 71 72 def addClass(self, cls): 73 """ 74 Everytime a class is created, we add it to the registry, so 75 that other classes can find it by name. We also call any 76 callbacks that are waiting for the class. 77 """ 78 if cls.__name__ in self.classes: 79 import sys 80 other = self.classes[cls.__name__] 81 raise ValueError( 82 "class %s is already in the registry (other class is " 83 "%r, from the module %s in %s; attempted new class is " 84 "%r, from the module %s in %s)" 85 % (cls.__name__, 86 other, other.__module__, 87 getattr(sys.modules.get(other.__module__), 88 '__file__', '(unknown)'), 89 cls, cls.__module__, 90 getattr(sys.modules.get(cls.__module__), 91 '__file__', '(unknown)'))) 92 self.classes[cls.__name__] = cls 93 if cls.__name__ in self.callbacks: 94 for callback, args, kw in self.callbacks[cls.__name__]: 95 callback(cls, *args, **kw) 96 del self.callbacks[cls.__name__] 97 for callback, args, kw in self.genericCallbacks: 98 callback(cls, *args, **kw) 99 100 def getClass(self, className): 101 try: 102 return self.classes[className] 103 except KeyError: 104 all = self.classes.keys() 105 all.sort() 106 raise KeyError( 107 "No class %s found in the registry %s (these classes " 108 "exist: %s)" 109 % (className, self.name or '[default]', ', '.join(all))) 110 111 def allClasses(self): 112 return self.classes.values() 113 114class _MasterRegistry(object): 115 """ 116 This singleton holds all the class registries. There can be 117 multiple registries to hold different unrelated sets of classes 118 that reside in the same process. These registries are named with 119 strings, and are created on demand. The MasterRegistry module 120 global holds the singleton. 121 """ 122 123 def __init__(self): 124 self.registries = {} 125 126 def registry(self, item): 127 if item not in self.registries: 128 self.registries[item] = ClassRegistry(item) 129 return self.registries[item] 130 131MasterRegistry = _MasterRegistry() 132registry = MasterRegistry.registry 133 134def findClass(name, class_registry=None): 135 return registry(class_registry).getClass(name) 136