stdlib_patches.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. """
  2. Patches, additions, and shortcuts for Python standard library functions.
  3. """
  4. ### subprocess
  5. from subprocess import (
  6. Popen,
  7. PIPE,
  8. DEVNULL,
  9. CompletedProcess,
  10. TimeoutExpired,
  11. CalledProcessError,
  12. )
  13. def run(*popenargs, input=None, capture_output=False, timeout=None, check=False, **kwargs):
  14. """Patched of subprocess.run to fix blocking io making timeout=innefective"""
  15. if input is not None:
  16. if 'stdin' in kwargs:
  17. raise ValueError('stdin and input arguments may not both be used.')
  18. kwargs['stdin'] = PIPE
  19. if capture_output:
  20. if ('stdout' in kwargs) or ('stderr' in kwargs):
  21. raise ValueError('stdout and stderr arguments may not be used '
  22. 'with capture_output.')
  23. kwargs['stdout'] = PIPE
  24. kwargs['stderr'] = PIPE
  25. with Popen(*popenargs, **kwargs) as process:
  26. try:
  27. stdout, stderr = process.communicate(input, timeout=timeout)
  28. except TimeoutExpired:
  29. process.kill()
  30. try:
  31. stdout, stderr = process.communicate(input, timeout=2)
  32. except:
  33. pass
  34. raise TimeoutExpired(popenargs[0][0], timeout)
  35. except BaseException as err:
  36. process.kill()
  37. # We don't call process.wait() as .__exit__ does that for us.
  38. raise
  39. retcode = process.poll()
  40. if check and retcode:
  41. raise CalledProcessError(retcode, process.args,
  42. output=stdout, stderr=stderr)
  43. return CompletedProcess(process.args, retcode, stdout, stderr)
  44. ### collections
  45. from sys import maxsize
  46. from itertools import islice
  47. from collections import deque
  48. _marker = object()
  49. class PeekableGenerator:
  50. """Peekable version of a normal python generator.
  51. Useful when you don't want to evaluate the entire iterable to look at
  52. a specific item at a given idx.
  53. """
  54. def __init__(self, iterable):
  55. self._it = iter(iterable)
  56. self._cache = deque()
  57. def __iter__(self):
  58. return self
  59. def __bool__(self):
  60. try:
  61. self.peek()
  62. except StopIteration:
  63. return False
  64. return True
  65. def __nonzero__(self):
  66. # For Python 2 compatibility
  67. return self.__bool__()
  68. def peek(self, default=_marker):
  69. """Return the item that will be next returned from ``next()``.
  70. Return ``default`` if there are no items left. If ``default`` is not
  71. provided, raise ``StopIteration``.
  72. """
  73. if not self._cache:
  74. try:
  75. self._cache.append(next(self._it))
  76. except StopIteration:
  77. if default is _marker:
  78. raise
  79. return default
  80. return self._cache[0]
  81. def prepend(self, *items):
  82. """Stack up items to be the next ones returned from ``next()`` or
  83. ``self.peek()``. The items will be returned in
  84. first in, first out order::
  85. >>> p = peekable([1, 2, 3])
  86. >>> p.prepend(10, 11, 12)
  87. >>> next(p)
  88. 10
  89. >>> list(p)
  90. [11, 12, 1, 2, 3]
  91. It is possible, by prepending items, to "resurrect" a peekable that
  92. previously raised ``StopIteration``.
  93. >>> p = peekable([])
  94. >>> next(p)
  95. Traceback (most recent call last):
  96. ...
  97. StopIteration
  98. >>> p.prepend(1)
  99. >>> next(p)
  100. 1
  101. >>> next(p)
  102. Traceback (most recent call last):
  103. ...
  104. StopIteration
  105. """
  106. self._cache.extendleft(reversed(items))
  107. def __next__(self):
  108. if self._cache:
  109. return self._cache.popleft()
  110. return next(self._it)
  111. def _get_slice(self, index):
  112. # Normalize the slice's arguments
  113. step = 1 if (index.step is None) else index.step
  114. if step > 0:
  115. start = 0 if (index.start is None) else index.start
  116. stop = maxsize if (index.stop is None) else index.stop
  117. elif step < 0:
  118. start = -1 if (index.start is None) else index.start
  119. stop = (-maxsize - 1) if (index.stop is None) else index.stop
  120. else:
  121. raise ValueError('slice step cannot be zero')
  122. # If either the start or stop index is negative, we'll need to cache
  123. # the rest of the iterable in order to slice from the right side.
  124. if (start < 0) or (stop < 0):
  125. self._cache.extend(self._it)
  126. # Otherwise we'll need to find the rightmost index and cache to that
  127. # point.
  128. else:
  129. n = min(max(start, stop) + 1, maxsize)
  130. cache_len = len(self._cache)
  131. if n >= cache_len:
  132. self._cache.extend(islice(self._it, n - cache_len))
  133. return list(self._cache)[index]
  134. def __getitem__(self, index):
  135. if isinstance(index, slice):
  136. return self._get_slice(index)
  137. cache_len = len(self._cache)
  138. if index < 0:
  139. self._cache.extend(self._it)
  140. elif index >= cache_len:
  141. self._cache.extend(islice(self._it, index + 1 - cache_len))
  142. return self._cache[index]