Skip to content

Commit 8b65cc7

Browse files
authored
Merge pull request #962 from kennethreitz/pexpect-fix
update the vendored pexpect for python 3.7 async support to fix #956
2 parents d89696e + e673604 commit 8b65cc7

File tree

7 files changed

+57
-32
lines changed

7 files changed

+57
-32
lines changed

pipenv/vendor/pexpect/async.py pipenv/vendor/pexpect/_async.py

+14-6
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,30 @@ def expect_async(expecter, timeout=None):
1212
idx = expecter.new_data(previously_read)
1313
if idx is not None:
1414
return idx
15-
16-
transport, pw = yield from asyncio.get_event_loop()\
17-
.connect_read_pipe(lambda: PatternWaiter(expecter), expecter.spawn)
18-
15+
if not expecter.spawn.async_pw_transport:
16+
pw = PatternWaiter()
17+
pw.set_expecter(expecter)
18+
transport, pw = yield from asyncio.get_event_loop()\
19+
.connect_read_pipe(lambda: pw, expecter.spawn)
20+
expecter.spawn.async_pw_transport = pw, transport
21+
else:
22+
pw, transport = expecter.spawn.async_pw_transport
23+
pw.set_expecter(expecter)
24+
transport.resume_reading()
1925
try:
2026
return (yield from asyncio.wait_for(pw.fut, timeout))
2127
except asyncio.TimeoutError as e:
2228
transport.pause_reading()
2329
return expecter.timeout(e)
2430

31+
2532
class PatternWaiter(asyncio.Protocol):
2633
transport = None
27-
def __init__(self, expecter):
34+
35+
def set_expecter(self, expecter):
2836
self.expecter = expecter
2937
self.fut = asyncio.Future()
30-
38+
3139
def found(self, result):
3240
if not self.fut.done():
3341
self.fut.set_result(result)

pipenv/vendor/pexpect/expect.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ def errored(self):
7979
def expect_loop(self, timeout=-1):
8080
"""Blocking expect"""
8181
spawn = self.spawn
82-
from . import EOF, TIMEOUT
8382

8483
if timeout is not None:
8584
end_time = time.time() + timeout
@@ -161,7 +160,7 @@ def __str__(self):
161160
return '\n'.join(ss)
162161

163162
def search(self, buffer, freshlen, searchwindowsize=None):
164-
'''This searches 'buffer' for the first occurence of one of the search
163+
'''This searches 'buffer' for the first occurrence of one of the search
165164
strings. 'freshlen' must indicate the number of bytes at the end of
166165
'buffer' which have not been searched before. It helps to avoid
167166
searching the same, possibly big, buffer over and over again.
@@ -220,7 +219,7 @@ class searcher_re(object):
220219
221220
start - index into the buffer, first byte of match
222221
end - index into the buffer, first byte after match
223-
match - the re.match object returned by a succesful re.search
222+
match - the re.match object returned by a successful re.search
224223
225224
'''
226225

@@ -267,7 +266,7 @@ def __str__(self):
267266
return '\n'.join(ss)
268267

269268
def search(self, buffer, freshlen, searchwindowsize=None):
270-
'''This searches 'buffer' for the first occurence of one of the regular
269+
'''This searches 'buffer' for the first occurrence of one of the regular
271270
expressions. 'freshlen' must indicate the number of bytes at the end of
272271
'buffer' which have not been searched before.
273272

pipenv/vendor/pexpect/fdpexpect.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'''This is like pexpect, but it will work with any file descriptor that you
2-
pass it. You are reponsible for opening and close the file descriptor.
2+
pass it. You are responsible for opening and close the file descriptor.
33
This allows you to use Pexpect with sockets and named pipes (FIFOs).
44
55
PEXPECT LICENSE

