_compat.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. from __future__ import absolute_import, unicode_literals
  2. import io
  3. import abc
  4. import sys
  5. import email
  6. if sys.version_info > (3,): # pragma: nocover
  7. import builtins
  8. from configparser import ConfigParser
  9. import contextlib
  10. FileNotFoundError = builtins.FileNotFoundError
  11. IsADirectoryError = builtins.IsADirectoryError
  12. NotADirectoryError = builtins.NotADirectoryError
  13. PermissionError = builtins.PermissionError
  14. map = builtins.map
  15. from itertools import filterfalse
  16. else: # pragma: nocover
  17. from backports.configparser import ConfigParser
  18. from itertools import imap as map # type: ignore
  19. from itertools import ifilterfalse as filterfalse
  20. import contextlib2 as contextlib
  21. FileNotFoundError = IOError, OSError
  22. IsADirectoryError = IOError, OSError
  23. NotADirectoryError = IOError, OSError
  24. PermissionError = IOError, OSError
  25. str = type('')
  26. suppress = contextlib.suppress
  27. if sys.version_info > (3, 5): # pragma: nocover
  28. import pathlib
  29. else: # pragma: nocover
  30. import pathlib2 as pathlib
  31. try:
  32. ModuleNotFoundError = builtins.FileNotFoundError
  33. except (NameError, AttributeError): # pragma: nocover
  34. ModuleNotFoundError = ImportError # type: ignore
  35. if sys.version_info >= (3,): # pragma: nocover
  36. from importlib.abc import MetaPathFinder
  37. else: # pragma: nocover
  38. class MetaPathFinder(object):
  39. __metaclass__ = abc.ABCMeta
  40. __metaclass__ = type
  41. __all__ = [
  42. 'install', 'NullFinder', 'MetaPathFinder', 'ModuleNotFoundError',
  43. 'pathlib', 'ConfigParser', 'map', 'suppress', 'FileNotFoundError',
  44. 'NotADirectoryError', 'email_message_from_string',
  45. ]
  46. def install(flag):
  47. """
  48. Class decorator for installation on sys.meta_path.
  49. Adds the backport DistributionFinder to sys.meta_path and
  50. attempts to disable the finder functionality of the stdlib
  51. DistributionFinder.
  52. """
  53. def dec_install(cls):
  54. if flag:
  55. sys.meta_path.append(cls())
  56. disable_stdlib_finder()
  57. return cls
  58. return dec_install
  59. def disable_stdlib_finder():
  60. """
  61. Give the backport primacy for discovering path-based distributions
  62. by monkey-patching the stdlib O_O.
  63. See #91 for more background for rationale on this sketchy
  64. behavior.
  65. """
  66. def matches(finder):
  67. return (
  68. getattr(finder, '__module__', None) == '_frozen_importlib_external'
  69. and hasattr(finder, 'find_distributions')
  70. )
  71. for finder in filter(matches, sys.meta_path): # pragma: nocover
  72. del finder.find_distributions
  73. class NullFinder:
  74. """
  75. A "Finder" (aka "MetaClassFinder") that never finds any modules,
  76. but may find distributions.
  77. """
  78. @staticmethod
  79. def find_spec(*args, **kwargs):
  80. return None
  81. # In Python 2, the import system requires finders
  82. # to have a find_module() method, but this usage
  83. # is deprecated in Python 3 in favor of find_spec().
  84. # For the purposes of this finder (i.e. being present
  85. # on sys.meta_path but having no other import
  86. # system functionality), the two methods are identical.
  87. find_module = find_spec
  88. def py2_message_from_string(text): # nocoverpy3
  89. # Work around https://bugs.python.org/issue25545 where
  90. # email.message_from_string cannot handle Unicode on Python 2.
  91. io_buffer = io.StringIO(text)
  92. return email.message_from_file(io_buffer)
  93. email_message_from_string = (
  94. py2_message_from_string
  95. if sys.version_info < (3,) else
  96. email.message_from_string
  97. )
  98. class PyPy_repr:
  99. """
  100. Override repr for EntryPoint objects on PyPy to avoid __iter__ access.
  101. Ref #97, #102.
  102. """
  103. affected = hasattr(sys, 'pypy_version_info')
  104. def __compat_repr__(self): # pragma: nocover
  105. def make_param(name):
  106. value = getattr(self, name)
  107. return '{name}={value!r}'.format(**locals())
  108. params = ', '.join(map(make_param, self._fields))
  109. return 'EntryPoint({params})'.format(**locals())
  110. if affected: # pragma: nocover
  111. __repr__ = __compat_repr__
  112. del affected
  113. # from itertools recipes
  114. def unique_everseen(iterable): # pragma: nocover
  115. "List unique elements, preserving order. Remember all elements ever seen."
  116. seen = set()
  117. seen_add = seen.add
  118. for element in filterfalse(seen.__contains__, iterable):
  119. seen_add(element)
  120. yield element
  121. unique_ordered = (
  122. unique_everseen if sys.version_info < (3, 7) else dict.fromkeys)