OpenSecurity/install/web.py-0.37/build/lib/web/utils.py
author om
Mon, 02 Dec 2013 14:02:05 +0100
changeset 3 65432e6c6042
permissions -rwxr-xr-x
initial deployment and project layout commit
om@3
     1
#!/usr/bin/env python
om@3
     2
"""
om@3
     3
General Utilities
om@3
     4
(part of web.py)
om@3
     5
"""
om@3
     6
om@3
     7
__all__ = [
om@3
     8
  "Storage", "storage", "storify", 
om@3
     9
  "Counter", "counter",
om@3
    10
  "iters", 
om@3
    11
  "rstrips", "lstrips", "strips", 
om@3
    12
  "safeunicode", "safestr", "utf8",
om@3
    13
  "TimeoutError", "timelimit",
om@3
    14
  "Memoize", "memoize",
om@3
    15
  "re_compile", "re_subm",
om@3
    16
  "group", "uniq", "iterview",
om@3
    17
  "IterBetter", "iterbetter",
om@3
    18
  "safeiter", "safewrite",
om@3
    19
  "dictreverse", "dictfind", "dictfindall", "dictincr", "dictadd",
om@3
    20
  "requeue", "restack",
om@3
    21
  "listget", "intget", "datestr",
om@3
    22
  "numify", "denumify", "commify", "dateify",
om@3
    23
  "nthstr", "cond",
om@3
    24
  "CaptureStdout", "capturestdout", "Profile", "profile",
om@3
    25
  "tryall",
om@3
    26
  "ThreadedDict", "threadeddict",
om@3
    27
  "autoassign",
om@3
    28
  "to36",
om@3
    29
  "safemarkdown",
om@3
    30
  "sendmail"
om@3
    31
]
om@3
    32
om@3
    33
import re, sys, time, threading, itertools, traceback, os
om@3
    34
om@3
    35
try:
om@3
    36
    import subprocess
om@3
    37
except ImportError: 
om@3
    38
    subprocess = None
om@3
    39
om@3
    40
try: import datetime
om@3
    41
except ImportError: pass
om@3
    42
om@3
    43
try: set
om@3
    44
except NameError:
om@3
    45
    from sets import Set as set
om@3
    46
    
om@3
    47
try:
om@3
    48
    from threading import local as threadlocal
om@3
    49
except ImportError:
om@3
    50
    from python23 import threadlocal
om@3
    51
om@3
    52
class Storage(dict):
om@3
    53
    """
om@3
    54
    A Storage object is like a dictionary except `obj.foo` can be used
om@3
    55
    in addition to `obj['foo']`.
om@3
    56
    
om@3
    57
        >>> o = storage(a=1)
om@3
    58
        >>> o.a
om@3
    59
        1
om@3
    60
        >>> o['a']
om@3
    61
        1
om@3
    62
        >>> o.a = 2
om@3
    63
        >>> o['a']
om@3
    64
        2
om@3
    65
        >>> del o.a
om@3
    66
        >>> o.a
om@3
    67
        Traceback (most recent call last):
om@3
    68
            ...
om@3
    69
        AttributeError: 'a'
om@3
    70
    
om@3
    71
    """
om@3
    72
    def __getattr__(self, key): 
om@3
    73
        try:
om@3
    74
            return self[key]
om@3
    75
        except KeyError, k:
om@3
    76
            raise AttributeError, k
om@3
    77
    
om@3
    78
    def __setattr__(self, key, value): 
om@3
    79
        self[key] = value
om@3
    80
    
om@3
    81
    def __delattr__(self, key):
om@3
    82
        try:
om@3
    83
            del self[key]
om@3
    84
        except KeyError, k:
om@3
    85
            raise AttributeError, k
om@3
    86
    
om@3
    87
    def __repr__(self):     
om@3
    88
        return '<Storage ' + dict.__repr__(self) + '>'
om@3
    89
om@3
    90
storage = Storage
om@3
    91
om@3
    92
def storify(mapping, *requireds, **defaults):
om@3
    93
    """
om@3
    94
    Creates a `storage` object from dictionary `mapping`, raising `KeyError` if
om@3
    95
    d doesn't have all of the keys in `requireds` and using the default 
om@3
    96
    values for keys found in `defaults`.
om@3
    97
om@3
    98
    For example, `storify({'a':1, 'c':3}, b=2, c=0)` will return the equivalent of
om@3
    99
    `storage({'a':1, 'b':2, 'c':3})`.
om@3
   100
    
om@3
   101
    If a `storify` value is a list (e.g. multiple values in a form submission), 
om@3
   102
    `storify` returns the last element of the list, unless the key appears in 
om@3
   103
    `defaults` as a list. Thus:
om@3
   104
    
om@3
   105
        >>> storify({'a':[1, 2]}).a
om@3
   106
        2
om@3
   107
        >>> storify({'a':[1, 2]}, a=[]).a
om@3
   108
        [1, 2]
om@3
   109
        >>> storify({'a':1}, a=[]).a
om@3
   110
        [1]
om@3
   111
        >>> storify({}, a=[]).a
om@3
   112
        []
om@3
   113
    
om@3
   114
    Similarly, if the value has a `value` attribute, `storify will return _its_
om@3
   115
    value, unless the key appears in `defaults` as a dictionary.
om@3
   116
    
om@3
   117
        >>> storify({'a':storage(value=1)}).a
om@3
   118
        1
om@3
   119
        >>> storify({'a':storage(value=1)}, a={}).a
om@3
   120
        <Storage {'value': 1}>
om@3
   121
        >>> storify({}, a={}).a
om@3
   122
        {}
om@3
   123
        
om@3
   124
    Optionally, keyword parameter `_unicode` can be passed to convert all values to unicode.
om@3
   125
    
om@3
   126
        >>> storify({'x': 'a'}, _unicode=True)
om@3
   127
        <Storage {'x': u'a'}>
om@3
   128
        >>> storify({'x': storage(value='a')}, x={}, _unicode=True)
om@3
   129
        <Storage {'x': <Storage {'value': 'a'}>}>
om@3
   130
        >>> storify({'x': storage(value='a')}, _unicode=True)
om@3
   131
        <Storage {'x': u'a'}>
om@3
   132
    """
om@3
   133
    _unicode = defaults.pop('_unicode', False)
om@3
   134
om@3
   135
    # if _unicode is callable object, use it convert a string to unicode.
om@3
   136
    to_unicode = safeunicode
om@3
   137
    if _unicode is not False and hasattr(_unicode, "__call__"):
om@3
   138
        to_unicode = _unicode
om@3
   139
    
om@3
   140
    def unicodify(s):
om@3
   141
        if _unicode and isinstance(s, str): return to_unicode(s)
om@3
   142
        else: return s
om@3
   143
        
om@3
   144
    def getvalue(x):
om@3
   145
        if hasattr(x, 'file') and hasattr(x, 'value'):
om@3
   146
            return x.value
om@3
   147
        elif hasattr(x, 'value'):
om@3
   148
            return unicodify(x.value)
om@3
   149
        else:
om@3
   150
            return unicodify(x)
om@3
   151
    
om@3
   152
    stor = Storage()
om@3
   153
    for key in requireds + tuple(mapping.keys()):
om@3
   154
        value = mapping[key]
om@3
   155
        if isinstance(value, list):
om@3
   156
            if isinstance(defaults.get(key), list):
om@3
   157
                value = [getvalue(x) for x in value]
om@3
   158
            else:
om@3
   159
                value = value[-1]
om@3
   160
        if not isinstance(defaults.get(key), dict):
om@3
   161
            value = getvalue(value)
om@3
   162
        if isinstance(defaults.get(key), list) and not isinstance(value, list):
om@3
   163
            value = [value]
om@3
   164
        setattr(stor, key, value)
om@3
   165
om@3
   166
    for (key, value) in defaults.iteritems():
om@3
   167
        result = value
om@3
   168
        if hasattr(stor, key): 
om@3
   169
            result = stor[key]
om@3
   170
        if value == () and not isinstance(result, tuple): 
om@3
   171
            result = (result,)
om@3
   172
        setattr(stor, key, result)
om@3
   173
    
om@3
   174
    return stor
om@3
   175
om@3
   176
class Counter(storage):
om@3
   177
    """Keeps count of how many times something is added.
om@3
   178
        
om@3
   179
        >>> c = counter()
om@3
   180
        >>> c.add('x')
om@3
   181
        >>> c.add('x')
om@3
   182
        >>> c.add('x')
om@3
   183
        >>> c.add('x')
om@3
   184
        >>> c.add('x')
om@3
   185
        >>> c.add('y')
om@3
   186
        >>> c
om@3
   187
        <Counter {'y': 1, 'x': 5}>
om@3
   188
        >>> c.most()
om@3
   189
        ['x']
om@3
   190
    """
