summaryrefslogtreecommitdiff
path: root/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/utils/misc.py
diff options
context:
space:
mode:
Diffstat (limited to 'venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/utils/misc.py')
-rw-r--r--venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/utils/misc.py851
1 files changed, 851 insertions, 0 deletions
diff --git a/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/utils/misc.py b/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/utils/misc.py
new file mode 100644
index 0000000..db84a7c
--- /dev/null
+++ b/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/utils/misc.py
@@ -0,0 +1,851 @@
1from __future__ import absolute_import
2
3import contextlib
4import errno
5import io
6import locale
7# we have a submodule named 'logging' which would shadow this if we used the
8# regular name:
9import logging as std_logging
10import os
11import posixpath
12import re
13import shutil
14import stat
15import subprocess
16import sys
17import tarfile
18import zipfile
19from collections import deque
20
21from pip._vendor import pkg_resources
22# NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is
23# why we ignore the type on this import.
24from pip._vendor.retrying import retry # type: ignore
25from pip._vendor.six import PY2
26from pip._vendor.six.moves import input
27
28from pip._internal.compat import console_to_str, expanduser, stdlib_pkgs
29from pip._internal.exceptions import InstallationError
30from pip._internal.locations import (
31 running_under_virtualenv, site_packages, user_site, virtualenv_no_global,
32 write_delete_marker_file,
33)
34
35if PY2:
36 from io import BytesIO as StringIO
37else:
38 from io import StringIO
39
40__all__ = ['rmtree', 'display_path', 'backup_dir',
41 'ask', 'splitext',
42 'format_size', 'is_installable_dir',
43 'is_svn_page', 'file_contents',
44 'split_leading_dir', 'has_leading_dir',
45 'normalize_path',
46 'renames', 'get_prog',
47 'unzip_file', 'untar_file', 'unpack_file', 'call_subprocess',
48 'captured_stdout', 'ensure_dir',
49 'ARCHIVE_EXTENSIONS', 'SUPPORTED_EXTENSIONS',
50 'get_installed_version']
51
52
53logger = std_logging.getLogger(__name__)
54
55BZ2_EXTENSIONS = ('.tar.bz2', '.tbz')
56XZ_EXTENSIONS = ('.tar.xz', '.txz', '.tlz', '.tar.lz', '.tar.lzma')
57ZIP_EXTENSIONS = ('.zip', '.whl')
58TAR_EXTENSIONS = ('.tar.gz', '.tgz', '.tar')
59ARCHIVE_EXTENSIONS = (
60 ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS)
61SUPPORTED_EXTENSIONS = ZIP_EXTENSIONS + TAR_EXTENSIONS
62try:
63 import bz2 # noqa
64 SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS
65except ImportError:
66 logger.debug('bz2 module is not available')
67
68try:
69 # Only for Python 3.3+
70 import lzma # noqa
71 SUPPORTED_EXTENSIONS += XZ_EXTENSIONS
72except ImportError:
73 logger.debug('lzma module is not available')
74
75
76def import_or_raise(pkg_or_module_string, ExceptionType, *args, **kwargs):
77 try:
78 return __import__(pkg_or_module_string)
79 except ImportError:
80 raise ExceptionType(*args, **kwargs)
81
82
83def ensure_dir(path):
84 """os.path.makedirs without EEXIST."""
85 try:
86 os.makedirs(path)
87 except OSError as e:
88 if e.errno != errno.EEXIST:
89 raise
90
91
92def get_prog():
93 try:
94 prog = os.path.basename(sys.argv[0])
95 if prog in ('__main__.py', '-c'):
96 return "%s -m pip" % sys.executable
97 else:
98 return prog
99 except (AttributeError, TypeError, IndexError):
100 pass
101 return 'pip'
102
103
104# Retry every half second for up to 3 seconds
105@retry(stop_max_delay=3000, wait_fixed=500)
106def rmtree(dir, ignore_errors=False):
107 shutil.rmtree(dir, ignore_errors=ignore_errors,
108 onerror=rmtree_errorhandler)
109
110
111def rmtree_errorhandler(func, path, exc_info):
112 """On Windows, the files in .svn are read-only, so when rmtree() tries to
113 remove them, an exception is thrown. We catch that here, remove the
114 read-only attribute, and hopefully continue without problems."""
115 # if file type currently read only
116 if os.stat(path).st_mode & stat.S_IREAD:
117 # convert to read/write
118 os.chmod(path, stat.S_IWRITE)
119 # use the original function to repeat the operation
120 func(path)
121 return
122 else:
123 raise
124
125
126def display_path(path):
127 """Gives the display value for a given path, making it relative to cwd
128 if possible."""
129 path = os.path.normcase(os.path.abspath(path))
130 if sys.version_info[0] == 2:
131 path = path.decode(sys.getfilesystemencoding(), 'replace')
132 path = path.encode(sys.getdefaultencoding(), 'replace')
133 if path.startswith(os.getcwd() + os.path.sep):
134 path = '.' + path[len(os.getcwd()):]
135 return path
136
137
138def backup_dir(dir, ext='.bak'):
139 """Figure out the name of a directory to back up the given dir to
140 (adding .bak, .bak2, etc)"""
141 n = 1
142 extension = ext
143 while os.path.exists(dir + extension):
144 n += 1
145 extension = ext + str(n)
146 return dir + extension
147
148
149def ask_path_exists(message, options):
150 for action in os.environ.get('PIP_EXISTS_ACTION', '').split():
151 if action in options:
152 return action
153 return ask(message, options)
154
155
156def ask(message, options):
157 """Ask the message interactively, with the given possible responses"""
158 while 1:
159 if os.environ.get('PIP_NO_INPUT'):
160 raise Exception(
161 'No input was expected ($PIP_NO_INPUT set); question: %s' %
162 message
163 )
164 response = input(message)
165 response = response.strip().lower()
166 if response not in options:
167 print(
168 'Your response (%r) was not one of the expected responses: '
169 '%s' % (response, ', '.join(options))
170 )
171 else:
172 return response
173
174
175def format_size(bytes):
176 if bytes > 1000 * 1000:
177 return '%.1fMB' % (bytes / 1000.0 / 1000)
178 elif bytes > 10 * 1000:
179 return '%ikB' % (bytes / 1000)
180 elif bytes > 1000:
181 return '%.1fkB' % (bytes / 1000.0)
182 else:
183 return '%ibytes' % bytes
184
185
186def is_installable_dir(path):
187 """Return True if `path` is a directory containing a setup.py file."""
188 if not os.path.isdir(path):
189 return False
190 setup_py = os.path.join(path, 'setup.py')
191 if os.path.isfile(setup_py):
192 return True
193 return False
194
195
196def is_svn_page(html):
197 """
198 Returns true if the page appears to be the index page of an svn repository
199 """
200 return (re.search(r'<title>[^<]*Revision \d+:', html) and
201 re.search(r'Powered by (?:<a[^>]*?>)?Subversion', html, re.I))
202
203
204def file_contents(filename):
205 with open(filename, 'rb') as fp:
206 return fp.read().decode('utf-8')
207
208
209def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE):
210 """Yield pieces of data from a file-like object until EOF."""
211 while True:
212 chunk = file.read(size)
213 if not chunk:
214 break
215 yield chunk
216
217
218def split_leading_dir(path):
219 path = path.lstrip('/').lstrip('\\')
220 if '/' in path and (('\\' in path and path.find('/') < path.find('\\')) or
221 '\\' not in path):
222 return path.split('/', 1)
223 elif '\\' in path:
224 return path.split('\\', 1)
225 else:
226 return path, ''
227
228
229def has_leading_dir(paths):
230 """Returns true if all the paths have the same leading path name
231 (i.e., everything is in one subdirectory in an archive)"""
232 common_prefix = None
233 for path in paths:
234 prefix, rest = split_leading_dir(path)
235 if not prefix:
236 return False
237 elif common_prefix is None:
238 common_prefix = prefix
239 elif prefix != common_prefix:
240 return False
241 return True
242
243
244def normalize_path(path, resolve_symlinks=True):
245 """
246 Convert a path to its canonical, case-normalized, absolute version.
247
248 """
249 path = expanduser(path)
250 if resolve_symlinks:
251 path = os.path.realpath(path)
252 else:
253 path = os.path.abspath(path)
254 return os.path.normcase(path)
255
256
257def splitext(path):
258 """Like os.path.splitext, but take off .tar too"""
259 base, ext = posixpath.splitext(path)
260 if base.lower().endswith('.tar'):
261 ext = base[-4:] + ext
262 base = base[:-4]
263 return base, ext
264
265
266def renames(old, new):
267 """Like os.renames(), but handles renaming across devices."""
268 # Implementation borrowed from os.renames().
269 head, tail = os.path.split(new)
270 if head and tail and not os.path.exists(head):
271 os.makedirs(head)
272
273 shutil.move(old, new)
274
275 head, tail = os.path.split(old)
276 if head and tail:
277 try:
278 os.removedirs(head)
279 except OSError:
280 pass
281
282
283def is_local(path):
284 """
285 Return True if path is within sys.prefix, if we're running in a virtualenv.
286
287 If we're not in a virtualenv, all paths are considered "local."
288
289 """
290 if not running_under_virtualenv():
291 return True
292 return normalize_path(path).startswith(normalize_path(sys.prefix))
293
294
295def dist_is_local(dist):
296 """
297 Return True if given Distribution object is installed locally
298 (i.e. within current virtualenv).
299
300 Always True if we're not in a virtualenv.
301
302 """
303 return is_local(dist_location(dist))
304
305
306def dist_in_usersite(dist):
307 """
308 Return True if given Distribution is installed in user site.
309 """
310 norm_path = normalize_path(dist_location(dist))
311 return norm_path.startswith(normalize_path(user_site))
312
313
314def dist_in_site_packages(dist):
315 """
316 Return True if given Distribution is installed in
317 sysconfig.get_python_lib().
318 """
319 return normalize_path(
320 dist_location(dist)
321 ).startswith(normalize_path(site_packages))
322
323
324def dist_is_editable(dist):
325 """Is distribution an editable install?"""
326 for path_item in sys.path:
327 egg_link = os.path.join(path_item, dist.project_name + '.egg-link')
328 if os.path.isfile(egg_link):
329 return True
330 return False
331
332
333def get_installed_distributions(local_only=True,
334 skip=stdlib_pkgs,
335 include_editables=True,
336 editables_only=False,
337 user_only=False):
338 """
339 Return a list of installed Distribution objects.
340
341 If ``local_only`` is True (default), only return installations
342 local to the current virtualenv, if in a virtualenv.
343
344 ``skip`` argument is an iterable of lower-case project names to
345 ignore; defaults to stdlib_pkgs
346
347 If ``include_editables`` is False, don't report editables.
348
349 If ``editables_only`` is True , only report editables.
350
351 If ``user_only`` is True , only report installations in the user
352 site directory.
353
354 """
355 if local_only:
356 local_test = dist_is_local
357 else:
358 def local_test(d):
359 return True
360
361 if include_editables:
362 def editable_test(d):
363 return True
364 else:
365 def editable_test(d):
366 return not dist_is_editable(d)
367
368 if editables_only:
369 def editables_only_test(d):
370 return dist_is_editable(d)
371 else:
372 def editables_only_test(d):
373 return True
374
375 if user_only:
376 user_test = dist_in_usersite
377 else:
378 def user_test(d):
379 return True
380
381 return [d for d in pkg_resources.working_set
382 if local_test(d) and
383 d.key not in skip and
384 editable_test(d) and
385 editables_only_test(d) and
386 user_test(d)
387 ]
388
389
390def egg_link_path(dist):
391 """
392 Return the path for the .egg-link file if it exists, otherwise, None.
393
394 There's 3 scenarios:
395 1) not in a virtualenv
396 try to find in site.USER_SITE, then site_packages
397 2) in a no-global virtualenv
398 try to find in site_packages
399 3) in a yes-global virtualenv
400 try to find in site_packages, then site.USER_SITE
401 (don't look in global location)
402
403 For #1 and #3, there could be odd cases, where there's an egg-link in 2
404 locations.
405
406 This method will just return the first one found.
407 """
408 sites = []
409 if running_under_virtualenv():
410 if virtualenv_no_global():
411 sites.append(site_packages)
412 else:
413 sites.append(site_packages)
414 if user_site:
415 sites.append(user_site)
416 else:
417 if user_site:
418 sites.append(user_site)
419 sites.append(site_packages)
420
421 for site in sites:
422 egglink = os.path.join(site, dist.project_name) + '.egg-link'
423 if os.path.isfile(egglink):
424 return egglink
425
426
427def dist_location(dist):
428 """
429 Get the site-packages location of this distribution. Generally
430 this is dist.location, except in the case of develop-installed
431 packages, where dist.location is the source code location, and we
432 want to know where the egg-link file is.
433
434 """
435 egg_link = egg_link_path(dist)
436 if egg_link:
437 return egg_link
438 return dist.location
439
440
441def current_umask():
442 """Get the current umask which involves having to set it temporarily."""
443 mask = os.umask(0)
444 os.umask(mask)
445 return mask
446
447
448def unzip_file(filename, location, flatten=True):
449 """
450 Unzip the file (with path `filename`) to the destination `location`. All
451 files are written based on system defaults and umask (i.e. permissions are
452 not preserved), except that regular file members with any execute
453 permissions (user, group, or world) have "chmod +x" applied after being
454 written. Note that for windows, any execute changes using os.chmod are
455 no-ops per the python docs.
456 """
457 ensure_dir(location)
458 zipfp = open(filename, 'rb')
459 try:
460 zip = zipfile.ZipFile(zipfp, allowZip64=True)
461 leading = has_leading_dir(zip.namelist()) and flatten
462 for info in zip.infolist():
463 name = info.filename
464 data = zip.read(name)
465 fn = name
466 if leading:
467 fn = split_leading_dir(name)[1]
468 fn = os.path.join(location, fn)
469 dir = os.path.dirname(fn)
470 if fn.endswith('/') or fn.endswith('\\'):
471 # A directory
472 ensure_dir(fn)
473 else:
474 ensure_dir(dir)
475 fp = open(fn, 'wb')
476 try:
477 fp.write(data)
478 finally:
479 fp.close()
480 mode = info.external_attr >> 16
481 # if mode and regular file and any execute permissions for
482 # user/group/world?
483 if mode and stat.S_ISREG(mode) and mode & 0o111:
484 # make dest file have execute for user/group/world
485 # (chmod +x) no-op on windows per python docs
486 os.chmod(fn, (0o777 - current_umask() | 0o111))
487 finally:
488 zipfp.close()
489
490
491def untar_file(filename, location):
492 """
493 Untar the file (with path `filename`) to the destination `location`.
494 All files are written based on system defaults and umask (i.e. permissions
495 are not preserved), except that regular file members with any execute
496 permissions (user, group, or world) have "chmod +x" applied after being
497 written. Note that for windows, any execute changes using os.chmod are
498 no-ops per the python docs.
499 """
500 ensure_dir(location)
501 if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'):
502 mode = 'r:gz'
503 elif filename.lower().endswith(BZ2_EXTENSIONS):
504 mode = 'r:bz2'
505 elif filename.lower().endswith(XZ_EXTENSIONS):
506 mode = 'r:xz'
507 elif filename.lower().endswith('.tar'):
508 mode = 'r'
509 else:
510 logger.warning(
511 'Cannot determine compression type for file %s', filename,
512 )
513 mode = 'r:*'
514 tar = tarfile.open(filename, mode)
515 try:
516 # note: python<=2.5 doesn't seem to know about pax headers, filter them
517 leading = has_leading_dir([
518 member.name for member in tar.getmembers()
519 if member.name != 'pax_global_header'
520 ])
521 for member in tar.getmembers():
522 fn = member.name
523 if fn == 'pax_global_header':
524 continue
525 if leading:
526 fn = split_leading_dir(fn)[1]
527 path = os.path.join(location, fn)
528 if member.isdir():
529 ensure_dir(path)
530 elif member.issym():
531 try:
532 tar._extract_member(member, path)
533 except Exception as exc:
534 # Some corrupt tar files seem to produce this
535 # (specifically bad symlinks)
536 logger.warning(
537 'In the tar file %s the member %s is invalid: %s',
538 filename, member.name, exc,
539 )
540 continue
541 else:
542 try:
543 fp = tar.extractfile(member)
544 except (KeyError, AttributeError) as exc:
545 # Some corrupt tar files seem to produce this
546 # (specifically bad symlinks)
547 logger.warning(
548 'In the tar file %s the member %s is invalid: %s',
549 filename, member.name, exc,
550 )
551 continue
552 ensure_dir(os.path.dirname(path))
553 with open(path, 'wb') as destfp:
554 shutil.copyfileobj(fp, destfp)
555 fp.close()
556 # Update the timestamp (useful for cython compiled files)
557 tar.utime(member, path)
558 # member have any execute permissions for user/group/world?
559 if member.mode & 0o111:
560 # make dest file have execute for user/group/world
561 # no-op on windows per python docs
562 os.chmod(path, (0o777 - current_umask() | 0o111))
563 finally:
564 tar.close()
565
566
567def unpack_file(filename, location, content_type, link):
568 filename = os.path.realpath(filename)
569 if (content_type == 'application/zip' or
570 filename.lower().endswith(ZIP_EXTENSIONS) or
571 zipfile.is_zipfile(filename)):
572 unzip_file(
573 filename,
574 location,
575 flatten=not filename.endswith('.whl')
576 )
577 elif (content_type == 'application/x-gzip' or
578 tarfile.is_tarfile(filename) or
579 filename.lower().endswith(
580 TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS)):
581 untar_file(filename, location)
582 elif (content_type and content_type.startswith('text/html') and
583 is_svn_page(file_contents(filename))):
584 # We don't really care about this
585 from pip._internal.vcs.subversion import Subversion
586 Subversion('svn+' + link.url).unpack(location)
587 else:
588 # FIXME: handle?
589 # FIXME: magic signatures?
590 logger.critical(
591 'Cannot unpack file %s (downloaded from %s, content-type: %s); '
592 'cannot detect archive format',
593 filename, location, content_type,
594 )
595 raise InstallationError(
596 'Cannot determine archive format of %s' % location
597 )
598
599
600def call_subprocess(cmd, show_stdout=True, cwd=None,
601 on_returncode='raise',
602 command_desc=None,
603 extra_environ=None, unset_environ=None, spinner=None):
604 """
605 Args:
606 unset_environ: an iterable of environment variable names to unset
607 prior to calling subprocess.Popen().
608 """
609 if unset_environ is None:
610 unset_environ = []
611 # This function's handling of subprocess output is confusing and I
612 # previously broke it terribly, so as penance I will write a long comment
613 # explaining things.
614 #
615 # The obvious thing that affects output is the show_stdout=
616 # kwarg. show_stdout=True means, let the subprocess write directly to our
617 # stdout. Even though it is nominally the default, it is almost never used
618 # inside pip (and should not be used in new code without a very good
619 # reason); as of 2016-02-22 it is only used in a few places inside the VCS
620 # wrapper code. Ideally we should get rid of it entirely, because it
621 # creates a lot of complexity here for a rarely used feature.
622 #
623 # Most places in pip set show_stdout=False. What this means is:
624 # - We connect the child stdout to a pipe, which we read.
625 # - By default, we hide the output but show a spinner -- unless the
626 # subprocess exits with an error, in which case we show the output.
627 # - If the --verbose option was passed (= loglevel is DEBUG), then we show
628 # the output unconditionally. (But in this case we don't want to show
629 # the output a second time if it turns out that there was an error.)
630 #
631 # stderr is always merged with stdout (even if show_stdout=True).
632 if show_stdout:
633 stdout = None
634 else:
635 stdout = subprocess.PIPE
636 if command_desc is None:
637 cmd_parts = []
638 for part in cmd:
639 if ' ' in part or '\n' in part or '"' in part or "'" in part:
640 part = '"%s"' % part.replace('"', '\\"')
641 cmd_parts.append(part)
642 command_desc = ' '.join(cmd_parts)
643 logger.debug("Running command %s", command_desc)
644 env = os.environ.copy()
645 if extra_environ:
646 env.update(extra_environ)
647 for name in unset_environ:
648 env.pop(name, None)
649 try:
650 proc = subprocess.Popen(
651 cmd, stderr=subprocess.STDOUT, stdin=subprocess.PIPE,
652 stdout=stdout, cwd=cwd, env=env,
653 )
654 proc.stdin.close()
655 except Exception as exc:
656 logger.critical(
657 "Error %s while executing command %s", exc, command_desc,
658 )
659 raise
660 all_output = []
661 if stdout is not None:
662 while True:
663 line = console_to_str(proc.stdout.readline())
664 if not line:
665 break
666 line = line.rstrip()
667 all_output.append(line + '\n')
668 if logger.getEffectiveLevel() <= std_logging.DEBUG:
669 # Show the line immediately
670 logger.debug(line)
671 else:
672 # Update the spinner
673 if spinner is not None:
674 spinner.spin()
675 try:
676 proc.wait()
677 finally:
678 if proc.stdout:
679 proc.stdout.close()
680 if spinner is not None:
681 if proc.returncode:
682 spinner.finish("error")
683 else:
684 spinner.finish("done")
685 if proc.returncode:
686 if on_returncode == 'raise':
687 if (logger.getEffectiveLevel() > std_logging.DEBUG and
688 not show_stdout):
689 logger.info(
690 'Complete output from command %s:', command_desc,
691 )
692 logger.info(
693 ''.join(all_output) +
694 '\n----------------------------------------'
695 )
696 raise InstallationError(
697 'Command "%s" failed with error code %s in %s'
698 % (command_desc, proc.returncode, cwd))
699 elif on_returncode == 'warn':
700 logger.warning(
701 'Command "%s" had error code %s in %s',
702 command_desc, proc.returncode, cwd,
703 )
704 elif on_returncode == 'ignore':
705 pass
706 else:
707 raise ValueError('Invalid value: on_returncode=%s' %
708 repr(on_returncode))
709 if not show_stdout:
710 return ''.join(all_output)
711
712
713def read_text_file(filename):
714 """Return the contents of *filename*.
715
716 Try to decode the file contents with utf-8, the preferred system encoding
717 (e.g., cp1252 on some Windows machines), and latin1, in that order.
718 Decoding a byte string with latin1 will never raise an error. In the worst
719 case, the returned string will contain some garbage characters.
720
721 """
722 with open(filename, 'rb') as fp:
723 data = fp.read()
724
725 encodings = ['utf-8', locale.getpreferredencoding(False), 'latin1']
726 for enc in encodings:
727 try:
728 data = data.decode(enc)
729 except UnicodeDecodeError:
730 continue
731 break
732
733 assert type(data) != bytes # Latin1 should have worked.
734 return data
735
736
737def _make_build_dir(build_dir):
738 os.makedirs(build_dir)
739 write_delete_marker_file(build_dir)
740
741
742class FakeFile(object):
743 """Wrap a list of lines in an object with readline() to make
744 ConfigParser happy."""
745 def __init__(self, lines):
746 self._gen = (l for l in lines)
747
748 def readline(self):
749 try:
750 try:
751 return next(self._gen)
752 except NameError:
753 return self._gen.next()
754 except StopIteration:
755 return ''
756
757 def __iter__(self):
758 return self._gen
759
760
761class StreamWrapper(StringIO):
762
763 @classmethod
764 def from_stream(cls, orig_stream):
765 cls.orig_stream = orig_stream
766 return cls()
767
768 # compileall.compile_dir() needs stdout.encoding to print to stdout
769 @property
770 def encoding(self):
771 return self.orig_stream.encoding
772
773
774@contextlib.contextmanager
775def captured_output(stream_name):
776 """Return a context manager used by captured_stdout/stdin/stderr
777 that temporarily replaces the sys stream *stream_name* with a StringIO.
778
779 Taken from Lib/support/__init__.py in the CPython repo.
780 """
781 orig_stdout = getattr(sys, stream_name)
782 setattr(sys, stream_name, StreamWrapper.from_stream(orig_stdout))
783 try:
784 yield getattr(sys, stream_name)
785 finally:
786 setattr(sys, stream_name, orig_stdout)
787
788
789def captured_stdout():
790 """Capture the output of sys.stdout:
791
792 with captured_stdout() as stdout:
793 print('hello')
794 self.assertEqual(stdout.getvalue(), 'hello\n')
795
796 Taken from Lib/support/__init__.py in the CPython repo.
797 """
798 return captured_output('stdout')
799
800
801class cached_property(object):
802 """A property that is only computed once per instance and then replaces
803 itself with an ordinary attribute. Deleting the attribute resets the
804 property.
805
806 Source: https://github.com/bottlepy/bottle/blob/0.11.5/bottle.py#L175
807 """
808
809 def __init__(self, func):
810 self.__doc__ = getattr(func, '__doc__')
811 self.func = func
812
813 def __get__(self, obj, cls):
814 if obj is None:
815 # We're being accessed from the class itself, not from an object
816 return self
817 value = obj.__dict__[self.func.__name__] = self.func(obj)
818 return value
819
820
821def get_installed_version(dist_name, lookup_dirs=None):
822 """Get the installed version of dist_name avoiding pkg_resources cache"""
823 # Create a requirement that we'll look for inside of setuptools.
824 req = pkg_resources.Requirement.parse(dist_name)
825
826 # We want to avoid having this cached, so we need to construct a new
827 # working set each time.
828 if lookup_dirs is None:
829 working_set = pkg_resources.WorkingSet()
830 else:
831 working_set = pkg_resources.WorkingSet(lookup_dirs)
832
833 # Get the installed distribution from our working set
834 dist = working_set.find(req)
835
836 # Check to see if we got an installed distribution or not, if we did
837 # we want to return it's version.
838 return dist.version if dist else None
839
840
841def consume(iterator):
842 """Consume an iterable at C speed."""
843 deque(iterator, maxlen=0)
844
845
846# Simulates an enum
847def enum(*sequential, **named):
848 enums = dict(zip(sequential, range(len(sequential))), **named)
849 reverse = {value: key for key, value in enums.items()}
850 enums['reverse_mapping'] = reverse
851 return type('Enum', (), enums)