Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hhhrrrttt222111
GitHub Repository: hhhrrrttt222111/Dorkify
Path: blob/master/venv/Lib/site-packages/pip/_internal/utils/misc.py
811 views
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
5
from __future__ import absolute_import
6
7
import contextlib
8
import errno
9
import getpass
10
import hashlib
11
import io
12
import logging
13
import os
14
import posixpath
15
import shutil
16
import stat
17
import sys
18
from collections import deque
19
20
from pip._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.
23
from pip._vendor.retrying import retry # type: ignore
24
from pip._vendor.six import PY2, text_type
25
from pip._vendor.six.moves import input, map, zip_longest
26
from pip._vendor.six.moves.urllib import parse as urllib_parse
27
from pip._vendor.six.moves.urllib.parse import unquote as urllib_unquote
28
29
from pip import __version__
30
from pip._internal.exceptions import CommandError
31
from pip._internal.locations import (
32
get_major_minor_version,
33
site_packages,
34
user_site,
35
)
36
from pip._internal.utils.compat import (
37
WINDOWS,
38
expanduser,
39
stdlib_pkgs,
40
str_to_display,
41
)
42
from pip._internal.utils.typing import MYPY_CHECK_RUNNING, cast
43
from pip._internal.utils.virtualenv import (
44
running_under_virtualenv,
45
virtualenv_no_global,
46
)
47
48
if PY2:
49
from io import BytesIO as StringIO
50
else:
51
from io import StringIO
52
53
if MYPY_CHECK_RUNNING:
54
from typing import (
55
Any, AnyStr, Container, Iterable, Iterator, List, Optional, Text,
56
Tuple, Union,
57
)
58
from pip._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
72
logger = logging.getLogger(__name__)
73
74
75
def 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
87
def 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
107
def 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
118
def get_prog():
119
# type: () -> str
120
try:
121
prog = os.path.basename(sys.argv[0])
122
if prog in ('__main__.py', '-c'):
123
return "{} -m pip".format(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)
133
def rmtree(dir, ignore_errors=False):
134
# type: (str, bool) -> None
135
shutil.rmtree(dir, ignore_errors=ignore_errors,
136
onerror=rmtree_errorhandler)
137
138
139
def 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
159
def 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
193
def 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
206
def 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
218
def 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
226
def _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: {}'.format(
232
message)
233
)
234
235
236
def 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
'{}'.format(response, ', '.join(options))
247
)
248
else:
249
return response
250
251
252
def ask_input(message):
253
# type: (str) -> str
254
"""Ask for input interactively."""
255
_check_no_input(message)
256
return input(message)
257
258
259
def 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
266
def format_size(bytes):
267
# type: (float) -> str
268
if bytes > 1000 * 1000:
269
return '{:.1f} MB'.format(bytes / 1000.0 / 1000)
270
elif bytes > 10 * 1000:
271
return '{} kB'.format(int(bytes / 1000))
272
elif bytes > 1000:
273
return '{:.1f} kB'.format(bytes / 1000.0)
274
else:
275
return '{} bytes'.format(int(bytes))
276
277
278
def tabulate(rows):
279
# type: (Iterable[Iterable[Any]]) -> Tuple[List[str], List[int]]
280
"""Return a list of formatted rows and a list of column sizes.
281
282
For example::
283
284
>>> tabulate([['foobar', 2000], [0xdeadbeef]])
285
(['foobar 2000', '3735928559'], [10, 4])
286
"""
287
rows = [tuple(map(str, row)) for row in rows]
288
sizes = [max(map(len, col)) for col in zip_longest(*rows, fillvalue='')]
289
table = [" ".join(map(str.ljust, row, sizes)).rstrip() for row in rows]
290
return table, sizes
291
292
293
def is_installable_dir(path):
294
# type: (str) -> bool
295
"""Is path is a directory containing setup.py or pyproject.toml?
296
"""
297
if not os.path.isdir(path):
298
return False
299
setup_py = os.path.join(path, 'setup.py')
300
if os.path.isfile(setup_py):
301
return True
302
pyproject_toml = os.path.join(path, 'pyproject.toml')
303
if os.path.isfile(pyproject_toml):
304
return True
305
return False
306
307
308
def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE):
309
"""Yield pieces of data from a file-like object until EOF."""
310
while True:
311
chunk = file.read(size)
312
if not chunk:
313
break
314
yield chunk
315
316
317
def normalize_path(path, resolve_symlinks=True):
318
# type: (str, bool) -> str
319
"""
320
Convert a path to its canonical, case-normalized, absolute version.
321
322
"""
323
path = expanduser(path)
324
if resolve_symlinks:
325
path = os.path.realpath(path)
326
else:
327
path = os.path.abspath(path)
328
return os.path.normcase(path)
329
330
331
def splitext(path):
332
# type: (str) -> Tuple[str, str]
333
"""Like os.path.splitext, but take off .tar too"""
334
base, ext = posixpath.splitext(path)
335
if base.lower().endswith('.tar'):
336
ext = base[-4:] + ext
337
base = base[:-4]
338
return base, ext
339
340
341
def renames(old, new):
342
# type: (str, str) -> None
343
"""Like os.renames(), but handles renaming across devices."""
344
# Implementation borrowed from os.renames().
345
head, tail = os.path.split(new)
346
if head and tail and not os.path.exists(head):
347
os.makedirs(head)
348
349
shutil.move(old, new)
350
351
head, tail = os.path.split(old)
352
if head and tail:
353
try:
354
os.removedirs(head)
355
except OSError:
356
pass
357
358
359
def is_local(path):
360
# type: (str) -> bool
361
"""
362
Return True if path is within sys.prefix, if we're running in a virtualenv.
363
364
If we're not in a virtualenv, all paths are considered "local."
365
366
Caution: this function assumes the head of path has been normalized
367
with normalize_path.
368
"""
369
if not running_under_virtualenv():
370
return True
371
return path.startswith(normalize_path(sys.prefix))
372
373
374
def dist_is_local(dist):
375
# type: (Distribution) -> bool
376
"""
377
Return True if given Distribution object is installed locally
378
(i.e. within current virtualenv).
379
380
Always True if we're not in a virtualenv.
381
382
"""
383
return is_local(dist_location(dist))
384
385
386
def dist_in_usersite(dist):
387
# type: (Distribution) -> bool
388
"""
389
Return True if given Distribution is installed in user site.
390
"""
391
return dist_location(dist).startswith(normalize_path(user_site))
392
393
394
def dist_in_site_packages(dist):
395
# type: (Distribution) -> bool
396
"""
397
Return True if given Distribution is installed in
398
sysconfig.get_python_lib().
399
"""
400
return dist_location(dist).startswith(normalize_path(site_packages))
401
402
403
def dist_is_editable(dist):
404
# type: (Distribution) -> bool
405
"""
406
Return True if given Distribution is an editable install.
407
"""
408
for path_item in sys.path:
409
egg_link = os.path.join(path_item, dist.project_name + '.egg-link')
410
if os.path.isfile(egg_link):
411
return True
412
return False
413
414
415
def get_installed_distributions(
416
local_only=True, # type: bool
417
skip=stdlib_pkgs, # type: Container[str]
418
include_editables=True, # type: bool
419
editables_only=False, # type: bool
420
user_only=False, # type: bool
421
paths=None # type: Optional[List[str]]
422
):
423
# type: (...) -> List[Distribution]
424
"""
425
Return a list of installed Distribution objects.
426
427
If ``local_only`` is True (default), only return installations
428
local to the current virtualenv, if in a virtualenv.
429
430
``skip`` argument is an iterable of lower-case project names to
431
ignore; defaults to stdlib_pkgs
432
433
If ``include_editables`` is False, don't report editables.
434
435
If ``editables_only`` is True , only report editables.
436
437
If ``user_only`` is True , only report installations in the user
438
site directory.
439
440
If ``paths`` is set, only report the distributions present at the
441
specified list of locations.
442
"""
443
if paths:
444
working_set = pkg_resources.WorkingSet(paths)
445
else:
446
working_set = pkg_resources.working_set
447
448
if local_only:
449
local_test = dist_is_local
450
else:
451
def local_test(d):
452
return True
453
454
if include_editables:
455
def editable_test(d):
456
return True
457
else:
458
def editable_test(d):
459
return not dist_is_editable(d)
460
461
if editables_only:
462
def editables_only_test(d):
463
return dist_is_editable(d)
464
else:
465
def editables_only_test(d):
466
return True
467
468
if user_only:
469
user_test = dist_in_usersite
470
else:
471
def user_test(d):
472
return True
473
474
return [d for d in working_set
475
if local_test(d) and
476
d.key not in skip and
477
editable_test(d) and
478
editables_only_test(d) and
479
user_test(d)
480
]
481
482
483
def egg_link_path(dist):
484
# type: (Distribution) -> Optional[str]
485
"""
486
Return the path for the .egg-link file if it exists, otherwise, None.
487
488
There's 3 scenarios:
489
1) not in a virtualenv
490
try to find in site.USER_SITE, then site_packages
491
2) in a no-global virtualenv
492
try to find in site_packages
493
3) in a yes-global virtualenv
494
try to find in site_packages, then site.USER_SITE
495
(don't look in global location)
496
497
For #1 and #3, there could be odd cases, where there's an egg-link in 2
498
locations.
499
500
This method will just return the first one found.
501
"""
502
sites = []
503
if running_under_virtualenv():
504
sites.append(site_packages)
505
if not virtualenv_no_global() and user_site:
506
sites.append(user_site)
507
else:
508
if user_site:
509
sites.append(user_site)
510
sites.append(site_packages)
511
512
for site in sites:
513
egglink = os.path.join(site, dist.project_name) + '.egg-link'
514
if os.path.isfile(egglink):
515
return egglink
516
return None
517
518
519
def dist_location(dist):
520
# type: (Distribution) -> str
521
"""
522
Get the site-packages location of this distribution. Generally
523
this is dist.location, except in the case of develop-installed
524
packages, where dist.location is the source code location, and we
525
want to know where the egg-link file is.
526
527
The returned location is normalized (in particular, with symlinks removed).
528
"""
529
egg_link = egg_link_path(dist)
530
if egg_link:
531
return normalize_path(egg_link)
532
return normalize_path(dist.location)
533
534
535
def write_output(msg, *args):
536
# type: (str, str) -> None
537
logger.info(msg, *args)
538
539
540
class FakeFile(object):
541
"""Wrap a list of lines in an object with readline() to make
542
ConfigParser happy."""
543
def __init__(self, lines):
544
self._gen = (l for l in lines)
545
546
def readline(self):
547
try:
548
try:
549
return next(self._gen)
550
except NameError:
551
return self._gen.next()
552
except StopIteration:
553
return ''
554
555
def __iter__(self):
556
return self._gen
557
558
559
class StreamWrapper(StringIO):
560
561
@classmethod
562
def from_stream(cls, orig_stream):
563
cls.orig_stream = orig_stream
564
return cls()
565
566
# compileall.compile_dir() needs stdout.encoding to print to stdout
567
@property
568
def encoding(self):
569
return self.orig_stream.encoding
570
571
572
@contextlib.contextmanager
573
def captured_output(stream_name):
574
"""Return a context manager used by captured_stdout/stdin/stderr
575
that temporarily replaces the sys stream *stream_name* with a StringIO.
576
577
Taken from Lib/support/__init__.py in the CPython repo.
578
"""
579
orig_stdout = getattr(sys, stream_name)
580
setattr(sys, stream_name, StreamWrapper.from_stream(orig_stdout))
581
try:
582
yield getattr(sys, stream_name)
583
finally:
584
setattr(sys, stream_name, orig_stdout)
585
586
587
def captured_stdout():
588
"""Capture the output of sys.stdout:
589
590
with captured_stdout() as stdout:
591
print('hello')
592
self.assertEqual(stdout.getvalue(), 'hello\n')
593
594
Taken from Lib/support/__init__.py in the CPython repo.
595
"""
596
return captured_output('stdout')
597
598
599
def captured_stderr():
600
"""
601
See captured_stdout().
602
"""
603
return captured_output('stderr')
604
605
606
class cached_property(object):
607
"""A property that is only computed once per instance and then replaces
608
itself with an ordinary attribute. Deleting the attribute resets the
609
property.
610
611
Source: https://github.com/bottlepy/bottle/blob/0.11.5/bottle.py#L175
612
"""
613
614
def __init__(self, func):
615
self.__doc__ = getattr(func, '__doc__')
616
self.func = func
617
618
def __get__(self, obj, cls):
619
if obj is None:
620
# We're being accessed from the class itself, not from an object
621
return self
622
value = obj.__dict__[self.func.__name__] = self.func(obj)
623
return value
624
625
626
def get_installed_version(dist_name, working_set=None):
627
"""Get the installed version of dist_name avoiding pkg_resources cache"""
628
# Create a requirement that we'll look for inside of setuptools.
629
req = pkg_resources.Requirement.parse(dist_name)
630
631
if working_set is None:
632
# We want to avoid having this cached, so we need to construct a new
633
# working set each time.
634
working_set = pkg_resources.WorkingSet()
635
636
# Get the installed distribution from our working set
637
dist = working_set.find(req)
638
639
# Check to see if we got an installed distribution or not, if we did
640
# we want to return it's version.
641
return dist.version if dist else None
642
643
644
def consume(iterator):
645
"""Consume an iterable at C speed."""
646
deque(iterator, maxlen=0)
647
648
649
# Simulates an enum
650
def enum(*sequential, **named):
651
enums = dict(zip(sequential, range(len(sequential))), **named)
652
reverse = {value: key for key, value in enums.items()}
653
enums['reverse_mapping'] = reverse
654
return type('Enum', (), enums)
655
656
657
def build_netloc(host, port):
658
# type: (str, Optional[int]) -> str
659
"""
660
Build a netloc from a host-port pair
661
"""
662
if port is None:
663
return host
664
if ':' in host:
665
# Only wrap host with square brackets when it is IPv6
666
host = '[{}]'.format(host)
667
return '{}:{}'.format(host, port)
668
669
670
def build_url_from_netloc(netloc, scheme='https'):
671
# type: (str, str) -> str
672
"""
673
Build a full URL from a netloc.
674
"""
675
if netloc.count(':') >= 2 and '@' not in netloc and '[' not in netloc:
676
# It must be a bare IPv6 address, so wrap it with brackets.
677
netloc = '[{}]'.format(netloc)
678
return '{}://{}'.format(scheme, netloc)
679
680
681
def parse_netloc(netloc):
682
# type: (str) -> Tuple[str, Optional[int]]
683
"""
684
Return the host-port pair from a netloc.
685
"""
686
url = build_url_from_netloc(netloc)
687
parsed = urllib_parse.urlparse(url)
688
return parsed.hostname, parsed.port
689
690
691
def split_auth_from_netloc(netloc):
692
"""
693
Parse out and remove the auth information from a netloc.
694
695
Returns: (netloc, (username, password)).
696
"""
697
if '@' not in netloc:
698
return netloc, (None, None)
699
700
# Split from the right because that's how urllib.parse.urlsplit()
701
# behaves if more than one @ is present (which can be checked using
702
# the password attribute of urlsplit()'s return value).
703
auth, netloc = netloc.rsplit('@', 1)
704
if ':' in auth:
705
# Split from the left because that's how urllib.parse.urlsplit()
706
# behaves if more than one : is present (which again can be checked
707
# using the password attribute of the return value)
708
user_pass = auth.split(':', 1)
709
else:
710
user_pass = auth, None
711
712
user_pass = tuple(
713
None if x is None else urllib_unquote(x) for x in user_pass
714
)
715
716
return netloc, user_pass
717
718
719
def redact_netloc(netloc):
720
# type: (str) -> str
721
"""
722
Replace the sensitive data in a netloc with "****", if it exists.
723
724
For example:
725
- "user:[email protected]" returns "user:****@example.com"
726
- "[email protected]" returns "****@example.com"
727
"""
728
netloc, (user, password) = split_auth_from_netloc(netloc)
729
if user is None:
730
return netloc
731
if password is None:
732
user = '****'
733
password = ''
734
else:
735
user = urllib_parse.quote(user)
736
password = ':****'
737
return '{user}{password}@{netloc}'.format(user=user,
738
password=password,
739
netloc=netloc)
740
741
742
def _transform_url(url, transform_netloc):
743
"""Transform and replace netloc in a url.
744
745
transform_netloc is a function taking the netloc and returning a
746
tuple. The first element of this tuple is the new netloc. The
747
entire tuple is returned.
748
749
Returns a tuple containing the transformed url as item 0 and the
750
original tuple returned by transform_netloc as item 1.
751
"""
752
purl = urllib_parse.urlsplit(url)
753
netloc_tuple = transform_netloc(purl.netloc)
754
# stripped url
755
url_pieces = (
756
purl.scheme, netloc_tuple[0], purl.path, purl.query, purl.fragment
757
)
758
surl = urllib_parse.urlunsplit(url_pieces)
759
return surl, netloc_tuple
760
761
762
def _get_netloc(netloc):
763
return split_auth_from_netloc(netloc)
764
765
766
def _redact_netloc(netloc):
767
return (redact_netloc(netloc),)
768
769
770
def split_auth_netloc_from_url(url):
771
# type: (str) -> Tuple[str, str, Tuple[str, str]]
772
"""
773
Parse a url into separate netloc, auth, and url with no auth.
774
775
Returns: (url_without_auth, netloc, (username, password))
776
"""
777
url_without_auth, (netloc, auth) = _transform_url(url, _get_netloc)
778
return url_without_auth, netloc, auth
779
780
781
def remove_auth_from_url(url):
782
# type: (str) -> str
783
"""Return a copy of url with 'username:password@' removed."""
784
# username/pass params are passed to subversion through flags
785
# and are not recognized in the url.
786
return _transform_url(url, _get_netloc)[0]
787
788
789
def redact_auth_from_url(url):
790
# type: (str) -> str
791
"""Replace the password in a given url with ****."""
792
return _transform_url(url, _redact_netloc)[0]
793
794
795
class HiddenText(object):
796
def __init__(
797
self,
798
secret, # type: str
799
redacted, # type: str
800
):
801
# type: (...) -> None
802
self.secret = secret
803
self.redacted = redacted
804
805
def __repr__(self):
806
# type: (...) -> str
807
return '<HiddenText {!r}>'.format(str(self))
808
809
def __str__(self):
810
# type: (...) -> str
811
return self.redacted
812
813
# This is useful for testing.
814
def __eq__(self, other):
815
# type: (Any) -> bool
816
if type(self) != type(other):
817
return False
818
819
# The string being used for redaction doesn't also have to match,
820
# just the raw, original string.
821
return (self.secret == other.secret)
822
823
# We need to provide an explicit __ne__ implementation for Python 2.
824
# TODO: remove this when we drop PY2 support.
825
def __ne__(self, other):
826
# type: (Any) -> bool
827
return not self == other
828
829
830
def hide_value(value):
831
# type: (str) -> HiddenText
832
return HiddenText(value, redacted='****')
833
834
835
def hide_url(url):
836
# type: (str) -> HiddenText
837
redacted = redact_auth_from_url(url)
838
return HiddenText(url, redacted=redacted)
839
840
841
def protect_pip_from_modification_on_windows(modifying_pip):
842
# type: (bool) -> None
843
"""Protection of pip.exe from modification on Windows
844
845
On Windows, any operation modifying pip should be run as:
846
python -m pip ...
847
"""
848
pip_names = [
849
"pip.exe",
850
"pip{}.exe".format(sys.version_info[0]),
851
"pip{}.{}.exe".format(*sys.version_info[:2])
852
]
853
854
# See https://github.com/pypa/pip/issues/1299 for more discussion
855
should_show_use_python_msg = (
856
modifying_pip and
857
WINDOWS and
858
os.path.basename(sys.argv[0]) in pip_names
859
)
860
861
if should_show_use_python_msg:
862
new_command = [
863
sys.executable, "-m", "pip"
864
] + sys.argv[1:]
865
raise CommandError(
866
'To modify pip, please run the following command:\n{}'
867
.format(" ".join(new_command))
868
)
869
870
871
def is_console_interactive():
872
# type: () -> bool
873
"""Is this console interactive?
874
"""
875
return sys.stdin is not None and sys.stdin.isatty()
876
877
878
def hash_file(path, blocksize=1 << 20):
879
# type: (str, int) -> Tuple[Any, int]
880
"""Return (hash, length) for path using hashlib.sha256()
881
"""
882
883
h = hashlib.sha256()
884
length = 0
885
with open(path, 'rb') as f:
886
for block in read_chunks(f, size=blocksize):
887
length += len(block)
888
h.update(block)
889
return h, length
890
891
892
def is_wheel_installed():
893
"""
894
Return whether the wheel package is installed.
895
"""
896
try:
897
import wheel # noqa: F401
898
except ImportError:
899
return False
900
901
return True
902
903
904
def pairwise(iterable):
905
# type: (Iterable[Any]) -> Iterator[Tuple[Any, Any]]
906
"""
907
Return paired elements.
908
909
For example:
910
s -> (s0, s1), (s2, s3), (s4, s5), ...
911
"""
912
iterable = iter(iterable)
913
return zip_longest(iterable, iterable)
914
915