om@3
   191
    def add(self, n):
om@3
   192
        self.setdefault(n, 0)
om@3
   193
        self[n] += 1
om@3
   194
    
om@3
   195
    def most(self):
om@3
   196
        """Returns the keys with maximum count."""
om@3
   197
        m = max(self.itervalues())
om@3
   198
        return [k for k, v in self.iteritems() if v == m]
om@3
   199
        
om@3
   200
    def least(self):
om@3
   201
        """Returns the keys with mininum count."""
om@3
   202
        m = min(self.itervalues())
om@3
   203
        return [k for k, v in self.iteritems() if v == m]
om@3
   204
om@3
   205
    def percent(self, key):
om@3
   206
       """Returns what percentage a certain key is of all entries.
om@3
   207
om@3
   208
           >>> c = counter()
om@3
   209
           >>> c.add('x')
om@3
   210
           >>> c.add('x')
om@3
   211
           >>> c.add('x')
om@3
   212
           >>> c.add('y')
om@3
   213
           >>> c.percent('x')
om@3
   214
           0.75
om@3
   215
           >>> c.percent('y')
om@3
   216
           0.25
om@3
   217
       """
om@3
   218
       return float(self[key])/sum(self.values())
om@3
   219
             
om@3
   220
    def sorted_keys(self):
om@3
   221
        """Returns keys sorted by value.
om@3
   222
             
om@3
   223
             >>> c = counter()
om@3
   224
             >>> c.add('x')
om@3
   225
             >>> c.add('x')
om@3
   226
             >>> c.add('y')
om@3
   227
             >>> c.sorted_keys()
om@3
   228
             ['x', 'y']
om@3
   229
        """
om@3
   230
        return sorted(self.keys(), key=lambda k: self[k], reverse=True)
om@3
   231
    
om@3
   232
    def sorted_values(self):
om@3
   233
        """Returns values sorted by value.
om@3
   234
            
om@3
   235
            >>> c = counter()
om@3
   236
            >>> c.add('x')
om@3
   237
            >>> c.add('x')
om@3
   238
            >>> c.add('y')
om@3
   239
            >>> c.sorted_values()
om@3
   240
            [2, 1]
om@3
   241
        """
om@3
   242
        return [self[k] for k in self.sorted_keys()]
om@3
   243
    
om@3
   244
    def sorted_items(self):
om@3
   245
        """Returns items sorted by value.
om@3
   246
            
om@3
   247
            >>> c = counter()
om@3
   248
            >>> c.add('x')
om@3
   249
            >>> c.add('x')
om@3
   250
            >>> c.add('y')
om@3
   251
            >>> c.sorted_items()
om@3
   252
            [('x', 2), ('y', 1)]
om@3
   253
        """
om@3
   254
        return [(k, self[k]) for k in self.sorted_keys()]
om@3
   255
    
om@3
   256
    def __repr__(self):
om@3
   257
        return '<Counter ' + dict.__repr__(self) + '>'
om@3
   258
       
om@3
   259
counter = Counter
om@3
   260
om@3
   261
iters = [list, tuple]
om@3
   262
import __builtin__
om@3
   263
if hasattr(__builtin__, 'set'):
om@3
   264
    iters.append(set)
om@3
   265
if hasattr(__builtin__, 'frozenset'):
om@3
   266
    iters.append(set)
om@3
   267
if sys.version_info < (2,6): # sets module deprecated in 2.6
om@3
   268
    try:
om@3
   269
        from sets import Set
om@3
   270
        iters.append(Set)
om@3
   271
    except ImportError: 
om@3
   272
        pass
om@3
   273
    
om@3
   274
class _hack(tuple): pass
om@3
   275
iters = _hack(iters)
om@3
   276
iters.__doc__ = """
om@3
   277
A list of iterable items (like lists, but not strings). Includes whichever
om@3
   278
of lists, tuples, sets, and Sets are available in this version of Python.
om@3
   279
"""
om@3
   280
om@3
   281
def _strips(direction, text, remove):
om@3
   282
    if isinstance(remove, iters):
om@3
   283
        for subr in remove:
om@3
   284
            text = _strips(direction, text, subr)
om@3
   285
        return text
om@3
   286
    
om@3
   287
    if direction == 'l': 
om@3
   288
        if text.startswith(remove): 
om@3
   289
            return text[len(remove):]
om@3
   290
    elif direction == 'r':
om@3
   291
        if text.endswith(remove):   
om@3
   292
            return text[:-len(remove)]
om@3
   293
    else: 
om@3
   294
        raise ValueError, "Direction needs to be r or l."
om@3
   295
    return text
om@3
   296
om@3
   297
def rstrips(text, remove):
om@3
   298
    """
om@3
   299
    removes the string `remove` from the right of `text`
om@3
   300
om@3
   301
        >>> rstrips("foobar", "bar")
om@3
   302
        'foo'
om@3
   303
    
om@3
   304
    """
om@3
   305
    return _strips('r', text, remove)
om@3
   306
om@3
   307
def lstrips(text, remove):
om@3
   308
    """
om@3
   309
    removes the string `remove` from the left of `text`
om@3
   310
    
om@3
   311
        >>> lstrips("foobar", "foo")
om@3
   312
        'bar'
om@3
   313
        >>> lstrips('http://foo.org/', ['http://', 'https://'])
om@3
   314
        'foo.org/'
om@3
   315
        >>> lstrips('FOOBARBAZ', ['FOO', 'BAR'])
om@3
   316
        'BAZ'
om@3
   317
        >>> lstrips('FOOBARBAZ', ['BAR', 'FOO'])
om@3
   318
        'BARBAZ'
om@3
   319
    
om@3
   320
    """
om@3
   321
    return _strips('l', text, remove)
om@3
   322
om@3
   323
def strips(text, remove):
om@3
   324
    """
om@3
   325
    removes the string `remove` from the both sides of `text`
om@3
   326
om@3
   327
        >>> strips("foobarfoo", "foo")
om@3
   328
        'bar'
om@3
   329
    
om@3
   330
    """
om@3
   331
    return rstrips(lstrips(text, remove), remove)
om@3
   332
om@3
   333
def safeunicode(obj, encoding='utf-8'):
om@3
   334
    r"""
om@3
   335
    Converts any given object to unicode string.
om@3
   336
    
om@3
   337
        >>> safeunicode('hello')
om@3
   338
        u'hello'
om@3
   339
        >>> safeunicode(2)
om@3
   340
        u'2'
om@3
   341
        >>> safeunicode('\xe1\x88\xb4')
om@3
   342
        u'\u1234'
om@3
   343
    """
om@3
   344
    t = type(obj)
om@3
   345
    if t is unicode:
om@3
   346
        return obj
om@3
   347
    elif t is str:
om@3
   348
        return obj.decode(encoding)
om@3
   349
    elif t in [int, float, bool]:
om@3
   350
        return unicode(obj)
om@3
   351
    elif hasattr(obj, '__unicode__') or isinstance(obj, unicode):
om@3
   352
        return unicode(obj)
om@3
   353
    else:
om@3
   354
        return str(obj).decode(encoding)
om@3
   355
    
om@3
   356
def safestr(obj, encoding='utf-8'):
om@3
   357
    r"""
om@3
   358
    Converts any given object to utf-8 encoded string. 
om@3
   359
    
om@3
   360
        >>> safestr('hello')
om@3
   361
        'hello'
om@3
   362
        >>> safestr(u'\u1234')
om@3
   363
        '\xe1\x88\xb4'
om@3
   364
        >>> safestr(2)
om@3
   365
        '2'
om@3
   366
    """
om@3
   367
    if isinstance(obj, unicode):
om@3
   368
        return obj.encode(encoding)
om@3
   369
    elif isinstance(obj, str):
om@3
   370
        return obj
om@3
   371
    elif hasattr(obj, 'next'): # iterator
om@3
   372
        return itertools.imap(safestr, obj)
om@3
   373
    else:
om@3
   374
        return str(obj)
om@3
   375
om@3
   376
# for backward-compatibility
om@3
   377
utf8 = safestr
om@3
   378
    
om@3
   379
class TimeoutError(Exception): pass
om@3
   380
