_compat.py 15 KB

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