Forgot to add these changes which are needed by the addressbook
[theonering] / src / util / misc.py
1 #!/usr/bin/env python
2
3 from __future__ import with_statement
4
5 import sys
6 import cPickle
7
8 import functools
9 import contextlib
10 import inspect
11
12 import optparse
13 import traceback
14 import warnings
15 import string
16
17
18 def printfmt(template):
19         """
20         This hides having to create the Template object and call substitute/safe_substitute on it. For example:
21
22         >>> num = 10
23         >>> word = "spam"
24         >>> printfmt("I would like to order $num units of $word, please") #doctest: +SKIP
25         I would like to order 10 units of spam, please
26         """
27         frame = inspect.stack()[-1][0]
28         try:
29                 print string.Template(template).safe_substitute(frame.f_locals)
30         finally:
31                 del frame
32
33
34 def is_special(name):
35         return name.startswith("__") and name.endswith("__")
36
37
38 def is_private(name):
39         return name.startswith("_") and not is_special(name)
40
41
42 def privatize(clsName, attributeName):
43         """
44         At runtime, make an attributeName private
45
46         Example:
47         >>> class Test(object):
48         ...     pass
49         ...
50         >>> try:
51         ...     dir(Test).index("_Test__me")
52         ...     print dir(Test)
53         ... except:
54         ...     print "Not Found"
55         Not Found
56         >>> setattr(Test, privatize(Test.__name__, "me"), "Hello World")
57         >>> try:
58         ...     dir(Test).index("_Test__me")
59         ...     print "Found"
60         ... except:
61         ...     print dir(Test)
62         0
63         Found
64         >>> print getattr(Test, obfuscate(Test.__name__, "__me"))
65         Hello World
66         >>>
67         >>> is_private(privatize(Test.__name__, "me"))
68         True
69         >>> is_special(privatize(Test.__name__, "me"))
70         False
71         """
72         return "".join(["_", clsName, "__", attributeName])
73
74
75 def obfuscate(clsName, attributeName):
76         """
77         At runtime, turn a private name into the obfuscated form
78
79         Example:
80         >>> class Test(object):
81         ...     __me = "Hello World"
82         ...
83         >>> try:
84         ...     dir(Test).index("_Test__me")
85         ...     print "Found"
86         ... except:
87         ...     print dir(Test)
88         0
89         Found
90         >>> print getattr(Test, obfuscate(Test.__name__, "__me"))
91         Hello World
92         >>> is_private(obfuscate(Test.__name__, "__me"))
93         True
94         >>> is_special(obfuscate(Test.__name__, "__me"))
95         False
96         """
97         return "".join(["_", clsName, attributeName])
98
99
100 class PAOptionParser(optparse.OptionParser, object):
101         """
102         >>> if __name__ == '__main__':
103         ...     #parser = PAOptionParser("My usage str")
104         ...     parser = PAOptionParser()
105         ...     parser.add_posarg("Foo", help="Foo usage")
106         ...     parser.add_posarg("Bar", dest="bar_dest")
107         ...     parser.add_posarg("Language", dest='tr_type', type="choice", choices=("Python", "Other"))
108         ...     parser.add_option('--stocksym', dest='symbol')
109         ...     values, args = parser.parse_args()
110         ...     print values, args
111         ...
112
113         python mycp.py  -h
114         python mycp.py
115         python mycp.py  foo
116         python mycp.py  foo bar
117
118         python mycp.py foo bar lava
119         Usage: pa.py <Foo> <Bar> <Language> [options]
120
121         Positional Arguments:
122         Foo: Foo usage
123         Bar:
124         Language:
125
126         pa.py: error: option --Language: invalid choice: 'lava' (choose from 'Python', 'Other'
127         """
128
129         def __init__(self, *args, **kw):
130                 self.posargs = []
131                 super(PAOptionParser, self).__init__(*args, **kw)
132
133         def add_posarg(self, *args, **kw):
134                 pa_help = kw.get("help", "")
135                 kw["help"] = optparse.SUPPRESS_HELP
136                 o = self.add_option("--%s" % args[0], *args[1:], **kw)
137                 self.posargs.append((args[0], pa_help))
138
139         def get_usage(self, *args, **kwargs):
140                 params = (' '.join(["<%s>" % arg[0] for arg in self.posargs]), '\n '.join(["%s: %s" % (arg) for arg in self.posargs]))
141                 self.usage = "%%prog %s [options]\n\nPositional Arguments:\n %s" % params
142                 return super(PAOptionParser, self).get_usage(*args, **kwargs)
143
144         def parse_args(self, *args, **kwargs):
145                 args = sys.argv[1:]
146                 args0 = []
147                 for p, v in zip(self.posargs, args):
148                         args0.append("--%s" % p[0])
149                         args0.append(v)
150                 args = args0 + args
151                 options, args = super(PAOptionParser, self).parse_args(args, **kwargs)
152                 if len(args) < len(self.posargs):
153                         msg = 'Missing value(s) for "%s"\n' % ", ".join([arg[0] for arg in self.posargs][len(args):])
154                         self.error(msg)
155                 return options, args
156
157
158 def explicitly(name, stackadd=0):
159         """
160         This is an alias for adding to '__all__'.  Less error-prone than using
161         __all__ itself, since setting __all__ directly is prone to stomping on
162         things implicitly exported via L{alias}.
163
164         @note Taken from PyExport (which could turn out pretty cool):
165         @li @a http://codebrowse.launchpad.net/~glyph/
166         @li @a http://glyf.livejournal.com/74356.html
167         """
168         packageVars = sys._getframe(1+stackadd).f_locals
169         globalAll = packageVars.setdefault('__all__', [])
170         globalAll.append(name)
171
172
173 def public(thunk):
174         """
175         This is a decorator, for convenience.  Rather than typing the name of your
176         function twice, you can decorate a function with this.
177
178         To be real, @public would need to work on methods as well, which gets into
179         supporting types...
180
181         @note Taken from PyExport (which could turn out pretty cool):
182         @li @a http://codebrowse.launchpad.net/~glyph/
183         @li @a http://glyf.livejournal.com/74356.html
184         """
185         explicitly(thunk.__name__, 1)
186         return thunk
187
188
189 def _append_docstring(obj, message):
190         if obj.__doc__ is None:
191                 obj.__doc__ = message
192         else:
193                 obj.__doc__ += message
194
195
196 def validate_decorator(decorator):
197
198         def simple(x):
199                 return x
200
201         f = simple
202         f.__name__ = "name"
203         f.__doc__ = "doc"
204         f.__dict__["member"] = True
205
206         g = decorator(f)
207
208         if f.__name__ != g.__name__:
209                 print f.__name__, "!=", g.__name__
210
211         if g.__doc__ is None:
212                 print decorator.__name__, "has no doc string"
213         elif not g.__doc__.startswith(f.__doc__):
214                 print g.__doc__, "didn't start with", f.__doc__
215
216         if not ("member" in g.__dict__ and g.__dict__["member"]):
217                 print "'member' not in ", g.__dict__
218
219
220 def deprecated_api(func):
221         """
222         This is a decorator which can be used to mark functions
223         as deprecated. It will result in a warning being emitted
224         when the function is used.
225
226         >>> validate_decorator(deprecated_api)
227         """
228
229         @functools.wraps(func)
230         def newFunc(*args, **kwargs):
231                 warnings.warn("Call to deprecated function %s." % func.__name__, category=DeprecationWarning)
232                 return func(*args, **kwargs)
233         _append_docstring(newFunc, "\n@deprecated")
234         return newFunc
235
236
237 def unstable_api(func):
238         """
239         This is a decorator which can be used to mark functions
240         as deprecated. It will result in a warning being emitted
241         when the function is used.
242
243         >>> validate_decorator(unstable_api)
244         """
245
246         @functools.wraps(func)
247         def newFunc(*args, **kwargs):
248                 warnings.warn("Call to unstable API function %s." % func.__name__, category=FutureWarning)
249                 return func(*args, **kwargs)
250         _append_docstring(newFunc, "\n@unstable")
251         return newFunc
252
253
254 def enabled(func):
255         """
256         This decorator doesn't add any behavior
257
258         >>> validate_decorator(enabled)
259         """
260         return func
261
262
263 def disabled(func):
264         """
265         This decorator disables the provided function, and does nothing
266
267         >>> validate_decorator(disabled)
268         """
269
270         @functools.wraps(func)
271         def emptyFunc(*args, **kargs):
272                 pass
273         _append_docstring(emptyFunc, "\n@note Temporarily Disabled")
274         return emptyFunc
275
276
277 def metadata(document=True, **kwds):
278         """
279         >>> validate_decorator(metadata(author="Ed"))
280         """
281
282         def decorate(func):
283                 for k, v in kwds.iteritems():
284                         setattr(func, k, v)
285                         if document:
286                                 _append_docstring(func, "\n@"+k+" "+v)
287                 return func
288         return decorate
289
290
291 def prop(func):
292         """Function decorator for defining property attributes
293
294         The decorated function is expected to return a dictionary
295         containing one or more of the following pairs:
296                 fget - function for getting attribute value
297                 fset - function for setting attribute value
298                 fdel - function for deleting attribute
299         This can be conveniently constructed by the locals() builtin
300         function; see:
301         http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/205183
302         @author http://kbyanc.blogspot.com/2007/06/python-property-attribute-tricks.html
303
304         Example:
305         >>> #Due to transformation from function to property, does not need to be validated
306         >>> #validate_decorator(prop)
307         >>> class MyExampleClass(object):
308         ...     @prop
309         ...     def foo():
310         ...             "The foo property attribute's doc-string"
311         ...             def fget(self):
312         ...                     print "GET"
313         ...                     return self._foo
314         ...             def fset(self, value):
315         ...                     print "SET"
316         ...                     self._foo = value
317         ...             return locals()
318         ...
319         >>> me = MyExampleClass()
320         >>> me.foo = 10
321         SET
322         >>> print me.foo
323         GET
324         10
325         """
326         return property(doc=func.__doc__, **func())
327
328
329 def print_handler(e):
330         """
331         @see ExpHandler
332         """
333         print "%s: %s" % (type(e).__name__, e)
334
335
336 def print_ignore(e):
337         """
338         @see ExpHandler
339         """
340         print 'Ignoring %s exception: %s' % (type(e).__name__, e)
341
342
343 def print_traceback(e):
344         """
345         @see ExpHandler
346         """
347         #print sys.exc_info()
348         traceback.print_exc(file=sys.stdout)
349
350
351 def ExpHandler(handler = print_handler, *exceptions):
352         """
353         An exception handling idiom using decorators
354         Examples
355         Specify exceptions in order, first one is handled first
356         last one last.
357
358         >>> validate_decorator(ExpHandler())
359         >>> @ExpHandler(print_ignore, ZeroDivisionError)
360         ... @ExpHandler(None, AttributeError, ValueError)
361         ... def f1():
362         ...     1/0
363         >>> @ExpHandler(print_traceback, ZeroDivisionError)
364         ... def f2():
365         ...     1/0
366         >>> @ExpHandler()
367         ... def f3(*pargs):
368         ...     l = pargs
369         ...     return l[10]
370         >>> @ExpHandler(print_traceback, ZeroDivisionError)
371         ... def f4():
372         ...     return 1
373         >>>
374         >>>
375         >>> f1()
376         Ignoring ZeroDivisionError exception: integer division or modulo by zero
377         >>> f2() # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
378         Traceback (most recent call last):
379         ...
380         ZeroDivisionError: integer division or modulo by zero
381         >>> f3()
382         IndexError: tuple index out of range
383         >>> f4()
384         1
385         """
386
387         def wrapper(f):
388                 localExceptions = exceptions
389                 if not localExceptions:
390                         localExceptions = [Exception]
391                 t = [(ex, handler) for ex in localExceptions]
392                 t.reverse()
393
394                 def newfunc(t, *args, **kwargs):
395                         ex, handler = t[0]
396                         try:
397                                 if len(t) == 1:
398                                         return f(*args, **kwargs)
399                                 else:
400                                         #Recurse for embedded try/excepts
401                                         dec_func = functools.partial(newfunc, t[1:])
402                                         dec_func = functools.update_wrapper(dec_func, f)
403                                         return dec_func(*args, **kwargs)
404                         except ex, e:
405                                 return handler(e)
406
407                 dec_func = functools.partial(newfunc, t)
408                 dec_func = functools.update_wrapper(dec_func, f)
409                 return dec_func
410         return wrapper
411
412
413 class bindclass(object):
414         """
415         >>> validate_decorator(bindclass)
416         >>> class Foo(BoundObject):
417         ...      @bindclass
418         ...      def foo(this_class, self):
419         ...              return this_class, self
420         ...
421         >>> class Bar(Foo):
422         ...      @bindclass
423         ...      def bar(this_class, self):
424         ...              return this_class, self
425         ...
426         >>> f = Foo()
427         >>> b = Bar()
428         >>>
429         >>> f.foo() # doctest: +ELLIPSIS
430         (<class '...Foo'>, <...Foo object at ...>)
431         >>> b.foo() # doctest: +ELLIPSIS
432         (<class '...Foo'>, <...Bar object at ...>)
433         >>> b.bar() # doctest: +ELLIPSIS
434         (<class '...Bar'>, <...Bar object at ...>)
435         """
436
437         def __init__(self, f):
438                 self.f = f
439                 self.__name__ = f.__name__
440                 self.__doc__ = f.__doc__
441                 self.__dict__.update(f.__dict__)
442                 self.m = None
443
444         def bind(self, cls, attr):
445
446                 def bound_m(*args, **kwargs):
447                         return self.f(cls, *args, **kwargs)
448                 bound_m.__name__ = attr
449                 self.m = bound_m
450
451         def __get__(self, obj, objtype=None):
452                 return self.m.__get__(obj, objtype)
453
454
455 class ClassBindingSupport(type):
456         "@see bindclass"
457
458         def __init__(mcs, name, bases, attrs):
459                 type.__init__(mcs, name, bases, attrs)
460                 for attr, val in attrs.iteritems():
461                         if isinstance(val, bindclass):
462                                 val.bind(mcs, attr)
463
464
465 class BoundObject(object):
466         "@see bindclass"
467         __metaclass__ = ClassBindingSupport
468
469
470 def bindfunction(f):
471         """
472         >>> validate_decorator(bindfunction)
473         >>> @bindfunction
474         ... def factorial(thisfunction, n):
475         ...      # Within this function the name 'thisfunction' refers to the factorial
476         ...      # function(with only one argument), even after 'factorial' is bound
477         ...      # to another object
478         ...      if n > 0:
479         ...              return n * thisfunction(n - 1)
480         ...      else:
481         ...              return 1
482         ...
483         >>> factorial(3)
484         6
485         """
486
487         @functools.wraps(f)
488         def bound_f(*args, **kwargs):
489                 return f(bound_f, *args, **kwargs)
490         return bound_f
491
492
493 class Memoize(object):
494         """
495         Memoize(fn) - an instance which acts like fn but memoizes its arguments
496         Will only work on functions with non-mutable arguments
497         @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52201
498
499         >>> validate_decorator(Memoize)
500         """
501
502         def __init__(self, fn):
503                 self.fn = fn
504                 self.__name__ = fn.__name__
505                 self.__doc__ = fn.__doc__
506                 self.__dict__.update(fn.__dict__)
507                 self.memo = {}
508
509         def __call__(self, *args):
510                 if args not in self.memo:
511                         self.memo[args] = self.fn(*args)
512                 return self.memo[args]
513
514
515 class MemoizeMutable(object):
516         """Memoize(fn) - an instance which acts like fn but memoizes its arguments
517         Will work on functions with mutable arguments(slower than Memoize)
518         @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52201
519
520         >>> validate_decorator(MemoizeMutable)
521         """
522
523         def __init__(self, fn):
524                 self.fn = fn
525                 self.__name__ = fn.__name__
526                 self.__doc__ = fn.__doc__
527                 self.__dict__.update(fn.__dict__)
528                 self.memo = {}
529
530         def __call__(self, *args, **kw):
531                 text = cPickle.dumps((args, kw))
532                 if text not in self.memo:
533                         self.memo[text] = self.fn(*args, **kw)
534                 return self.memo[text]
535
536
537 callTraceIndentationLevel = 0
538
539
540 def call_trace(f):
541         """
542         Synchronization decorator.
543
544         >>> validate_decorator(call_trace)
545         >>> @call_trace
546         ... def a(a, b, c):
547         ...     pass
548         >>> a(1, 2, c=3)
549         Entering a((1, 2), {'c': 3})
550         Exiting a((1, 2), {'c': 3})
551         """
552
553         @functools.wraps(f)
554         def verboseTrace(*args, **kw):
555                 global callTraceIndentationLevel
556
557                 print "%sEntering %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw)
558                 callTraceIndentationLevel += 1
559                 try:
560                         result = f(*args, **kw)
561                 except:
562                         callTraceIndentationLevel -= 1
563                         print "%sException %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw)
564                         raise
565                 callTraceIndentationLevel -= 1
566                 print "%sExiting %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw)
567                 return result
568
569         @functools.wraps(f)
570         def smallTrace(*args, **kw):
571                 global callTraceIndentationLevel
572
573                 print "%sEntering %s" % ("\t"*callTraceIndentationLevel, f.__name__)
574                 callTraceIndentationLevel += 1
575                 try:
576                         result = f(*args, **kw)
577                 except:
578                         callTraceIndentationLevel -= 1
579                         print "%sException %s" % ("\t"*callTraceIndentationLevel, f.__name__)
580                         raise
581                 callTraceIndentationLevel -= 1
582                 print "%sExiting %s" % ("\t"*callTraceIndentationLevel, f.__name__)
583                 return result
584
585         #return smallTrace
586         return verboseTrace
587
588
589 @contextlib.contextmanager
590 def lexical_scope(*args):
591         """
592         @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/520586
593         Example:
594         >>> b = 0
595         >>> with lexical_scope(1) as (a):
596         ...     print a
597         ...
598         1
599         >>> with lexical_scope(1,2,3) as (a,b,c):
600         ...     print a,b,c
601         ...
602         1 2 3
603         >>> with lexical_scope():
604         ...     d = 10
605         ...     def foo():
606         ...             pass
607         ...
608         >>> print b
609         2
610         """
611
612         frame = inspect.currentframe().f_back.f_back
613         saved = frame.f_locals.keys()
614         try:
615                 if not args:
616                         yield
617                 elif len(args) == 1:
618                         yield args[0]
619                 else:
620                         yield args
621         finally:
622                 f_locals = frame.f_locals
623                 for key in (x for x in f_locals.keys() if x not in saved):
624                         del f_locals[key]
625                 del frame