Merge pull request #962 from kennethreitz/pexpect-fix

update the vendored pexpect for python 3.7 async support to fix #956
This commit is contained in:
Erin O'Connell
2017-10-24 00:54:06 -06:00
committed by GitHub
7 changed files with 57 additions and 32 deletions
@@ -12,22 +12,30 @@ def expect_async(expecter, timeout=None):
idx = expecter.new_data(previously_read)
if idx is not None:
return idx
transport, pw = yield from asyncio.get_event_loop()\
.connect_read_pipe(lambda: PatternWaiter(expecter), expecter.spawn)
if not expecter.spawn.async_pw_transport:
pw = PatternWaiter()
pw.set_expecter(expecter)
transport, pw = yield from asyncio.get_event_loop()\
.connect_read_pipe(lambda: pw, expecter.spawn)
expecter.spawn.async_pw_transport = pw, transport
else:
pw, transport = expecter.spawn.async_pw_transport
pw.set_expecter(expecter)
transport.resume_reading()
try:
return (yield from asyncio.wait_for(pw.fut, timeout))
except asyncio.TimeoutError as e:
transport.pause_reading()
return expecter.timeout(e)
class PatternWaiter(asyncio.Protocol):
transport = None
def __init__(self, expecter):
def set_expecter(self, expecter):
self.expecter = expecter
self.fut = asyncio.Future()
def found(self, result):
if not self.fut.done():
self.fut.set_result(result)
+3 -4
View File
@@ -79,7 +79,6 @@ class Expecter(object):
def expect_loop(self, timeout=-1):
"""Blocking expect"""
spawn = self.spawn
from . import EOF, TIMEOUT
if timeout is not None:
end_time = time.time() + timeout
@@ -161,7 +160,7 @@ class searcher_string(object):
return '\n'.join(ss)
def search(self, buffer, freshlen, searchwindowsize=None):
'''This searches 'buffer' for the first occurence of one of the search
'''This searches 'buffer' for the first occurrence of one of the search
strings. 'freshlen' must indicate the number of bytes at the end of
'buffer' which have not been searched before. It helps to avoid
searching the same, possibly big, buffer over and over again.
@@ -220,7 +219,7 @@ class searcher_re(object):
start - index into the buffer, first byte of match
end - index into the buffer, first byte after match
match - the re.match object returned by a succesful re.search
match - the re.match object returned by a successful re.search
'''
@@ -267,7 +266,7 @@ class searcher_re(object):
return '\n'.join(ss)
def search(self, buffer, freshlen, searchwindowsize=None):
'''This searches 'buffer' for the first occurence of one of the regular
'''This searches 'buffer' for the first occurrence of one of the regular
expressions. 'freshlen' must indicate the number of bytes at the end of
'buffer' which have not been searched before.
+1 -1
View File
@@ -1,5 +1,5 @@
'''This is like pexpect, but it will work with any file descriptor that you
pass it. You are reponsible for opening and close the file descriptor.
pass it. You are responsible for opening and close the file descriptor.
This allows you to use Pexpect with sockets and named pipes (FIFOs).
PEXPECT LICENSE
+7 -3
View File
@@ -106,8 +106,9 @@ class spawn(SpawnBase):
child = pexpect.spawn('some_command')
child.logfile = sys.stdout
# In Python 3, spawnu should be used to give str to stdout:
child = pexpect.spawnu('some_command')
# In Python 3, we'll use the ``encoding`` argument to decode data
# from the subprocess and handle it as unicode:
child = pexpect.spawn('some_command', encoding='utf-8')
child.logfile = sys.stdout
The logfile_read and logfile_send members can be used to separately log
@@ -315,7 +316,10 @@ class spawn(SpawnBase):
and SIGINT). '''
self.flush()
self.ptyproc.close(force=force)
with _wrap_ptyprocess_err():
# PtyProcessError may be raised if it is not possible to terminate
# the child.
self.ptyproc.close(force=force)
self.isalive() # Update exit status from ptyproc
self.child_fd = -1
+3 -3
View File
@@ -114,12 +114,12 @@ class pxssh (spawn):
#prompt command different than the regex.
# used to match the command-line prompt
self.UNIQUE_PROMPT = "\[PEXPECT\][\$\#] "
self.UNIQUE_PROMPT = r"\[PEXPECT\][\$\#] "
self.PROMPT = self.UNIQUE_PROMPT
# used to set shell command-line prompt to UNIQUE_PROMPT.
self.PROMPT_SET_SH = "PS1='[PEXPECT]\$ '"
self.PROMPT_SET_CSH = "set prompt='[PEXPECT]\$ '"
self.PROMPT_SET_SH = r"PS1='[PEXPECT]\$ '"
self.PROMPT_SET_CSH = r"set prompt='[PEXPECT]\$ '"
self.SSH_OPTS = ("-o'RSAAuthentication=no'"
+ " -o 'PubkeyAuthentication=no'")
# Disabling host key checking, makes you vulnerable to MITM attacks.
+1 -1
View File
@@ -69,7 +69,7 @@ def constrain (n, min, max):
class screen:
'''This object maintains the state of a virtual text screen as a
rectangluar array. This maintains a virtual cursor position and handles
rectangular array. This maintains a virtual cursor position and handles
scrolling as characters are added. This supports most of the methods needed
by an ANSI text screen. Row and column indexes are 1-based (not zero-based,
like arrays).
+28 -14
View File
@@ -115,6 +115,8 @@ class SpawnBase(object):
self.linesep = os.linesep.decode('ascii')
# This can handle unicode in both Python 2 and 3
self.write_to_stdout = sys.stdout.write
# storage for async transport
self.async_pw_transport = None
def _log(self, s, direction):
if self.logfile is not None:
@@ -221,7 +223,7 @@ class SpawnBase(object):
self._pattern_type_err(p)
return compiled_pattern_list
def expect(self, pattern, timeout=-1, searchwindowsize=-1, async=False):
def expect(self, pattern, timeout=-1, searchwindowsize=-1, async_=False, **kw):
'''This seeks through the stream until a pattern is matched. The
pattern is overloaded and may take several types. The pattern can be a
StringType, EOF, a compiled re, or a list of any of those types.
@@ -305,7 +307,7 @@ class SpawnBase(object):
If you are trying to optimize for speed then see expect_list().
On Python 3.4, or Python 3.3 with asyncio installed, passing
``async=True`` will make this return an :mod:`asyncio` coroutine,
``async_=True`` will make this return an :mod:`asyncio` coroutine,
which you can yield from to get the same result that this method would
normally give directly. So, inside a coroutine, you can replace this code::
@@ -313,15 +315,19 @@ class SpawnBase(object):
With this non-blocking form::
index = yield from p.expect(patterns, async=True)
index = yield from p.expect(patterns, async_=True)
'''
if 'async' in kw:
async_ = kw.pop('async')
if kw:
raise TypeError("Unknown keyword arguments: {}".format(kw))
compiled_pattern_list = self.compile_pattern_list(pattern)
return self.expect_list(compiled_pattern_list,
timeout, searchwindowsize, async)
timeout, searchwindowsize, async_)
def expect_list(self, pattern_list, timeout=-1, searchwindowsize=-1,
async=False):
async_=False, **kw):
'''This takes a list of compiled regular expressions and returns the
index into the pattern_list that matched the child output. The list may
also contain EOF or TIMEOUT(which are not compiled regular
@@ -331,21 +337,25 @@ class SpawnBase(object):
the expect() method. This is called by expect().
Like :meth:`expect`, passing ``async=True`` will make this return an
Like :meth:`expect`, passing ``async_=True`` will make this return an
asyncio coroutine.
'''
if timeout == -1:
timeout = self.timeout
if 'async' in kw:
async_ = kw.pop('async')
if kw:
raise TypeError("Unknown keyword arguments: {}".format(kw))
exp = Expecter(self, searcher_re(pattern_list), searchwindowsize)
if async:
from .async import expect_async
if async_:
from ._async import expect_async
return expect_async(exp, timeout)
else:
return exp.expect_loop(timeout)
def expect_exact(self, pattern_list, timeout=-1, searchwindowsize=-1,
async=False):
async_=False, **kw):
'''This is similar to expect(), but uses plain string matching instead
of compiled regular expressions in 'pattern_list'. The 'pattern_list'
@@ -359,11 +369,15 @@ class SpawnBase(object):
This method is also useful when you don't want to have to worry about
escaping regular expression characters that you want to match.
Like :meth:`expect`, passing ``async=True`` will make this return an
Like :meth:`expect`, passing ``async_=True`` will make this return an
asyncio coroutine.
'''
if timeout == -1:
timeout = self.timeout
if 'async' in kw:
async_ = kw.pop('async')
if kw:
raise TypeError("Unknown keyword arguments: {}".format(kw))
if (isinstance(pattern_list, self.allowed_string_types) or
pattern_list in (TIMEOUT, EOF)):
@@ -383,8 +397,8 @@ class SpawnBase(object):
pattern_list = [prepare_pattern(p) for p in pattern_list]
exp = Expecter(self, searcher_string(pattern_list), searchwindowsize)
if async:
from .async import expect_async
if async_:
from ._async import expect_async
return expect_async(exp, timeout)
else:
return exp.expect_loop(timeout)
@@ -415,7 +429,7 @@ class SpawnBase(object):
# I could have done this more directly by not using expect(), but
# I deliberately decided to couple read() to expect() so that
# I would catch any bugs early and ensure consistant behavior.
# I would catch any bugs early and ensure consistent behavior.
# It's a little less efficient, but there is less for me to
# worry about if I have to later modify read() or expect().
# Note, it's OK if size==-1 in the regex. That just means it
@@ -487,7 +501,7 @@ class SpawnBase(object):
# For 'with spawn(...) as child:'
def __enter__(self):
return self
def __exit__(self, etype, evalue, tb):
# We rely on subclasses to implement close(). If they don't, it's not
# clear what a context manager should do.