1# The following comment should be removed at some point in the future. 2# mypy: strict-optional=False 3# mypy: disallow-untyped-defs=False 4 5from __future__ import absolute_import 6 7import contextlib 8import errno 9import getpass 10import hashlib 11import io 12import logging 13import os 14import posixpath 15import shutil 16import stat 17import sys 18from collections import deque 19 20from pipenv.patched.notpip._vendor import pkg_resources 21# NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is 22# why we ignore the type on this import. 23from pipenv.patched.notpip._vendor.retrying import retry # type: ignore 24from pipenv.patched.notpip._vendor.six import PY2, text_type 25from pipenv.patched.notpip._vendor.six.moves import input 26from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse 27from pipenv.patched.notpip._vendor.six.moves.urllib.parse import unquote as urllib_unquote 28 29from pipenv.patched.notpip import __version__ 30from pipenv.patched.notpip._internal.exceptions import CommandError 31from pipenv.patched.notpip._internal.locations import ( 32 get_major_minor_version, 33 site_packages, 34 user_site, 35) 36from pipenv.patched.notpip._internal.utils.compat import ( 37 WINDOWS, 38 expanduser, 39 stdlib_pkgs, 40 str_to_display, 41) 42from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING, cast 43from pipenv.patched.notpip._internal.utils.virtualenv import ( 44 running_under_virtualenv, 45 virtualenv_no_global, 46) 47 48if PY2: 49 from io import BytesIO as StringIO 50else: 51 from io import StringIO 52 53if MYPY_CHECK_RUNNING: 54 from typing import ( 55 Any, AnyStr, Container, Iterable, List, Optional, Text, 56 Tuple, Union, 57 ) 58 from pipenv.patched.notpip._vendor.pkg_resources import Distribution 59 60 VersionInfo = Tuple[int, int, int] 61 62 63__all__ = ['rmtree', 'display_path', 'backup_dir', 64 'ask', 'splitext', 65 'format_size', 'is_installable_dir', 66 'normalize_path', 67 'renames', 'get_prog', 68 'captured_stdout', 'ensure_dir', 69 'get_installed_version', 'remove_auth_from_url'] 70 71 72logger = logging.getLogger(__name__) 73 74 75def get_pip_version(): 76 # type: () -> str 77 pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..") 78 pip_pkg_dir = os.path.abspath(pip_pkg_dir) 79 80 return ( 81 'pip {} from {} (python {})'.format( 82 __version__, pip_pkg_dir, get_major_minor_version(), 83 ) 84 ) 85 86 87def normalize_version_info(py_version_info): 88 # type: (Tuple[int, ...]) -> Tuple[int, int, int] 89 """ 90 Convert a tuple of ints representing a Python version to one of length 91 three. 92 93 :param py_version_info: a tuple of ints representing a Python version, 94 or None to specify no version. The tuple can have any length. 95 96 :return: a tuple of length three if `py_version_info` is non-None. 97 Otherwise, return `py_version_info` unchanged (i.e. None). 98 """ 99 if len(py_version_info) < 3: 100 py_version_info += (3 - len(py_version_info)) * (0,) 101 elif len(py_version_info) > 3: 102 py_version_info = py_version_info[:3] 103 104 return cast('VersionInfo', py_version_info) 105 106 107def ensure_dir(path): 108 # type: (AnyStr) -> None 109 """os.path.makedirs without EEXIST.""" 110 try: 111 os.makedirs(path) 112 except OSError as e: 113 # Windows can raise spurious ENOTEMPTY errors. See #6426. 114 if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY: 115 raise 116 117 118def get_prog(): 119 # type: () -> str 120 try: 121 prog = os.path.basename(sys.argv[0]) 122 if prog in ('__main__.py', '-c'): 123 return "%s -m pip" % sys.executable 124 else: 125 return prog 126 except (AttributeError, TypeError, IndexError): 127 pass 128 return 'pip' 129 130 131# Retry every half second for up to 3 seconds 132@retry(stop_max_delay=3000, wait_fixed=500) 133def rmtree(dir, ignore_errors=False): 134 # type: (str, bool) -> None 135 from pipenv.vendor.vistir.path import rmtree as vistir_rmtree, handle_remove_readonly 136 vistir_rmtree(dir, onerror=handle_remove_readonly, ignore_errors=ignore_errors) 137 138 139def rmtree_errorhandler(func, path, exc_info): 140 """On Windows, the files in .svn are read-only, so when rmtree() tries to 141 remove them, an exception is thrown. We catch that here, remove the 142 read-only attribute, and hopefully continue without problems.""" 143 try: 144 has_attr_readonly = not (os.stat(path).st_mode & stat.S_IWRITE) 145 except (IOError, OSError): 146 # it's equivalent to os.path.exists 147 return 148 149 if has_attr_readonly: 150 # convert to read/write 151 os.chmod(path, stat.S_IWRITE) 152 # use the original function to repeat the operation 153 func(path) 154 return 155 else: 156 raise 157 158 159def path_to_display(path): 160 # type: (Optional[Union[str, Text]]) -> Optional[Text] 161 """ 162 Convert a bytes (or text) path to text (unicode in Python 2) for display 163 and logging purposes. 164 165 This function should never error out. Also, this function is mainly needed 166 for Python 2 since in Python 3 str paths are already text. 167 """ 168 if path is None: 169 return None 170 if isinstance(path, text_type): 171 return path 172 # Otherwise, path is a bytes object (str in Python 2). 173 try: 174 display_path = path.decode(sys.getfilesystemencoding(), 'strict') 175 except UnicodeDecodeError: 176 # Include the full bytes to make troubleshooting easier, even though 177 # it may not be very human readable. 178 if PY2: 179 # Convert the bytes to a readable str representation using 180 # repr(), and then convert the str to unicode. 181 # Also, we add the prefix "b" to the repr() return value both 182 # to make the Python 2 output look like the Python 3 output, and 183 # to signal to the user that this is a bytes representation. 184 display_path = str_to_display('b{!r}'.format(path)) 185 else: 186 # Silence the "F821 undefined name 'ascii'" flake8 error since 187 # in Python 3 ascii() is a built-in. 188 display_path = ascii(path) # noqa: F821 189 190 return display_path 191 192 193def display_path(path): 194 # type: (Union[str, Text]) -> str 195 """Gives the display value for a given path, making it relative to cwd 196 if possible.""" 197 path = os.path.normcase(os.path.abspath(path)) 198 if sys.version_info[0] == 2: 199 path = path.decode(sys.getfilesystemencoding(), 'replace') 200 path = path.encode(sys.getdefaultencoding(), 'replace') 201 if path.startswith(os.getcwd() + os.path.sep): 202 path = '.' + path[len(os.getcwd()):] 203 return path 204 205 206def backup_dir(dir, ext='.bak'): 207 # type: (str, str) -> str 208 """Figure out the name of a directory to back up the given dir to 209 (adding .bak, .bak2, etc)""" 210 n = 1 211 extension = ext 212 while os.path.exists(dir + extension): 213 n += 1 214 extension = ext + str(n) 215 return dir + extension 216 217 218def ask_path_exists(message, options): 219 # type: (str, Iterable[str]) -> str 220 for action in os.environ.get('PIP_EXISTS_ACTION', '').split(): 221 if action in options: 222 return action 223 return ask(message, options) 224 225 226def _check_no_input(message): 227 # type: (str) -> None 228 """Raise an error if no input is allowed.""" 229 if os.environ.get('PIP_NO_INPUT'): 230 raise Exception( 231 'No input was expected ($PIP_NO_INPUT set); question: %s' % 232 message 233 ) 234 235 236def ask(message, options): 237 # type: (str, Iterable[str]) -> str 238 """Ask the message interactively, with the given possible responses""" 239 while 1: 240 _check_no_input(message) 241 response = input(message) 242 response = response.strip().lower() 243 if response not in options: 244 print( 245 'Your response (%r) was not one of the expected responses: ' 246 '%s' % (response, ', '.join(options)) 247 ) 248 else: 249 return response 250 251 252def ask_input(message): 253 # type: (str) -> str 254 """Ask for input interactively.""" 255 _check_no_input(message) 256 return input(message) 257 258 259def ask_password(message): 260 # type: (str) -> str 261 """Ask for a password interactively.""" 262 _check_no_input(message) 263 return getpass.getpass(message) 264 265 266def format_size(bytes): 267 # type: (float) -> str 268 if bytes > 1000 * 1000: 269 return '%.1f MB' % (bytes / 1000.0 / 1000) 270 elif bytes > 10 * 1000: 271 return '%i kB' % (bytes / 1000) 272 elif bytes > 1000: 273 return '%.1f kB' % (bytes / 1000.0) 274 else: 275 return '%i bytes' % bytes 276 277 278def is_installable_dir(path): 279 # type: (str) -> bool 280 """Is path is a directory containing setup.py or pyproject.toml? 281 """ 282 if not os.path.isdir(path): 283 return False 284 setup_py = os.path.join(path, 'setup.py') 285 if os.path.isfile(setup_py): 286 return True 287 pyproject_toml = os.path.join(path, 'pyproject.toml') 288 if os.path.isfile(pyproject_toml): 289 return True 290 return False 291 292 293def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE): 294 """Yield pieces of data from a file-like object until EOF.""" 295 while True: 296 chunk = file.read(size) 297 if not chunk: 298 break 299 yield chunk 300 301 302def normalize_path(path, resolve_symlinks=True): 303 # type: (str, bool) -> str 304 """ 305 Convert a path to its canonical, case-normalized, absolute version. 306 307 """ 308 path = expanduser(path) 309 if resolve_symlinks: 310 path = os.path.realpath(path) 311 else: 312 path = os.path.abspath(path) 313 return os.path.normcase(path) 314 315 316def splitext(path): 317 # type: (str) -> Tuple[str, str] 318 """Like os.path.splitext, but take off .tar too""" 319 base, ext = posixpath.splitext(path) 320 if base.lower().endswith('.tar'): 321 ext = base[-4:] + ext 322 base = base[:-4] 323 return base, ext 324 325 326def renames(old, new): 327 # type: (str, str) -> None 328 """Like os.renames(), but handles renaming across devices.""" 329 # Implementation borrowed from os.renames(). 330 head, tail = os.path.split(new) 331 if head and tail and not os.path.exists(head): 332 os.makedirs(head) 333 334 shutil.move(old, new) 335 336 head, tail = os.path.split(old) 337 if head and tail: 338 try: 339 os.removedirs(head) 340 except OSError: 341 pass 342 343 344def is_local(path): 345 # type: (str) -> bool 346 """ 347 Return True if path is within sys.prefix, if we're running in a virtualenv. 348 349 If we're not in a virtualenv, all paths are considered "local." 350 351 Caution: this function assumes the head of path has been normalized 352 with normalize_path. 353 """ 354 if not running_under_virtualenv(): 355 return True 356 return path.startswith(normalize_path(sys.prefix)) 357 358 359def dist_is_local(dist): 360 # type: (Distribution) -> bool 361 """ 362 Return True if given Distribution object is installed locally 363 (i.e. within current virtualenv). 364 365 Always True if we're not in a virtualenv. 366 367 """ 368 return is_local(dist_location(dist)) 369 370 371def dist_in_usersite(dist): 372 # type: (Distribution) -> bool 373 """ 374 Return True if given Distribution is installed in user site. 375 """ 376 return dist_location(dist).startswith(normalize_path(user_site)) 377 378 379def dist_in_site_packages(dist): 380 # type: (Distribution) -> bool 381 """ 382 Return True if given Distribution is installed in 383 sysconfig.get_python_lib(). 384 """ 385 return dist_location(dist).startswith(normalize_path(site_packages)) 386 387 388def dist_is_editable(dist): 389 # type: (Distribution) -> bool 390 """ 391 Return True if given Distribution is an editable install. 392 """ 393 for path_item in sys.path: 394 egg_link = os.path.join(path_item, dist.project_name + '.egg-link') 395 if os.path.isfile(egg_link): 396 return True 397 return False 398 399 400def get_installed_distributions( 401 local_only=True, # type: bool 402 skip=stdlib_pkgs, # type: Container[str] 403 include_editables=True, # type: bool 404 editables_only=False, # type: bool 405 user_only=False, # type: bool 406 paths=None # type: Optional[List[str]] 407): 408 # type: (...) -> List[Distribution] 409 """ 410 Return a list of installed Distribution objects. 411 412 If ``local_only`` is True (default), only return installations 413 local to the current virtualenv, if in a virtualenv. 414 415 ``skip`` argument is an iterable of lower-case project names to 416 ignore; defaults to stdlib_pkgs 417 418 If ``include_editables`` is False, don't report editables. 419 420 If ``editables_only`` is True , only report editables. 421 422 If ``user_only`` is True , only report installations in the user 423 site directory. 424 425 If ``paths`` is set, only report the distributions present at the 426 specified list of locations. 427 """ 428 if paths: 429 working_set = pkg_resources.WorkingSet(paths) 430 else: 431 working_set = pkg_resources.working_set 432 433 if local_only: 434 local_test = dist_is_local 435 else: 436 def local_test(d): 437 return True 438 439 if include_editables: 440 def editable_test(d): 441 return True 442 else: 443 def editable_test(d): 444 return not dist_is_editable(d) 445 446 if editables_only: 447 def editables_only_test(d): 448 return dist_is_editable(d) 449 else: 450 def editables_only_test(d): 451 return True 452 453 if user_only: 454 user_test = dist_in_usersite 455 else: 456 def user_test(d): 457 return True 458 459 return [d for d in working_set 460 if local_test(d) and 461 d.key not in skip and 462 editable_test(d) and 463 editables_only_test(d) and 464 user_test(d) 465 ] 466 467 468def egg_link_path(dist): 469 # type: (Distribution) -> Optional[str] 470 """ 471 Return the path for the .egg-link file if it exists, otherwise, None. 472 473 There's 3 scenarios: 474 1) not in a virtualenv 475 try to find in site.USER_SITE, then site_packages 476 2) in a no-global virtualenv 477 try to find in site_packages 478 3) in a yes-global virtualenv 479 try to find in site_packages, then site.USER_SITE 480 (don't look in global location) 481 482 For #1 and #3, there could be odd cases, where there's an egg-link in 2 483 locations. 484 485 This method will just return the first one found. 486 """ 487 sites = [] 488 if running_under_virtualenv(): 489 sites.append(site_packages) 490 if not virtualenv_no_global() and user_site: 491 sites.append(user_site) 492 else: 493 if user_site: 494 sites.append(user_site) 495 sites.append(site_packages) 496 497 for site in sites: 498 egglink = os.path.join(site, dist.project_name) + '.egg-link' 499 if os.path.isfile(egglink): 500 return egglink 501 return None 502 503 504def dist_location(dist): 505 # type: (Distribution) -> str 506 """ 507 Get the site-packages location of this distribution. Generally 508 this is dist.location, except in the case of develop-installed 509 packages, where dist.location is the source code location, and we 510 want to know where the egg-link file is. 511 512 The returned location is normalized (in particular, with symlinks removed). 513 """ 514 egg_link = egg_link_path(dist) 515 if egg_link: 516 return normalize_path(egg_link) 517 return normalize_path(dist.location) 518 519 520def write_output(msg, *args): 521 # type: (str, str) -> None 522 logger.info(msg, *args) 523 524 525class FakeFile(object): 526 """Wrap a list of lines in an object with readline() to make 527 ConfigParser happy.""" 528 def __init__(self, lines): 529 self._gen = (l for l in lines) 530 531 def readline(self): 532 try: 533 try: 534 return next(self._gen) 535 except NameError: 536 return self._gen.next() 537 except StopIteration: 538 return '' 539 540 def __iter__(self): 541 return self._gen 542 543 544class StreamWrapper(StringIO): 545 546 @classmethod 547 def from_stream(cls, orig_stream): 548 cls.orig_stream = orig_stream 549 return cls() 550 551 # compileall.compile_dir() needs stdout.encoding to print to stdout 552 @property 553 def encoding(self): 554 return self.orig_stream.encoding 555 556 557@contextlib.contextmanager 558def captured_output(stream_name): 559 """Return a context manager used by captured_stdout/stdin/stderr 560 that temporarily replaces the sys stream *stream_name* with a StringIO. 561 562 Taken from Lib/support/__init__.py in the CPython repo. 563 """ 564 orig_stdout = getattr(sys, stream_name) 565 setattr(sys, stream_name, StreamWrapper.from_stream(orig_stdout)) 566 try: 567 yield getattr(sys, stream_name) 568 finally: 569 setattr(sys, stream_name, orig_stdout) 570 571 572def captured_stdout(): 573 """Capture the output of sys.stdout: 574 575 with captured_stdout() as stdout: 576 print('hello') 577 self.assertEqual(stdout.getvalue(), 'hello\n') 578 579 Taken from Lib/support/__init__.py in the CPython repo. 580 """ 581 return captured_output('stdout') 582 583 584def captured_stderr(): 585 """ 586 See captured_stdout(). 587 """ 588 return captured_output('stderr') 589 590 591class cached_property(object): 592 """A property that is only computed once per instance and then replaces 593 itself with an ordinary attribute. Deleting the attribute resets the 594 property. 595 596 Source: https://github.com/bottlepy/bottle/blob/0.11.5/bottle.py#L175 597 """ 598 599 def __init__(self, func): 600 self.__doc__ = getattr(func, '__doc__') 601 self.func = func 602 603 def __get__(self, obj, cls): 604 if obj is None: 605 # We're being accessed from the class itself, not from an object 606 return self 607 value = obj.__dict__[self.func.__name__] = self.func(obj) 608 return value 609 610 611def get_installed_version(dist_name, working_set=None): 612 """Get the installed version of dist_name avoiding pkg_resources cache""" 613 # Create a requirement that we'll look for inside of setuptools. 614 req = pkg_resources.Requirement.parse(dist_name) 615 616 if working_set is None: 617 # We want to avoid having this cached, so we need to construct a new 618 # working set each time. 619 working_set = pkg_resources.WorkingSet() 620 621 # Get the installed distribution from our working set 622 dist = working_set.find(req) 623 624 # Check to see if we got an installed distribution or not, if we did 625 # we want to return it's version. 626 return dist.version if dist else None 627 628 629def consume(iterator): 630 """Consume an iterable at C speed.""" 631 deque(iterator, maxlen=0) 632 633 634# Simulates an enum 635def enum(*sequential, **named): 636 enums = dict(zip(sequential, range(len(sequential))), **named) 637 reverse = {value: key for key, value in enums.items()} 638 enums['reverse_mapping'] = reverse 639 return type('Enum', (), enums) 640 641 642def build_netloc(host, port): 643 # type: (str, Optional[int]) -> str 644 """ 645 Build a netloc from a host-port pair 646 """ 647 if port is None: 648 return host 649 if ':' in host: 650 # Only wrap host with square brackets when it is IPv6 651 host = '[{}]'.format(host) 652 return '{}:{}'.format(host, port) 653 654 655def build_url_from_netloc(netloc, scheme='https'): 656 # type: (str, str) -> str 657 """ 658 Build a full URL from a netloc. 659 """ 660 if netloc.count(':') >= 2 and '@' not in netloc and '[' not in netloc: 661 # It must be a bare IPv6 address, so wrap it with brackets. 662 netloc = '[{}]'.format(netloc) 663 return '{}://{}'.format(scheme, netloc) 664 665 666def parse_netloc(netloc): 667 # type: (str) -> Tuple[str, Optional[int]] 668 """ 669 Return the host-port pair from a netloc. 670 """ 671 url = build_url_from_netloc(netloc) 672 parsed = urllib_parse.urlparse(url) 673 return parsed.hostname, parsed.port 674 675 676def split_auth_from_netloc(netloc): 677 """ 678 Parse out and remove the auth information from a netloc. 679 680 Returns: (netloc, (username, password)). 681 """ 682 if '@' not in netloc: 683 return netloc, (None, None) 684 685 # Split from the right because that's how urllib.parse.urlsplit() 686 # behaves if more than one @ is present (which can be checked using 687 # the password attribute of urlsplit()'s return value). 688 auth, netloc = netloc.rsplit('@', 1) 689 if ':' in auth: 690 # Split from the left because that's how urllib.parse.urlsplit() 691 # behaves if more than one : is present (which again can be checked 692 # using the password attribute of the return value) 693 user_pass = auth.split(':', 1) 694 else: 695 user_pass = auth, None 696 697 user_pass = tuple( 698 None if x is None else urllib_unquote(x) for x in user_pass 699 ) 700 701 return netloc, user_pass 702 703 704def redact_netloc(netloc): 705 # type: (str) -> str 706 """ 707 Replace the sensitive data in a netloc with "****", if it exists. 708 709 For example: 710 - "user:pass@example.com" returns "user:****@example.com" 711 - "accesstoken@example.com" returns "****@example.com" 712 """ 713 netloc, (user, password) = split_auth_from_netloc(netloc) 714 if user is None: 715 return netloc 716 if password is None: 717 user = '****' 718 password = '' 719 else: 720 user = urllib_parse.quote(user) 721 password = ':****' 722 return '{user}{password}@{netloc}'.format(user=user, 723 password=password, 724 netloc=netloc) 725 726 727def _transform_url(url, transform_netloc): 728 """Transform and replace netloc in a url. 729 730 transform_netloc is a function taking the netloc and returning a 731 tuple. The first element of this tuple is the new netloc. The 732 entire tuple is returned. 733 734 Returns a tuple containing the transformed url as item 0 and the 735 original tuple returned by transform_netloc as item 1. 736 """ 737 purl = urllib_parse.urlsplit(url) 738 netloc_tuple = transform_netloc(purl.netloc) 739 # stripped url 740 url_pieces = ( 741 purl.scheme, netloc_tuple[0], purl.path, purl.query, purl.fragment 742 ) 743 surl = urllib_parse.urlunsplit(url_pieces) 744 return surl, netloc_tuple 745 746 747def _get_netloc(netloc): 748 return split_auth_from_netloc(netloc) 749 750 751def _redact_netloc(netloc): 752 return (redact_netloc(netloc),) 753 754 755def split_auth_netloc_from_url(url): 756 # type: (str) -> Tuple[str, str, Tuple[str, str]] 757 """ 758 Parse a url into separate netloc, auth, and url with no auth. 759 760 Returns: (url_without_auth, netloc, (username, password)) 761 """ 762 url_without_auth, (netloc, auth) = _transform_url(url, _get_netloc) 763 return url_without_auth, netloc, auth 764 765 766def remove_auth_from_url(url): 767 # type: (str) -> str 768 """Return a copy of url with 'username:password@' removed.""" 769 # username/pass params are passed to subversion through flags 770 # and are not recognized in the url. 771 return _transform_url(url, _get_netloc)[0] 772 773 774def redact_auth_from_url(url): 775 # type: (str) -> str 776 """Replace the password in a given url with ****.""" 777 return _transform_url(url, _redact_netloc)[0] 778 779 780class HiddenText(object): 781 def __init__( 782 self, 783 secret, # type: str 784 redacted, # type: str 785 ): 786 # type: (...) -> None 787 self.secret = secret 788 self.redacted = redacted 789 790 def __repr__(self): 791 # type: (...) -> str 792 return '<HiddenText {!r}>'.format(str(self)) 793 794 def __str__(self): 795 # type: (...) -> str 796 return self.redacted 797 798 # This is useful for testing. 799 def __eq__(self, other): 800 # type: (Any) -> bool 801 if type(self) != type(other): 802 return False 803 804 # The string being used for redaction doesn't also have to match, 805 # just the raw, original string. 806 return (self.secret == other.secret) 807 808 # We need to provide an explicit __ne__ implementation for Python 2. 809 # TODO: remove this when we drop PY2 support. 810 def __ne__(self, other): 811 # type: (Any) -> bool 812 return not self == other 813 814 815def hide_value(value): 816 # type: (str) -> HiddenText 817 return HiddenText(value, redacted='****') 818 819 820def hide_url(url): 821 # type: (str) -> HiddenText 822 redacted = redact_auth_from_url(url) 823 return HiddenText(url, redacted=redacted) 824 825 826def protect_pip_from_modification_on_windows(modifying_pip): 827 # type: (bool) -> None 828 """Protection of pip.exe from modification on Windows 829 830 On Windows, any operation modifying pip should be run as: 831 python -m pip ... 832 """ 833 pip_names = [ 834 "pip.exe", 835 "pip{}.exe".format(sys.version_info[0]), 836 "pip{}.{}.exe".format(*sys.version_info[:2]) 837 ] 838 839 # See https://github.com/pypa/pip/issues/1299 for more discussion 840 should_show_use_python_msg = ( 841 modifying_pip and 842 WINDOWS and 843 os.path.basename(sys.argv[0]) in pip_names 844 ) 845 846 if should_show_use_python_msg: 847 new_command = [ 848 sys.executable, "-m", "pip" 849 ] + sys.argv[1:] 850 raise CommandError( 851 'To modify pip, please run the following command:\n{}' 852 .format(" ".join(new_command)) 853 ) 854 855 856def is_console_interactive(): 857 # type: () -> bool 858 """Is this console interactive? 859 """ 860 return sys.stdin is not None and sys.stdin.isatty() 861 862 863def hash_file(path, blocksize=1 << 20): 864 # type: (str, int) -> Tuple[Any, int] 865 """Return (hash, length) for path using hashlib.sha256() 866 """ 867 868 h = hashlib.sha256() 869 length = 0 870 with open(path, 'rb') as f: 871 for block in read_chunks(f, size=blocksize): 872 length += len(block) 873 h.update(block) 874 return h, length 875 876 877def is_wheel_installed(): 878 """ 879 Return whether the wheel package is installed. 880 """ 881 try: 882 import wheel # noqa: F401 883 except ImportError: 884 return False 885 886 return True 887