Package grassyknoll :: Package lib :: Module meta
[hide private]

Source Code for Module grassyknoll.lib.meta

  1  """meta-programming magic""" 
  2  import os.path 
  3  import logging 
  4  import new 
  5  import glob 
  6  import sys 
  7  import threading 
  8  import inspect 
  9   
10 -class Spatch(dict):
11 __slots__=['default'] 12
13 - def __init__(self, default=None, *args, **kwargs):
14 self.default=default 15 super(Spatch, self).__init__(*args, **kwargs)
16
17 - def register(self, func, names):
18 for n in names: 19 self.add(n, func)
20
21 - def unregister(self, names):
22 for n in names: 23 self.pop(n)
24
25 - def add(self, name, func):
26 if name in self: 27 raise ValueError, "func exists for %s"%name 28 self[name]=func
29
30 - def getDefault(self, name):
31 return self.get(name, self.default)
32
33 -class Factory(object):
34 """easier construction of instance factories. 35 36 This class is intended to replace the closure-over-constructor pattern 37 while being safer and easier to work with. Such closures are typically 38 used when there are several implementations of the same interface, but 39 each has a different constructor, or as a means of 'configuring' an 40 instance before it's actually needed. Rather than writing a lambda, you 41 can assign attributes to factory and then call the factory to produce a 42 new instance. Finally, Factories can be pickled (assuming all attributes 43 can). 44 45 In the following examples, we mix in L{FactoryMixin} to provide a 46 C{factory} L{classmethod} on the base class. This isn't strictly 47 necessary, but looks nice and is recommend so that you hurt the little 48 user's brains less: 49 50 >>> class Foo(FactoryMixin): 51 ... def __init__(self, foo): 52 ... self.foo=foo 53 ... 54 >>> foo_factory=Foo.factory() 55 >>> foo_factory.foo=66 56 57 Factories have a L{bind} method that can be used to set several attributes 58 at once and returns the factory. It is useful for binding arguments 59 without assigning the factory to a local variable. 60 61 >>> foo_factory2=foo_factory.bind(foo=11) 62 >>> foo_factory2 is foo_factory 63 True 64 >>> foo_factory.foo 65 11 66 67 You can also bind attributes when constructing the factory 68 69 >>> foo_factory=Foo.factory(foo=11) 70 >>> foo_factory.foo 71 11 72 73 Factories ensure that attributes match up with arguments; this makes 74 finding errors easier (instead of raising a C{unexpected keyword argument} 75 later). 76 77 >>> foo_factory.bar=42 #doctest: +IGNORE_EXCEPTION_DETAIL 78 Traceback (most recent call last): 79 File "<stdin>", line 1, in <module> 80 AttributeError: 'No such argument bar' 81 82 Call the Factory to get a new instance: 83 84 >>> foo=foo_factory() 85 >>> foo.foo 86 11 87 88 Arguments to the factory override attributes: 89 90 >>> foo=foo_factory(foo=1111) 91 >>> foo.foo 92 1111 93 94 Each call to the factory returns a new instance. 95 96 >>> foo2=foo_factory() 97 >>> foo2 is foo 98 False 99 100 >>> class Bar(Foo): 101 ... def __init__(self, bar, **kwargs): 102 ... super(Bar, self).__init__(**kwargs) 103 ... self.bar=bar 104 ... 105 >>> bar_factory=Bar.factory() 106 107 The set of valid attributes is the union of all __init__ arguments in the 108 inheritance chain: 109 110 >>> bar_factory.foo=11 111 >>> bar_factory.bar=42 112 >>> bar_factory.quux=666 #doctest: +IGNORE_EXCEPTION_DETAIL 113 Traceback (most recent call last): 114 File "<stdin>", line 1, in <module> 115 AttributeError: 'No such argument quux' 116 117 >>> bar=bar_factory() 118 >>> bar.foo 119 11 120 >>> bar.bar 121 42 122 123 If you want to use a Factory directly, be sure to pass it a I{class}, not 124 an instance. 125 126 >>> Factory(bar) # doctest:+ELLIPSIS, +IGNORE_EXCEPTION_DETAIL 127 Traceback (most recent call last): 128 File "<stdin>", line 1, in <module> 129 TypeError: must provide class, not <grassyknoll.lib.meta.Bar object at ...> 130 """
131 - def __init__(self, cls):
132 if not isinstance(cls, type): 133 raise TypeError("must provide class, not %r"%cls) 134 135 super(Factory, self).__setattr__('_cls', cls) 136 super(Factory, self).__setattr__('_args', set()) 137 for c in cls.__mro__: 138 try: 139 a=inspect.getargspec(c.__init__.im_func)[0] 140 except AttributeError: 141 pass # we get this for builtins & the like 142 else: 143 a.remove('self') 144 self._args.update(a)
145
146 - def __setattr__(self, name, value):
147 if name not in self._args: 148 raise AttributeError("No such argument %s"%name) 149 else: 150 super(Factory, self).__setattr__(name, value)
151
152 - def __call__(self, *args, **kwargs):
153 d=self.__dict__.copy() 154 del d['_cls'] 155 del d['_args'] 156 d.update(kwargs) 157 return self._cls(*args, **d)
158
159 - def bind(self, **kwargs):
160 """set attributes to kwargs and return self""" 161 for k, v in kwargs.iteritems(): 162 setattr(self, k, v) 163 return self
164
165 -class FactoryMixin(object):
166 """a mixin providing L{factory}, which produces L{Factory}s""" 167 168 @classmethod
169 - def factory(cls, **kwargs):
170 """return a L{Factory} for the class, binding kwargs""" 171 return Factory(cls).bind(**kwargs)
172
173 -class AutoLogger(object):
174 """descriptor that returns a logger named after the class or subclass 175 176 Simply do something like: 177 >>> import logging 178 >>> logging.basicConfig(level=logging.DEBUG, stream=sys.stdout) 179 >>> class Foo(object): 180 ... logger=AutoLogger() 181 182 >>> class Bar(Foo): 183 ... pass 184 185 >>> f=Foo() 186 >>> f.logger.error("I am foo") 187 ERROR:Foo:I am foo 188 >>> b=Bar() 189 >>> b.logger.error("I am bar") 190 ERROR:Bar:I am bar 191 >>> Foo.logger.error("This logs from class Foo") 192 ERROR:Foo:This logs from class Foo 193 >>> Bar.logger.error("And this is from class Bar") 194 ERROR:Bar:And this is from class Bar 195 196 """ 197
198 - def __init__(self):
199 self.__loggers={}
200
201 - def __get__(self, obj, objtype=None):
202 ## save loggers in a dict. This is basically the same thing 203 ## getLogger() does, but doing this has less overhead and locking in 204 ## the case where the logger already exists. 205 logname=objtype.__name__ 206 try: 207 return self.__loggers[logname] 208 except KeyError: 209 logger=self.__loggers[logname]=logging.getLogger(logname) 210 return logger
211
212 -def importString(source, filename, globals_=None, locals_=None):
213 """construct a module from source code. 214 215 The module will I{not} appear in L{sys.path} 216 217 @arg source: source code of the module 218 @type source: string 219 220 @arg filename: a filename to use for the module's __file__ 221 @type filename: string 222 223 @arg globals_: globals to pass to L{eval}(). Defaults to globals() 224 @type globals_: dict 225 226 @arg locals_: locals to pass to L{eval}(). Defaults to empty dict 227 @type locals_: dict 228 229 @rtype: a module 230 """ 231 if globals_ is None: globals_=globals() 232 if locals_ is None: locals_={} 233 locals_['__file__']=filename 234 co=compile(source, filename, 'exec') 235 eval(co, globals_, locals_) 236 mod=new.module(os.path.splitext(os.path.basename(filename))[0]) 237 mod.__dict__.update(locals_) 238 return mod
239
240 -def importFile(f, globals_=None, locals_=None):
241 """construct a module from a file 242 243 The module will I{not} appear in L{sys.path} 244 245 @arg f: filename or open file 246 @type f: string or L{file} 247 248 @arg globals_: globals to pass to L{eval}(). Defaults to globals() 249 @type globals_: dict 250 251 @arg locals_: locals to pass to L{eval}(). Defaults to empty dict 252 @type locals_: dict 253 254 @rtype: a module 255 """ 256 if isinstance(f, str): 257 f=file(f, 'r') 258 259 return importString(f.read(), f.name, globals_, locals_)
260
261 -def findModule(name):
262 """find a module 263 264 @arg name: name of the module to find. Call with __name__ to find 265 yourself. 266 @type name: string 267 268 @rtype: module 269 @raises KeyError: if no module called C{name} exists 270 """ 271 return sys.modules[name]
272
273 -def findPackageModules(path):
274 """ 275 @arg path: a filesystem path 276 @type path: string 277 278 @returns: a list of all modules in path 279 @rtype: list of string 280 """ 281 if os.path.isfile(path): 282 path=os.path.dirname(path) 283 284 return filter(lambda f: f != '__init__', 285 (os.path.basename(f).rsplit('.', 1)[0] 286 for f in glob.glob(os.path.join(path, '*.py'))))
287
288 -def importPackageModules(pkgname):
289 """import and return a list of all modules in pkgname 290 291 This only works with the usual Python package layout. More obscure/magic 292 variants (such as __path__) won't work. 293 294 @arg pkgname: the name of a package. Use __name__ from __init__.py to get 295 your own modules. 296 @type pkgname: string 297 298 @returns: all modules in pkgname 299 @rtype: list of modules 300 """ 301 path=findModule(pkgname).__file__ 302 # need to specify a fromlist so we get the module instead of the package 303 return [__import__("%s.%s"%(pkgname, f), globals(), {}, ['__name__']) 304 for f in findPackageModules(path)]
305
306 -class LocalState(object):
307 """a instance with support for thread-local attributes. See L{localProperty}. 308 309 @ivar __localstate__: somewhere to keep thread-local attributes 310 @type __localstate__: L{threading.local} 311 """
312 - def __init__(self):
313 self.__localstate__=threading.local()
314
315 -def localProperty(func):
316 """a read-only thread-local property. 317 318 Use this function as a decorator. When first accessed in B{each thread}, 319 it will create a thread-local attribute with the return value of the 320 decoratored function. 321 322 XXX extend to support set/delete? 323 """ 324 325 @property 326 def getter(self): 327 local=self.__localstate__ 328 try: 329 return getattr(local, func.__name__) 330 except AttributeError: 331 x=func(self) 332 setattr(local, func.__name__, x) 333 return x
334 return getter 335