pipenv/vendor/pexpect/pty_spawn.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,9 @@ def __init__(self, command, args=[], timeout=30, maxread=2000,
106106
child = pexpect.spawn('some_command')
107107
child.logfile = sys.stdout
108108
109-
# In Python 3, spawnu should be used to give str to stdout:
110-
child = pexpect.spawnu('some_command')
109+
# In Python 3, we'll use the ``encoding`` argument to decode data
110+
# from the subprocess and handle it as unicode:
111+
child = pexpect.spawn('some_command', encoding='utf-8')
111112
child.logfile = sys.stdout
112113
113114
The logfile_read and logfile_send members can be used to separately log
@@ -315,7 +316,10 @@ def close(self, force=True):
315316
and SIGINT). '''
316317

317318
self.flush()
318-
self.ptyproc.close(force=force)
319+
with _wrap_ptyprocess_err():
320+
# PtyProcessError may be raised if it is not possible to terminate
321+
# the child.
322+
self.ptyproc.close(force=force)
319323
self.isalive() # Update exit status from ptyproc
320324
self.child_fd = -1
321325

pipenv/vendor/pexpect/pxssh.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,12 @@ def __init__ (self, timeout=30, maxread=2000, searchwindowsize=None,
114114
#prompt command different than the regex.
115115

116116
# used to match the command-line prompt
117-
self.UNIQUE_PROMPT = "\[PEXPECT\][\$\#] "
117+
self.UNIQUE_PROMPT = r"\[PEXPECT\][\$\#] "
118118
self.PROMPT = self.UNIQUE_PROMPT
119119

120120
# used to set shell command-line prompt to UNIQUE_PROMPT.
121-
self.PROMPT_SET_SH = "PS1='[PEXPECT]\$ '"
122-
self.PROMPT_SET_CSH = "set prompt='[PEXPECT]\$ '"
121+
self.PROMPT_SET_SH = r"PS1='[PEXPECT]\$ '"
122+
self.PROMPT_SET_CSH = r"set prompt='[PEXPECT]\$ '"
123123
self.SSH_OPTS = ("-o'RSAAuthentication=no'"
124124
+ " -o 'PubkeyAuthentication=no'")
125125
# Disabling host key checking, makes you vulnerable to MITM attacks.

pipenv/vendor/pexpect/screen.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def constrain (n, min, max):
6969

7070
class screen:
7171
'''This object maintains the state of a virtual text screen as a
72-
rectangluar array. This maintains a virtual cursor position and handles
72+
rectangular array. This maintains a virtual cursor position and handles
7373
scrolling as characters are added. This supports most of the methods needed
7474
by an ANSI text screen. Row and column indexes are 1-based (not zero-based,
7575
like arrays).

pipenv/vendor/pexpect/spawnbase.py

+28-14
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ def write_to_stdout(b):
115115
self.linesep = os.linesep.decode('ascii')
116116
# This can handle unicode in both Python 2 and 3
117117
self.write_to_stdout = sys.stdout.write
118+
# storage for async transport
119+
self.async_pw_transport = None
118120

119121
def _log(self, s, direction):
120122
if self.logfile is not None:
@@ -221,7 +223,7 @@ def compile_pattern_list(self, patterns):
221223
self._pattern_type_err(p)
222224
return compiled_pattern_list
223225

224-
def expect(self, pattern, timeout=-1, searchwindowsize=-1, async=False):
226+
def expect(self, pattern, timeout=-1, searchwindowsize=-1, async_=False, **kw):
225227
'''This seeks through the stream until a pattern is matched. The
226228
pattern is overloaded and may take several types. The pattern can be a
227229
StringType, EOF, a compiled re, or a list of any of those types.
@@ -305,23 +307,27 @@ def expect(self, pattern, timeout=-1, searchwindowsize=-1, async=False):
305307
If you are trying to optimize for speed then see expect_list().
306308
307309
On Python 3.4, or Python 3.3 with asyncio installed, passing
308-
``async=True`` will make this return an :mod:`asyncio` coroutine,
310+
``async_=True`` will make this return an :mod:`asyncio` coroutine,
309311
which you can yield from to get the same result that this method would
310312
normally give directly. So, inside a coroutine, you can replace this code::
311313
312314
index = p.expect(patterns)
313315
314316
With this non-blocking form::
315317
316-
index = yield from p.expect(patterns, async=True)
318+
index = yield from p.expect(patterns, async_=True)
317319
'''
320+
if 'async' in kw:
321+
async_ = kw.pop('async')
322+
if kw:
323+
raise TypeError("Unknown keyword arguments: {}".format(kw))
318324

