3 from __future__ import with_statement
19 _indentationLevel = [0]
24 def log_call_decorator(func):
26 @functools.wraps(func)
27 def wrapper(*args, **kwds):
28 logger.debug("%s> %s" % (" " * _indentationLevel[0], func.__name__, ))
29 _indentationLevel[0] += 1
31 return func(*args, **kwds)
33 _indentationLevel[0] -= 1
34 logger.debug("%s< %s" % (" " * _indentationLevel[0], func.__name__, ))
38 return log_call_decorator
41 def log_exception(logger):
43 def log_exception_decorator(func):
45 @functools.wraps(func)
46 def wrapper(*args, **kwds):
48 return func(*args, **kwds)
50 logger.exception(func.__name__)
55 return log_exception_decorator
58 def printfmt(template):
60 This hides having to create the Template object and call substitute/safe_substitute on it. For example:
64 >>> printfmt("I would like to order $num units of $word, please") #doctest: +SKIP
65 I would like to order 10 units of spam, please
67 frame = inspect.stack()[-1][0]
69 print string.Template(template).safe_substitute(frame.f_locals)
75 return name.startswith("__") and name.endswith("__")
79 return name.startswith("_") and not is_special(name)
82 def privatize(clsName, attributeName):
84 At runtime, make an attributeName private
87 >>> class Test(object):
91 ... dir(Test).index("_Test__me")
96 >>> setattr(Test, privatize(Test.__name__, "me"), "Hello World")
98 ... dir(Test).index("_Test__me")
104 >>> print getattr(Test, obfuscate(Test.__name__, "__me"))
107 >>> is_private(privatize(Test.__name__, "me"))
109 >>> is_special(privatize(Test.__name__, "me"))
112 return "".join(["_", clsName, "__", attributeName])
115 def obfuscate(clsName, attributeName):
117 At runtime, turn a private name into the obfuscated form
120 >>> class Test(object):
121 ... __me = "Hello World"
124 ... dir(Test).index("_Test__me")
130 >>> print getattr(Test, obfuscate(Test.__name__, "__me"))
132 >>> is_private(obfuscate(Test.__name__, "__me"))
134 >>> is_special(obfuscate(Test.__name__, "__me"))
137 return "".join(["_", clsName, attributeName])
140 class PAOptionParser(optparse.OptionParser, object):
142 >>> if __name__ == '__main__':
143 ... #parser = PAOptionParser("My usage str")
144 ... parser = PAOptionParser()
145 ... parser.add_posarg("Foo", help="Foo usage")
146 ... parser.add_posarg("Bar", dest="bar_dest")
147 ... parser.add_posarg("Language", dest='tr_type', type="choice", choices=("Python", "Other"))
148 ... parser.add_option('--stocksym', dest='symbol')
149 ... values, args = parser.parse_args()
150 ... print values, args
156 python mycp.py foo bar
158 python mycp.py foo bar lava
159 Usage: pa.py <Foo> <Bar> <Language> [options]
161 Positional Arguments:
166 pa.py: error: option --Language: invalid choice: 'lava' (choose from 'Python', 'Other'
169 def __init__(self, *args, **kw):
171 super(PAOptionParser, self).__init__(*args, **kw)
173 def add_posarg(self, *args, **kw):
174 pa_help = kw.get("help", "")
175 kw["help"] = optparse.SUPPRESS_HELP
176 o = self.add_option("--%s" % args[0], *args[1:], **kw)
177 self.posargs.append((args[0], pa_help))
179 def get_usage(self, *args, **kwargs):
180 params = (' '.join(["<%s>" % arg[0] for arg in self.posargs]), '\n '.join(["%s: %s" % (arg) for arg in self.posargs]))
181 self.usage = "%%prog %s [options]\n\nPositional Arguments:\n %s" % params
182 return super(PAOptionParser, self).get_usage(*args, **kwargs)
184 def parse_args(self, *args, **kwargs):
187 for p, v in zip(self.posargs, args):
188 args0.append("--%s" % p[0])
191 options, args = super(PAOptionParser, self).parse_args(args, **kwargs)
192 if len(args) < len(self.posargs):
193 msg = 'Missing value(s) for "%s"\n' % ", ".join([arg[0] for arg in self.posargs][len(args):])
198 def explicitly(name, stackadd=0):
200 This is an alias for adding to '__all__'. Less error-prone than using
201 __all__ itself, since setting __all__ directly is prone to stomping on
202 things implicitly exported via L{alias}.
204 @note Taken from PyExport (which could turn out pretty cool):
205 @li @a http://codebrowse.launchpad.net/~glyph/
206 @li @a http://glyf.livejournal.com/74356.html
208 packageVars = sys._getframe(1+stackadd).f_locals
209 globalAll = packageVars.setdefault('__all__', [])
210 globalAll.append(name)
215 This is a decorator, for convenience. Rather than typing the name of your
216 function twice, you can decorate a function with this.
218 To be real, @public would need to work on methods as well, which gets into
221 @note Taken from PyExport (which could turn out pretty cool):
222 @li @a http://codebrowse.launchpad.net/~glyph/
223 @li @a http://glyf.livejournal.com/74356.html
225 explicitly(thunk.__name__, 1)
229 def _append_docstring(obj, message):
230 if obj.__doc__ is None:
231 obj.__doc__ = message
233 obj.__doc__ += message
236 def validate_decorator(decorator):
244 f.__dict__["member"] = True
248 if f.__name__ != g.__name__:
249 print f.__name__, "!=", g.__name__
251 if g.__doc__ is None:
252 print decorator.__name__, "has no doc string"
253 elif not g.__doc__.startswith(f.__doc__):
254 print g.__doc__, "didn't start with", f.__doc__
256 if not ("member" in g.__dict__ and g.__dict__["member"]):
257 print "'member' not in ", g.__dict__
260 def deprecated_api(func):
262 This is a decorator which can be used to mark functions
263 as deprecated. It will result in a warning being emitted
264 when the function is used.
266 >>> validate_decorator(deprecated_api)
269 @functools.wraps(func)
270 def newFunc(*args, **kwargs):
271 warnings.warn("Call to deprecated function %s." % func.__name__, category=DeprecationWarning)
272 return func(*args, **kwargs)
274 _append_docstring(newFunc, "\n@deprecated")
278 def unstable_api(func):
280 This is a decorator which can be used to mark functions
281 as deprecated. It will result in a warning being emitted
282 when the function is used.
284 >>> validate_decorator(unstable_api)
287 @functools.wraps(func)
288 def newFunc(*args, **kwargs):
289 warnings.warn("Call to unstable API function %s." % func.__name__, category=FutureWarning)
290 return func(*args, **kwargs)
291 _append_docstring(newFunc, "\n@unstable")
297 This decorator doesn't add any behavior
299 >>> validate_decorator(enabled)
306 This decorator disables the provided function, and does nothing
308 >>> validate_decorator(disabled)
311 @functools.wraps(func)
312 def emptyFunc(*args, **kargs):
314 _append_docstring(emptyFunc, "\n@note Temporarily Disabled")
318 def metadata(document=True, **kwds):
320 >>> validate_decorator(metadata(author="Ed"))
324 for k, v in kwds.iteritems():
327 _append_docstring(func, "\n@"+k+" "+v)
333 """Function decorator for defining property attributes
335 The decorated function is expected to return a dictionary
336 containing one or more of the following pairs:
337 fget - function for getting attribute value
338 fset - function for setting attribute value
339 fdel - function for deleting attribute
340 This can be conveniently constructed by the locals() builtin
342 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/205183
343 @author http://kbyanc.blogspot.com/2007/06/python-property-attribute-tricks.html
346 >>> #Due to transformation from function to property, does not need to be validated
347 >>> #validate_decorator(prop)
348 >>> class MyExampleClass(object):
351 ... "The foo property attribute's doc-string"
355 ... def fset(self, value):
357 ... self._foo = value
360 >>> me = MyExampleClass()
367 return property(doc=func.__doc__, **func())
370 def print_handler(e):
374 print "%s: %s" % (type(e).__name__, e)
381 print 'Ignoring %s exception: %s' % (type(e).__name__, e)
384 def print_traceback(e):
388 #print sys.exc_info()
389 traceback.print_exc(file=sys.stdout)
392 def ExpHandler(handler = print_handler, *exceptions):
394 An exception handling idiom using decorators
396 Specify exceptions in order, first one is handled first
399 >>> validate_decorator(ExpHandler())
400 >>> @ExpHandler(print_ignore, ZeroDivisionError)
401 ... @ExpHandler(None, AttributeError, ValueError)
404 >>> @ExpHandler(print_traceback, ZeroDivisionError)
411 >>> @ExpHandler(print_traceback, ZeroDivisionError)
417 Ignoring ZeroDivisionError exception: integer division or modulo by zero
418 >>> f2() # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
419 Traceback (most recent call last):
421 ZeroDivisionError: integer division or modulo by zero
423 IndexError: tuple index out of range
429 localExceptions = exceptions
430 if not localExceptions:
431 localExceptions = [Exception]
432 t = [(ex, handler) for ex in localExceptions]
435 def newfunc(t, *args, **kwargs):
439 return f(*args, **kwargs)
441 #Recurse for embedded try/excepts
442 dec_func = functools.partial(newfunc, t[1:])
443 dec_func = functools.update_wrapper(dec_func, f)
444 return dec_func(*args, **kwargs)
448 dec_func = functools.partial(newfunc, t)
449 dec_func = functools.update_wrapper(dec_func, f)
454 def into_debugger(func):
456 >>> validate_decorator(into_debugger)
459 @functools.wraps(func)
460 def newFunc(*args, **kwargs):
462 return func(*args, **kwargs)
470 class bindclass(object):
472 >>> validate_decorator(bindclass)
473 >>> class Foo(BoundObject):
475 ... def foo(this_class, self):
476 ... return this_class, self
480 ... def bar(this_class, self):
481 ... return this_class, self
486 >>> f.foo() # doctest: +ELLIPSIS
487 (<class '...Foo'>, <...Foo object at ...>)
488 >>> b.foo() # doctest: +ELLIPSIS
489 (<class '...Foo'>, <...Bar object at ...>)
490 >>> b.bar() # doctest: +ELLIPSIS
491 (<class '...Bar'>, <...Bar object at ...>)
494 def __init__(self, f):
496 self.__name__ = f.__name__
497 self.__doc__ = f.__doc__
498 self.__dict__.update(f.__dict__)
501 def bind(self, cls, attr):
503 def bound_m(*args, **kwargs):
504 return self.f(cls, *args, **kwargs)
505 bound_m.__name__ = attr
508 def __get__(self, obj, objtype=None):
509 return self.m.__get__(obj, objtype)
512 class ClassBindingSupport(type):
515 def __init__(mcs, name, bases, attrs):
516 type.__init__(mcs, name, bases, attrs)
517 for attr, val in attrs.iteritems():
518 if isinstance(val, bindclass):
522 class BoundObject(object):
524 __metaclass__ = ClassBindingSupport
529 >>> validate_decorator(bindfunction)
531 ... def factorial(thisfunction, n):
532 ... # Within this function the name 'thisfunction' refers to the factorial
533 ... # function(with only one argument), even after 'factorial' is bound
534 ... # to another object
536 ... return n * thisfunction(n - 1)
545 def bound_f(*args, **kwargs):
546 return f(bound_f, *args, **kwargs)
550 class Memoize(object):
552 Memoize(fn) - an instance which acts like fn but memoizes its arguments
553 Will only work on functions with non-mutable arguments
554 @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52201
556 >>> validate_decorator(Memoize)
559 def __init__(self, fn):
561 self.__name__ = fn.__name__
562 self.__doc__ = fn.__doc__
563 self.__dict__.update(fn.__dict__)
566 def __call__(self, *args):
567 if args not in self.memo:
568 self.memo[args] = self.fn(*args)
569 return self.memo[args]
572 class MemoizeMutable(object):
573 """Memoize(fn) - an instance which acts like fn but memoizes its arguments
574 Will work on functions with mutable arguments(slower than Memoize)
575 @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52201
577 >>> validate_decorator(MemoizeMutable)
580 def __init__(self, fn):
582 self.__name__ = fn.__name__
583 self.__doc__ = fn.__doc__
584 self.__dict__.update(fn.__dict__)
587 def __call__(self, *args, **kw):
588 text = cPickle.dumps((args, kw))
589 if text not in self.memo:
590 self.memo[text] = self.fn(*args, **kw)
591 return self.memo[text]
594 callTraceIndentationLevel = 0
599 Synchronization decorator.
601 >>> validate_decorator(call_trace)
606 Entering a((1, 2), {'c': 3})
607 Exiting a((1, 2), {'c': 3})
611 def verboseTrace(*args, **kw):
612 global callTraceIndentationLevel
614 print "%sEntering %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw)
615 callTraceIndentationLevel += 1
617 result = f(*args, **kw)
619 callTraceIndentationLevel -= 1
620 print "%sException %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw)
622 callTraceIndentationLevel -= 1
623 print "%sExiting %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw)
627 def smallTrace(*args, **kw):
628 global callTraceIndentationLevel
630 print "%sEntering %s" % ("\t"*callTraceIndentationLevel, f.__name__)
631 callTraceIndentationLevel += 1
633 result = f(*args, **kw)
635 callTraceIndentationLevel -= 1
636 print "%sException %s" % ("\t"*callTraceIndentationLevel, f.__name__)
638 callTraceIndentationLevel -= 1
639 print "%sExiting %s" % ("\t"*callTraceIndentationLevel, f.__name__)
646 @contextlib.contextmanager
647 def lexical_scope(*args):
649 @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/520586
652 >>> with lexical_scope(1) as (a):
656 >>> with lexical_scope(1,2,3) as (a,b,c):
660 >>> with lexical_scope():
669 frame = inspect.currentframe().f_back.f_back
670 saved = frame.f_locals.keys()
679 f_locals = frame.f_locals
680 for key in (x for x in f_locals.keys() if x not in saved):
685 def normalize_number(prettynumber):
687 function to take a phone number and strip out all non-numeric
690 >>> normalize_number("+012-(345)-678-90")
692 >>> normalize_number("1-(345)-678-9000")
694 >>> normalize_number("+1-(345)-678-9000")
697 uglynumber = re.sub('[^0-9+]', '', prettynumber)
698 if uglynumber.startswith("+"):
700 elif uglynumber.startswith("1") and len(uglynumber) == 11:
701 uglynumber = "+"+uglynumber
702 elif len(uglynumber) == 10:
703 uglynumber = "+1"+uglynumber
707 #validateRe = re.compile("^\+?[0-9]{10,}$")
708 #assert validateRe.match(uglynumber) is not None
713 _VALIDATE_RE = re.compile("^\+?[0-9]{10,}$")
716 def is_valid_number(number):
718 @returns If This number be called ( syntax validation only )
720 return _VALIDATE_RE.match(number) is not None
723 def parse_version(versionText):
725 >>> parse_version("0.5.2")
730 for number in versionText.split(".")
734 def compare_versions(leftParsedVersion, rightParsedVersion):
736 >>> compare_versions([0, 1, 2], [0, 1, 2])
738 >>> compare_versions([0, 1, 2], [0, 1, 3])
740 >>> compare_versions([0, 1, 2], [0, 2, 2])
742 >>> compare_versions([0, 1, 2], [1, 1, 2])
744 >>> compare_versions([0, 1, 3], [0, 1, 2])
746 >>> compare_versions([0, 2, 2], [0, 1, 2])
748 >>> compare_versions([1, 1, 2], [0, 1, 2])
751 for left, right in zip(leftParsedVersion, rightParsedVersion):