_compat.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. """Module which provides compatibility with older Python versions.
  5. This is more future-compatible rather than the opposite (prefer latest
  6. Python 3 way of doing things).
  7. """
  8. import collections
  9. import errno
  10. import functools
  11. import os
  12. import sys
  13. import types
  14. __all__ = [
  15. # constants
  16. "PY3",
  17. # builtins
  18. "long", "range", "super", "unicode", "basestring",
  19. # literals
  20. "u", "b",
  21. # collections module
  22. "lru_cache",
  23. # shutil module
  24. "which", "get_terminal_size",
  25. # python 3 exceptions
  26. "FileNotFoundError", "PermissionError", "ProcessLookupError",
  27. "InterruptedError", "ChildProcessError", "FileExistsError"]
  28. PY3 = sys.version_info[0] == 3
  29. _SENTINEL = object()
  30. if PY3:
  31. long = int
  32. xrange = range
  33. unicode = str
  34. basestring = str
  35. range = range
  36. def u(s):
  37. return s
  38. def b(s):
  39. return s.encode("latin-1")
  40. else:
  41. long = long
  42. range = xrange
  43. unicode = unicode
  44. basestring = basestring
  45. def u(s):
  46. return unicode(s, "unicode_escape")
  47. def b(s):
  48. return s
  49. # --- builtins
  50. # Python 3 super().
  51. # Taken from "future" package.
  52. # Credit: Ryan Kelly
  53. if PY3:
  54. super = super
  55. else:
  56. _builtin_super = super
  57. def super(type_=_SENTINEL, type_or_obj=_SENTINEL, framedepth=1):
  58. """Like Python 3 builtin super(). If called without any arguments
  59. it attempts to infer them at runtime.
  60. """
  61. if type_ is _SENTINEL:
  62. f = sys._getframe(framedepth)
  63. try:
  64. # Get the function's first positional argument.
  65. type_or_obj = f.f_locals[f.f_code.co_varnames[0]]
  66. except (IndexError, KeyError):
  67. raise RuntimeError('super() used in a function with no args')
  68. try:
  69. # Get the MRO so we can crawl it.
  70. mro = type_or_obj.__mro__
  71. except (AttributeError, RuntimeError):
  72. try:
  73. mro = type_or_obj.__class__.__mro__
  74. except AttributeError:
  75. raise RuntimeError('super() used in a non-newstyle class')
  76. for type_ in mro:
  77. # Find the class that owns the currently-executing method.
  78. for meth in type_.__dict__.values():
  79. # Drill down through any wrappers to the underlying func.
  80. # This handles e.g. classmethod() and staticmethod().
  81. try:
  82. while not isinstance(meth, types.FunctionType):
  83. if isinstance(meth, property):
  84. # Calling __get__ on the property will invoke
  85. # user code which might throw exceptions or
  86. # have side effects
  87. meth = meth.fget
  88. else:
  89. try:
  90. meth = meth.__func__
  91. except AttributeError:
  92. meth = meth.__get__(type_or_obj, type_)
  93. except (AttributeError, TypeError):
  94. continue
  95. if meth.func_code is f.f_code:
  96. break # found
  97. else:
  98. # Not found. Move onto the next class in MRO.
  99. continue
  100. break # found
  101. else:
  102. raise RuntimeError('super() called outside a method')
  103. # Dispatch to builtin super().
  104. if type_or_obj is not _SENTINEL:
  105. return _builtin_super(type_, type_or_obj)
  106. return _builtin_super(type_)
  107. # --- exceptions
  108. if PY3:
  109. FileNotFoundError = FileNotFoundError # NOQA
  110. PermissionError = PermissionError # NOQA
  111. ProcessLookupError = ProcessLookupError # NOQA
  112. InterruptedError = InterruptedError # NOQA
  113. ChildProcessError = ChildProcessError # NOQA
  114. FileExistsError = FileExistsError # NOQA
  115. else:
  116. # https://github.com/PythonCharmers/python-future/blob/exceptions/
  117. # src/future/types/exceptions/pep3151.py
  118. import platform
  119. def _instance_checking_exception(base_exception=Exception):
  120. def wrapped(instance_checker):
  121. class TemporaryClass(base_exception):
  122. def __init__(self, *args, **kwargs):
  123. if len(args) == 1 and isinstance(args[0], TemporaryClass):
  124. unwrap_me = args[0]
  125. for attr in dir(unwrap_me):
  126. if not attr.startswith('__'):
  127. setattr(self, attr, getattr(unwrap_me, attr))
  128. else:
  129. super(TemporaryClass, self).__init__(*args, **kwargs)
  130. class __metaclass__(type):
  131. def __instancecheck__(cls, inst):
  132. return instance_checker(inst)
  133. def __subclasscheck__(cls, classinfo):
  134. value = sys.exc_info()[1]
  135. return isinstance(value, cls)
  136. TemporaryClass.__name__ = instance_checker.__name__
  137. TemporaryClass.__doc__ = instance_checker.__doc__
  138. return TemporaryClass
  139. return wrapped
  140. @_instance_checking_exception(EnvironmentError)
  141. def FileNotFoundError(inst):
  142. return getattr(inst, 'errno', _SENTINEL) == errno.ENOENT
  143. @_instance_checking_exception(EnvironmentError)
  144. def ProcessLookupError(inst):
  145. return getattr(inst, 'errno', _SENTINEL) == errno.ESRCH
  146. @_instance_checking_exception(EnvironmentError)
  147. def PermissionError(inst):
  148. return getattr(inst, 'errno', _SENTINEL) in (
  149. errno.EACCES, errno.EPERM)
  150. @_instance_checking_exception(EnvironmentError)
  151. def InterruptedError(inst):
  152. return getattr(inst, 'errno', _SENTINEL) == errno.EINTR
  153. @_instance_checking_exception(EnvironmentError)
  154. def ChildProcessError(inst):
  155. return getattr(inst, 'errno', _SENTINEL) == errno.ECHILD
  156. @_instance_checking_exception(EnvironmentError)
  157. def FileExistsError(inst):
  158. return getattr(inst, 'errno', _SENTINEL) == errno.EEXIST
  159. if platform.python_implementation() != "CPython":
  160. try:
  161. raise OSError(errno.EEXIST, "perm")
  162. except FileExistsError:
  163. pass
  164. except OSError:
  165. raise RuntimeError(
  166. "broken or incompatible Python implementation, see: "
  167. "https://github.com/giampaolo/psutil/issues/1659")
  168. # --- stdlib additions
  169. # py 3.2 functools.lru_cache
  170. # Taken from: http://code.activestate.com/recipes/578078
  171. # Credit: Raymond Hettinger
  172. try:
  173. from functools import lru_cache
  174. except ImportError:
  175. try:
  176. from threading import RLock
  177. except ImportError:
  178. from dummy_threading import RLock
  179. _CacheInfo = collections.namedtuple(
  180. "CacheInfo", ["hits", "misses", "maxsize", "currsize"])
  181. class _HashedSeq(list):
  182. __slots__ = 'hashvalue'
  183. def __init__(self, tup, hash=hash):
  184. self[:] = tup
  185. self.hashvalue = hash(tup)
  186. def __hash__(self):
  187. return self.hashvalue
  188. def _make_key(args, kwds, typed,
  189. kwd_mark=(object(), ),
  190. fasttypes=set((int, str, frozenset, type(None))),
  191. sorted=sorted, tuple=tuple, type=type, len=len):
  192. key = args
  193. if kwds:
  194. sorted_items = sorted(kwds.items())
  195. key += kwd_mark
  196. for item in sorted_items:
  197. key += item
  198. if typed:
  199. key += tuple(type(v) for v in args)
  200. if kwds:
  201. key += tuple(type(v) for k, v in sorted_items)
  202. elif len(key) == 1 and type(key[0]) in fasttypes:
  203. return key[0]
  204. return _HashedSeq(key)
  205. def lru_cache(maxsize=100, typed=False):
  206. """Least-recently-used cache decorator, see:
  207. http://docs.python.org/3/library/functools.html#functools.lru_cache
  208. """
  209. def decorating_function(user_function):
  210. cache = dict()
  211. stats = [0, 0]
  212. HITS, MISSES = 0, 1
  213. make_key = _make_key
  214. cache_get = cache.get
  215. _len = len
  216. lock = RLock()
  217. root = []
  218. root[:] = [root, root, None, None]
  219. nonlocal_root = [root]
  220. PREV, NEXT, KEY, RESULT = 0, 1, 2, 3
  221. if maxsize == 0:
  222. def wrapper(*args, **kwds):
  223. result = user_function(*args, **kwds)
  224. stats[MISSES] += 1
  225. return result
  226. elif maxsize is None:
  227. def wrapper(*args, **kwds):
  228. key = make_key(args, kwds, typed)
  229. result = cache_get(key, root)
  230. if result is not root:
  231. stats[HITS] += 1
  232. return result
  233. result = user_function(*args, **kwds)
  234. cache[key] = result
  235. stats[MISSES] += 1
  236. return result
  237. else:
  238. def wrapper(*args, **kwds):
  239. if kwds or typed:
  240. key = make_key(args, kwds, typed)
  241. else:
  242. key = args
  243. lock.acquire()
  244. try:
  245. link = cache_get(key)
  246. if link is not None:
  247. root, = nonlocal_root
  248. link_prev, link_next, key, result = link
  249. link_prev[NEXT] = link_next
  250. link_next[PREV] = link_prev
  251. last = root[PREV]
  252. last[NEXT] = root[PREV] = link
  253. link[PREV] = last
  254. link[NEXT] = root
  255. stats[HITS] += 1
  256. return result
  257. finally:
  258. lock.release()
  259. result = user_function(*args, **kwds)
  260. lock.acquire()
  261. try:
  262. root, = nonlocal_root
  263. if key in cache:
  264. pass
  265. elif _len(cache) >= maxsize:
  266. oldroot = root
  267. oldroot[KEY] = key
  268. oldroot[RESULT] = result
  269. root = nonlocal_root[0] = oldroot[NEXT]
  270. oldkey = root[KEY]
  271. root[KEY] = root[RESULT] = None
  272. del cache[oldkey]
  273. cache[key] = oldroot
  274. else:
  275. last = root[PREV]
  276. link = [last, root, key, result]
  277. last[NEXT] = root[PREV] = cache[key] = link
  278. stats[MISSES] += 1
  279. finally:
  280. lock.release()
  281. return result
  282. def cache_info():
  283. """Report cache statistics"""
  284. lock.acquire()
  285. try:
  286. return _CacheInfo(stats[HITS], stats[MISSES], maxsize,
  287. len(cache))
  288. finally:
  289. lock.release()
  290. def cache_clear():
  291. """Clear the cache and cache statistics"""
  292. lock.acquire()
  293. try:
  294. cache.clear()
  295. root = nonlocal_root[0]
  296. root[:] = [root, root, None, None]
  297. stats[:] = [0, 0]
  298. finally:
  299. lock.release()
  300. wrapper.__wrapped__ = user_function
  301. wrapper.cache_info = cache_info
  302. wrapper.cache_clear = cache_clear
  303. return functools.update_wrapper(wrapper, user_function)
  304. return decorating_function
  305. # python 3.3
  306. try:
  307. from shutil import which
  308. except ImportError:
  309. def which(cmd, mode=os.F_OK | os.X_OK, path=None):
  310. """Given a command, mode, and a PATH string, return the path which
  311. conforms to the given mode on the PATH, or None if there is no such
  312. file.
  313. `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
  314. of os.environ.get("PATH"), or can be overridden with a custom search
  315. path.
  316. """
  317. def _access_check(fn, mode):
  318. return (os.path.exists(fn) and os.access(fn, mode) and
  319. not os.path.isdir(fn))
  320. if os.path.dirname(cmd):
  321. if _access_check(cmd, mode):
  322. return cmd
  323. return None
  324. if path is None:
  325. path = os.environ.get("PATH", os.defpath)
  326. if not path:
  327. return None
  328. path = path.split(os.pathsep)
  329. if sys.platform == "win32":
  330. if os.curdir not in path:
  331. path.insert(0, os.curdir)
  332. pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
  333. if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
  334. files = [cmd]
  335. else:
  336. files = [cmd + ext for ext in pathext]
  337. else:
  338. files = [cmd]
  339. seen = set()
  340. for dir in path:
  341. normdir = os.path.normcase(dir)
  342. if normdir not in seen:
  343. seen.add(normdir)
  344. for thefile in files:
  345. name = os.path.join(dir, thefile)
  346. if _access_check(name, mode):
  347. return name
  348. return None
  349. # python 3.3
  350. try:
  351. from shutil import get_terminal_size
  352. except ImportError:
  353. def get_terminal_size(fallback=(80, 24)):
  354. try:
  355. import fcntl
  356. import termios
  357. import struct
  358. except ImportError:
  359. return fallback
  360. else:
  361. try:
  362. # This should work on Linux.
  363. res = struct.unpack(
  364. 'hh', fcntl.ioctl(1, termios.TIOCGWINSZ, '1234'))
  365. return (res[1], res[0])
  366. except Exception:
  367. return fallback