1#!/usr/bin/env python
2
3# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Module which provides compatibility with older Python versions."""
8
9import collections
10import functools
11import sys
12
13__all__ = ["PY3", "long", "xrange", "unicode", "callable", "lru_cache"]
14
15PY3 = sys.version_info[0] == 3
16
17if PY3:
18    long = int
19    xrange = range
20    unicode = str
21
22    def u(s):
23        return s
24else:
25    long = long
26    xrange = xrange
27    unicode = unicode
28
29    def u(s):
30        return unicode(s, "unicode_escape")
31
32
33# removed in 3.0, reintroduced in 3.2
34try:
35    callable = callable
36except NameError:
37    def callable(obj):
38        return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
39
40
41# --- stdlib additions
42
43
44# py 3.2 functools.lru_cache
45# Taken from: http://code.activestate.com/recipes/578078
46# Credit: Raymond Hettinger
47try:
48    from functools import lru_cache
49except ImportError:
50    try:
51        from threading import RLock
52    except ImportError:
53        from dummy_threading import RLock
54
55    _CacheInfo = collections.namedtuple(
56        "CacheInfo", ["hits", "misses", "maxsize", "currsize"])
57
58    class _HashedSeq(list):
59        __slots__ = 'hashvalue'
60
61        def __init__(self, tup, hash=hash):
62            self[:] = tup
63            self.hashvalue = hash(tup)
64
65        def __hash__(self):
66            return self.hashvalue
67
68    def _make_key(args, kwds, typed,
69                  kwd_mark=(object(), ),
70                  fasttypes=set((int, str, frozenset, type(None))),
71                  sorted=sorted, tuple=tuple, type=type, len=len):
72        key = args
73        if kwds:
74            sorted_items = sorted(kwds.items())
75            key += kwd_mark
76            for item in sorted_items:
77                key += item
78        if typed:
79            key += tuple(type(v) for v in args)
80            if kwds:
81                key += tuple(type(v) for k, v in sorted_items)
82        elif len(key) == 1 and type(key[0]) in fasttypes:
83            return key[0]
84        return _HashedSeq(key)
85
86    def lru_cache(maxsize=100, typed=False):
87        """Least-recently-used cache decorator, see:
88        http://docs.python.org/3/library/functools.html#functools.lru_cache
89        """
90        def decorating_function(user_function):
91            cache = dict()
92            stats = [0, 0]
93            HITS, MISSES = 0, 1
94            make_key = _make_key
95            cache_get = cache.get
96            _len = len
97            lock = RLock()
98            root = []
99            root[:] = [root, root, None, None]
100            nonlocal_root = [root]
101            PREV, NEXT, KEY, RESULT = 0, 1, 2, 3
102            if maxsize == 0:
103                def wrapper(*args, **kwds):
104                    result = user_function(*args, **kwds)
105                    stats[MISSES] += 1
106                    return result
107            elif maxsize is None:
108                def wrapper(*args, **kwds):
109                    key = make_key(args, kwds, typed)
110                    result = cache_get(key, root)
111                    if result is not root:
112                        stats[HITS] += 1
113                        return result
114                    result = user_function(*args, **kwds)
115                    cache[key] = result
116                    stats[MISSES] += 1
117                    return result
118            else:
119                def wrapper(*args, **kwds):
120                    if kwds or typed:
121                        key = make_key(args, kwds, typed)
122                    else:
123                        key = args
124                    lock.acquire()
125                    try:
126                        link = cache_get(key)
127                        if link is not None:
128                            root, = nonlocal_root
129                            link_prev, link_next, key, result = link
130                            link_prev[NEXT] = link_next
131                            link_next[PREV] = link_prev
132                            last = root[PREV]
133                            last[NEXT] = root[PREV] = link
134                            link[PREV] = last
135                            link[NEXT] = root
136                            stats[HITS] += 1
137                            return result
138                    finally:
139                        lock.release()
140                    result = user_function(*args, **kwds)
141                    lock.acquire()
142                    try:
143                        root, = nonlocal_root
144                        if key in cache:
145                            pass
146                        elif _len(cache) >= maxsize:
147                            oldroot = root
148                            oldroot[KEY] = key
149                            oldroot[RESULT] = result
150                            root = nonlocal_root[0] = oldroot[NEXT]
151                            oldkey = root[KEY]
152                            root[KEY] = root[RESULT] = None
153                            del cache[oldkey]
154                            cache[key] = oldroot
155                        else:
156                            last = root[PREV]
157                            link = [last, root, key, result]
158                            last[NEXT] = root[PREV] = cache[key] = link
159                        stats[MISSES] += 1
160                    finally:
161                        lock.release()
162                    return result
163
164            def cache_info():
165                """Report cache statistics"""
166                lock.acquire()
167                try:
168                    return _CacheInfo(stats[HITS], stats[MISSES], maxsize,
169                                      len(cache))
170                finally:
171                    lock.release()
172
173            def cache_clear():
174                """Clear the cache and cache statistics"""
175                lock.acquire()
176                try:
177                    cache.clear()
178                    root = nonlocal_root[0]
179                    root[:] = [root, root, None, None]
180                    stats[:] = [0, 0]
181                finally:
182                    lock.release()
183
184            wrapper.__wrapped__ = user_function
185            wrapper.cache_info = cache_info
186            wrapper.cache_clear = cache_clear
187            return functools.update_wrapper(wrapper, user_function)
188
189        return decorating_function
190