1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/OpenSecurity/install/web.py-0.37/web/utils.py Mon Dec 02 14:02:05 2013 +0100
1.3 @@ -0,0 +1,1526 @@
1.4 +#!/usr/bin/env python
1.5 +"""
1.6 +General Utilities
1.7 +(part of web.py)
1.8 +"""
1.9 +
1.10 +__all__ = [
1.11 + "Storage", "storage", "storify",
1.12 + "Counter", "counter",
1.13 + "iters",
1.14 + "rstrips", "lstrips", "strips",
1.15 + "safeunicode", "safestr", "utf8",
1.16 + "TimeoutError", "timelimit",
1.17 + "Memoize", "memoize",
1.18 + "re_compile", "re_subm",
1.19 + "group", "uniq", "iterview",
1.20 + "IterBetter", "iterbetter",
1.21 + "safeiter", "safewrite",
1.22 + "dictreverse", "dictfind", "dictfindall", "dictincr", "dictadd",
1.23 + "requeue", "restack",
1.24 + "listget", "intget", "datestr",
1.25 + "numify", "denumify", "commify", "dateify",
1.26 + "nthstr", "cond",
1.27 + "CaptureStdout", "capturestdout", "Profile", "profile",
1.28 + "tryall",
1.29 + "ThreadedDict", "threadeddict",
1.30 + "autoassign",
1.31 + "to36",
1.32 + "safemarkdown",
1.33 + "sendmail"
1.34 +]
1.35 +
1.36 +import re, sys, time, threading, itertools, traceback, os
1.37 +
1.38 +try:
1.39 + import subprocess
1.40 +except ImportError:
1.41 + subprocess = None
1.42 +
1.43 +try: import datetime
1.44 +except ImportError: pass
1.45 +
1.46 +try: set
1.47 +except NameError:
1.48 + from sets import Set as set
1.49 +
1.50 +try:
1.51 + from threading import local as threadlocal
1.52 +except ImportError:
1.53 + from python23 import threadlocal
1.54 +
1.55 +class Storage(dict):
1.56 + """
1.57 + A Storage object is like a dictionary except `obj.foo` can be used
1.58 + in addition to `obj['foo']`.
1.59 +
1.60 + >>> o = storage(a=1)
1.61 + >>> o.a
1.62 + 1
1.63 + >>> o['a']
1.64 + 1
1.65 + >>> o.a = 2
1.66 + >>> o['a']
1.67 + 2
1.68 + >>> del o.a
1.69 + >>> o.a
1.70 + Traceback (most recent call last):
1.71 + ...
1.72 + AttributeError: 'a'
1.73 +
1.74 + """
1.75 + def __getattr__(self, key):
1.76 + try:
1.77 + return self[key]
1.78 + except KeyError, k:
1.79 + raise AttributeError, k
1.80 +
1.81 + def __setattr__(self, key, value):
1.82 + self[key] = value
1.83 +
1.84 + def __delattr__(self, key):
1.85 + try:
1.86 + del self[key]
1.87 + except KeyError, k:
1.88 + raise AttributeError, k
1.89 +
1.90 + def __repr__(self):
1.91 + return '<Storage ' + dict.__repr__(self) + '>'
1.92 +
1.93 +storage = Storage
1.94 +
1.95 +def storify(mapping, *requireds, **defaults):
1.96 + """
1.97 + Creates a `storage` object from dictionary `mapping`, raising `KeyError` if
1.98 + d doesn't have all of the keys in `requireds` and using the default
1.99 + values for keys found in `defaults`.
1.100 +
1.101 + For example, `storify({'a':1, 'c':3}, b=2, c=0)` will return the equivalent of
1.102 + `storage({'a':1, 'b':2, 'c':3})`.
1.103 +
1.104 + If a `storify` value is a list (e.g. multiple values in a form submission),
1.105 + `storify` returns the last element of the list, unless the key appears in
1.106 + `defaults` as a list. Thus:
1.107 +
1.108 + >>> storify({'a':[1, 2]}).a
1.109 + 2
1.110 + >>> storify({'a':[1, 2]}, a=[]).a
1.111 + [1, 2]
1.112 + >>> storify({'a':1}, a=[]).a
1.113 + [1]
1.114 + >>> storify({}, a=[]).a
1.115 + []
1.116 +
1.117 + Similarly, if the value has a `value` attribute, `storify will return _its_
1.118 + value, unless the key appears in `defaults` as a dictionary.
1.119 +
1.120 + >>> storify({'a':storage(value=1)}).a
1.121 + 1
1.122 + >>> storify({'a':storage(value=1)}, a={}).a
1.123 + <Storage {'value': 1}>
1.124 + >>> storify({}, a={}).a
1.125 + {}
1.126 +
1.127 + Optionally, keyword parameter `_unicode` can be passed to convert all values to unicode.
1.128 +
1.129 + >>> storify({'x': 'a'}, _unicode=True)
1.130 + <Storage {'x': u'a'}>
1.131 + >>> storify({'x': storage(value='a')}, x={}, _unicode=True)
1.132 + <Storage {'x': <Storage {'value': 'a'}>}>
1.133 + >>> storify({'x': storage(value='a')}, _unicode=True)
1.134 + <Storage {'x': u'a'}>
1.135 + """
1.136 + _unicode = defaults.pop('_unicode', False)
1.137 +
1.138 + # if _unicode is callable object, use it convert a string to unicode.
1.139 + to_unicode = safeunicode
1.140 + if _unicode is not False and hasattr(_unicode, "__call__"):
1.141 + to_unicode = _unicode
1.142 +
1.143 + def unicodify(s):
1.144 + if _unicode and isinstance(s, str): return to_unicode(s)
1.145 + else: return s
1.146 +
1.147 + def getvalue(x):
1.148 + if hasattr(x, 'file') and hasattr(x, 'value'):
1.149 + return x.value
1.150 + elif hasattr(x, 'value'):
1.151 + return unicodify(x.value)
1.152 + else:
1.153 + return unicodify(x)
1.154 +
1.155 + stor = Storage()
1.156 + for key in requireds + tuple(mapping.keys()):
1.157 + value = mapping[key]
1.158 + if isinstance(value, list):
1.159 + if isinstance(defaults.get(key), list):
1.160 + value = [getvalue(x) for x in value]
1.161 + else:
1.162 + value = value[-1]
1.163 + if not isinstance(defaults.get(key), dict):
1.164 + value = getvalue(value)
1.165 + if isinstance(defaults.get(key), list) and not isinstance(value, list):
1.166 + value = [value]
1.167 + setattr(stor, key, value)
1.168 +
1.169 + for (key, value) in defaults.iteritems():
1.170 + result = value
1.171 + if hasattr(stor, key):
1.172 + result = stor[key]
1.173 + if value == () and not isinstance(result, tuple):
1.174 + result = (result,)
1.175 + setattr(stor, key, result)
1.176 +
1.177 + return stor
1.178 +
1.179 +class Counter(storage):
1.180 + """Keeps count of how many times something is added.
1.181 +
1.182 + >>> c = counter()
1.183 + >>> c.add('x')
1.184 + >>> c.add('x')
1.185 + >>> c.add('x')
1.186 + >>> c.add('x')
1.187 + >>> c.add('x')
1.188 + >>> c.add('y')
1.189 + >>> c
1.190 + <Counter {'y': 1, 'x': 5}>
1.191 + >>> c.most()
1.192 + ['x']
1.193 + """
1.194 + def add(self, n):
1.195 + self.setdefault(n, 0)
1.196 + self[n] += 1
1.197 +
1.198 + def most(self):
1.199 + """Returns the keys with maximum count."""
1.200 + m = max(self.itervalues())
1.201 + return [k for k, v in self.iteritems() if v == m]
1.202 +
1.203 + def least(self):
1.204 + """Returns the keys with mininum count."""
1.205 + m = min(self.itervalues())
1.206 + return [k for k, v in self.iteritems() if v == m]
1.207 +
1.208 + def percent(self, key):
1.209 + """Returns what percentage a certain key is of all entries.
1.210 +
1.211 + >>> c = counter()
1.212 + >>> c.add('x')
1.213 + >>> c.add('x')
1.214 + >>> c.add('x')
1.215 + >>> c.add('y')
1.216 + >>> c.percent('x')
1.217 + 0.75
1.218 + >>> c.percent('y')
1.219 + 0.25
1.220 + """
1.221 + return float(self[key])/sum(self.values())
1.222 +
1.223 + def sorted_keys(self):
1.224 + """Returns keys sorted by value.
1.225 +
1.226 + >>> c = counter()
1.227 + >>> c.add('x')
1.228 + >>> c.add('x')
1.229 + >>> c.add('y')
1.230 + >>> c.sorted_keys()
1.231 + ['x', 'y']
1.232 + """
1.233 + return sorted(self.keys(), key=lambda k: self[k], reverse=True)
1.234 +
1.235 + def sorted_values(self):
1.236 + """Returns values sorted by value.
1.237 +
1.238 + >>> c = counter()
1.239 + >>> c.add('x')
1.240 + >>> c.add('x')
1.241 + >>> c.add('y')
1.242 + >>> c.sorted_values()
1.243 + [2, 1]
1.244 + """
1.245 + return [self[k] for k in self.sorted_keys()]
1.246 +
1.247 + def sorted_items(self):
1.248 + """Returns items sorted by value.
1.249 +
1.250 + >>> c = counter()
1.251 + >>> c.add('x')
1.252 + >>> c.add('x')
1.253 + >>> c.add('y')
1.254 + >>> c.sorted_items()
1.255 + [('x', 2), ('y', 1)]
1.256 + """
1.257 + return [(k, self[k]) for k in self.sorted_keys()]
1.258 +
1.259 + def __repr__(self):
1.260 + return '<Counter ' + dict.__repr__(self) + '>'
1.261 +
1.262 +counter = Counter
1.263 +
1.264 +iters = [list, tuple]
1.265 +import __builtin__
1.266 +if hasattr(__builtin__, 'set'):
1.267 + iters.append(set)
1.268 +if hasattr(__builtin__, 'frozenset'):
1.269 + iters.append(set)
1.270 +if sys.version_info < (2,6): # sets module deprecated in 2.6
1.271 + try:
1.272 + from sets import Set
1.273 + iters.append(Set)
1.274 + except ImportError:
1.275 + pass
1.276 +
1.277 +class _hack(tuple): pass
1.278 +iters = _hack(iters)
1.279 +iters.__doc__ = """
1.280 +A list of iterable items (like lists, but not strings). Includes whichever
1.281 +of lists, tuples, sets, and Sets are available in this version of Python.
1.282 +"""
1.283 +
1.284 +def _strips(direction, text, remove):
1.285 + if isinstance(remove, iters):
1.286 + for subr in remove:
1.287 + text = _strips(direction, text, subr)
1.288 + return text
1.289 +
1.290 + if direction == 'l':
1.291 + if text.startswith(remove):
1.292 + return text[len(remove):]
1.293 + elif direction == 'r':
1.294 + if text.endswith(remove):
1.295 + return text[:-len(remove)]
1.296 + else:
1.297 + raise ValueError, "Direction needs to be r or l."
1.298 + return text
1.299 +
1.300 +def rstrips(text, remove):
1.301 + """
1.302 + removes the string `remove` from the right of `text`
1.303 +
1.304 + >>> rstrips("foobar", "bar")
1.305 + 'foo'
1.306 +
1.307 + """
1.308 + return _strips('r', text, remove)
1.309 +
1.310 +def lstrips(text, remove):
1.311 + """
1.312 + removes the string `remove` from the left of `text`
1.313 +
1.314 + >>> lstrips("foobar", "foo")
1.315 + 'bar'
1.316 + >>> lstrips('http://foo.org/', ['http://', 'https://'])
1.317 + 'foo.org/'
1.318 + >>> lstrips('FOOBARBAZ', ['FOO', 'BAR'])
1.319 + 'BAZ'
1.320 + >>> lstrips('FOOBARBAZ', ['BAR', 'FOO'])
1.321 + 'BARBAZ'
1.322 +
1.323 + """
1.324 + return _strips('l', text, remove)
1.325 +
1.326 +def strips(text, remove):
1.327 + """
1.328 + removes the string `remove` from the both sides of `text`
1.329 +
1.330 + >>> strips("foobarfoo", "foo")
1.331 + 'bar'
1.332 +
1.333 + """
1.334 + return rstrips(lstrips(text, remove), remove)
1.335 +
1.336 +def safeunicode(obj, encoding='utf-8'):
1.337 + r"""
1.338 + Converts any given object to unicode string.
1.339 +
1.340 + >>> safeunicode('hello')
1.341 + u'hello'
1.342 + >>> safeunicode(2)
1.343 + u'2'
1.344 + >>> safeunicode('\xe1\x88\xb4')
1.345 + u'\u1234'
1.346 + """
1.347 + t = type(obj)
1.348 + if t is unicode:
1.349 + return obj
1.350 + elif t is str:
1.351 + return obj.decode(encoding)
1.352 + elif t in [int, float, bool]:
1.353 + return unicode(obj)
1.354 + elif hasattr(obj, '__unicode__') or isinstance(obj, unicode):
1.355 + return unicode(obj)
1.356 + else:
1.357 + return str(obj).decode(encoding)
1.358 +
1.359 +def safestr(obj, encoding='utf-8'):
1.360 + r"""
1.361 + Converts any given object to utf-8 encoded string.
1.362 +
1.363 + >>> safestr('hello')
1.364 + 'hello'
1.365 + >>> safestr(u'\u1234')
1.366 + '\xe1\x88\xb4'
1.367 + >>> safestr(2)
1.368 + '2'
1.369 + """
1.370 + if isinstance(obj, unicode):
1.371 + return obj.encode(encoding)
1.372 + elif isinstance(obj, str):
1.373 + return obj
1.374 + elif hasattr(obj, 'next'): # iterator
1.375 + return itertools.imap(safestr, obj)
1.376 + else:
1.377 + return str(obj)
1.378 +
1.379 +# for backward-compatibility
1.380 +utf8 = safestr
1.381 +
1.382 +class TimeoutError(Exception): pass
1.383 +def timelimit(timeout):
1.384 + """
1.385 + A decorator to limit a function to `timeout` seconds, raising `TimeoutError`
1.386 + if it takes longer.
1.387 +
1.388 + >>> import time
1.389 + >>> def meaningoflife():
1.390 + ... time.sleep(.2)
1.391 + ... return 42
1.392 + >>>
1.393 + >>> timelimit(.1)(meaningoflife)()
1.394 + Traceback (most recent call last):
1.395 + ...
1.396 + TimeoutError: took too long
1.397 + >>> timelimit(1)(meaningoflife)()
1.398 + 42
1.399 +
1.400 + _Caveat:_ The function isn't stopped after `timeout` seconds but continues
1.401 + executing in a separate thread. (There seems to be no way to kill a thread.)
1.402 +
1.403 + inspired by <http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/473878>
1.404 + """
1.405 + def _1(function):
1.406 + def _2(*args, **kw):
1.407 + class Dispatch(threading.Thread):
1.408 + def __init__(self):
1.409 + threading.Thread.__init__(self)
1.410 + self.result = None
1.411 + self.error = None
1.412 +
1.413 + self.setDaemon(True)
1.414 + self.start()
1.415 +
1.416 + def run(self):
1.417 + try:
1.418 + self.result = function(*args, **kw)
1.419 + except:
1.420 + self.error = sys.exc_info()
1.421 +
1.422 + c = Dispatch()
1.423 + c.join(timeout)
1.424 + if c.isAlive():
1.425 + raise TimeoutError, 'took too long'
1.426 + if c.error:
1.427 + raise c.error[0], c.error[1]
1.428 + return c.result
1.429 + return _2
1.430 + return _1
1.431 +
1.432 +class Memoize:
1.433 + """
1.434 + 'Memoizes' a function, caching its return values for each input.
1.435 + If `expires` is specified, values are recalculated after `expires` seconds.
1.436 + If `background` is specified, values are recalculated in a separate thread.
1.437 +
1.438 + >>> calls = 0
1.439 + >>> def howmanytimeshaveibeencalled():
1.440 + ... global calls
1.441 + ... calls += 1
1.442 + ... return calls
1.443 + >>> fastcalls = memoize(howmanytimeshaveibeencalled)
1.444 + >>> howmanytimeshaveibeencalled()
1.445 + 1
1.446 + >>> howmanytimeshaveibeencalled()
1.447 + 2
1.448 + >>> fastcalls()
1.449 + 3
1.450 + >>> fastcalls()
1.451 + 3
1.452 + >>> import time
1.453 + >>> fastcalls = memoize(howmanytimeshaveibeencalled, .1, background=False)
1.454 + >>> fastcalls()
1.455 + 4
1.456 + >>> fastcalls()
1.457 + 4
1.458 + >>> time.sleep(.2)
1.459 + >>> fastcalls()
1.460 + 5
1.461 + >>> def slowfunc():
1.462 + ... time.sleep(.1)
1.463 + ... return howmanytimeshaveibeencalled()
1.464 + >>> fastcalls = memoize(slowfunc, .2, background=True)
1.465 + >>> fastcalls()
1.466 + 6
1.467 + >>> timelimit(.05)(fastcalls)()
1.468 + 6
1.469 + >>> time.sleep(.2)
1.470 + >>> timelimit(.05)(fastcalls)()
1.471 + 6
1.472 + >>> timelimit(.05)(fastcalls)()
1.473 + 6
1.474 + >>> time.sleep(.2)
1.475 + >>> timelimit(.05)(fastcalls)()
1.476 + 7
1.477 + >>> fastcalls = memoize(slowfunc, None, background=True)
1.478 + >>> threading.Thread(target=fastcalls).start()
1.479 + >>> time.sleep(.01)
1.480 + >>> fastcalls()
1.481 + 9
1.482 + """
1.483 + def __init__(self, func, expires=None, background=True):
1.484 + self.func = func
1.485 + self.cache = {}
1.486 + self.expires = expires
1.487 + self.background = background
1.488 + self.running = {}
1.489 +
1.490 + def __call__(self, *args, **keywords):
1.491 + key = (args, tuple(keywords.items()))
1.492 + if not self.running.get(key):
1.493 + self.running[key] = threading.Lock()
1.494 + def update(block=False):
1.495 + if self.running[key].acquire(block):
1.496 + try:
1.497 + self.cache[key] = (self.func(*args, **keywords), time.time())
1.498 + finally:
1.499 + self.running[key].release()
1.500 +
1.501 + if key not in self.cache:
1.502 + update(block=True)
1.503 + elif self.expires and (time.time() - self.cache[key][1]) > self.expires:
1.504 + if self.background:
1.505 + threading.Thread(target=update).start()
1.506 + else:
1.507 + update()
1.508 + return self.cache[key][0]
1.509 +
1.510 +memoize = Memoize
1.511 +
1.512 +re_compile = memoize(re.compile) #@@ threadsafe?
1.513 +re_compile.__doc__ = """
1.514 +A memoized version of re.compile.
1.515 +"""
1.516 +
1.517 +class _re_subm_proxy:
1.518 + def __init__(self):
1.519 + self.match = None
1.520 + def __call__(self, match):
1.521 + self.match = match
1.522 + return ''
1.523 +
1.524 +def re_subm(pat, repl, string):
1.525 + """
1.526 + Like re.sub, but returns the replacement _and_ the match object.
1.527 +
1.528 + >>> t, m = re_subm('g(oo+)fball', r'f\\1lish', 'goooooofball')
1.529 + >>> t
1.530 + 'foooooolish'
1.531 + >>> m.groups()
1.532 + ('oooooo',)
1.533 + """
1.534 + compiled_pat = re_compile(pat)
1.535 + proxy = _re_subm_proxy()
1.536 + compiled_pat.sub(proxy.__call__, string)
1.537 + return compiled_pat.sub(repl, string), proxy.match
1.538 +
1.539 +def group(seq, size):
1.540 + """
1.541 + Returns an iterator over a series of lists of length size from iterable.
1.542 +
1.543 + >>> list(group([1,2,3,4], 2))
1.544 + [[1, 2], [3, 4]]
1.545 + >>> list(group([1,2,3,4,5], 2))
1.546 + [[1, 2], [3, 4], [5]]
1.547 + """
1.548 + def take(seq, n):
1.549 + for i in xrange(n):
1.550 + yield seq.next()
1.551 +
1.552 + if not hasattr(seq, 'next'):
1.553 + seq = iter(seq)
1.554 + while True:
1.555 + x = list(take(seq, size))
1.556 + if x:
1.557 + yield x
1.558 + else:
1.559 + break
1.560 +
1.561 +def uniq(seq, key=None):
1.562 + """
1.563 + Removes duplicate elements from a list while preserving the order of the rest.
1.564 +
1.565 + >>> uniq([9,0,2,1,0])
1.566 + [9, 0, 2, 1]
1.567 +
1.568 + The value of the optional `key` parameter should be a function that
1.569 + takes a single argument and returns a key to test the uniqueness.
1.570 +
1.571 + >>> uniq(["Foo", "foo", "bar"], key=lambda s: s.lower())
1.572 + ['Foo', 'bar']
1.573 + """
1.574 + key = key or (lambda x: x)
1.575 + seen = set()
1.576 + result = []
1.577 + for v in seq:
1.578 + k = key(v)
1.579 + if k in seen:
1.580 + continue
1.581 + seen.add(k)
1.582 + result.append(v)
1.583 + return result
1.584 +
1.585 +def iterview(x):
1.586 + """
1.587 + Takes an iterable `x` and returns an iterator over it
1.588 + which prints its progress to stderr as it iterates through.
1.589 + """
1.590 + WIDTH = 70
1.591 +
1.592 + def plainformat(n, lenx):
1.593 + return '%5.1f%% (%*d/%d)' % ((float(n)/lenx)*100, len(str(lenx)), n, lenx)
1.594 +
1.595 + def bars(size, n, lenx):
1.596 + val = int((float(n)*size)/lenx + 0.5)
1.597 + if size - val:
1.598 + spacing = ">" + (" "*(size-val))[1:]
1.599 + else:
1.600 + spacing = ""
1.601 + return "[%s%s]" % ("="*val, spacing)
1.602 +
1.603 + def eta(elapsed, n, lenx):
1.604 + if n == 0:
1.605 + return '--:--:--'
1.606 + if n == lenx:
1.607 + secs = int(elapsed)
1.608 + else:
1.609 + secs = int((elapsed/n) * (lenx-n))
1.610 + mins, secs = divmod(secs, 60)
1.611 + hrs, mins = divmod(mins, 60)
1.612 +
1.613 + return '%02d:%02d:%02d' % (hrs, mins, secs)
1.614 +
1.615 + def format(starttime, n, lenx):
1.616 + out = plainformat(n, lenx) + ' '
1.617 + if n == lenx:
1.618 + end = ' '
1.619 + else:
1.620 + end = ' ETA '
1.621 + end += eta(time.time() - starttime, n, lenx)
1.622 + out += bars(WIDTH - len(out) - len(end), n, lenx)
1.623 + out += end
1.624 + return out
1.625 +
1.626 + starttime = time.time()
1.627 + lenx = len(x)
1.628 + for n, y in enumerate(x):
1.629 + sys.stderr.write('\r' + format(starttime, n, lenx))
1.630 + yield y
1.631 + sys.stderr.write('\r' + format(starttime, n+1, lenx) + '\n')
1.632 +
1.633 +class IterBetter:
1.634 + """
1.635 + Returns an object that can be used as an iterator
1.636 + but can also be used via __getitem__ (although it
1.637 + cannot go backwards -- that is, you cannot request
1.638 + `iterbetter[0]` after requesting `iterbetter[1]`).
1.639 +
1.640 + >>> import itertools
1.641 + >>> c = iterbetter(itertools.count())
1.642 + >>> c[1]
1.643 + 1
1.644 + >>> c[5]
1.645 + 5
1.646 + >>> c[3]
1.647 + Traceback (most recent call last):
1.648 + ...
1.649 + IndexError: already passed 3
1.650 +
1.651 + For boolean test, IterBetter peeps at first value in the itertor without effecting the iteration.
1.652 +
1.653 + >>> c = iterbetter(iter(range(5)))
1.654 + >>> bool(c)
1.655 + True
1.656 + >>> list(c)
1.657 + [0, 1, 2, 3, 4]
1.658 + >>> c = iterbetter(iter([]))
1.659 + >>> bool(c)
1.660 + False
1.661 + >>> list(c)
1.662 + []
1.663 + """
1.664 + def __init__(self, iterator):
1.665 + self.i, self.c = iterator, 0
1.666 +
1.667 + def __iter__(self):
1.668 + if hasattr(self, "_head"):
1.669 + yield self._head
1.670 +
1.671 + while 1:
1.672 + yield self.i.next()
1.673 + self.c += 1
1.674 +
1.675 + def __getitem__(self, i):
1.676 + #todo: slices
1.677 + if i < self.c:
1.678 + raise IndexError, "already passed "+str(i)
1.679 + try:
1.680 + while i > self.c:
1.681 + self.i.next()
1.682 + self.c += 1
1.683 + # now self.c == i
1.684 + self.c += 1
1.685 + return self.i.next()
1.686 + except StopIteration:
1.687 + raise IndexError, str(i)
1.688 +
1.689 + def __nonzero__(self):
1.690 + if hasattr(self, "__len__"):
1.691 + return len(self) != 0
1.692 + elif hasattr(self, "_head"):
1.693 + return True
1.694 + else:
1.695 + try:
1.696 + self._head = self.i.next()
1.697 + except StopIteration:
1.698 + return False
1.699 + else:
1.700 + return True
1.701 +
1.702 +iterbetter = IterBetter
1.703 +
1.704 +def safeiter(it, cleanup=None, ignore_errors=True):
1.705 + """Makes an iterator safe by ignoring the exceptions occured during the iteration.
1.706 + """
1.707 + def next():
1.708 + while True:
1.709 + try:
1.710 + return it.next()
1.711 + except StopIteration:
1.712 + raise
1.713 + except:
1.714 + traceback.print_exc()
1.715 +
1.716 + it = iter(it)
1.717 + while True:
1.718 + yield next()
1.719 +
1.720 +def safewrite(filename, content):
1.721 + """Writes the content to a temp file and then moves the temp file to
1.722 + given filename to avoid overwriting the existing file in case of errors.
1.723 + """
1.724 + f = file(filename + '.tmp', 'w')
1.725 + f.write(content)
1.726 + f.close()
1.727 + os.rename(f.name, filename)
1.728 +
1.729 +def dictreverse(mapping):
1.730 + """
1.731 + Returns a new dictionary with keys and values swapped.
1.732 +
1.733 + >>> dictreverse({1: 2, 3: 4})
1.734 + {2: 1, 4: 3}
1.735 + """
1.736 + return dict([(value, key) for (key, value) in mapping.iteritems()])
1.737 +
1.738 +def dictfind(dictionary, element):
1.739 + """
1.740 + Returns a key whose value in `dictionary` is `element`
1.741 + or, if none exists, None.
1.742 +
1.743 + >>> d = {1:2, 3:4}
1.744 + >>> dictfind(d, 4)
1.745 + 3
1.746 + >>> dictfind(d, 5)
1.747 + """
1.748 + for (key, value) in dictionary.iteritems():
1.749 + if element is value:
1.750 + return key
1.751 +
1.752 +def dictfindall(dictionary, element):
1.753 + """
1.754 + Returns the keys whose values in `dictionary` are `element`
1.755 + or, if none exists, [].
1.756 +
1.757 + >>> d = {1:4, 3:4}
1.758 + >>> dictfindall(d, 4)
1.759 + [1, 3]
1.760 + >>> dictfindall(d, 5)
1.761 + []
1.762 + """
1.763 + res = []
1.764 + for (key, value) in dictionary.iteritems():
1.765 + if element is value:
1.766 + res.append(key)
1.767 + return res
1.768 +
1.769 +def dictincr(dictionary, element):
1.770 + """
1.771 + Increments `element` in `dictionary`,
1.772 + setting it to one if it doesn't exist.
1.773 +
1.774 + >>> d = {1:2, 3:4}
1.775 + >>> dictincr(d, 1)
1.776 + 3
1.777 + >>> d[1]
1.778 + 3
1.779 + >>> dictincr(d, 5)
1.780 + 1
1.781 + >>> d[5]
1.782 + 1
1.783 + """
1.784 + dictionary.setdefault(element, 0)
1.785 + dictionary[element] += 1
1.786 + return dictionary[element]
1.787 +
1.788 +def dictadd(*dicts):
1.789 + """
1.790 + Returns a dictionary consisting of the keys in the argument dictionaries.
1.791 + If they share a key, the value from the last argument is used.
1.792 +
1.793 + >>> dictadd({1: 0, 2: 0}, {2: 1, 3: 1})
1.794 + {1: 0, 2: 1, 3: 1}
1.795 + """
1.796 + result = {}
1.797 + for dct in dicts:
1.798 + result.update(dct)
1.799 + return result
1.800 +
1.801 +def requeue(queue, index=-1):
1.802 + """Returns the element at index after moving it to the beginning of the queue.
1.803 +
1.804 + >>> x = [1, 2, 3, 4]
1.805 + >>> requeue(x)
1.806 + 4
1.807 + >>> x
1.808 + [4, 1, 2, 3]
1.809 + """
1.810 + x = queue.pop(index)
1.811 + queue.insert(0, x)
1.812 + return x
1.813 +
1.814 +def restack(stack, index=0):
1.815 + """Returns the element at index after moving it to the top of stack.
1.816 +
1.817 + >>> x = [1, 2, 3, 4]
1.818 + >>> restack(x)
1.819 + 1
1.820 + >>> x
1.821 + [2, 3, 4, 1]
1.822 + """
1.823 + x = stack.pop(index)
1.824 + stack.append(x)
1.825 + return x
1.826 +
1.827 +def listget(lst, ind, default=None):
1.828 + """
1.829 + Returns `lst[ind]` if it exists, `default` otherwise.
1.830 +
1.831 + >>> listget(['a'], 0)
1.832 + 'a'
1.833 + >>> listget(['a'], 1)
1.834 + >>> listget(['a'], 1, 'b')
1.835 + 'b'
1.836 + """
1.837 + if len(lst)-1 < ind:
1.838 + return default
1.839 + return lst[ind]
1.840 +
1.841 +def intget(integer, default=None):
1.842 + """
1.843 + Returns `integer` as an int or `default` if it can't.
1.844 +
1.845 + >>> intget('3')
1.846 + 3
1.847 + >>> intget('3a')
1.848 + >>> intget('3a', 0)
1.849 + 0
1.850 + """
1.851 + try:
1.852 + return int(integer)
1.853 + except (TypeError, ValueError):
1.854 + return default
1.855 +
1.856 +def datestr(then, now=None):
1.857 + """
1.858 + Converts a (UTC) datetime object to a nice string representation.
1.859 +
1.860 + >>> from datetime import datetime, timedelta
1.861 + >>> d = datetime(1970, 5, 1)
1.862 + >>> datestr(d, now=d)
1.863 + '0 microseconds ago'
1.864 + >>> for t, v in {
1.865 + ... timedelta(microseconds=1): '1 microsecond ago',
1.866 + ... timedelta(microseconds=2): '2 microseconds ago',
1.867 + ... -timedelta(microseconds=1): '1 microsecond from now',
1.868 + ... -timedelta(microseconds=2): '2 microseconds from now',
1.869 + ... timedelta(microseconds=2000): '2 milliseconds ago',
1.870 + ... timedelta(seconds=2): '2 seconds ago',
1.871 + ... timedelta(seconds=2*60): '2 minutes ago',
1.872 + ... timedelta(seconds=2*60*60): '2 hours ago',
1.873 + ... timedelta(days=2): '2 days ago',
1.874 + ... }.iteritems():
1.875 + ... assert datestr(d, now=d+t) == v
1.876 + >>> datestr(datetime(1970, 1, 1), now=d)
1.877 + 'January 1'
1.878 + >>> datestr(datetime(1969, 1, 1), now=d)
1.879 + 'January 1, 1969'
1.880 + >>> datestr(datetime(1970, 6, 1), now=d)
1.881 + 'June 1, 1970'
1.882 + >>> datestr(None)
1.883 + ''
1.884 + """
1.885 + def agohence(n, what, divisor=None):
1.886 + if divisor: n = n // divisor
1.887 +
1.888 + out = str(abs(n)) + ' ' + what # '2 day'
1.889 + if abs(n) != 1: out += 's' # '2 days'
1.890 + out += ' ' # '2 days '
1.891 + if n < 0:
1.892 + out += 'from now'
1.893 + else:
1.894 + out += 'ago'
1.895 + return out # '2 days ago'
1.896 +
1.897 + oneday = 24 * 60 * 60
1.898 +
1.899 + if not then: return ""
1.900 + if not now: now = datetime.datetime.utcnow()
1.901 + if type(now).__name__ == "DateTime":
1.902 + now = datetime.datetime.fromtimestamp(now)
1.903 + if type(then).__name__ == "DateTime":
1.904 + then = datetime.datetime.fromtimestamp(then)
1.905 + elif type(then).__name__ == "date":
1.906 + then = datetime.datetime(then.year, then.month, then.day)
1.907 +
1.908 + delta = now - then
1.909 + deltaseconds = int(delta.days * oneday + delta.seconds + delta.microseconds * 1e-06)
1.910 + deltadays = abs(deltaseconds) // oneday
1.911 + if deltaseconds < 0: deltadays *= -1 # fix for oddity of floor
1.912 +
1.913 + if deltadays:
1.914 + if abs(deltadays) < 4:
1.915 + return agohence(deltadays, 'day')
1.916 +
1.917 + try:
1.918 + out = then.strftime('%B %e') # e.g. 'June 3'
1.919 + except ValueError:
1.920 + # %e doesn't work on Windows.
1.921 + out = then.strftime('%B %d') # e.g. 'June 03'
1.922 +
1.923 + if then.year != now.year or deltadays < 0:
1.924 + out += ', %s' % then.year
1.925 + return out
1.926 +
1.927 + if int(deltaseconds):
1.928 + if abs(deltaseconds) > (60 * 60):
1.929 + return agohence(deltaseconds, 'hour', 60 * 60)
1.930 + elif abs(deltaseconds) > 60:
1.931 + return agohence(deltaseconds, 'minute', 60)
1.932 + else:
1.933 + return agohence(deltaseconds, 'second')
1.934 +
1.935 + deltamicroseconds = delta.microseconds
1.936 + if delta.days: deltamicroseconds = int(delta.microseconds - 1e6) # datetime oddity
1.937 + if abs(deltamicroseconds) > 1000:
1.938 + return agohence(deltamicroseconds, 'millisecond', 1000)
1.939 +
1.940 + return agohence(deltamicroseconds, 'microsecond')
1.941 +
1.942 +def numify(string):
1.943 + """
1.944 + Removes all non-digit characters from `string`.
1.945 +
1.946 + >>> numify('800-555-1212')
1.947 + '8005551212'
1.948 + >>> numify('800.555.1212')
1.949 + '8005551212'
1.950 +
1.951 + """
1.952 + return ''.join([c for c in str(string) if c.isdigit()])
1.953 +
1.954 +def denumify(string, pattern):
1.955 + """
1.956 + Formats `string` according to `pattern`, where the letter X gets replaced
1.957 + by characters from `string`.
1.958 +
1.959 + >>> denumify("8005551212", "(XXX) XXX-XXXX")
1.960 + '(800) 555-1212'
1.961 +
1.962 + """
1.963 + out = []
1.964 + for c in pattern:
1.965 + if c == "X":
1.966 + out.append(string[0])
1.967 + string = string[1:]
1.968 + else:
1.969 + out.append(c)
1.970 + return ''.join(out)
1.971 +
1.972 +def commify(n):
1.973 + """
1.974 + Add commas to an integer `n`.
1.975 +
1.976 + >>> commify(1)
1.977 + '1'
1.978 + >>> commify(123)
1.979 + '123'
1.980 + >>> commify(1234)
1.981 + '1,234'
1.982 + >>> commify(1234567890)
1.983 + '1,234,567,890'
1.984 + >>> commify(123.0)
1.985 + '123.0'
1.986 + >>> commify(1234.5)
1.987 + '1,234.5'
1.988 + >>> commify(1234.56789)
1.989 + '1,234.56789'
1.990 + >>> commify('%.2f' % 1234.5)
1.991 + '1,234.50'
1.992 + >>> commify(None)
1.993 + >>>
1.994 +
1.995 + """
1.996 + if n is None: return None
1.997 + n = str(n)
1.998 + if '.' in n:
1.999 + dollars, cents = n.split('.')
1.1000 + else:
1.1001 + dollars, cents = n, None
1.1002 +
1.1003 + r = []
1.1004 + for i, c in enumerate(str(dollars)[::-1]):
1.1005 + if i and (not (i % 3)):
1.1006 + r.insert(0, ',')
1.1007 + r.insert(0, c)
1.1008 + out = ''.join(r)
1.1009 + if cents:
1.1010 + out += '.' + cents
1.1011 + return out
1.1012 +
1.1013 +def dateify(datestring):
1.1014 + """
1.1015 + Formats a numified `datestring` properly.
1.1016 + """
1.1017 + return denumify(datestring, "XXXX-XX-XX XX:XX:XX")
1.1018 +
1.1019 +
1.1020 +def nthstr(n):
1.1021 + """
1.1022 + Formats an ordinal.
1.1023 + Doesn't handle negative numbers.
1.1024 +
1.1025 + >>> nthstr(1)
1.1026 + '1st'
1.1027 + >>> nthstr(0)
1.1028 + '0th'
1.1029 + >>> [nthstr(x) for x in [2, 3, 4, 5, 10, 11, 12, 13, 14, 15]]
1.1030 + ['2nd', '3rd', '4th', '5th', '10th', '11th', '12th', '13th', '14th', '15th']
1.1031 + >>> [nthstr(x) for x in [91, 92, 93, 94, 99, 100, 101, 102]]
1.1032 + ['91st', '92nd', '93rd', '94th', '99th', '100th', '101st', '102nd']
1.1033 + >>> [nthstr(x) for x in [111, 112, 113, 114, 115]]
1.1034 + ['111th', '112th', '113th', '114th', '115th']
1.1035 +
1.1036 + """
1.1037 +
1.1038 + assert n >= 0
1.1039 + if n % 100 in [11, 12, 13]: return '%sth' % n
1.1040 + return {1: '%sst', 2: '%snd', 3: '%srd'}.get(n % 10, '%sth') % n
1.1041 +
1.1042 +def cond(predicate, consequence, alternative=None):
1.1043 + """
1.1044 + Function replacement for if-else to use in expressions.
1.1045 +
1.1046 + >>> x = 2
1.1047 + >>> cond(x % 2 == 0, "even", "odd")
1.1048 + 'even'
1.1049 + >>> cond(x % 2 == 0, "even", "odd") + '_row'
1.1050 + 'even_row'
1.1051 + """
1.1052 + if predicate:
1.1053 + return consequence
1.1054 + else:
1.1055 + return alternative
1.1056 +
1.1057 +class CaptureStdout:
1.1058 + """
1.1059 + Captures everything `func` prints to stdout and returns it instead.
1.1060 +
1.1061 + >>> def idiot():
1.1062 + ... print "foo"
1.1063 + >>> capturestdout(idiot)()
1.1064 + 'foo\\n'
1.1065 +
1.1066 + **WARNING:** Not threadsafe!
1.1067 + """
1.1068 + def __init__(self, func):
1.1069 + self.func = func
1.1070 + def __call__(self, *args, **keywords):
1.1071 + from cStringIO import StringIO
1.1072 + # Not threadsafe!
1.1073 + out = StringIO()
1.1074 + oldstdout = sys.stdout
1.1075 + sys.stdout = out
1.1076 + try:
1.1077 + self.func(*args, **keywords)
1.1078 + finally:
1.1079 + sys.stdout = oldstdout
1.1080 + return out.getvalue()
1.1081 +
1.1082 +capturestdout = CaptureStdout
1.1083 +
1.1084 +class Profile:
1.1085 + """
1.1086 + Profiles `func` and returns a tuple containing its output
1.1087 + and a string with human-readable profiling information.
1.1088 +
1.1089 + >>> import time
1.1090 + >>> out, inf = profile(time.sleep)(.001)
1.1091 + >>> out
1.1092 + >>> inf[:10].strip()
1.1093 + 'took 0.0'
1.1094 + """
1.1095 + def __init__(self, func):
1.1096 + self.func = func
1.1097 + def __call__(self, *args): ##, **kw): kw unused
1.1098 + import hotshot, hotshot.stats, os, tempfile ##, time already imported
1.1099 + f, filename = tempfile.mkstemp()
1.1100 + os.close(f)
1.1101 +
1.1102 + prof = hotshot.Profile(filename)
1.1103 +
1.1104 + stime = time.time()
1.1105 + result = prof.runcall(self.func, *args)
1.1106 + stime = time.time() - stime
1.1107 + prof.close()
1.1108 +
1.1109 + import cStringIO
1.1110 + out = cStringIO.StringIO()
1.1111 + stats = hotshot.stats.load(filename)
1.1112 + stats.stream = out
1.1113 + stats.strip_dirs()
1.1114 + stats.sort_stats('time', 'calls')
1.1115 + stats.print_stats(40)
1.1116 + stats.print_callers()
1.1117 +
1.1118 + x = '\n\ntook '+ str(stime) + ' seconds\n'
1.1119 + x += out.getvalue()
1.1120 +
1.1121 + # remove the tempfile
1.1122 + try:
1.1123 + os.remove(filename)
1.1124 + except IOError:
1.1125 + pass
1.1126 +
1.1127 + return result, x
1.1128 +
1.1129 +profile = Profile
1.1130 +
1.1131 +
1.1132 +import traceback
1.1133 +# hack for compatibility with Python 2.3:
1.1134 +if not hasattr(traceback, 'format_exc'):
1.1135 + from cStringIO import StringIO
1.1136 + def format_exc(limit=None):
1.1137 + strbuf = StringIO()
1.1138 + traceback.print_exc(limit, strbuf)
1.1139 + return strbuf.getvalue()
1.1140 + traceback.format_exc = format_exc
1.1141 +
1.1142 +def tryall(context, prefix=None):
1.1143 + """
1.1144 + Tries a series of functions and prints their results.
1.1145 + `context` is a dictionary mapping names to values;
1.1146 + the value will only be tried if it's callable.
1.1147 +
1.1148 + >>> tryall(dict(j=lambda: True))
1.1149 + j: True
1.1150 + ----------------------------------------
1.1151 + results:
1.1152 + True: 1
1.1153 +
1.1154 + For example, you might have a file `test/stuff.py`
1.1155 + with a series of functions testing various things in it.
1.1156 + At the bottom, have a line:
1.1157 +
1.1158 + if __name__ == "__main__": tryall(globals())
1.1159 +
1.1160 + Then you can run `python test/stuff.py` and get the results of
1.1161 + all the tests.
1.1162 + """
1.1163 + context = context.copy() # vars() would update
1.1164 + results = {}
1.1165 + for (key, value) in context.iteritems():
1.1166 + if not hasattr(value, '__call__'):
1.1167 + continue
1.1168 + if prefix and not key.startswith(prefix):
1.1169 + continue
1.1170 + print key + ':',
1.1171 + try:
1.1172 + r = value()
1.1173 + dictincr(results, r)
1.1174 + print r
1.1175 + except:
1.1176 + print 'ERROR'
1.1177 + dictincr(results, 'ERROR')
1.1178 + print ' ' + '\n '.join(traceback.format_exc().split('\n'))
1.1179 +
1.1180 + print '-'*40
1.1181 + print 'results:'
1.1182 + for (key, value) in results.iteritems():
1.1183 + print ' '*2, str(key)+':', value
1.1184 +
1.1185 +class ThreadedDict(threadlocal):
1.1186 + """
1.1187 + Thread local storage.
1.1188 +
1.1189 + >>> d = ThreadedDict()
1.1190 + >>> d.x = 1
1.1191 + >>> d.x
1.1192 + 1
1.1193 + >>> import threading
1.1194 + >>> def f(): d.x = 2
1.1195 + ...
1.1196 + >>> t = threading.Thread(target=f)
1.1197 + >>> t.start()
1.1198 + >>> t.join()
1.1199 + >>> d.x
1.1200 + 1
1.1201 + """
1.1202 + _instances = set()
1.1203 +
1.1204 + def __init__(self):
1.1205 + ThreadedDict._instances.add(self)
1.1206 +
1.1207 + def __del__(self):
1.1208 + ThreadedDict._instances.remove(self)
1.1209 +
1.1210 + def __hash__(self):
1.1211 + return id(self)
1.1212 +
1.1213 + def clear_all():
1.1214 + """Clears all ThreadedDict instances.
1.1215 + """
1.1216 + for t in list(ThreadedDict._instances):
1.1217 + t.clear()
1.1218 + clear_all = staticmethod(clear_all)
1.1219 +
1.1220 + # Define all these methods to more or less fully emulate dict -- attribute access
1.1221 + # is built into threading.local.
1.1222 +
1.1223 + def __getitem__(self, key):
1.1224 + return self.__dict__[key]
1.1225 +
1.1226 + def __setitem__(self, key, value):
1.1227 + self.__dict__[key] = value
1.1228 +
1.1229 + def __delitem__(self, key):
1.1230 + del self.__dict__[key]
1.1231 +
1.1232 + def __contains__(self, key):
1.1233 + return key in self.__dict__
1.1234 +
1.1235 + has_key = __contains__
1.1236 +
1.1237 + def clear(self):
1.1238 + self.__dict__.clear()
1.1239 +
1.1240 + def copy(self):
1.1241 + return self.__dict__.copy()
1.1242 +
1.1243 + def get(self, key, default=None):
1.1244 + return self.__dict__.get(key, default)
1.1245 +
1.1246 + def items(self):
1.1247 + return self.__dict__.items()
1.1248 +
1.1249 + def iteritems(self):
1.1250 + return self.__dict__.iteritems()
1.1251 +
1.1252 + def keys(self):
1.1253 + return self.__dict__.keys()
1.1254 +
1.1255 + def iterkeys(self):
1.1256 + return self.__dict__.iterkeys()
1.1257 +
1.1258 + iter = iterkeys
1.1259 +
1.1260 + def values(self):
1.1261 + return self.__dict__.values()
1.1262 +
1.1263 + def itervalues(self):
1.1264 + return self.__dict__.itervalues()
1.1265 +
1.1266 + def pop(self, key, *args):
1.1267 + return self.__dict__.pop(key, *args)
1.1268 +
1.1269 + def popitem(self):
1.1270 + return self.__dict__.popitem()
1.1271 +
1.1272 + def setdefault(self, key, default=None):
1.1273 + return self.__dict__.setdefault(key, default)
1.1274 +
1.1275 + def update(self, *args, **kwargs):
1.1276 + self.__dict__.update(*args, **kwargs)
1.1277 +
1.1278 + def __repr__(self):
1.1279 + return '<ThreadedDict %r>' % self.__dict__
1.1280 +
1.1281 + __str__ = __repr__
1.1282 +
1.1283 +threadeddict = ThreadedDict
1.1284 +
1.1285 +def autoassign(self, locals):
1.1286 + """
1.1287 + Automatically assigns local variables to `self`.
1.1288 +
1.1289 + >>> self = storage()
1.1290 + >>> autoassign(self, dict(a=1, b=2))
1.1291 + >>> self
1.1292 + <Storage {'a': 1, 'b': 2}>
1.1293 +
1.1294 + Generally used in `__init__` methods, as in:
1.1295 +
1.1296 + def __init__(self, foo, bar, baz=1): autoassign(self, locals())
1.1297 + """
1.1298 + for (key, value) in locals.iteritems():
1.1299 + if key == 'self':
1.1300 + continue
1.1301 + setattr(self, key, value)
1.1302 +
1.1303 +def to36(q):
1.1304 + """
1.1305 + Converts an integer to base 36 (a useful scheme for human-sayable IDs).
1.1306 +
1.1307 + >>> to36(35)
1.1308 + 'z'
1.1309 + >>> to36(119292)
1.1310 + '2k1o'
1.1311 + >>> int(to36(939387374), 36)
1.1312 + 939387374
1.1313 + >>> to36(0)
1.1314 + '0'
1.1315 + >>> to36(-393)
1.1316 + Traceback (most recent call last):
1.1317 + ...
1.1318 + ValueError: must supply a positive integer
1.1319 +
1.1320 + """
1.1321 + if q < 0: raise ValueError, "must supply a positive integer"
1.1322 + letters = "0123456789abcdefghijklmnopqrstuvwxyz"
1.1323 + converted = []
1.1324 + while q != 0:
1.1325 + q, r = divmod(q, 36)
1.1326 + converted.insert(0, letters[r])
1.1327 + return "".join(converted) or '0'
1.1328 +
1.1329 +
1.1330 +r_url = re_compile('(?<!\()(http://(\S+))')
1.1331 +def safemarkdown(text):
1.1332 + """
1.1333 + Converts text to HTML following the rules of Markdown, but blocking any
1.1334 + outside HTML input, so that only the things supported by Markdown
1.1335 + can be used. Also converts raw URLs to links.
1.1336 +
1.1337 + (requires [markdown.py](http://webpy.org/markdown.py))
1.1338 + """
1.1339 + from markdown import markdown
1.1340 + if text:
1.1341 + text = text.replace('<', '<')
1.1342 + # TODO: automatically get page title?
1.1343 + text = r_url.sub(r'<\1>', text)
1.1344 + text = markdown(text)
1.1345 + return text
1.1346 +
1.1347 +def sendmail(from_address, to_address, subject, message, headers=None, **kw):
1.1348 + """
1.1349 + Sends the email message `message` with mail and envelope headers
1.1350 + for from `from_address_` to `to_address` with `subject`.
1.1351 + Additional email headers can be specified with the dictionary
1.1352 + `headers.
1.1353 +
1.1354 + Optionally cc, bcc and attachments can be specified as keyword arguments.
1.1355 + Attachments must be an iterable and each attachment can be either a
1.1356 + filename or a file object or a dictionary with filename, content and
1.1357 + optionally content_type keys.
1.1358 +
1.1359 + If `web.config.smtp_server` is set, it will send the message
1.1360 + to that SMTP server. Otherwise it will look for
1.1361 + `/usr/sbin/sendmail`, the typical location for the sendmail-style
1.1362 + binary. To use sendmail from a different path, set `web.config.sendmail_path`.
1.1363 + """
1.1364 + attachments = kw.pop("attachments", [])
1.1365 + mail = _EmailMessage(from_address, to_address, subject, message, headers, **kw)
1.1366 +
1.1367 + for a in attachments:
1.1368 + if isinstance(a, dict):
1.1369 + mail.attach(a['filename'], a['content'], a.get('content_type'))
1.1370 + elif hasattr(a, 'read'): # file
1.1371 + filename = os.path.basename(getattr(a, "name", ""))
1.1372 + content_type = getattr(a, 'content_type', None)
1.1373 + mail.attach(filename, a.read(), content_type)
1.1374 + elif isinstance(a, basestring):
1.1375 + f = open(a, 'rb')
1.1376 + content = f.read()
1.1377 + f.close()
1.1378 + filename = os.path.basename(a)
1.1379 + mail.attach(filename, content, None)
1.1380 + else:
1.1381 + raise ValueError, "Invalid attachment: %s" % repr(a)
1.1382 +
1.1383 + mail.send()
1.1384 +
1.1385 +class _EmailMessage:
1.1386 + def __init__(self, from_address, to_address, subject, message, headers=None, **kw):
1.1387 + def listify(x):
1.1388 + if not isinstance(x, list):
1.1389 + return [safestr(x)]
1.1390 + else:
1.1391 + return [safestr(a) for a in x]
1.1392 +
1.1393 + subject = safestr(subject)
1.1394 + message = safestr(message)
1.1395 +
1.1396 + from_address = safestr(from_address)
1.1397 + to_address = listify(to_address)
1.1398 + cc = listify(kw.get('cc', []))
1.1399 + bcc = listify(kw.get('bcc', []))
1.1400 + recipients = to_address + cc + bcc
1.1401 +
1.1402 + import email.Utils
1.1403 + self.from_address = email.Utils.parseaddr(from_address)[1]
1.1404 + self.recipients = [email.Utils.parseaddr(r)[1] for r in recipients]
1.1405 +
1.1406 + self.headers = dictadd({
1.1407 + 'From': from_address,
1.1408 + 'To': ", ".join(to_address),
1.1409 + 'Subject': subject
1.1410 + }, headers or {})
1.1411 +
1.1412 + if cc:
1.1413 + self.headers['Cc'] = ", ".join(cc)
1.1414 +
1.1415 + self.message = self.new_message()
1.1416 + self.message.add_header("Content-Transfer-Encoding", "7bit")
1.1417 + self.message.add_header("Content-Disposition", "inline")
1.1418 + self.message.add_header("MIME-Version", "1.0")
1.1419 + self.message.set_payload(message, 'utf-8')
1.1420 + self.multipart = False
1.1421 +
1.1422 + def new_message(self):
1.1423 + from email.Message import Message
1.1424 + return Message()
1.1425 +
1.1426 + def attach(self, filename, content, content_type=None):
1.1427 + if not self.multipart:
1.1428 + msg = self.new_message()
1.1429 + msg.add_header("Content-Type", "multipart/mixed")
1.1430 + msg.attach(self.message)
1.1431 + self.message = msg
1.1432 + self.multipart = True
1.1433 +
1.1434 + import mimetypes
1.1435 + try:
1.1436 + from email import encoders
1.1437 + except:
1.1438 + from email import Encoders as encoders
1.1439 +
1.1440 + content_type = content_type or mimetypes.guess_type(filename)[0] or "applcation/octet-stream"
1.1441 +
1.1442 + msg = self.new_message()
1.1443 + msg.set_payload(content)
1.1444 + msg.add_header('Content-Type', content_type)
1.1445 + msg.add_header('Content-Disposition', 'attachment', filename=filename)
1.1446 +
1.1447 + if not content_type.startswith("text/"):
1.1448 + encoders.encode_base64(msg)
1.1449 +
1.1450 + self.message.attach(msg)
1.1451 +
1.1452 + def prepare_message(self):
1.1453 + for k, v in self.headers.iteritems():
1.1454 + if k.lower() == "content-type":
1.1455 + self.message.set_type(v)
1.1456 + else:
1.1457 + self.message.add_header(k, v)
1.1458 +
1.1459 + self.headers = {}
1.1460 +
1.1461 + def send(self):
1.1462 + try:
1.1463 + import webapi
1.1464 + except ImportError:
1.1465 + webapi = Storage(config=Storage())
1.1466 +
1.1467 + self.prepare_message()
1.1468 + message_text = self.message.as_string()
1.1469 +
1.1470 + if webapi.config.get('smtp_server'):
1.1471 + server = webapi.config.get('smtp_server')
1.1472 + port = webapi.config.get('smtp_port', 0)
1.1473 + username = webapi.config.get('smtp_username')
1.1474 + password = webapi.config.get('smtp_password')
1.1475 + debug_level = webapi.config.get('smtp_debuglevel', None)
1.1476 + starttls = webapi.config.get('smtp_starttls', False)
1.1477 +
1.1478 + import smtplib
1.1479 + smtpserver = smtplib.SMTP(server, port)
1.1480 +
1.1481 + if debug_level:
1.1482 + smtpserver.set_debuglevel(debug_level)
1.1483 +
1.1484 + if starttls:
1.1485 + smtpserver.ehlo()
1.1486 + smtpserver.starttls()
1.1487 + smtpserver.ehlo()
1.1488 +
1.1489 + if username and password:
1.1490 + smtpserver.login(username, password)
1.1491 +
1.1492 + smtpserver.sendmail(self.from_address, self.recipients, message_text)
1.1493 + smtpserver.quit()
1.1494 + elif webapi.config.get('email_engine') == 'aws':
1.1495 + import boto.ses
1.1496 + c = boto.ses.SESConnection(
1.1497 + aws_access_key_id=webapi.config.get('aws_access_key_id'),
1.1498 + aws_secret_access_key=web.api.config.get('aws_secret_access_key'))
1.1499 + c.send_raw_email(self.from_address, message_text, self.from_recipients)
1.1500 + else:
1.1501 + sendmail = webapi.config.get('sendmail_path', '/usr/sbin/sendmail')
1.1502 +
1.1503 + assert not self.from_address.startswith('-'), 'security'
1.1504 + for r in self.recipients:
1.1505 + assert not r.startswith('-'), 'security'
1.1506 +
1.1507 + cmd = [sendmail, '-f', self.from_address] + self.recipients
1.1508 +
1.1509 + if subprocess:
1.1510 + p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
1.1511 + p.stdin.write(message_text)
1.1512 + p.stdin.close()
1.1513 + p.wait()
1.1514 + else:
1.1515 + i, o = os.popen2(cmd)
1.1516 + i.write(message)
1.1517 + i.close()
1.1518 + o.close()
1.1519 + del i, o
1.1520 +
1.1521 + def __repr__(self):
1.1522 + return "<EmailMessage>"
1.1523 +
1.1524 + def __str__(self):
1.1525 + return self.message.as_string()
1.1526 +
1.1527 +if __name__ == "__main__":
1.1528 + import doctest
1.1529 + doctest.testmod()