From e6736044efb087053e251998e20b2a60a7756f97 Mon Sep 17 00:00:00 2001 From: Erin O'Connell Date: Tue, 24 Oct 2017 00:27:06 -0600 Subject: [PATCH] update the vendored pexpect for python 3.7 async support --- pipenv/vendor/pexpect/{async.py => _async.py} | 20 ++++++--- pipenv/vendor/pexpect/expect.py | 7 ++-- pipenv/vendor/pexpect/fdpexpect.py | 2 +- pipenv/vendor/pexpect/pty_spawn.py | 10 +++-- pipenv/vendor/pexpect/pxssh.py | 6 +-- pipenv/vendor/pexpect/screen.py | 2 +- pipenv/vendor/pexpect/spawnbase.py | 42 ++++++++++++------- 7 files changed, 57 insertions(+), 32 deletions(-) rename pipenv/vendor/pexpect/{async.py => _async.py} (82%) diff --git a/pipenv/vendor/pexpect/async.py b/pipenv/vendor/pexpect/_async.py similarity index 82% rename from pipenv/vendor/pexpect/async.py rename to pipenv/vendor/pexpect/_async.py index 136fc0e2..3a1a1adb 100644 --- a/pipenv/vendor/pexpect/async.py +++ b/pipenv/vendor/pexpect/_async.py @@ -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) diff --git a/pipenv/vendor/pexpect/expect.py b/pipenv/vendor/pexpect/expect.py index 660cfb53..7e07cfa4 100644 --- a/pipenv/vendor/pexpect/expect.py +++ b/pipenv/vendor/pexpect/expect.py @@ -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. diff --git a/pipenv/vendor/pexpect/fdpexpect.py b/pipenv/vendor/pexpect/fdpexpect.py index ac7443e5..cd608044 100644 --- a/pipenv/vendor/pexpect/fdpexpect.py +++ b/pipenv/vendor/pexpect/fdpexpect.py @@ -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 diff --git a/pipenv/vendor/pexpect/pty_spawn.py b/pipenv/vendor/pexpect/pty_spawn.py index d1c6df7b..4afda6a7 100644 --- a/pipenv/vendor/pexpect/pty_spawn.py +++ b/pipenv/vendor/pexpect/pty_spawn.py @@ -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 diff --git a/pipenv/vendor/pexpect/pxssh.py b/pipenv/vendor/pexpect/pxssh.py index a9c01d53..1f097ab7 100644 --- a/pipenv/vendor/pexpect/pxssh.py +++ b/pipenv/vendor/pexpect/pxssh.py @@ -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. diff --git a/pipenv/vendor/pexpect/screen.py b/pipenv/vendor/pexpect/screen.py index 0bced89d..5ab45b94 100644 --- a/pipenv/vendor/pexpect/screen.py +++ b/pipenv/vendor/pexpect/screen.py @@ -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). diff --git a/pipenv/vendor/pexpect/spawnbase.py b/pipenv/vendor/pexpect/spawnbase.py index 5dd24b56..9cdcba63 100644 --- a/pipenv/vendor/pexpect/spawnbase.py +++ b/pipenv/vendor/pexpect/spawnbase.py @@ -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.