Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
901 lines (730 sloc) 20.1 KB
#!/usr/bin/env python
from __future__ import with_statement
import sys
import re
import cPickle
import functools
import contextlib
import inspect
import optparse
import traceback
import warnings
import string
class AnyData(object):
pass
_indentationLevel = [0]
def log_call(logger):
def log_call_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwds):
logger.debug("%s> %s" % (" " * _indentationLevel[0], func.__name__, ))
_indentationLevel[0] += 1
try:
return func(*args, **kwds)
finally:
_indentationLevel[0] -= 1
logger.debug("%s< %s" % (" " * _indentationLevel[0], func.__name__, ))
return wrapper
return log_call_decorator
def log_exception(logger):
def log_exception_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwds):
try:
return func(*args, **kwds)
except Exception as e:
logger.exception("Internal Error: %s" % e)
raise
return wrapper
return log_exception_decorator
def printfmt(template):
"""
This hides having to create the Template object and call substitute/safe_substitute on it. For example:
>>> num = 10
>>> word = "spam"
>>> printfmt("I would like to order $num units of $word, please") #doctest: +SKIP
I would like to order 10 units of spam, please
"""
frame = inspect.stack()[-1][0]
try:
print string.Template(template).safe_substitute(frame.f_locals)
finally:
del frame
def is_special(name):
return name.startswith("__") and name.endswith("__")
def is_private(name):
return name.startswith("_") and not is_special(name)
def privatize(clsName, attributeName):
"""
At runtime, make an attributeName private
Example:
>>> class Test(object):
... pass
...
>>> try:
... dir(Test).index("_Test__me")
... print dir(Test)
... except:
... print "Not Found"
Not Found
>>> setattr(Test, privatize(Test.__name__, "me"), "Hello World")
>>> try:
... dir(Test).index("_Test__me")
... print "Found"
... except:
... print dir(Test)
0
Found
>>> print getattr(Test, obfuscate(Test.__name__, "__me"))
Hello World
>>>
>>> is_private(privatize(Test.__name__, "me"))
True
>>> is_special(privatize(Test.__name__, "me"))
False
"""
return "".join(["_", clsName, "__", attributeName])
def obfuscate(clsName, attributeName):
"""
At runtime, turn a private name into the obfuscated form
Example:
>>> class Test(object):
... __me = "Hello World"
...
>>> try:
... dir(Test).index("_Test__me")
... print "Found"
... except:
... print dir(Test)
0
Found
>>> print getattr(Test, obfuscate(Test.__name__, "__me"))
Hello World
>>> is_private(obfuscate(Test.__name__, "__me"))
True
>>> is_special(obfuscate(Test.__name__, "__me"))
False
"""
return "".join(["_", clsName, attributeName])
class PAOptionParser(optparse.OptionParser, object):
"""
>>> if __name__ == '__main__':
... #parser = PAOptionParser("My usage str")
... parser = PAOptionParser()
... parser.add_posarg("Foo", help="Foo usage")
... parser.add_posarg("Bar", dest="bar_dest")
... parser.add_posarg("Language", dest='tr_type', type="choice", choices=("Python", "Other"))
... parser.add_option('--stocksym', dest='symbol')
... values, args = parser.parse_args()
... print values, args
...
python mycp.py -h
python mycp.py
python mycp.py foo
python mycp.py foo bar
python mycp.py foo bar lava
Usage: pa.py <Foo> <Bar> <Language> [options]
Positional Arguments:
Foo: Foo usage
Bar:
Language:
pa.py: error: option --Language: invalid choice: 'lava' (choose from 'Python', 'Other'
"""
def __init__(self, *args, **kw):
self.posargs = []
super(PAOptionParser, self).__init__(*args, **kw)
def add_posarg(self, *args, **kw):
pa_help = kw.get("help", "")
kw["help"] = optparse.SUPPRESS_HELP
o = self.add_option("--%s" % args[0], *args[1:], **kw)
self.posargs.append((args[0], pa_help))
def get_usage(self, *args, **kwargs):
params = (' '.join(["<%s>" % arg[0] for arg in self.posargs]), '\n '.join(["%s: %s" % (arg) for arg in self.posargs]))
self.usage = "%%prog %s [options]\n\nPositional Arguments:\n %s" % params
return super(PAOptionParser, self).get_usage(*args, **kwargs)
def parse_args(self, *args, **kwargs):
args = sys.argv[1:]
args0 = []
for p, v in zip(self.posargs, args):
args0.append("--%s" % p[0])
args0.append(v)
args = args0 + args
options, args = super(PAOptionParser, self).parse_args(args, **kwargs)
if len(args) < len(self.posargs):
msg = 'Missing value(s) for "%s"\n' % ", ".join([arg[0] for arg in self.posargs][len(args):])
self.error(msg)
return options, args
def explicitly(name, stackadd=0):
"""
This is an alias for adding to '__all__'. Less error-prone than using
__all__ itself, since setting __all__ directly is prone to stomping on
things implicitly exported via L{alias}.
@note Taken from PyExport (which could turn out pretty cool):
@li @a http://codebrowse.launchpad.net/~glyph/
@li @a http://glyf.livejournal.com/74356.html
"""
packageVars = sys._getframe(1+stackadd).f_locals
globalAll = packageVars.setdefault('__all__', [])
globalAll.append(name)
def public(thunk):
"""
This is a decorator, for convenience. Rather than typing the name of your
function twice, you can decorate a function with this.
To be real, @public would need to work on methods as well, which gets into
supporting types...
@note Taken from PyExport (which could turn out pretty cool):
@li @a http://codebrowse.launchpad.net/~glyph/
@li @a http://glyf.livejournal.com/74356.html
"""
explicitly(thunk.__name__, 1)
return thunk
def _append_docstring(obj, message):
if obj.__doc__ is None:
obj.__doc__ = message
else:
obj.__doc__ += message
def validate_decorator(decorator):
def simple(x):
return x
f = simple
f.__name__ = "name"
f.__doc__ = "doc"
f.__dict__["member"] = True
g = decorator(f)
if f.__name__ != g.__name__:
print f.__name__, "!=", g.__name__
if g.__doc__ is None:
print decorator.__name__, "has no doc string"
elif not g.__doc__.startswith(f.__doc__):
print g.__doc__, "didn't start with", f.__doc__
if not ("member" in g.__dict__ and g.__dict__["member"]):
print "'member' not in ", g.__dict__
def deprecated_api(func):
"""
This is a decorator which can be used to mark functions
as deprecated. It will result in a warning being emitted
when the function is used.
>>> validate_decorator(deprecated_api)
"""
@functools.wraps(func)
def newFunc(*args, **kwargs):
warnings.warn("Call to deprecated function %s." % func.__name__, category=DeprecationWarning)
return func(*args, **kwargs)
_append_docstring(newFunc, "\n@deprecated")
return newFunc
def unstable_api(func):
"""
This is a decorator which can be used to mark functions
as deprecated. It will result in a warning being emitted
when the function is used.
>>> validate_decorator(unstable_api)
"""
@functools.wraps(func)
def newFunc(*args, **kwargs):
warnings.warn("Call to unstable API function %s." % func.__name__, category=FutureWarning)
return func(*args, **kwargs)
_append_docstring(newFunc, "\n@unstable")
return newFunc
def enabled(func):
"""
This decorator doesn't add any behavior
>>> validate_decorator(enabled)
"""
return func
def disabled(func):
"""
This decorator disables the provided function, and does nothing
>>> validate_decorator(disabled)
"""
@functools.wraps(func)
def emptyFunc(*args, **kargs):
pass
_append_docstring(emptyFunc, "\n@note Temporarily Disabled")
return emptyFunc
def metadata(document=True, **kwds):
"""
>>> validate_decorator(metadata(author="Ed"))
"""
def decorate(func):
for k, v in kwds.iteritems():
setattr(func, k, v)
if document:
_append_docstring(func, "\n@"+k+" "+v)
return func
return decorate
def prop(func):
"""Function decorator for defining property attributes
The decorated function is expected to return a dictionary
containing one or more of the following pairs:
fget - function for getting attribute value
fset - function for setting attribute value
fdel - function for deleting attribute
This can be conveniently constructed by the locals() builtin
function; see:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/205183
@author http://kbyanc.blogspot.com/2007/06/python-property-attribute-tricks.html
Example:
>>> #Due to transformation from function to property, does not need to be validated
>>> #validate_decorator(prop)
>>> class MyExampleClass(object):
... @prop
... def foo():
... "The foo property attribute's doc-string"
... def fget(self):
... print "GET"
... return self._foo
... def fset(self, value):
... print "SET"
... self._foo = value
... return locals()
...
>>> me = MyExampleClass()
>>> me.foo = 10
SET
>>> print me.foo
GET
10
"""
return property(doc=func.__doc__, **func())
def print_handler(e):
"""
@see ExpHandler
"""
print "%s: %s" % (type(e).__name__, e)
def print_ignore(e):
"""
@see ExpHandler
"""
print 'Ignoring %s exception: %s' % (type(e).__name__, e)
def print_traceback(e):
"""
@see ExpHandler
"""
#print sys.exc_info()
traceback.print_exc(file=sys.stdout)
def ExpHandler(handler = print_handler, *exceptions):
"""
An exception handling idiom using decorators
Examples
Specify exceptions in order, first one is handled first
last one last.
>>> validate_decorator(ExpHandler())
>>> @ExpHandler(print_ignore, ZeroDivisionError)
... @ExpHandler(None, AttributeError, ValueError)
... def f1():
... 1/0
>>> @ExpHandler(print_traceback, ZeroDivisionError)
... def f2():
... 1/0
>>> @ExpHandler()
... def f3(*pargs):
... l = pargs
... return l[10]
>>> @ExpHandler(print_traceback, ZeroDivisionError)
... def f4():
... return 1
>>>
>>>
>>> f1()
Ignoring ZeroDivisionError exception: integer division or modulo by zero
>>> f2() # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Traceback (most recent call last):
...
ZeroDivisionError: integer division or modulo by zero
>>> f3()
IndexError: tuple index out of range
>>> f4()
1
"""
def wrapper(f):
localExceptions = exceptions
if not localExceptions:
localExceptions = [Exception]
t = [(ex, handler) for ex in localExceptions]
t.reverse()
def newfunc(t, *args, **kwargs):
ex, handler = t[0]
try:
if len(t) == 1:
return f(*args, **kwargs)
else:
#Recurse for embedded try/excepts
dec_func = functools.partial(newfunc, t[1:])
dec_func = functools.update_wrapper(dec_func, f)
return dec_func(*args, **kwargs)
except ex, e:
return handler(e)
dec_func = functools.partial(newfunc, t)
dec_func = functools.update_wrapper(dec_func, f)
return dec_func
return wrapper
def into_debugger(func):
"""
>>> validate_decorator(into_debugger)
"""
@functools.wraps(func)
def newFunc(*args, **kwargs):
try:
return func(*args, **kwargs)
except:
import pdb
pdb.post_mortem()
return newFunc
class bindclass(object):
"""
>>> validate_decorator(bindclass)
>>> class Foo(BoundObject):
... @bindclass
... def foo(this_class, self):
... return this_class, self
...
>>> class Bar(Foo):
... @bindclass
... def bar(this_class, self):
... return this_class, self
...
>>> f = Foo()
>>> b = Bar()
>>>
>>> f.foo() # doctest: +ELLIPSIS
(<class '...Foo'>, <...Foo object at ...>)
>>> b.foo() # doctest: +ELLIPSIS
(<class '...Foo'>, <...Bar object at ...>)
>>> b.bar() # doctest: +ELLIPSIS
(<class '...Bar'>, <...Bar object at ...>)
"""
def __init__(self, f):
self.f = f
self.__name__ = f.__name__
self.__doc__ = f.__doc__
self.__dict__.update(f.__dict__)
self.m = None
def bind(self, cls, attr):
def bound_m(*args, **kwargs):
return self.f(cls, *args, **kwargs)
bound_m.__name__ = attr
self.m = bound_m
def __get__(self, obj, objtype=None):
return self.m.__get__(obj, objtype)
class ClassBindingSupport(type):
"@see bindclass"
def __init__(mcs, name, bases, attrs):
type.__init__(mcs, name, bases, attrs)
for attr, val in attrs.iteritems():
if isinstance(val, bindclass):
val.bind(mcs, attr)
class BoundObject(object):
"@see bindclass"
__metaclass__ = ClassBindingSupport
def bindfunction(f):
"""
>>> validate_decorator(bindfunction)
>>> @bindfunction
... def factorial(thisfunction, n):
... # Within this function the name 'thisfunction' refers to the factorial
... # function(with only one argument), even after 'factorial' is bound
... # to another object
... if n > 0:
... return n * thisfunction(n - 1)
... else:
... return 1
...
>>> factorial(3)
6
"""
@functools.wraps(f)
def bound_f(*args, **kwargs):
return f(bound_f, *args, **kwargs)
return bound_f
class Memoize(object):
"""
Memoize(fn) - an instance which acts like fn but memoizes its arguments
Will only work on functions with non-mutable arguments
@note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52201
>>> validate_decorator(Memoize)
"""
def __init__(self, fn):
self.fn = fn
self.__name__ = fn.__name__
self.__doc__ = fn.__doc__
self.__dict__.update(fn.__dict__)
self.memo = {}
def __call__(self, *args):
if args not in self.memo:
self.memo[args] = self.fn(*args)
return self.memo[args]
class MemoizeMutable(object):
"""Memoize(fn) - an instance which acts like fn but memoizes its arguments
Will work on functions with mutable arguments(slower than Memoize)
@note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52201
>>> validate_decorator(MemoizeMutable)
"""
def __init__(self, fn):
self.fn = fn
self.__name__ = fn.__name__
self.__doc__ = fn.__doc__
self.__dict__.update(fn.__dict__)
self.memo = {}
def __call__(self, *args, **kw):
text = cPickle.dumps((args, kw))
if text not in self.memo:
self.memo[text] = self.fn(*args, **kw)
return self.memo[text]
callTraceIndentationLevel = 0
def call_trace(f):
"""
Synchronization decorator.
>>> validate_decorator(call_trace)
>>> @call_trace
... def a(a, b, c):
... pass
>>> a(1, 2, c=3)
Entering a((1, 2), {'c': 3})
Exiting a((1, 2), {'c': 3})
"""
@functools.wraps(f)
def verboseTrace(*args, **kw):
global callTraceIndentationLevel
print "%sEntering %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw)
callTraceIndentationLevel += 1
try:
result = f(*args, **kw)
except:
callTraceIndentationLevel -= 1
print "%sException %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw)
raise
callTraceIndentationLevel -= 1
print "%sExiting %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw)
return result
@functools.wraps(f)
def smallTrace(*args, **kw):
global callTraceIndentationLevel
print "%sEntering %s" % ("\t"*callTraceIndentationLevel, f.__name__)
callTraceIndentationLevel += 1
try:
result = f(*args, **kw)
except:
callTraceIndentationLevel -= 1
print "%sException %s" % ("\t"*callTraceIndentationLevel, f.__name__)
raise
callTraceIndentationLevel -= 1
print "%sExiting %s" % ("\t"*callTraceIndentationLevel, f.__name__)
return result
#return smallTrace
return verboseTrace
@contextlib.contextmanager
def nested_break():
"""
>>> with nested_break() as mylabel:
... for i in xrange(3):
... print "Outer", i
... for j in xrange(3):
... if i == 2: raise mylabel
... if j == 2: break
... print "Inner", j
... print "more processing"
Outer 0
Inner 0
Inner 1
Outer 1
Inner 0
Inner 1
Outer 2
"""
class NestedBreakException(Exception):
pass
try:
yield NestedBreakException
except NestedBreakException:
pass
@contextlib.contextmanager
def lexical_scope(*args):
"""
@note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/520586
Example:
>>> b = 0
>>> with lexical_scope(1) as (a):
... print a
...
1
>>> with lexical_scope(1,2,3) as (a,b,c):
... print a,b,c
...
1 2 3
>>> with lexical_scope():
... d = 10
... def foo():
... pass
...
>>> print b
2
"""
frame = inspect.currentframe().f_back.f_back
saved = frame.f_locals.keys()
try:
if not args:
yield
elif len(args) == 1:
yield args[0]
else:
yield args
finally:
f_locals = frame.f_locals
for key in (x for x in f_locals.keys() if x not in saved):
del f_locals[key]
del frame
def normalize_number(prettynumber):
"""
function to take a phone number and strip out all non-numeric
characters
>>> normalize_number("+012-(345)-678-90")
'+01234567890'
>>> normalize_number("1-(345)-678-9000")
'+13456789000'
>>> normalize_number("+1-(345)-678-9000")
'+13456789000'
"""
uglynumber = re.sub('[^0-9+]', '', prettynumber)
if uglynumber.startswith("+"):
pass
elif uglynumber.startswith("1"):
uglynumber = "+"+uglynumber
elif 10 <= len(uglynumber):
assert uglynumber[0] not in ("+", "1"), "Number format confusing"
uglynumber = "+1"+uglynumber
else:
pass
return uglynumber
_VALIDATE_RE = re.compile("^\+?[0-9]{10,}$")
def is_valid_number(number):
"""
@returns If This number be called ( syntax validation only )
"""
return _VALIDATE_RE.match(number) is not None
def make_ugly(prettynumber):
"""
function to take a phone number and strip out all non-numeric
characters
>>> make_ugly("+012-(345)-678-90")
'+01234567890'
"""
return normalize_number(prettynumber)
def _make_pretty_with_areacode(phonenumber):
prettynumber = "(%s)" % (phonenumber[0:3], )
if 3 < len(phonenumber):
prettynumber += " %s" % (phonenumber[3:6], )
if 6 < len(phonenumber):
prettynumber += "-%s" % (phonenumber[6:], )
return prettynumber
def _make_pretty_local(phonenumber):
prettynumber = "%s" % (phonenumber[0:3], )
if 3 < len(phonenumber):
prettynumber += "-%s" % (phonenumber[3:], )
return prettynumber
def _make_pretty_international(phonenumber):
prettynumber = phonenumber
if phonenumber.startswith("1"):
prettynumber = "1 "
prettynumber += _make_pretty_with_areacode(phonenumber[1:])
return prettynumber
def make_pretty(phonenumber):
"""
Function to take a phone number and return the pretty version
pretty numbers:
if phonenumber begins with 0:
...-(...)-...-....
if phonenumber begins with 1: ( for gizmo callback numbers )
1 (...)-...-....
if phonenumber is 13 digits:
(...)-...-....
if phonenumber is 10 digits:
...-....
>>> make_pretty("12")
'12'
>>> make_pretty("1234567")
'123-4567'
>>> make_pretty("2345678901")
'+1 (234) 567-8901'
>>> make_pretty("12345678901")
'+1 (234) 567-8901'
>>> make_pretty("01234567890")
'+012 (345) 678-90'
>>> make_pretty("+01234567890")
'+012 (345) 678-90'
>>> make_pretty("+12")
'+1 (2)'
>>> make_pretty("+123")
'+1 (23)'
>>> make_pretty("+1234")
'+1 (234)'
"""
if phonenumber is None or phonenumber == "":
return ""
phonenumber = normalize_number(phonenumber)
if phonenumber == "":
return ""
elif phonenumber[0] == "+":
prettynumber = _make_pretty_international(phonenumber[1:])
if not prettynumber.startswith("+"):
prettynumber = "+"+prettynumber
elif 8 < len(phonenumber) and phonenumber[0] in ("1", ):
prettynumber = _make_pretty_international(phonenumber)
elif 7 < len(phonenumber):
prettynumber = _make_pretty_with_areacode(phonenumber)
elif 3 < len(phonenumber):
prettynumber = _make_pretty_local(phonenumber)
else:
prettynumber = phonenumber
return prettynumber.strip()
def similar_ugly_numbers(lhs, rhs):
return (
lhs == rhs or
lhs[1:] == rhs and lhs.startswith("1") or
lhs[2:] == rhs and lhs.startswith("+1") or
lhs == rhs[1:] and rhs.startswith("1") or
lhs == rhs[2:] and rhs.startswith("+1")
)
def abbrev_relative_date(date):
"""
>>> abbrev_relative_date("42 hours ago")
'42 h'
>>> abbrev_relative_date("2 days ago")
'2 d'
>>> abbrev_relative_date("4 weeks ago")
'4 w'
"""
parts = date.split(" ")
return "%s %s" % (parts[0], parts[1][0])
def parse_version(versionText):
"""
>>> parse_version("0.5.2")
[0, 5, 2]
"""
return [
int(number)
for number in versionText.split(".")
]
def compare_versions(leftParsedVersion, rightParsedVersion):
"""
>>> compare_versions([0, 1, 2], [0, 1, 2])
0
>>> compare_versions([0, 1, 2], [0, 1, 3])
-1
>>> compare_versions([0, 1, 2], [0, 2, 2])
-1
>>> compare_versions([0, 1, 2], [1, 1, 2])
-1
>>> compare_versions([0, 1, 3], [0, 1, 2])
1
>>> compare_versions([0, 2, 2], [0, 1, 2])
1
>>> compare_versions([1, 1, 2], [0, 1, 2])
1
"""
for left, right in zip(leftParsedVersion, rightParsedVersion):
if left < right:
return -1
elif right < left:
return 1
else:
return 0