3 from __future__ import with_statement
19 class AnyData(object):
24 _indentationLevel = [0]
29 def log_call_decorator(func):
31 @functools.wraps(func)
32 def wrapper(*args, **kwds):
33 logger.debug("%s> %s" % (" " * _indentationLevel[0], func.__name__, ))
34 _indentationLevel[0] += 1
36 return func(*args, **kwds)
38 _indentationLevel[0] -= 1
39 logger.debug("%s< %s" % (" " * _indentationLevel[0], func.__name__, ))
43 return log_call_decorator
46 def log_exception(logger):
48 def log_exception_decorator(func):
50 @functools.wraps(func)
51 def wrapper(*args, **kwds):
53 return func(*args, **kwds)
55 logger.exception(func.__name__)
60 return log_exception_decorator
63 def printfmt(template):
65 This hides having to create the Template object and call substitute/safe_substitute on it. For example:
69 >>> printfmt("I would like to order $num units of $word, please") #doctest: +SKIP
70 I would like to order 10 units of spam, please
72 frame = inspect.stack()[-1][0]
74 print string.Template(template).safe_substitute(frame.f_locals)
80 return name.startswith("__") and name.endswith("__")
84 return name.startswith("_") and not is_special(name)
87 def privatize(clsName, attributeName):
89 At runtime, make an attributeName private
92 >>> class Test(object):
96 ... dir(Test).index("_Test__me")
101 >>> setattr(Test, privatize(Test.__name__, "me"), "Hello World")
103 ... dir(Test).index("_Test__me")
109 >>> print getattr(Test, obfuscate(Test.__name__, "__me"))
112 >>> is_private(privatize(Test.__name__, "me"))
114 >>> is_special(privatize(Test.__name__, "me"))
117 return "".join(["_", clsName, "__", attributeName])
120 def obfuscate(clsName, attributeName):
122 At runtime, turn a private name into the obfuscated form
125 >>> class Test(object):
126 ... __me = "Hello World"
129 ... dir(Test).index("_Test__me")
135 >>> print getattr(Test, obfuscate(Test.__name__, "__me"))
137 >>> is_private(obfuscate(Test.__name__, "__me"))
139 >>> is_special(obfuscate(Test.__name__, "__me"))
142 return "".join(["_", clsName, attributeName])
145 class PAOptionParser(optparse.OptionParser, object):
147 >>> if __name__ == '__main__':
148 ... #parser = PAOptionParser("My usage str")
149 ... parser = PAOptionParser()
150 ... parser.add_posarg("Foo", help="Foo usage")
151 ... parser.add_posarg("Bar", dest="bar_dest")
152 ... parser.add_posarg("Language", dest='tr_type', type="choice", choices=("Python", "Other"))
153 ... parser.add_option('--stocksym', dest='symbol')
154 ... values, args = parser.parse_args()
155 ... print values, args
161 python mycp.py foo bar
163 python mycp.py foo bar lava
164 Usage: pa.py <Foo> <Bar> <Language> [options]
166 Positional Arguments:
171 pa.py: error: option --Language: invalid choice: 'lava' (choose from 'Python', 'Other'
174 def __init__(self, *args, **kw):
176 super(PAOptionParser, self).__init__(*args, **kw)
178 def add_posarg(self, *args, **kw):
179 pa_help = kw.get("help", "")
180 kw["help"] = optparse.SUPPRESS_HELP
181 o = self.add_option("--%s" % args[0], *args[1:], **kw)
182 self.posargs.append((args[0], pa_help))
184 def get_usage(self, *args, **kwargs):
185 params = (' '.join(["<%s>" % arg[0] for arg in self.posargs]), '\n '.join(["%s: %s" % (arg) for arg in self.posargs]))
186 self.usage = "%%prog %s [options]\n\nPositional Arguments:\n %s" % params
187 return super(PAOptionParser, self).get_usage(*args, **kwargs)
189 def parse_args(self, *args, **kwargs):
192 for p, v in zip(self.posargs, args):
193 args0.append("--%s" % p[0])
196 options, args = super(PAOptionParser, self).parse_args(args, **kwargs)
197 if len(args) < len(self.posargs):
198 msg = 'Missing value(s) for "%s"\n' % ", ".join([arg[0] for arg in self.posargs][len(args):])
203 def explicitly(name, stackadd=0):
205 This is an alias for adding to '__all__'. Less error-prone than using
206 __all__ itself, since setting __all__ directly is prone to stomping on
207 things implicitly exported via L{alias}.
209 @note Taken from PyExport (which could turn out pretty cool):
210 @li @a http://codebrowse.launchpad.net/~glyph/
211 @li @a http://glyf.livejournal.com/74356.html
213 packageVars = sys._getframe(1+stackadd).f_locals
214 globalAll = packageVars.setdefault('__all__', [])
215 globalAll.append(name)
220 This is a decorator, for convenience. Rather than typing the name of your
221 function twice, you can decorate a function with this.
223 To be real, @public would need to work on methods as well, which gets into
226 @note Taken from PyExport (which could turn out pretty cool):
227 @li @a http://codebrowse.launchpad.net/~glyph/
228 @li @a http://glyf.livejournal.com/74356.html
230 explicitly(thunk.__name__, 1)
234 def _append_docstring(obj, message):
235 if obj.__doc__ is None:
236 obj.__doc__ = message
238 obj.__doc__ += message
241 def validate_decorator(decorator):
249 f.__dict__["member"] = True
253 if f.__name__ != g.__name__:
254 print f.__name__, "!=", g.__name__
256 if g.__doc__ is None:
257 print decorator.__name__, "has no doc string"
258 elif not g.__doc__.startswith(f.__doc__):
259 print g.__doc__, "didn't start with", f.__doc__
261 if not ("member" in g.__dict__ and g.__dict__["member"]):
262 print "'member' not in ", g.__dict__
265 def deprecated_api(func):
267 This is a decorator which can be used to mark functions
268 as deprecated. It will result in a warning being emitted
269 when the function is used.
271 >>> validate_decorator(deprecated_api)
274 @functools.wraps(func)
275 def newFunc(*args, **kwargs):
276 warnings.warn("Call to deprecated function %s." % func.__name__, category=DeprecationWarning)
277 return func(*args, **kwargs)
279 _append_docstring(newFunc, "\n@deprecated")
283 def unstable_api(func):
285 This is a decorator which can be used to mark functions
286 as deprecated. It will result in a warning being emitted
287 when the function is used.
289 >>> validate_decorator(unstable_api)
292 @functools.wraps(func)
293 def newFunc(*args, **kwargs):
294 warnings.warn("Call to unstable API function %s." % func.__name__, category=FutureWarning)
295 return func(*args, **kwargs)
296 _append_docstring(newFunc, "\n@unstable")
302 This decorator doesn't add any behavior
304 >>> validate_decorator(enabled)
311 This decorator disables the provided function, and does nothing
313 >>> validate_decorator(disabled)
316 @functools.wraps(func)
317 def emptyFunc(*args, **kargs):
319 _append_docstring(emptyFunc, "\n@note Temporarily Disabled")
323 def metadata(document=True, **kwds):
325 >>> validate_decorator(metadata(author="Ed"))
329 for k, v in kwds.iteritems():
332 _append_docstring(func, "\n@"+k+" "+v)
338 """Function decorator for defining property attributes
340 The decorated function is expected to return a dictionary
341 containing one or more of the following pairs:
342 fget - function for getting attribute value
343 fset - function for setting attribute value
344 fdel - function for deleting attribute
345 This can be conveniently constructed by the locals() builtin
347 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/205183
348 @author http://kbyanc.blogspot.com/2007/06/python-property-attribute-tricks.html
351 >>> #Due to transformation from function to property, does not need to be validated
352 >>> #validate_decorator(prop)
353 >>> class MyExampleClass(object):
356 ... "The foo property attribute's doc-string"
360 ... def fset(self, value):
362 ... self._foo = value
365 >>> me = MyExampleClass()
372 return property(doc=func.__doc__, **func())
375 def print_handler(e):
379 print "%s: %s" % (type(e).__name__, e)
386 print 'Ignoring %s exception: %s' % (type(e).__name__, e)
389 def print_traceback(e):
393 #print sys.exc_info()
394 traceback.print_exc(file=sys.stdout)
397 def ExpHandler(handler = print_handler, *exceptions):
399 An exception handling idiom using decorators
401 Specify exceptions in order, first one is handled first
404 >>> validate_decorator(ExpHandler())
405 >>> @ExpHandler(print_ignore, ZeroDivisionError)
406 ... @ExpHandler(None, AttributeError, ValueError)
409 >>> @ExpHandler(print_traceback, ZeroDivisionError)
416 >>> @ExpHandler(print_traceback, ZeroDivisionError)
422 Ignoring ZeroDivisionError exception: integer division or modulo by zero
423 >>> f2() # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
424 Traceback (most recent call last):
426 ZeroDivisionError: integer division or modulo by zero
428 IndexError: tuple index out of range
434 localExceptions = exceptions
435 if not localExceptions:
436 localExceptions = [Exception]
437 t = [(ex, handler) for ex in localExceptions]
440 def newfunc(t, *args, **kwargs):
444 return f(*args, **kwargs)
446 #Recurse for embedded try/excepts
447 dec_func = functools.partial(newfunc, t[1:])
448 dec_func = functools.update_wrapper(dec_func, f)
449 return dec_func(*args, **kwargs)
453 dec_func = functools.partial(newfunc, t)
454 dec_func = functools.update_wrapper(dec_func, f)
459 def into_debugger(func):
461 >>> validate_decorator(into_debugger)
464 @functools.wraps(func)
465 def newFunc(*args, **kwargs):
467 return func(*args, **kwargs)
475 class bindclass(object):
477 >>> validate_decorator(bindclass)
478 >>> class Foo(BoundObject):
480 ... def foo(this_class, self):
481 ... return this_class, self
485 ... def bar(this_class, self):
486 ... return this_class, self
491 >>> f.foo() # doctest: +ELLIPSIS
492 (<class '...Foo'>, <...Foo object at ...>)
493 >>> b.foo() # doctest: +ELLIPSIS
494 (<class '...Foo'>, <...Bar object at ...>)
495 >>> b.bar() # doctest: +ELLIPSIS
496 (<class '...Bar'>, <...Bar object at ...>)
499 def __init__(self, f):
501 self.__name__ = f.__name__
502 self.__doc__ = f.__doc__
503 self.__dict__.update(f.__dict__)
506 def bind(self, cls, attr):
508 def bound_m(*args, **kwargs):
509 return self.f(cls, *args, **kwargs)
510 bound_m.__name__ = attr
513 def __get__(self, obj, objtype=None):
514 return self.m.__get__(obj, objtype)
517 class ClassBindingSupport(type):
520 def __init__(mcs, name, bases, attrs):
521 type.__init__(mcs, name, bases, attrs)
522 for attr, val in attrs.iteritems():
523 if isinstance(val, bindclass):
527 class BoundObject(object):
529 __metaclass__ = ClassBindingSupport
534 >>> validate_decorator(bindfunction)
536 ... def factorial(thisfunction, n):
537 ... # Within this function the name 'thisfunction' refers to the factorial
538 ... # function(with only one argument), even after 'factorial' is bound
539 ... # to another object
541 ... return n * thisfunction(n - 1)
550 def bound_f(*args, **kwargs):
551 return f(bound_f, *args, **kwargs)
555 class Memoize(object):
557 Memoize(fn) - an instance which acts like fn but memoizes its arguments
558 Will only work on functions with non-mutable arguments
559 @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52201
561 >>> validate_decorator(Memoize)
564 def __init__(self, fn):
566 self.__name__ = fn.__name__
567 self.__doc__ = fn.__doc__
568 self.__dict__.update(fn.__dict__)
571 def __call__(self, *args):
572 if args not in self.memo:
573 self.memo[args] = self.fn(*args)
574 return self.memo[args]
577 class MemoizeMutable(object):
578 """Memoize(fn) - an instance which acts like fn but memoizes its arguments
579 Will work on functions with mutable arguments(slower than Memoize)
580 @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52201
582 >>> validate_decorator(MemoizeMutable)
585 def __init__(self, fn):
587 self.__name__ = fn.__name__
588 self.__doc__ = fn.__doc__
589 self.__dict__.update(fn.__dict__)
592 def __call__(self, *args, **kw):
593 text = cPickle.dumps((args, kw))
594 if text not in self.memo:
595 self.memo[text] = self.fn(*args, **kw)
596 return self.memo[text]
599 callTraceIndentationLevel = 0
604 Synchronization decorator.
606 >>> validate_decorator(call_trace)
611 Entering a((1, 2), {'c': 3})
612 Exiting a((1, 2), {'c': 3})
616 def verboseTrace(*args, **kw):
617 global callTraceIndentationLevel
619 print "%sEntering %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw)
620 callTraceIndentationLevel += 1
622 result = f(*args, **kw)
624 callTraceIndentationLevel -= 1
625 print "%sException %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw)
627 callTraceIndentationLevel -= 1
628 print "%sExiting %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw)
632 def smallTrace(*args, **kw):
633 global callTraceIndentationLevel
635 print "%sEntering %s" % ("\t"*callTraceIndentationLevel, f.__name__)
636 callTraceIndentationLevel += 1
638 result = f(*args, **kw)
640 callTraceIndentationLevel -= 1
641 print "%sException %s" % ("\t"*callTraceIndentationLevel, f.__name__)
643 callTraceIndentationLevel -= 1
644 print "%sExiting %s" % ("\t"*callTraceIndentationLevel, f.__name__)
651 @contextlib.contextmanager
652 def lexical_scope(*args):
654 @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/520586
657 >>> with lexical_scope(1) as (a):
661 >>> with lexical_scope(1,2,3) as (a,b,c):
665 >>> with lexical_scope():
674 frame = inspect.currentframe().f_back.f_back
675 saved = frame.f_locals.keys()
684 f_locals = frame.f_locals
685 for key in (x for x in f_locals.keys() if x not in saved):
690 def normalize_number(prettynumber):
692 function to take a phone number and strip out all non-numeric
695 >>> normalize_number("+012-(345)-678-90")
697 >>> normalize_number("1-(345)-678-9000")
699 >>> normalize_number("+1-(345)-678-9000")
702 uglynumber = re.sub('[^0-9+]', '', prettynumber)
703 if uglynumber.startswith("+"):
705 elif uglynumber.startswith("1"):
706 uglynumber = "+"+uglynumber
707 elif 10 <= len(uglynumber):
708 assert uglynumber[0] not in ("+", "1"), "Number format confusing"
709 uglynumber = "+1"+uglynumber
716 _VALIDATE_RE = re.compile("^\+?[0-9]{10,}$")
719 def is_valid_number(number):
721 @returns If This number be called ( syntax validation only )
723 return _VALIDATE_RE.match(number) is not None
726 def make_ugly(prettynumber):
728 function to take a phone number and strip out all non-numeric
731 >>> make_ugly("+012-(345)-678-90")
734 return normalize_number(prettynumber)
737 def _make_pretty_with_areacode(phonenumber):
738 prettynumber = "(%s)" % (phonenumber[0:3], )
739 if 3 < len(phonenumber):
740 prettynumber += " %s" % (phonenumber[3:6], )
741 if 6 < len(phonenumber):
742 prettynumber += "-%s" % (phonenumber[6:], )
746 def _make_pretty_local(phonenumber):
747 prettynumber = "%s" % (phonenumber[0:3], )
748 if 3 < len(phonenumber):
749 prettynumber += "-%s" % (phonenumber[3:], )
753 def _make_pretty_international(phonenumber):
754 prettynumber = phonenumber
755 if phonenumber.startswith("1"):
757 prettynumber += _make_pretty_with_areacode(phonenumber[1:])
761 def make_pretty(phonenumber):
763 Function to take a phone number and return the pretty version
765 if phonenumber begins with 0:
767 if phonenumber begins with 1: ( for gizmo callback numbers )
769 if phonenumber is 13 digits:
771 if phonenumber is 10 digits:
773 >>> make_pretty("12")
775 >>> make_pretty("1234567")
777 >>> make_pretty("2345678901")
779 >>> make_pretty("12345678901")
781 >>> make_pretty("01234567890")
783 >>> make_pretty("+01234567890")
785 >>> make_pretty("+12")
787 >>> make_pretty("+123")
789 >>> make_pretty("+1234")
792 if phonenumber is None or phonenumber == "":
795 phonenumber = normalize_number(phonenumber)
797 if phonenumber == "":
799 elif phonenumber[0] == "+":
800 prettynumber = _make_pretty_international(phonenumber[1:])
801 if not prettynumber.startswith("+"):
802 prettynumber = "+"+prettynumber
803 elif 8 < len(phonenumber) and phonenumber[0] in ("1", ):
804 prettynumber = _make_pretty_international(phonenumber)
805 elif 7 < len(phonenumber):
806 prettynumber = _make_pretty_with_areacode(phonenumber)
807 elif 3 < len(phonenumber):
808 prettynumber = _make_pretty_local(phonenumber)
810 prettynumber = phonenumber
811 return prettynumber.strip()
814 def similar_ugly_numbers(lhs, rhs):
817 lhs[1:] == rhs and lhs.startswith("1") or
818 lhs[2:] == rhs and lhs.startswith("+1") or
819 lhs == rhs[1:] and rhs.startswith("1") or
820 lhs == rhs[2:] and rhs.startswith("+1")
824 def abbrev_relative_date(date):
826 >>> abbrev_relative_date("42 hours ago")
828 >>> abbrev_relative_date("2 days ago")
830 >>> abbrev_relative_date("4 weeks ago")
833 parts = date.split(" ")
834 return "%s %s" % (parts[0], parts[1][0])
837 def parse_version(versionText):
839 >>> parse_version("0.5.2")
844 for number in versionText.split(".")
848 def compare_versions(leftParsedVersion, rightParsedVersion):
850 >>> compare_versions([0, 1, 2], [0, 1, 2])
852 >>> compare_versions([0, 1, 2], [0, 1, 3])
854 >>> compare_versions([0, 1, 2], [0, 2, 2])
856 >>> compare_versions([0, 1, 2], [1, 1, 2])
858 >>> compare_versions([0, 1, 3], [0, 1, 2])
860 >>> compare_versions([0, 2, 2], [0, 1, 2])
862 >>> compare_versions([1, 1, 2], [0, 1, 2])
865 for left, right in zip(leftParsedVersion, rightParsedVersion):