319325
compiled_pattern_list = self.compile_pattern_list(pattern)
320326
return self.expect_list(compiled_pattern_list,
321-
timeout, searchwindowsize, async)
327+
timeout, searchwindowsize, async_)
322328

323329
def expect_list(self, pattern_list, timeout=-1, searchwindowsize=-1,
324-
async=False):
330+
async_=False, **kw):
325331
'''This takes a list of compiled regular expressions and returns the
326332
index into the pattern_list that matched the child output. The list may
327333
also contain EOF or TIMEOUT(which are not compiled regular
@@ -331,21 +337,25 @@ def expect_list(self, pattern_list, timeout=-1, searchwindowsize=-1,
331337
the expect() method. This is called by expect().
332338
333339
334-
Like :meth:`expect`, passing ``async=True`` will make this return an
340+
Like :meth:`expect`, passing ``async_=True`` will make this return an
335341
asyncio coroutine.
336342
'''
337343
if timeout == -1:
338344
timeout = self.timeout
345+
if 'async' in kw:
346+
async_ = kw.pop('async')
347+
if kw:
348+
raise TypeError("Unknown keyword arguments: {}".format(kw))
339349

340350
exp = Expecter(self, searcher_re(pattern_list), searchwindowsize)
341-
if async:
342-
from .async import expect_async
351+
if async_:
352+
from ._async import expect_async
343353
return expect_async(exp, timeout)
344354
else:
345355
return exp.expect_loop(timeout)
346356

347357
def expect_exact(self, pattern_list, timeout=-1, searchwindowsize=-1,
348-
async=False):
358+
async_=False, **kw):
349359

350360
'''This is similar to expect(), but uses plain string matching instead
351361
of compiled regular expressions in 'pattern_list'. The 'pattern_list'
@@ -359,11 +369,15 @@ def expect_exact(self, pattern_list, timeout=-1, searchwindowsize=-1,
359369
This method is also useful when you don't want to have to worry about
360370
escaping regular expression characters that you want to match.
361371
362-
Like :meth:`expect`, passing ``async=True`` will make this return an
372+
Like :meth:`expect`, passing ``async_=True`` will make this return an
363373
asyncio coroutine.
364374
'''
365375
if timeout == -1:
366376
timeout = self.timeout
377+
if 'async' in kw:
378+
async_ = kw.pop('async')
379+
if kw:
380+
raise TypeError("Unknown keyword arguments: {}".format(kw))
367381

368382
if (isinstance(pattern_list, self.allowed_string_types) or
369383
pattern_list in (TIMEOUT, EOF)):
@@ -383,8 +397,8 @@ def prepare_pattern(pattern):
383397
pattern_list = [prepare_pattern(p) for p in pattern_list]
384398

385399
exp = Expecter(self, searcher_string(pattern_list), searchwindowsize)
386-
if async:
387-
from .async import expect_async
400+
if async_:
401+
from ._async import expect_async
388402
return expect_async(exp, timeout)
389403
else:
390404
return exp.expect_loop(timeout)
@@ -415,7 +429,7 @@ def read(self, size=-1):
415429

416430
# I could have done this more directly by not using expect(), but
417431
# I deliberately decided to couple read() to expect() so that
418-
# I would catch any bugs early and ensure consistant behavior.
432+
# I would catch any bugs early and ensure consistent behavior.
419433
# It's a little less efficient, but there is less for me to
420434
# worry about if I have to later modify read() or expect().
421435
# Note, it's OK if size==-1 in the regex. That just means it
@@ -487,7 +501,7 @@ def isatty(self):
487501
# For 'with spawn(...) as child:'
488502
def __enter__(self):
489503
return self
490-
504+
491505
def __exit__(self, etype, evalue, tb):
492506
# We rely on subclasses to implement close(). If they don't, it's not
493507
# clear what a context manager should do.

0 commit comments

Comments
 (0)