def timelimit(timeout):
om@3
   381
    """
om@3
   382
    A decorator to limit a function to `timeout` seconds, raising `TimeoutError`
om@3
   383
    if it takes longer.
om@3
   384
    
om@3
   385
        >>> import time
om@3
   386
        >>> def meaningoflife():
om@3
   387
        ...     time.sleep(.2)
om@3
   388
        ...     return 42
om@3
   389
        >>> 
om@3
   390
        >>> timelimit(.1)(meaningoflife)()
om@3
   391
        Traceback (most recent call last):
om@3
   392
            ...
om@3
   393
        TimeoutError: took too long
om@3
   394
        >>> timelimit(1)(meaningoflife)()
om@3
   395
        42
om@3
   396
om@3
   397
    _Caveat:_ The function isn't stopped after `timeout` seconds but continues 
om@3
   398
    executing in a separate thread. (There seems to be no way to kill a thread.)
om@3
   399
om@3
   400
    inspired by <http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/473878>
om@3
   401
    """
om@3
   402
    def _1(function):
om@3
   403
        def _2(*args, **kw):
om@3
   404
            class Dispatch(threading.Thread):
om@3
   405
                def __init__(self):
om@3
   406
                    threading.Thread.__init__(self)
om@3
   407
                    self.result = None
om@3
   408
                    self.error = None
om@3
   409
om@3
   410
                    self.setDaemon(True)
om@3
   411
                    self.start()
om@3
   412
om@3
   413
                def run(self):
om@3
   414
                    try:
om@3
   415
                        self.result = function(*args, **kw)
om@3
   416
                    except:
om@3
   417
                        self.error = sys.exc_info()
om@3
   418
om@3
   419
            c = Dispatch()
om@3
   420
            c.join(timeout)
om@3
   421
            if c.isAlive():
om@3
   422
                raise TimeoutError, 'took too long'
om@3
   423
            if c.error:
om@3
   424
                raise c.error[0], c.error[1]
om@3
   425
            return c.result
om@3
   426
        return _2
om@3
   427
    return _1
om@3
   428
om@3
   429
class Memoize:
om@3
   430
    """
om@3
   431
    'Memoizes' a function, caching its return values for each input.
om@3
   432
    If `expires` is specified, values are recalculated after `expires` seconds.
om@3
   433
    If `background` is specified, values are recalculated in a separate thread.
om@3
   434
    
om@3
   435
        >>> calls = 0
om@3
   436
        >>> def howmanytimeshaveibeencalled():
om@3
   437
        ...     global calls
om@3
   438
        ...     calls += 1
om@3
   439
        ...     return calls
om@3
   440
        >>> fastcalls = memoize(howmanytimeshaveibeencalled)
om@3
   441
        >>> howmanytimeshaveibeencalled()
om@3
   442
        1
om@3
   443
        >>> howmanytimeshaveibeencalled()
om@3
   444
        2
om@3
   445
        >>> fastcalls()
om@3
   446
        3
om@3
   447
        >>> fastcalls()
om@3
   448
        3
om@3
   449
        >>> import time
om@3
   450
        >>> fastcalls = memoize(howmanytimeshaveibeencalled, .1, background=False)
om@3
   451
        >>> fastcalls()
om@3
   452
        4
om@3
   453
        >>> fastcalls()
om@3
   454
        4
om@3
   455
        >>> time.sleep(.2)
om@3
   456
        >>> fastcalls()
om@3
   457
        5
om@3
   458
        >>> def slowfunc():
om@3
   459
        ...     time.sleep(.1)
om@3
   460
        ...     return howmanytimeshaveibeencalled()
om@3
   461
        >>> fastcalls = memoize(slowfunc, .2, background=True)
om@3
   462
        >>> fastcalls()
om@3
   463
        6
om@3
   464
        >>> timelimit(.05)(fastcalls)()
om@3
   465
        6
om@3
   466
        >>> time.sleep(.2)
om@3
   467
        >>> timelimit(.05)(fastcalls)()
om@3
   468
        6
om@3
   469
        >>> timelimit(.05)(fastcalls)()
om@3
   470
        6
om@3
   471
        >>> time.sleep(.2)
om@3
   472
        >>> timelimit(.05)(fastcalls)()
om@3
   473
        7
om@3
   474
        >>> fastcalls = memoize(slowfunc, None, background=True)
om@3
   475
        >>> threading.Thread(target=fastcalls).start()
om@3
   476
        >>> time.sleep(.01)
om@3
   477
        >>> fastcalls()
om@3
   478
        9
om@3
   479
    """
om@3
   480
    def __init__(self, func, expires=None, background=True): 
om@3
   481
        self.func = func
om@3
   482
        self.cache = {}
om@3
   483
        self.expires = expires
om@3
   484
        self.background = background
om@3
   485
        self.running = {}
om@3
   486
    
om@3
   487
    def __call__(self, *args, **keywords):
om@3
   488
        key = (args, tuple(keywords.items()))
om@3
   489
        if not self.running.get(key):
om@3
   490
            self.running[key] = threading.Lock()
om@3
   491
        def update(block=False):
om@3
   492
            if self.running[key].acquire(block):
om@3
   493
                try:
om@3
   494
                    self.cache[key] = (self.func(*args, **keywords), time.time())
om@3
   495
                finally:
om@3
   496
                    self.running[key].release()
om@3
   497
        
om@3
   498
        if key not in self.cache: 
om@3
   499
            update(block=True)
om@3
   500
        elif self.expires and (time.time() - self.cache[key][1]) > self.expires:
om@3
   501
            if self.background:
om@3
   502
                threading.Thread(target=update).start()
om@3
   503
            else:
om@3
   504
                update()
om@3
   505
        return self.cache[key][0]
om@3
   506
om@3
   507
memoize = Memoize
om@3
   508
om@3
   509
re_compile = memoize(re.compile) #@@ threadsafe?
om@3
   510
re_compile.__doc__ = """
om@3
   511
A memoized version of re.compile.
om@3
   512
"""
om@3
   513
om@3
   514
class _re_subm_proxy:
om@3
   515
    def __init__(self): 
om@3
   516
        self.match = None
om@3
   517
    def __call__(self, match): 
om@3
   518
        self.match = match
om@3
   519
        return ''
om@3
   520
om@3
   521
def re_subm(pat, repl, string):
om@3
   522
    """
om@3
   523
    Like re.sub, but returns the replacement _and_ the match object.
om@3
   524
    
om@3
   525
        >>> t, m = re_subm('g(oo+)fball', r'f\\1lish', 'goooooofball')
om@3
   526
        >>> t
om@3
   527
        'foooooolish'
om@3
   528
        >>> m.groups()
om@3
   529
        ('oooooo',)
om@3
   530
    """
om@3
   531
    compiled_pat = re_compile(pat)
om@3
   532
    proxy = _re_subm_proxy()
om@3
   533
    compiled_pat.sub(proxy.__call__, string)
om@3
   534
    return compiled_pat.sub(repl, string), proxy.match
om@3
   535
om@3
   536
def group(seq, size): 
om@3
   537
    """
om@3
   538
    Returns an iterator over a series of lists of length size from iterable.
om@3
   539
om@3
   540
        >>> list(group([1,2,3,4], 2))
om@3
   541
        [[1, 2], [3, 4]]
om@3
   542
        >>> list(group([1,2,3,4,5], 2))
om@3
   543
        [[1, 2], [3, 4], [5]]
om@3
   544
    """
om@3
   545
    def take(seq, n):
om@3
   546
        for i in xrange(n):
om@3
   547
            yield seq.next()
om@3
   548
om@3
   549
    if not hasattr(seq, 'next'):  
om@3
   550
        seq = iter(seq)
om@3
   551
    while True: 
om@3
   552
        x = list(take(seq, size))
om@3
   553
        if x:
om@3
   554
            yield x
om@3
   555
        else:
om@3
   556
            break
om@3
   557
om@3
   558
def uniq(seq, key=None):
om@3
   559
    """
om@3
   560
    Removes duplicate elements from a list while preserving the order of the rest.
om@3
   561
om@3
   562
        >>> uniq([9,0,2,1,0])
om@3
   563
        [9, 0, 2, 1]
om@3
   564
om@3
   565
    The value of the optional `key` parameter should be a function that
om@3
   566
    takes a single argument and returns a key to test the uniqueness.
om@3
   567
om@3
   568
        >>> uniq(["Foo", "foo", "bar"], key=lambda s: s.lower())
om@3
   569
        ['Foo', 'bar']
om@3
   570
    """
om@3
   571
    key = key or (lambda x: x)
om@3
   572
    seen = set()
om@3
   573
    result = []
om@3
   574
    for v in seq:
om@3
   575
        k = key(v)
om@3
   576
        if k in seen:
om@3
   577
            continue
om@3
   578
        seen.add(k)
om@3
   579
        result.append(v)
om@3
   580
    return result
om@3
   581
om@3
   582
