Path: blob/master/venv/Lib/site-packages/pip/_internal/utils/compat.py
811 views
"""Stuff that differs in different Python versions and platform1distributions."""23# The following comment should be removed at some point in the future.4# mypy: disallow-untyped-defs=False56from __future__ import absolute_import, division78import codecs9import locale10import logging11import os12import shutil13import sys1415from pip._vendor.six import PY2, text_type1617from pip._internal.utils.typing import MYPY_CHECK_RUNNING1819if MYPY_CHECK_RUNNING:20from typing import Optional, Text, Tuple, Union2122try:23import ipaddress24except ImportError:25try:26from pip._vendor import ipaddress # type: ignore27except ImportError:28import ipaddr as ipaddress # type: ignore29ipaddress.ip_address = ipaddress.IPAddress # type: ignore30ipaddress.ip_network = ipaddress.IPNetwork # type: ignore313233__all__ = [34"ipaddress", "uses_pycache", "console_to_str",35"get_path_uid", "stdlib_pkgs", "WINDOWS", "samefile", "get_terminal_size",36]373839logger = logging.getLogger(__name__)4041if PY2:42import imp4344try:45cache_from_source = imp.cache_from_source # type: ignore46except AttributeError:47# does not use __pycache__48cache_from_source = None4950uses_pycache = cache_from_source is not None51else:52uses_pycache = True53from importlib.util import cache_from_source545556if PY2:57# In Python 2.7, backslashreplace exists58# but does not support use for decoding.59# We implement our own replace handler for this60# situation, so that we can consistently use61# backslash replacement for all versions.62def backslashreplace_decode_fn(err):63raw_bytes = (err.object[i] for i in range(err.start, err.end))64# Python 2 gave us characters - convert to numeric bytes65raw_bytes = (ord(b) for b in raw_bytes)66return u"".join(map(u"\\x{:x}".format, raw_bytes)), err.end67codecs.register_error(68"backslashreplace_decode",69backslashreplace_decode_fn,70)71backslashreplace_decode = "backslashreplace_decode"72else:73backslashreplace_decode = "backslashreplace"747576def has_tls():77# type: () -> bool78try:79import _ssl # noqa: F401 # ignore unused80return True81except ImportError:82pass8384from pip._vendor.urllib3.util import IS_PYOPENSSL85return IS_PYOPENSSL868788def str_to_display(data, desc=None):89# type: (Union[bytes, Text], Optional[str]) -> Text90"""91For display or logging purposes, convert a bytes object (or text) to92text (e.g. unicode in Python 2) safe for output.9394:param desc: An optional phrase describing the input data, for use in95the log message if a warning is logged. Defaults to "Bytes object".9697This function should never error out and so can take a best effort98approach. It is okay to be lossy if needed since the return value is99just for display.100101We assume the data is in the locale preferred encoding. If it won't102decode properly, we warn the user but decode as best we can.103104We also ensure that the output can be safely written to standard output105without encoding errors.106"""107if isinstance(data, text_type):108return data109110# Otherwise, data is a bytes object (str in Python 2).111# First, get the encoding we assume. This is the preferred112# encoding for the locale, unless that is not found, or113# it is ASCII, in which case assume UTF-8114encoding = locale.getpreferredencoding()115if (not encoding) or codecs.lookup(encoding).name == "ascii":116encoding = "utf-8"117118# Now try to decode the data - if we fail, warn the user and119# decode with replacement.120try:121decoded_data = data.decode(encoding)122except UnicodeDecodeError:123if desc is None:124desc = 'Bytes object'125msg_format = '{} does not appear to be encoded as %s'.format(desc)126logger.warning(msg_format, encoding)127decoded_data = data.decode(encoding, errors=backslashreplace_decode)128129# Make sure we can print the output, by encoding it to the output130# encoding with replacement of unencodable characters, and then131# decoding again.132# We use stderr's encoding because it's less likely to be133# redirected and if we don't find an encoding we skip this134# step (on the assumption that output is wrapped by something135# that won't fail).136# The double getattr is to deal with the possibility that we're137# being called in a situation where sys.__stderr__ doesn't exist,138# or doesn't have an encoding attribute. Neither of these cases139# should occur in normal pip use, but there's no harm in checking140# in case people use pip in (unsupported) unusual situations.141output_encoding = getattr(getattr(sys, "__stderr__", None),142"encoding", None)143144if output_encoding:145output_encoded = decoded_data.encode(146output_encoding,147errors="backslashreplace"148)149decoded_data = output_encoded.decode(output_encoding)150151return decoded_data152153154def console_to_str(data):155# type: (bytes) -> Text156"""Return a string, safe for output, of subprocess output.157"""158return str_to_display(data, desc='Subprocess output')159160161def get_path_uid(path):162# type: (str) -> int163"""164Return path's uid.165166Does not follow symlinks:167https://github.com/pypa/pip/pull/935#discussion_r5307003168169Placed this function in compat due to differences on AIX and170Jython, that should eventually go away.171172:raises OSError: When path is a symlink or can't be read.173"""174if hasattr(os, 'O_NOFOLLOW'):175fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)176file_uid = os.fstat(fd).st_uid177os.close(fd)178else: # AIX and Jython179# WARNING: time of check vulnerability, but best we can do w/o NOFOLLOW180if not os.path.islink(path):181# older versions of Jython don't have `os.fstat`182file_uid = os.stat(path).st_uid183else:184# raise OSError for parity with os.O_NOFOLLOW above185raise OSError(186"{} is a symlink; Will not return uid for symlinks".format(187path)188)189return file_uid190191192def expanduser(path):193# type: (str) -> str194"""195Expand ~ and ~user constructions.196197Includes a workaround for https://bugs.python.org/issue14768198"""199expanded = os.path.expanduser(path)200if path.startswith('~/') and expanded.startswith('//'):201expanded = expanded[1:]202return expanded203204205# packages in the stdlib that may have installation metadata, but should not be206# considered 'installed'. this theoretically could be determined based on207# dist.location (py27:`sysconfig.get_paths()['stdlib']`,208# py26:sysconfig.get_config_vars('LIBDEST')), but fear platform variation may209# make this ineffective, so hard-coding210stdlib_pkgs = {"python", "wsgiref", "argparse"}211212213# windows detection, covers cpython and ironpython214WINDOWS = (sys.platform.startswith("win") or215(sys.platform == 'cli' and os.name == 'nt'))216217218def samefile(file1, file2):219# type: (str, str) -> bool220"""Provide an alternative for os.path.samefile on Windows/Python2"""221if hasattr(os.path, 'samefile'):222return os.path.samefile(file1, file2)223else:224path1 = os.path.normcase(os.path.abspath(file1))225path2 = os.path.normcase(os.path.abspath(file2))226return path1 == path2227228229if hasattr(shutil, 'get_terminal_size'):230def get_terminal_size():231# type: () -> Tuple[int, int]232"""233Returns a tuple (x, y) representing the width(x) and the height(y)234in characters of the terminal window.235"""236return tuple(shutil.get_terminal_size()) # type: ignore237else:238def get_terminal_size():239# type: () -> Tuple[int, int]240"""241Returns a tuple (x, y) representing the width(x) and the height(y)242in characters of the terminal window.243"""244def ioctl_GWINSZ(fd):245try:246import fcntl247import termios248import struct249cr = struct.unpack_from(250'hh',251fcntl.ioctl(fd, termios.TIOCGWINSZ, '12345678')252)253except Exception:254return None255if cr == (0, 0):256return None257return cr258cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)259if not cr:260if sys.platform != "win32":261try:262fd = os.open(os.ctermid(), os.O_RDONLY)263cr = ioctl_GWINSZ(fd)264os.close(fd)265except Exception:266pass267if not cr:268cr = (os.environ.get('LINES', 25), os.environ.get('COLUMNS', 80))269return int(cr[1]), int(cr[0])270271272