comparison lib/python3.8/site-packages/pip/_internal/utils/compat.py @ 0:9e54283cc701 draft

"planemo upload commit d12c32a45bcd441307e632fca6d9af7d60289d44"
author guerler
date Mon, 27 Jul 2020 03:47:31 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:9e54283cc701
1 """Stuff that differs in different Python versions and platform
2 distributions."""
3
4 # The following comment should be removed at some point in the future.
5 # mypy: disallow-untyped-defs=False
6
7 from __future__ import absolute_import, division
8
9 import codecs
10 import locale
11 import logging
12 import os
13 import shutil
14 import sys
15
16 from pip._vendor.six import PY2, text_type
17
18 from pip._internal.utils.typing import MYPY_CHECK_RUNNING
19
20 if MYPY_CHECK_RUNNING:
21 from typing import Optional, Text, Tuple, Union
22
23 try:
24 import ipaddress
25 except ImportError:
26 try:
27 from pip._vendor import ipaddress # type: ignore
28 except ImportError:
29 import ipaddr as ipaddress # type: ignore
30 ipaddress.ip_address = ipaddress.IPAddress # type: ignore
31 ipaddress.ip_network = ipaddress.IPNetwork # type: ignore
32
33
34 __all__ = [
35 "ipaddress", "uses_pycache", "console_to_str",
36 "get_path_uid", "stdlib_pkgs", "WINDOWS", "samefile", "get_terminal_size",
37 ]
38
39
40 logger = logging.getLogger(__name__)
41
42 if PY2:
43 import imp
44
45 try:
46 cache_from_source = imp.cache_from_source # type: ignore
47 except AttributeError:
48 # does not use __pycache__
49 cache_from_source = None
50
51 uses_pycache = cache_from_source is not None
52 else:
53 uses_pycache = True
54 from importlib.util import cache_from_source
55
56
57 if PY2:
58 # In Python 2.7, backslashreplace exists
59 # but does not support use for decoding.
60 # We implement our own replace handler for this
61 # situation, so that we can consistently use
62 # backslash replacement for all versions.
63 def backslashreplace_decode_fn(err):
64 raw_bytes = (err.object[i] for i in range(err.start, err.end))
65 # Python 2 gave us characters - convert to numeric bytes
66 raw_bytes = (ord(b) for b in raw_bytes)
67 return u"".join(u"\\x%x" % c for c in raw_bytes), err.end
68 codecs.register_error(
69 "backslashreplace_decode",
70 backslashreplace_decode_fn,
71 )
72 backslashreplace_decode = "backslashreplace_decode"
73 else:
74 backslashreplace_decode = "backslashreplace"
75
76
77 def has_tls():
78 # type: () -> bool
79 try:
80 import _ssl # noqa: F401 # ignore unused
81 return True
82 except ImportError:
83 pass
84
85 from pip._vendor.urllib3.util import IS_PYOPENSSL
86 return IS_PYOPENSSL
87
88
89 def str_to_display(data, desc=None):
90 # type: (Union[bytes, Text], Optional[str]) -> Text
91 """
92 For display or logging purposes, convert a bytes object (or text) to
93 text (e.g. unicode in Python 2) safe for output.
94
95 :param desc: An optional phrase describing the input data, for use in
96 the log message if a warning is logged. Defaults to "Bytes object".
97
98 This function should never error out and so can take a best effort
99 approach. It is okay to be lossy if needed since the return value is
100 just for display.
101
102 We assume the data is in the locale preferred encoding. If it won't
103 decode properly, we warn the user but decode as best we can.
104
105 We also ensure that the output can be safely written to standard output
106 without encoding errors.
107 """
108 if isinstance(data, text_type):
109 return data
110
111 # Otherwise, data is a bytes object (str in Python 2).
112 # First, get the encoding we assume. This is the preferred
113 # encoding for the locale, unless that is not found, or
114 # it is ASCII, in which case assume UTF-8
115 encoding = locale.getpreferredencoding()
116 if (not encoding) or codecs.lookup(encoding).name == "ascii":
117 encoding = "utf-8"
118
119 # Now try to decode the data - if we fail, warn the user and
120 # decode with replacement.
121 try:
122 decoded_data = data.decode(encoding)
123 except UnicodeDecodeError:
124 if desc is None:
125 desc = 'Bytes object'
126 msg_format = '{} does not appear to be encoded as %s'.format(desc)
127 logger.warning(msg_format, encoding)
128 decoded_data = data.decode(encoding, errors=backslashreplace_decode)
129
130 # Make sure we can print the output, by encoding it to the output
131 # encoding with replacement of unencodable characters, and then
132 # decoding again.
133 # We use stderr's encoding because it's less likely to be
134 # redirected and if we don't find an encoding we skip this
135 # step (on the assumption that output is wrapped by something
136 # that won't fail).
137 # The double getattr is to deal with the possibility that we're
138 # being called in a situation where sys.__stderr__ doesn't exist,
139 # or doesn't have an encoding attribute. Neither of these cases
140 # should occur in normal pip use, but there's no harm in checking
141 # in case people use pip in (unsupported) unusual situations.
142 output_encoding = getattr(getattr(sys, "__stderr__", None),
143 "encoding", None)
144
145 if output_encoding:
146 output_encoded = decoded_data.encode(
147 output_encoding,
148 errors="backslashreplace"
149 )
150 decoded_data = output_encoded.decode(output_encoding)
151
152 return decoded_data
153
154
155 def console_to_str(data):
156 # type: (bytes) -> Text
157 """Return a string, safe for output, of subprocess output.
158 """
159 return str_to_display(data, desc='Subprocess output')
160
161
162 def get_path_uid(path):
163 # type: (str) -> int
164 """
165 Return path's uid.
166
167 Does not follow symlinks:
168 https://github.com/pypa/pip/pull/935#discussion_r5307003
169
170 Placed this function in compat due to differences on AIX and
171 Jython, that should eventually go away.
172
173 :raises OSError: When path is a symlink or can't be read.
174 """
175 if hasattr(os, 'O_NOFOLLOW'):
176 fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
177 file_uid = os.fstat(fd).st_uid
178 os.close(fd)
179 else: # AIX and Jython
180 # WARNING: time of check vulnerability, but best we can do w/o NOFOLLOW
181 if not os.path.islink(path):
182 # older versions of Jython don't have `os.fstat`
183 file_uid = os.stat(path).st_uid
184 else:
185 # raise OSError for parity with os.O_NOFOLLOW above
186 raise OSError(
187 "%s is a symlink; Will not return uid for symlinks" % path
188 )
189 return file_uid
190
191
192 def expanduser(path):
193 # type: (str) -> str
194 """
195 Expand ~ and ~user constructions.
196
197 Includes a workaround for https://bugs.python.org/issue14768
198 """
199 expanded = os.path.expanduser(path)
200 if path.startswith('~/') and expanded.startswith('//'):
201 expanded = expanded[1:]
202 return expanded
203
204
205 # packages in the stdlib that may have installation metadata, but should not be
206 # considered 'installed'. this theoretically could be determined based on
207 # dist.location (py27:`sysconfig.get_paths()['stdlib']`,
208 # py26:sysconfig.get_config_vars('LIBDEST')), but fear platform variation may
209 # make this ineffective, so hard-coding
210 stdlib_pkgs = {"python", "wsgiref", "argparse"}
211
212
213 # windows detection, covers cpython and ironpython
214 WINDOWS = (sys.platform.startswith("win") or
215 (sys.platform == 'cli' and os.name == 'nt'))
216
217
218 def samefile(file1, file2):
219 # type: (str, str) -> bool
220 """Provide an alternative for os.path.samefile on Windows/Python2"""
221 if hasattr(os.path, 'samefile'):
222 return os.path.samefile(file1, file2)
223 else:
224 path1 = os.path.normcase(os.path.abspath(file1))
225 path2 = os.path.normcase(os.path.abspath(file2))
226 return path1 == path2
227
228
229 if hasattr(shutil, 'get_terminal_size'):
230 def get_terminal_size():
231 # type: () -> Tuple[int, int]
232 """
233 Returns a tuple (x, y) representing the width(x) and the height(y)
234 in characters of the terminal window.
235 """
236 return tuple(shutil.get_terminal_size()) # type: ignore
237 else:
238 def get_terminal_size():
239 # type: () -> Tuple[int, int]
240 """
241 Returns a tuple (x, y) representing the width(x) and the height(y)
242 in characters of the terminal window.
243 """
244 def ioctl_GWINSZ(fd):
245 try:
246 import fcntl
247 import termios
248 import struct
249 cr = struct.unpack_from(
250 'hh',
251 fcntl.ioctl(fd, termios.TIOCGWINSZ, '12345678')
252 )
253 except Exception:
254 return None
255 if cr == (0, 0):
256 return None
257 return cr
258 cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
259 if not cr:
260 if sys.platform != "win32":
261 try:
262 fd = os.open(os.ctermid(), os.O_RDONLY)
263 cr = ioctl_GWINSZ(fd)
264 os.close(fd)
265 except Exception:
266 pass
267 if not cr:
268 cr = (os.environ.get('LINES', 25), os.environ.get('COLUMNS', 80))
269 return int(cr[1]), int(cr[0])