def iterview(x):
om@3
   583
   """
om@3
   584
   Takes an iterable `x` and returns an iterator over it
om@3
   585
   which prints its progress to stderr as it iterates through.
om@3
   586
   """
om@3
   587
   WIDTH = 70
om@3
   588
om@3
   589
   def plainformat(n, lenx):
om@3
   590
       return '%5.1f%% (%*d/%d)' % ((float(n)/lenx)*100, len(str(lenx)), n, lenx)
om@3
   591
om@3
   592
   def bars(size, n, lenx):
om@3
   593
       val = int((float(n)*size)/lenx + 0.5)
om@3
   594
       if size - val:
om@3
   595
           spacing = ">" + (" "*(size-val))[1:]
om@3
   596
       else:
om@3
   597
           spacing = ""
om@3
   598
       return "[%s%s]" % ("="*val, spacing)
om@3
   599
om@3
   600
   def eta(elapsed, n, lenx):
om@3
   601
       if n == 0:
om@3
   602
           return '--:--:--'
om@3
   603
       if n == lenx:
om@3
   604
           secs = int(elapsed)
om@3
   605
       else:
om@3
   606
           secs = int((elapsed/n) * (lenx-n))
om@3
   607
       mins, secs = divmod(secs, 60)
om@3
   608
       hrs, mins = divmod(mins, 60)
om@3
   609
om@3
   610
       return '%02d:%02d:%02d' % (hrs, mins, secs)
om@3
   611
om@3
   612
   def format(starttime, n, lenx):
om@3
   613
       out = plainformat(n, lenx) + ' '
om@3
   614
       if n == lenx:
om@3
   615
           end = '     '
om@3
   616
       else:
om@3
   617
           end = ' ETA '
om@3
   618
       end += eta(time.time() - starttime, n, lenx)
om@3
   619
       out += bars(WIDTH - len(out) - len(end), n, lenx)
om@3
   620
       out += end
om@3
   621
       return out
om@3
   622
om@3
   623
   starttime = time.time()
om@3
   624
   lenx = len(x)
om@3
   625
   for n, y in enumerate(x):
om@3
   626
       sys.stderr.write('\r' + format(starttime, n, lenx))
om@3
   627
       yield y
om@3
   628
   sys.stderr.write('\r' + format(starttime, n+1, lenx) + '\n')
om@3
   629
om@3
   630
class IterBetter:
om@3
   631
    """
om@3
   632
    Returns an object that can be used as an iterator 
om@3
   633
    but can also be used via __getitem__ (although it 
om@3
   634
    cannot go backwards -- that is, you cannot request 
om@3
   635
    `iterbetter[0]` after requesting `iterbetter[1]`).
om@3
   636
    
om@3
   637
        >>> import itertools
om@3
   638
        >>> c = iterbetter(itertools.count())
om@3
   639
        >>> c[1]
om@3
   640
        1
om@3
   641
        >>> c[5]
om@3
   642
        5
om@3
   643
        >>> c[3]
om@3
   644
        Traceback (most recent call last):
om@3
   645
            ...
om@3
   646
        IndexError: already passed 3
om@3
   647
om@3
   648
    For boolean test, IterBetter peeps at first value in the itertor without effecting the iteration.
om@3
   649
om@3
   650
        >>> c = iterbetter(iter(range(5)))
om@3
   651
        >>> bool(c)
om@3
   652
        True
om@3
   653
        >>> list(c)
om@3
   654
        [0, 1, 2, 3, 4]
om@3
   655
        >>> c = iterbetter(iter([]))
om@3
   656
        >>> bool(c)
om@3
   657
        False
om@3
   658
        >>> list(c)
om@3
   659
        []
om@3
   660
    """
om@3
   661
    def __init__(self, iterator): 
om@3
   662
        self.i, self.c = iterator, 0
om@3
   663
om@3
   664
    def __iter__(self): 
om@3
   665
        if hasattr(self, "_head"):
om@3
   666
            yield self._head
om@3
   667
om@3
   668
        while 1:    
om@3
   669
            yield self.i.next()
om@3
   670
            self.c += 1
om@3
   671
om@3
   672
    def __getitem__(self, i):
om@3
   673
        #todo: slices
om@3
   674
        if i < self.c: 
om@3
   675
            raise IndexError, "already passed "+str(i)
om@3
   676
        try:
om@3
   677
            while i > self.c: 
om@3
   678
                self.i.next()
om@3
   679
                self.c += 1
om@3
   680
            # now self.c == i
om@3
   681
            self.c += 1
om@3
   682
            return self.i.next()
om@3
   683
        except StopIteration: 
om@3
   684
            raise IndexError, str(i)
om@3
   685
            
om@3
   686
    def __nonzero__(self):
om@3
   687
        if hasattr(self, "__len__"):
om@3
   688
            return len(self) != 0
om@3
   689
        elif hasattr(self, "_head"):
om@3
   690
            return True
om@3
   691
        else:
om@3
   692
            try:
om@3
   693
                self._head = self.i.next()
om@3
   694
            except StopIteration:
om@3
   695
                return False
om@3
   696
            else:
om@3
   697
                return True
om@3
   698
om@3
   699
iterbetter = IterBetter
om@3
   700
om@3
   701
def safeiter(it, cleanup=None, ignore_errors=True):
om@3
   702
    """Makes an iterator safe by ignoring the exceptions occured during the iteration.
om@3
   703
    """
om@3
   704
    def next():
om@3
   705
        while True:
om@3
   706
            try:
om@3
   707
                return it.next()
om@3
   708
            except StopIteration:
om@3
   709
                raise
om@3
   710
            except:
om@3
   711
                traceback.print_exc()
om@3
   712
om@3
   713
    it = iter(it)
om@3
   714
    while True:
om@3
   715
        yield next()
om@3
   716
om@3
   717
def safewrite(filename, content):
om@3
   718
    """Writes the content to a temp file and then moves the temp file to 
om@3
   719
    given filename to avoid overwriting the existing file in case of errors.
om@3
   720
    """
om@3
   721
    f = file(filename + '.tmp', 'w')
om@3
   722
    f.write(content)
om@3
   723
    f.close()
om@3
   724
    os.rename(f.name, filename)
om@3
   725
om@3
   726
def dictreverse(mapping):
om@3
   727
    """
om@3
   728
    Returns a new dictionary with keys and values swapped.
om@3
   729
    
om@3
   730
        >>> dictreverse({1: 2, 3: 4})
om@3
   731
        {2: 1, 4: 3}
om@3
   732
    """
om@3
   733
    return dict([(value, key) for (key, value) in mapping.iteritems()])
om@3
   734
om@3
   735
def dictfind(dictionary, element):
om@3
   736
    """
om@3
   737
    Returns a key whose value in `dictionary` is `element` 
om@3
   738
    or, if none exists, None.
om@3
   739
    
om@3
   740
        >>> d = {1:2, 3:4}
om@3
   741
        >>> dictfind(d, 4)
om@3
   742
        3
om@3
   743
        >>> dictfind(d, 5)
om@3
   744
    """
om@3
   745
    for (key, value) in dictionary.iteritems():
om@3
   746
        if element is value: 
om@3
   747
            return key
om@3
   748
om@3
   749
def dictfindall(dictionary, element):
om@3
   750
    """
om@3
   751
    Returns the keys whose values in `dictionary` are `element`
om@3
   752
    or, if none exists, [].
om@3
   753
    
om@3
   754
        >>> d = {1:4, 3:4}
om@3
   755
        >>> dictfindall(d, 4)
om@3
   756
        [1, 3]
om@3
   757
        >>> dictfindall(d, 5)
om@3
   758
        []
om@3
   759
    """
om@3
   760
    res = []
om@3
   761
    for (key, value) in dictionary.iteritems():
om@3
   762
        if element is value:
om@3
   763
            res.append(key)
om@3
   764
    return res
om@3
   765
om@3
   766
def dictincr(dictionary, element):
om@3
   767
    """
om@3
   768
    Increments `element` in `dictionary`, 
om@3
   769
    setting it to one if it doesn't exist.
om@3
   770
    
om@3
   771
        >>> d = {1:2, 3:4}
om@3
   772
        >>> dictincr(d, 1)
om@3
   773
        3
om@3
   774
        >>> d[1]
om@3
   775
        3
om@3
   776
        >>> dictincr(d, 5)
om@3
   777
        1
om@3
   778
        >>> d[5]
om@3
   779
        1
om@3
   780
    """
om@3
   781
    dictionary.setdefault(element, 0)
om@3
   782
    dictionary[element] += 1
om@3
   783
    return dictionary[element]
om@3
   784
om@3
   785
