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