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
11 __slots__=['default']
12
13 - def __init__(self, default=None, *args, **kwargs):
16
18 for n in names:
19 self.add(n, func)
20
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
31 return self.get(name, self.default)
32
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 """
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
142 else:
143 a.remove('self')
144 self._args.update(a)
145
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
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
166 """a mixin providing L{factory}, which produces L{Factory}s"""
167
168 @classmethod
170 """return a L{Factory} for the class, binding kwargs"""
171 return Factory(cls).bind(**kwargs)
172
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
200
201 - def __get__(self, obj, objtype=None):
202
203
204
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
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
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
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
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
303 return [__import__("%s.%s"%(pkgname, f), globals(), {}, ['__name__'])
304 for f in findPackageModules(path)]
305
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 """
313 self.__localstate__=threading.local()
314
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