def dictadd(*dicts):
om@3
   786
    """
om@3
   787
    Returns a dictionary consisting of the keys in the argument dictionaries.
om@3
   788
    If they share a key, the value from the last argument is used.
om@3
   789
    
om@3
   790
        >>> dictadd({1: 0, 2: 0}, {2: 1, 3: 1})
om@3
   791
        {1: 0, 2: 1, 3: 1}
om@3
   792
    """
om@3
   793
    result = {}
om@3
   794
    for dct in dicts:
om@3
   795
        result.update(dct)
om@3
   796
    return result
om@3
   797
om@3
   798
def requeue(queue, index=-1):
om@3
   799
    """Returns the element at index after moving it to the beginning of the queue.
om@3
   800
om@3
   801
        >>> x = [1, 2, 3, 4]
om@3
   802
        >>> requeue(x)
om@3
   803
        4
om@3
   804
        >>> x
om@3
   805
        [4, 1, 2, 3]
om@3
   806
    """
om@3
   807
    x = queue.pop(index)
om@3
   808
    queue.insert(0, x)
om@3
   809
    return x
om@3
   810
om@3
   811
def restack(stack, index=0):
om@3
   812
    """Returns the element at index after moving it to the top of stack.
om@3
   813
om@3
   814
           >>> x = [1, 2, 3, 4]
om@3
   815
           >>> restack(x)
om@3
   816
           1
om@3
   817
           >>> x
om@3
   818
           [2, 3, 4, 1]
om@3
   819
    """
om@3
   820
    x = stack.pop(index)
om@3
   821
    stack.append(x)
om@3
   822
    return x
om@3
   823
om@3
   824
def listget(lst, ind, default=None):
om@3
   825
    """
om@3
   826
    Returns `lst[ind]` if it exists, `default` otherwise.
om@3
   827
    
om@3
   828
        >>> listget(['a'], 0)
om@3
   829
        'a'
om@3
   830
        >>> listget(['a'], 1)
om@3
   831
        >>> listget(['a'], 1, 'b')
om@3
   832
        'b'
om@3
   833
    """
om@3
   834
    if len(lst)-1 < ind: 
om@3
   835
        return default
om@3
   836
    return lst[ind]
om@3
   837
om@3
   838
def intget(integer, default=None):
om@3
   839
    """
om@3
   840
    Returns `integer` as an int or `default` if it can't.
om@3
   841
    
om@3
   842
        >>> intget('3')
om@3
   843
        3
om@3
   844
        >>> intget('3a')
om@3
   845
        >>> intget('3a', 0)
om@3
   846
        0
om@3
   847
    """
om@3
   848
    try:
om@3
   849
        return int(integer)
om@3
   850
    except (TypeError, ValueError):
om@3
   851
        return default
om@3
   852
om@3
   853
def datestr(then, now=None):
om@3
   854
    """
om@3
   855
    Converts a (UTC) datetime object to a nice string representation.
om@3
   856
    
om@3
   857
        >>> from datetime import datetime, timedelta
om@3
   858
        >>> d = datetime(1970, 5, 1)
om@3
   859
        >>> datestr(d, now=d)
om@3
   860
        '0 microseconds ago'
om@3
   861
        >>> for t, v in {
om@3
   862
        ...   timedelta(microseconds=1): '1 microsecond ago',
om@3
   863
        ...   timedelta(microseconds=2): '2 microseconds ago',
om@3
   864
        ...   -timedelta(microseconds=1): '1 microsecond from now',
om@3
   865
        ...   -timedelta(microseconds=2): '2 microseconds from now',
om@3
   866
        ...   timedelta(microseconds=2000): '2 milliseconds ago',
om@3
   867
        ...   timedelta(seconds=2): '2 seconds ago',
om@3
   868
        ...   timedelta(seconds=2*60): '2 minutes ago',
om@3
   869
        ...   timedelta(seconds=2*60*60): '2 hours ago',
om@3
   870
        ...   timedelta(days=2): '2 days ago',
om@3
   871
        ... }.iteritems():
om@3
   872
        ...     assert datestr(d, now=d+t) == v
om@3
   873
        >>> datestr(datetime(1970, 1, 1), now=d)
om@3
   874
        'January  1'
om@3
   875
        >>> datestr(datetime(1969, 1, 1), now=d)
om@3
   876
        'January  1, 1969'
om@3
   877
        >>> datestr(datetime(1970, 6, 1), now=d)
om@3
   878
        'June  1, 1970'
om@3
   879
        >>> datestr(None)
om@3
   880
        ''
om@3
   881
    """
om@3
   882
    def agohence(n, what, divisor=None):
om@3
   883
        if divisor: n = n // divisor
om@3
   884
om@3
   885
        out = str(abs(n)) + ' ' + what       # '2 day'
om@3
   886
        if abs(n) != 1: out += 's'           # '2 days'
om@3
   887
        out += ' '                           # '2 days '
om@3
   888
        if n < 0:
om@3
   889
            out += 'from now'
om@3
   890
        else:
om@3
   891
            out += 'ago'
om@3
   892
        return out                           # '2 days ago'
om@3
   893
om@3
   894
    oneday = 24 * 60 * 60
om@3
   895
om@3
   896
    if not then: return ""
om@3
   897
    if not now: now = datetime.datetime.utcnow()
om@3
   898
    if type(now).__name__ == "DateTime":
om@3
   899
        now = datetime.datetime.fromtimestamp(now)
om@3
   900
    if type(then).__name__ == "DateTime":
om@3
   901
        then = datetime.datetime.fromtimestamp(then)
om@3
   902
    elif type(then).__name__ == "date":
om@3
   903
        then = datetime.datetime(then.year, then.month, then.day)
om@3
   904
om@3
   905
    delta = now - then
om@3
   906
    deltaseconds = int(delta.days * oneday + delta.seconds + delta.microseconds * 1e-06)
om@3
   907
    deltadays = abs(deltaseconds) // oneday
om@3
   908
    if deltaseconds < 0: deltadays *= -1 # fix for oddity of floor
om@3
   909
om@3
   910
    if deltadays:
om@3
   911
        if abs(deltadays) < 4:
om@3
   912
            return agohence(deltadays, 'day')
om@3
   913
om@3
   914
        try:
om@3
   915
            out = then.strftime('%B %e') # e.g. 'June  3'
om@3
   916
        except ValueError:
om@3
   917
            # %e doesn't work on Windows.
om@3
   918
            out = then.strftime('%B %d') # e.g. 'June 03'
om@3
   919
om@3
   920
        if then.year != now.year or deltadays < 0:
om@3
   921
            out += ', %s' % then.year
om@3
   922
        return out
om@3
   923
om@3
   924
    if int(deltaseconds):
om@3
   925
        if abs(deltaseconds) > (60 * 60):
om@3
   926
            return agohence(deltaseconds, 'hour', 60 * 60)
om@3
   927
        elif abs(deltaseconds) > 60:
om@3
   928
            return agohence(deltaseconds, 'minute', 60)
om@3
   929
        else:
om@3
   930
            return agohence(deltaseconds, 'second')
om@3
   931
om@3
   932
    deltamicroseconds = delta.microseconds
om@3
   933
    if delta.days: deltamicroseconds = int(delta.microseconds - 1e6) # datetime oddity
om@3
   934
    if abs(deltamicroseconds) > 1000:
om@3
   935
        return agohence(deltamicroseconds, 'millisecond', 1000)
om@3
   936
om@3
   937
    return agohence(deltamicroseconds, 'microsecond')
om@3
   938
om@3
   939
def numify(string):
om@3
   940
    """
om@3
   941
    Removes all non-digit characters from `string`.
om@3
   942
    
om@3
   943
        >>> numify('800-555-1212')
om@3
   944
        '8005551212'
om@3
   945
        >>> numify('800.555.1212')
om@3
   946
        '8005551212'
om@3
   947
    
om@3
   948
    """
om@3
   949
    return ''.join([c for c in str(string) if c.isdigit()])
om@3
   950
om@3
   951
def denumify(string, pattern):
om@3
   952
    """
om@3
   953
    Formats `string` according to `pattern`, where the letter X gets replaced
om@3
   954
    by characters from `string`.
om@3
   955
    
om@3
   956
        >>> denumify("8005551212", "(XXX) XXX-XXXX")
om@3
   957
        '(800) 555-1212'
om@3
   958
    
om@3
   959
    """
om@3
   960
    out = []
om@3
   961
    for c in pattern:
om@3
   962
        if c == "X":
om@3
   963
            out.append(string[0])
om@3
   964
            string = string[1:]
om@3
   965
        else:
om@3
   966
            out.append(c)
om@3
   967
    return ''.join(out)
om@3
   968
om@3
   969
def commify(n):
om@3
   970
    """
om@3
   971
    Add commas to an integer `n`.
om@3
   972
om@3
   973
        >>> commify(1)
om@3
   974
        '1'
om@3
   975
        >>> commify(123)
om@3
   976
        '123'
om@3
   977
        >>> commify(1234)
om@3
   978
        '1,234'
om@3
   979
        >>> commify(1234567890)
om@3
   980
        '1,234,567,890'
om@3
   981
        >>> commify(123.0)
om@3
   982
        '123.0'
om@3
   983
        >>> commify(1234.5)
om@3
   984
        '1,234.5'
om@3
   985
        >>> commify(1234.56789)
om@3
   986
        '1,234.56789'
om@3
   987
        >>> commify('%.2f' % 1234.5)
om@3
   988
        '1,234.50'
om@3
   989
        >>> commify(None)
om@3
   990
        >>>
om@3
   991
om@3
   992
    """
om@3
   993
    if n is None: return None
om@3
   994
    n = str(n)
om@3
   995
    if '.' in n:
om@3
   996
        dollars, cents = n.split('.')
om@3
   997
    else:
om@3
   998
        dollars, cents = n, None
om@3
   999
om@3
  1000
    r = []
om@3
  1001
    for i, c in enumerate(str(dollars)[::-1]):
om@3
  1002
        if i and (not (i % 3)):
om@3
  1003
            r.insert(0, ',')
om@3
  1004
        r.insert(0, c)
om@3
  1005
    out = ''.join(r)
om@3
  1006
    if cents:
om@3
  1007
        out += '.' + cents
om@3
  1008
    return out
om@3
  1009
om@3
  1010
def dateify(datestring):
om@3
  1011
    """
om@3
  1012
    Formats a numified `datestring` properly.
om@3
  1013
    """
om@3
  1014
    return denumify(datestring, "XXXX-XX-XX XX:XX:XX")
om@3
  1015
om@3
  1016
om@3
  1017
def nthstr(n):
om@3
  1018
    """
om@3
  1019
    Formats an ordinal.
om@3
  1020
    Doesn't handle negative numbers.
om@3
  1021
om@3
  1022
        >>> nthstr(1)
om@3
  1023
        '1st'
om@3
  1024
        >>> nthstr(0)
om@3
  1025
        '0th'
om@3
  1026
        >>> [nthstr(x) for x in [2, 3, 4, 5, 10, 11, 12, 13, 14, 15]]
om@3
  1027
        ['2nd', '3rd', '4th', '5th', '10th', '11th', '12th', '13th', '14th', '15th']
om@3
  1028
        >>> [nthstr(x) for x in [91, 92, 93, 94, 99, 100, 101, 102]]
om@3
  1029
        ['91st', '92nd', '93rd', '94th', '99th', '100th', '101st', '102nd']
om@3
  1030
        >>> [nthstr(x) for x in [111, 112, 113, 114, 115]]
om@3
  1031
        ['111th', '112th', '113th', '114th', '115th']
om@3
  1032
om@3
  1033
    """
om@3
  1034
    
om@3
  1035
    assert n >= 0
om@3
  1036
    if n % 100 in [11, 12, 13]: return '%sth' % n
om@3
  1037
    return {1: '%sst', 2: '%snd', 3: '%srd'}.get(n % 10, '%sth') % n
om@3
  1038
om@3
  1039
def cond(predicate, consequence, alternative=None):
om@3
  1040
    """
om@3
  1041
    Function replacement for if-else to use in expressions.
om@3
  1042
        
om@3
  1043
        >>> x = 2
om@3
  1044
        >>> cond(x % 2 == 0, "even", "odd")
om@3
  1045
        'even'
om@3
  1046
        >>> cond(x % 2 == 0, "even", "odd") + '_row'
om@3
  1047
        'even_row'
om@3
  1048
    """
om@3
  1049
    if predicate:
om@3
  1050
        return consequence
om@3
  1051
    else:
om@3
  1052
        return alternative
om@3
  1053
om@3
  1054
class CaptureStdout:
om@3
  1055
    """
om@3
  1056
    Captures everything `func` prints to stdout and returns it instead.
om@3
  1057
    
om@3
  1058
        >>> def idiot():
om@3
  1059
        ...     print "foo"
om@3
  1060
        >>> capturestdout(idiot)()
om@3
  1061
        'foo\\n'
om@3
  1062
    
om@3
  1063
    **WARNING:** Not threadsafe!
om@3
  1064
    """
om@3
  1065
    def __init__(self, func): 
om@3
  1066
        self.func = func
om@3
  1067
    def __call__(self, *args, **keywords):
om@3
  1068
        from cStringIO import StringIO
om@3
  1069
        # Not threadsafe!
om@3
  1070
        out = StringIO()
om@3
  1071
        oldstdout = sys.stdout
om@3
  1072
        sys.stdout = out
om@3
  1073
        try: 
om@3
  1074
            self.func(*args, **keywords)
om@3
  1075
        finally: 
om@3
  1076
            sys.stdout = oldstdout
om@3
  1077
        return out.getvalue()
om@3
  1078
om@3
  1079
capturestdout = CaptureStdout
om@3
  1080
om@3
  1081
class Profile:
om@3
  1082
    """
om@3
  1083
    Profiles `func` and returns a tuple containing its output
om@3
  1084
    and a string with human-readable profiling information.
om@3
  1085
        
om@3
  1086
        >>> import time
om@3
  1087
        >>> out, inf = profile(time.sleep)(.001)
om@3
  1088
        >>> out
om@3
  1089
        >>> inf[:10].strip()
om@3
  1090
        'took 0.0'
om@3
  1091
    """
om@3
  1092
    def __init__(self, func): 
om@3
  1093
        self.func = func
om@3
  1094
    def __call__(self, *args): ##, **kw):   kw unused
om@3
  1095
        import hotshot, hotshot.stats, os, tempfile ##, time already imported
om@3
  1096
        f, filename = tempfile.mkstemp()
om@3
  1097
        os.close(f)
om@3
  1098
        
om@3
  1099
        prof = hotshot.Profile(filename)
om@3
  1100
om@3
  1101
        stime = time.time()
om@3
  1102
        result = prof.runcall(self.func, *args)
om@3
  1103
        stime = time.time() - stime
om@3
  1104
        prof.close()
om@3
  1105
om@3
  1106
        import cStringIO
om@3
  1107
        out = cStringIO.StringIO()
om@3
  1108
        stats = hotshot.stats.load(filename)
om@3
  1109
        stats.stream = out
om@3
  1110
        stats.strip_dirs()
om@3
  1111
        stats.sort_stats('time', 'calls')
om@3
  1112
        stats.print_stats(40)
om@3
  1113
        stats.print_callers()
om@3
  1114
om@3
  1115
        x =  '\n\ntook '+ str(stime) + ' seconds\n'
om@3
  1116
        x += out.getvalue()
om@3
  1117
om@3
  1118
        # remove the tempfile
om@3
  1119
        try:
om@3
  1120
            os.remove(filename)
om@3
  1121
        except IOError:
om@3
  1122
            pass
om@3
  1123
            
om@3
  1124
        return result, x
om@3
  1125
om@3
  1126
profile = Profile
om@3
  1127
om@3
  1128
om@3
  1129
import traceback
om@3
  1130
# hack for compatibility with Python 2.3:
om@3
  1131
if not hasattr(traceback, 'format_exc'):
om@3
  1132
    from cStringIO import StringIO
om@3
  1133
    def format_exc(limit=None):
om@3
  1134
        strbuf = StringIO()
om@3
  1135
        traceback.print_exc(limit, strbuf)
om@3
  1136
        return strbuf.getvalue()
om@3
  1137
    traceback.format_exc = format_exc
om@3
  1138
om@3
  1139
def tryall(context, prefix=None):
om@3
  1140
    """
om@3
  1141
    Tries a series of functions and prints their results. 
om@3
  1142
    `context` is a dictionary mapping names to values; 
om@3
  1143
    the value will only be tried if it's callable.
om@3
  1144
    
om@3
  1145
        >>> tryall(dict(j=lambda: True))
om@3
  1146
        j: True
om@3
  1147
        ----------------------------------------
om@3
  1148
        results:
om@3
  1149
           True: 1
om@3
  1150
om@3
  1151
    For example, you might have a file `test/stuff.py` 
om@3
  1152
    with a series of functions testing various things in it. 
om@3
  1153
    At the bottom, have a line:
om@3
  1154
om@3
  1155
        if __name__ == "__main__": tryall(globals())
om@3
  1156
om@3
  1157
    Then you can run `python test/stuff.py` and get the results of 
om@3
  1158
    all the tests.
om@3
  1159
    """
om@3
  1160
    context = context.copy() # vars() would update
om@3
  1161
    results = {}
om@3
  1162
    for (key, value) in context.iteritems():
om@3
  1163
        if not hasattr(value, '__call__'): 
om@3
  1164
            continue
om@3
  1165
        if prefix and not key.startswith(prefix): 
om@3
  1166
            continue
om@3
  1167
        print key + ':',
om@3
  1168
        try:
om@3
  1169
            r = value()
om@3
  1170
            dictincr(results, r)
om@3
  1171
            print r
om@3
  1172
        except:
om@3
  1173
            print 'ERROR'
om@3
  1174
            dictincr(results, 'ERROR')
om@3
  1175
            print '   ' + '\n   '.join(traceback.format_exc().split('\n'))
om@3
  1176
        
om@3
  1177
    print '-'*40
om@3
  1178
    print 'results:'
om@3
  1179
    for (key, value) in results.iteritems():
om@3
  1180
        print ' '*2, str(key)+':', value
om@3
  1181
        
om@3
  1182
class ThreadedDict(threadlocal):
om@3
  1183
    """
om@3
  1184
    Thread local storage.
om@3
  1185
    
om@3
  1186
        >>> d = ThreadedDict()
om@3
  1187
        >>> d.x = 1
om@3
  1188
        >>> d.x
om@3
  1189
        1
om@3
  1190
        >>> import threading
om@3
  1191
        >>> def f(): d.x = 2
om@3
  1192
        ...
om@3
  1193
        >>> t = threading.Thread(target=f)
om@3
  1194
        >>> t.start()
om@3
  1195
        >>> t.join()
om@3
  1196
        >>> d.x
om@3
  1197
        1
om@3
  1198
    """
om@3
  1199
    _instances = set()
om@3
  1200
    
om@3
  1201
    def __init__(self):
om@3
  1202
        ThreadedDict._instances.add(self)
om@3
  1203
        
om@3
  1204
    def __del__(self):
om@3
  1205
        ThreadedDict._instances.remove(self)
om@3
  1206
        
om@3
  1207
    def __hash__(self):
om@3
  1208
        return id(self)
om@3
  1209
    
om@3
  1210
    def clear_all():
om@3
  1211
        """Clears all ThreadedDict instances.
om@3
  1212
        """
om@3
  1213
        for t in list(ThreadedDict._instances):
om@3
  1214
            t.clear()
om@3
  1215
    clear_all = staticmethod(clear_all)
om@3
  1216
    
om@3
  1217
    # Define all these methods to more or less fully emulate dict -- attribute access
om@3
  1218
    # is built into threading.local.
om@3
  1219
om@3
  1220
    def __getitem__(self, key):
om@3
  1221
        return self.__dict__[key]
om@3
  1222
om@3
  1223
    def __setitem__(self, key, value):
om@3
  1224
        self.__dict__[key] = value
om@3
  1225
om@3
  1226
    def __delitem__(self, key):
om@3
  1227
        del self.__dict__[key]
om@3
  1228
om@3
  1229
    def __contains__(self, key):
om@3
  1230
        return key in self.__dict__
om@3
  1231
om@3
  1232
    has_key = __contains__
om@3
  1233
        
om@3
  1234
    def clear(self):
om@3
  1235
        self.__dict__.clear()
om@3
  1236
om@3
  1237
    def copy(self):
om@3
  1238
        return self.__dict__.copy()
om@3
  1239
om@3
  1240
    def get(self, key, default=None):
om@3
  1241
        return self.__dict__.get(key, default)
om@3
  1242
om@3
  1243
    def items(self):
om@3
  1244
        return self.__dict__.items()
om@3
  1245
om@3
  1246
    def iteritems(self):
om@3
  1247
        return self.__dict__.iteritems()
om@3
  1248
om@3
  1249
    def keys(self):
om@3
  1250
        return self.__dict__.keys()
om@3
  1251
om@3
  1252
    def iterkeys(self):
om@3
  1253
        return self.__dict__.iterkeys()
om@3
  1254
om@3
  1255
    iter = iterkeys
om@3
  1256
om@3
  1257
    def values(self):
om@3
  1258
        return self.__dict__.values()
om@3
  1259
om@3
  1260
    def itervalues(self):
om@3
  1261
        return self.__dict__.itervalues()
om@3
  1262
om@3
  1263
    def pop(self, key, *args):
om@3
  1264
        return self.__dict__.pop(key, *args)
om@3
  1265
om@3
  1266
    def popitem(self):
om@3
  1267
        return self.__dict__.popitem()
om@3
  1268
om@3
  1269
    def setdefault(self, key, default=None):
om@3
  1270
        return self.__dict__.setdefault(key, default)
om@3
  1271
om@3
  1272
    def update(self, *args, **kwargs):
om@3
  1273
        self.__dict__.update(*args, **kwargs)
om@3
  1274
om@3
  1275
    def __repr__(self):
om@3
  1276
        return '<ThreadedDict %r>' % self.__dict__
om@3
  1277
om@3
  1278
    __str__ = __repr__
om@3
  1279
    
om@3
  1280
threadeddict = ThreadedDict
om@3
  1281
om@3
  1282
def autoassign(self, locals):
om@3
  1283
    """
om@3
  1284
    Automatically assigns local variables to `self`.
om@3
  1285
    
om@3
  1286
        >>> self = storage()
om@3
  1287
        >>> autoassign(self, dict(a=1, b=2))
om@3
  1288
        >>> self
om@3
  1289
        <Storage {'a': 1, 'b': 2}>
om@3
  1290
    
om@3
  1291
    Generally used in `__init__` methods, as in:
om@3
  1292
om@3
  1293
        def __init__(self, foo, bar, baz=1): autoassign(self, locals())
om@3
  1294
    """
om@3
  1295
    for (key, value) in locals.iteritems():
om@3
  1296
        if key == 'self': 
om@3
  1297
            continue
om@3
  1298
        setattr(self, key, value)
om@3
  1299
om@3
  1300
def to36(q):
om@3
  1301
    """
om@3
  1302
    Converts an integer to base 36 (a useful scheme for human-sayable IDs).
om@3
  1303
    
om@3
  1304
        >>> to36(35)
om@3
  1305
        'z'
om@3
  1306
        >>> to36(119292)
om@3
  1307
        '2k1o'
om@3
  1308
        >>> int(to36(939387374), 36)
om@3
  1309
        939387374
om@3
  1310
        >>> to36(0)
om@3
  1311
        '0'
om@3
  1312
        >>> to36(-393)
om@3
  1313
        Traceback (most recent call last):
om@3
  1314
            ... 
om@3
  1315
        ValueError: must supply a positive integer
om@3
  1316
    
om@3
  1317
    """
om@3
  1318
    if q < 0: raise ValueError, "must supply a positive integer"
om@3
  1319
    letters = "0123456789abcdefghijklmnopqrstuvwxyz"
om@3
  1320
    converted = []
om@3
  1321
    while q != 0:
om@3
  1322
        q, r = divmod(q, 36)
om@3
  1323
        converted.insert(0, letters[r])
om@3
  1324
    return "".join(converted) or '0'
om@3
  1325
om@3
  1326
om@3
  1327
r_url = re_compile('(?<!\()(http://(\S+))')
om@3
  1328
def safemarkdown(text):
om@3
  1329
    """
om@3
  1330
    Converts text to HTML following the rules of Markdown, but blocking any
om@3
  1331
    outside HTML input, so that only the things supported by Markdown
om@3
  1332
    can be used. Also converts raw URLs to links.
om@3
  1333
om@3
  1334
    (requires [markdown.py](http://webpy.org/markdown.py))
om@3
  1335
    """
om@3
  1336
    from markdown import markdown
om@3
  1337
    if text:
om@3
  1338
        text = text.replace('<', '&lt;')
om@3
  1339
        # TODO: automatically get page title?
om@3
  1340
        text = r_url.sub(r'<\1>', text)
om@3
  1341
        text = markdown(text)
om@3
  1342
        return text
om@3
  1343
om@3
  1344
def sendmail(from_address, to_address, subject, message, headers=None, **kw):
om@3
  1345
    """
om@3
  1346
    Sends the email message `message` with mail and envelope headers
om@3
  1347
    for from `from_address_` to `to_address` with `subject`. 
om@3
  1348
    Additional email headers can be specified with the dictionary 
om@3
  1349
    `headers.
om@3
  1350
    
om@3
  1351
    Optionally cc, bcc and attachments can be specified as keyword arguments.
om@3
  1352
    Attachments must be an iterable and each attachment can be either a 
om@3
  1353
    filename or a file object or a dictionary with filename, content and 
om@3
  1354
    optionally content_type keys.
om@3
  1355
om@3
  1356
    If `web.config.smtp_server` is set, it will send the message
om@3
  1357
    to that SMTP server. Otherwise it will look for 
om@3
  1358
    `/usr/sbin/sendmail`, the typical location for the sendmail-style
om@3
  1359
    binary. To use sendmail from a different path, set `web.config.sendmail_path`.
om@3
  1360
    """
om@3
  1361
    attachments = kw.pop("attachments", [])
om@3
  1362
    mail = _EmailMessage(from_address, to_address, subject, message, headers, **kw)
om@3
  1363
om@3
  1364
    for a in attachments:
om@3
  1365
        if isinstance(a, dict):
om@3
  1366
            mail.attach(a['filename'], a['content'], a.get('content_type'))
om@3
  1367
        elif hasattr(a, 'read'): # file
om@3
  1368
            filename = os.path.basename(getattr(a, "name", ""))
om@3
  1369
            content_type = getattr(a, 'content_type', None)
om@3
  1370
            mail.attach(filename, a.read(), content_type)
om@3
  1371
        elif isinstance(a, basestring):
om@3
  1372
            f = open(a, 'rb')
om@3
  1373
            content = f.read()
om@3
  1374
            f.close()
om@3
  1375
            filename = os.path.basename(a)
om@3
  1376
            mail.attach(filename, content, None)
om@3
  1377
        else:
om@3
  1378
            raise ValueError, "Invalid attachment: %s" % repr(a)
om@3
  1379
            
om@3
  1380
    mail.send()
om@3
  1381
om@3
  1382
class _EmailMessage:
om@3
  1383
    def __init__(self, from_address, to_address, subject, message, headers=None, **kw):
om@3
  1384
        def listify(x):
om@3
  1385
            if not isinstance(x, list):
om@3
  1386
                return [safestr(x)]
om@3
  1387
            else:
om@3
  1388
                return [safestr(a) for a in x]
om@3
  1389
    
om@3
  1390
        subject = safestr(subject)
om@3
  1391
        message = safestr(message)
om@3
  1392
om@3
  1393
        from_address = safestr(from_address)
om@3
  1394
        to_address = listify(to_address)    
om@3
  1395
        cc = listify(kw.get('cc', []))
om@3
  1396
        bcc = listify(kw.get('bcc', []))
om@3
  1397
        recipients = to_address + cc + bcc
om@3
  1398
om@3
  1399
        import email.Utils
om@3
  1400
        self.from_address = email.Utils.parseaddr(from_address)[1]
om@3
  1401
        self.recipients = [email.Utils.parseaddr(r)[1] for r in recipients]        
om@3
  1402
    
om@3
  1403
        self.headers = dictadd({
om@3
  1404
          'From': from_address,
om@3
  1405
          'To': ", ".join(to_address),
om@3
  1406
          'Subject': subject
om@3
  1407
        }, headers or {})
om@3
  1408
om@3
  1409
        if cc:
om@3
  1410
            self.headers['Cc'] = ", ".join(cc)
om@3
  1411
    
om@3
  1412
        self.message = self.new_message()
om@3
  1413
        self.message.add_header("Content-Transfer-Encoding", "7bit")
om@3
  1414
        self.message.add_header("Content-Disposition", "inline")
om@3
  1415
        self.message.add_header("MIME-Version", "1.0")
om@3
  1416
        self.message.set_payload(message, 'utf-8')
om@3
  1417
        self.multipart = False
om@3
  1418
        
om@3
  1419
    def new_message(self):
om@3
  1420
        from email.Message import Message
om@3
  1421
        return Message()
om@3
  1422
        
om@3
  1423
    def attach(self, filename, content, content_type=None):
om@3
  1424
        if not self.multipart:
om@3
  1425
            msg = self.new_message()
om@3
  1426
            msg.add_header("Content-Type", "multipart/mixed")
om@3
  1427
            msg.attach(self.message)
om@3
  1428
            self.message = msg
om@3
  1429
            self.multipart = True
om@3
  1430
                        
om@3
  1431
        import mimetypes
om@3
  1432
        try:
om@3
  1433
            from email import encoders
om@3
  1434
        except:
om@3
  1435
            from email import Encoders as encoders
om@3
  1436
            
om@3
  1437
        content_type = content_type or mimetypes.guess_type(filename)[0] or "applcation/octet-stream"
om@3
  1438
        
om@3
  1439
        msg = self.new_message()
om@3
  1440
        msg.set_payload(content)
om@3
  1441
        msg.add_header('Content-Type', content_type)
om@3
  1442
        msg.add_header('Content-Disposition', 'attachment', filename=filename)
om@3
  1443
        
om@3
  1444
        if not content_type.startswith("text/"):
om@3
  1445
            encoders.encode_base64(msg)
om@3
  1446
            
om@3
  1447
        self.message.attach(msg)
om@3
  1448
om@3
  1449
    def prepare_message(self):
om@3
  1450
        for k, v in self.headers.iteritems():
om@3
  1451
            if k.lower() == "content-type":
om@3
  1452
                self.message.set_type(v)
om@3
  1453
            else:
om@3
  1454
                self.message.add_header(k, v)
om@3
  1455
om@3
  1456
        self.headers = {}
om@3
  1457
om@3
  1458
    def send(self):
om@3
  1459
        try:
om@3
  1460
            import webapi
om@3
  1461
        except ImportError:
om@3
  1462
            webapi = Storage(config=Storage())
om@3
  1463
om@3
  1464
        self.prepare_message()
om@3
  1465
        message_text = self.message.as_string()
om@3
  1466
    
om@3
  1467
        if webapi.config.get('smtp_server'):
om@3
  1468
            server = webapi.config.get('smtp_server')
om@3
  1469
            port = webapi.config.get('smtp_port', 0)
om@3
  1470
            username = webapi.config.get('smtp_username') 
om@3
  1471
            password = webapi.config.get('smtp_password')
om@3
  1472
            debug_level = webapi.config.get('smtp_debuglevel', None)
om@3
  1473
            starttls = webapi.config.get('smtp_starttls', False)
om@3
  1474
om@3
  1475
            import smtplib
om@3
  1476
            smtpserver = smtplib.SMTP(server, port)
om@3
  1477
om@3
  1478
            if debug_level:
om@3
  1479
                smtpserver.set_debuglevel(debug_level)
om@3
  1480
om@3
  1481
            if starttls:
om@3
  1482
                smtpserver.ehlo()
om@3
  1483
                smtpserver.starttls()
om@3
  1484
                smtpserver.ehlo()
om@3
  1485
om@3
  1486
            if username and password:
om@3
  1487
                smtpserver.login(username, password)
om@3
  1488
om@3
  1489
            smtpserver.sendmail(self.from_address, self.recipients, message_text)
om@3
  1490
            smtpserver.quit()
om@3
  1491
        elif webapi.config.get('email_engine') == 'aws':
om@3
  1492
            import boto.ses
om@3
  1493
            c = boto.ses.SESConnection(
om@3
  1494
              aws_access_key_id=webapi.config.get('aws_access_key_id'),
om@3
  1495
              aws_secret_access_key=web.api.config.get('aws_secret_access_key'))
om@3
  1496
            c.send_raw_email(self.from_address, message_text, self.from_recipients)
om@3
  1497
        else:
om@3
  1498
            sendmail = webapi.config.get('sendmail_path', '/usr/sbin/sendmail')
om@3
  1499
        
om@3
  1500
            assert not self.from_address.startswith('-'), 'security'
om@3
  1501
            for r in self.recipients:
om@3
  1502
                assert not r.startswith('-'), 'security'
om@3
  1503
                
om@3
  1504
            cmd = [sendmail, '-f', self.from_address] + self.recipients
om@3
  1505
om@3
  1506
            if subprocess:
om@3
  1507
                p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
om@3
  1508
                p.stdin.write(message_text)
om@3
  1509
                p.stdin.close()
om@3
  1510
                p.wait()
om@3
  1511
            else:
om@3
  1512
                i, o = os.popen2(cmd)
om@3
  1513
                i.write(message)
om@3
  1514
                i.close()
om@3
  1515
                o.close()
om@3
  1516
                del i, o
om@3
  1517
                
om@3
  1518
    def __repr__(self):
om@3
  1519
        return "<EmailMessage>"
om@3
  1520
    
om@3
  1521
    def __str__(self):
om@3
  1522
        return self.message.as_string()
om@3
  1523
om@3
  1524
if __name__ == "__main__":
om@3
  1525
    import doctest
om@3
  1526
    doctest.testmod()