From bd09055fe6a5f8078fa420545390b01e4a0c44fa Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sat, 3 Oct 2015 12:11:01 +0100 Subject: Make Pexpect importable on Windows --- pexpect/__init__.py | 160 +++------------------------------------------------- pexpect/run.py | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 153 deletions(-) create mode 100644 pexpect/run.py diff --git a/pexpect/__init__.py b/pexpect/__init__.py index db5be16..1901531 100644 --- a/pexpect/__init__.py +++ b/pexpect/__init__.py @@ -64,168 +64,22 @@ PEXPECT LICENSE ''' import sys -import types +PY3 = (sys.version_info[0] >= 3) from .exceptions import ExceptionPexpect, EOF, TIMEOUT from .utils import split_command_line, which, is_executable_file -from .pty_spawn import spawn, spawnu, PY3 from .expect import Expecter, searcher_re, searcher_string +if sys.platform != 'win32': + # On Unix, these are available at the top level for backwards compatibility + from .pty_spawn import spawn, spawnu + from .run import run, runu + __version__ = '4.0.dev' __revision__ = '' __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu', 'which', 'split_command_line', '__version__', '__revision__'] -def run(command, timeout=30, withexitstatus=False, events=None, - extra_args=None, logfile=None, cwd=None, env=None, **kwargs): - - ''' - This function runs the given command; waits for it to finish; then - returns all output as a string. STDERR is included in output. If the full - path to the command is not given then the path is searched. - - Note that lines are terminated by CR/LF (\\r\\n) combination even on - UNIX-like systems because this is the standard for pseudottys. If you set - 'withexitstatus' to true, then run will return a tuple of (command_output, - exitstatus). If 'withexitstatus' is false then this returns just - command_output. - - The run() function can often be used instead of creating a spawn instance. - For example, the following code uses spawn:: - - from pexpect import * - child = spawn('scp foo user@example.com:.') - child.expect('(?i)password') - child.sendline(mypassword) - - The previous code can be replace with the following:: - - from pexpect import * - run('scp foo user@example.com:.', events={'(?i)password': mypassword}) - - **Examples** - - Start the apache daemon on the local machine:: - - from pexpect import * - run("/usr/local/apache/bin/apachectl start") - - Check in a file using SVN:: - - from pexpect import * - run("svn ci -m 'automatic commit' my_file.py") - - Run a command and capture exit status:: - - from pexpect import * - (command_output, exitstatus) = run('ls -l /bin', withexitstatus=1) - - The following will run SSH and execute 'ls -l' on the remote machine. The - password 'secret' will be sent if the '(?i)password' pattern is ever seen:: - - run("ssh username@machine.example.com 'ls -l'", - events={'(?i)password':'secret\\n'}) - - This will start mencoder to rip a video from DVD. This will also display - progress ticks every 5 seconds as it runs. For example:: - - from pexpect import * - def print_ticks(d): - print d['event_count'], - run("mencoder dvd://1 -o video.avi -oac copy -ovc copy", - events={TIMEOUT:print_ticks}, timeout=5) - - The 'events' argument should be either a dictionary or a tuple list that - contains patterns and responses. Whenever one of the patterns is seen - in the command output, run() will send the associated response string. - So, run() in the above example can be also written as: - - run("mencoder dvd://1 -o video.avi -oac copy -ovc copy", - events=[(TIMEOUT,print_ticks)], timeout=5) - - Use a tuple list for events if the command output requires a delicate - control over what pattern should be matched, since the tuple list is passed - to pexpect() as its pattern list, with the order of patterns preserved. - - Note that you should put newlines in your string if Enter is necessary. - - Like the example above, the responses may also contain a callback, either - a function or method. It should accept a dictionary value as an argument. - The dictionary contains all the locals from the run() function, so you can - access the child spawn object or any other variable defined in run() - (event_count, child, and extra_args are the most useful). A callback may - return True to stop the current run process. Otherwise run() continues - until the next event. A callback may also return a string which will be - sent to the child. 'extra_args' is not used by directly run(). It provides - a way to pass data to a callback function through run() through the locals - dictionary passed to a callback. - - Like :class:`spawn`, passing *encoding* will make it work with unicode - instead of bytes. You can pass *codec_errors* to control how errors in - encoding and decoding are handled. - ''' - if timeout == -1: - child = spawn(command, maxread=2000, logfile=logfile, cwd=cwd, env=env, - **kwargs) - else: - child = spawn(command, timeout=timeout, maxread=2000, logfile=logfile, - cwd=cwd, env=env, **kwargs) - if isinstance(events, list): - patterns= [x for x,y in events] - responses = [y for x,y in events] - elif isinstance(events, dict): - patterns = list(events.keys()) - responses = list(events.values()) - else: - # This assumes EOF or TIMEOUT will eventually cause run to terminate. - patterns = None - responses = None - child_result_list = [] - event_count = 0 - while True: - try: - index = child.expect(patterns) - if isinstance(child.after, child.allowed_string_types): - child_result_list.append(child.before + child.after) - else: - # child.after may have been a TIMEOUT or EOF, - # which we don't want appended to the list. - child_result_list.append(child.before) - if isinstance(responses[index], child.allowed_string_types): - child.send(responses[index]) - elif (isinstance(responses[index], types.FunctionType) or - isinstance(responses[index], types.MethodType)): - callback_result = responses[index](locals()) - sys.stdout.flush() - if isinstance(callback_result, child.allowed_string_types): - child.send(callback_result) - elif callback_result: - break - else: - raise TypeError("parameter `event' at index {index} must be " - "a string, method, or function: {value!r}" - .format(index=index, value=responses[index])) - event_count = event_count + 1 - except TIMEOUT: - child_result_list.append(child.before) - break - except EOF: - child_result_list.append(child.before) - break - child_result = child.string_type().join(child_result_list) - if withexitstatus: - child.close() - return (child_result, child.exitstatus) - else: - return child_result - -def runu(command, timeout=30, withexitstatus=False, events=None, - extra_args=None, logfile=None, cwd=None, env=None, **kwargs): - """Deprecated: pass encoding to run() instead. - """ - kwargs.setdefault('encoding', 'utf-8') - return run(command, timeout=timeout, withexitstatus=withexitstatus, - events=events, extra_args=extra_args, logfile=logfile, cwd=cwd, - env=env, **kwargs) + # vim: set shiftround expandtab tabstop=4 shiftwidth=4 ft=python autoindent : diff --git a/pexpect/run.py b/pexpect/run.py new file mode 100644 index 0000000..d9dfe76 --- /dev/null +++ b/pexpect/run.py @@ -0,0 +1,157 @@ +import sys +import types + +from .exceptions import EOF, TIMEOUT +from .pty_spawn import spawn + +def run(command, timeout=30, withexitstatus=False, events=None, + extra_args=None, logfile=None, cwd=None, env=None, **kwargs): + + ''' + This function runs the given command; waits for it to finish; then + returns all output as a string. STDERR is included in output. If the full + path to the command is not given then the path is searched. + + Note that lines are terminated by CR/LF (\\r\\n) combination even on + UNIX-like systems because this is the standard for pseudottys. If you set + 'withexitstatus' to true, then run will return a tuple of (command_output, + exitstatus). If 'withexitstatus' is false then this returns just + command_output. + + The run() function can often be used instead of creating a spawn instance. + For example, the following code uses spawn:: + + from pexpect import * + child = spawn('scp foo user@example.com:.') + child.expect('(?i)password') + child.sendline(mypassword) + + The previous code can be replace with the following:: + + from pexpect import * + run('scp foo user@example.com:.', events={'(?i)password': mypassword}) + + **Examples** + + Start the apache daemon on the local machine:: + + from pexpect import * + run("/usr/local/apache/bin/apachectl start") + + Check in a file using SVN:: + + from pexpect import * + run("svn ci -m 'automatic commit' my_file.py") + + Run a command and capture exit status:: + + from pexpect import * + (command_output, exitstatus) = run('ls -l /bin', withexitstatus=1) + + The following will run SSH and execute 'ls -l' on the remote machine. The + password 'secret' will be sent if the '(?i)password' pattern is ever seen:: + + run("ssh username@machine.example.com 'ls -l'", + events={'(?i)password':'secret\\n'}) + + This will start mencoder to rip a video from DVD. This will also display + progress ticks every 5 seconds as it runs. For example:: + + from pexpect import * + def print_ticks(d): + print d['event_count'], + run("mencoder dvd://1 -o video.avi -oac copy -ovc copy", + events={TIMEOUT:print_ticks}, timeout=5) + + The 'events' argument should be either a dictionary or a tuple list that + contains patterns and responses. Whenever one of the patterns is seen + in the command output, run() will send the associated response string. + So, run() in the above example can be also written as: + + run("mencoder dvd://1 -o video.avi -oac copy -ovc copy", + events=[(TIMEOUT,print_ticks)], timeout=5) + + Use a tuple list for events if the command output requires a delicate + control over what pattern should be matched, since the tuple list is passed + to pexpect() as its pattern list, with the order of patterns preserved. + + Note that you should put newlines in your string if Enter is necessary. + + Like the example above, the responses may also contain a callback, either + a function or method. It should accept a dictionary value as an argument. + The dictionary contains all the locals from the run() function, so you can + access the child spawn object or any other variable defined in run() + (event_count, child, and extra_args are the most useful). A callback may + return True to stop the current run process. Otherwise run() continues + until the next event. A callback may also return a string which will be + sent to the child. 'extra_args' is not used by directly run(). It provides + a way to pass data to a callback function through run() through the locals + dictionary passed to a callback. + + Like :class:`spawn`, passing *encoding* will make it work with unicode + instead of bytes. You can pass *codec_errors* to control how errors in + encoding and decoding are handled. + ''' + if timeout == -1: + child = spawn(command, maxread=2000, logfile=logfile, cwd=cwd, env=env, + **kwargs) + else: + child = spawn(command, timeout=timeout, maxread=2000, logfile=logfile, + cwd=cwd, env=env, **kwargs) + if isinstance(events, list): + patterns= [x for x,y in events] + responses = [y for x,y in events] + elif isinstance(events, dict): + patterns = list(events.keys()) + responses = list(events.values()) + else: + # This assumes EOF or TIMEOUT will eventually cause run to terminate. + patterns = None + responses = None + child_result_list = [] + event_count = 0 + while True: + try: + index = child.expect(patterns) + if isinstance(child.after, child.allowed_string_types): + child_result_list.append(child.before + child.after) + else: + # child.after may have been a TIMEOUT or EOF, + # which we don't want appended to the list. + child_result_list.append(child.before) + if isinstance(responses[index], child.allowed_string_types): + child.send(responses[index]) + elif (isinstance(responses[index], types.FunctionType) or + isinstance(responses[index], types.MethodType)): + callback_result = responses[index](locals()) + sys.stdout.flush() + if isinstance(callback_result, child.allowed_string_types): + child.send(callback_result) + elif callback_result: + break + else: + raise TypeError("parameter `event' at index {index} must be " + "a string, method, or function: {value!r}" + .format(index=index, value=responses[index])) + event_count = event_count + 1 + except TIMEOUT: + child_result_list.append(child.before) + break + except EOF: + child_result_list.append(child.before) + break + child_result = child.string_type().join(child_result_list) + if withexitstatus: + child.close() + return (child_result, child.exitstatus) + else: + return child_result + +def runu(command, timeout=30, withexitstatus=False, events=None, + extra_args=None, logfile=None, cwd=None, env=None, **kwargs): + """Deprecated: pass encoding to run() instead. + """ + kwargs.setdefault('encoding', 'utf-8') + return run(command, timeout=timeout, withexitstatus=withexitstatus, + events=events, extra_args=extra_args, logfile=logfile, cwd=cwd, + env=env, **kwargs) -- cgit v1.2.1 From 6d68b93dd1e7e548bc51bee0edf6584efd5d833a Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sat, 3 Oct 2015 12:12:01 +0100 Subject: Clear up a few unused imports --- pexpect/pty_spawn.py | 2 -- pexpect/replwrap.py | 1 - 2 files changed, 3 deletions(-) diff --git a/pexpect/pty_spawn.py b/pexpect/pty_spawn.py index 09c008b..1d9554b 100644 --- a/pexpect/pty_spawn.py +++ b/pexpect/pty_spawn.py @@ -2,10 +2,8 @@ import os import sys import time import select -import re import pty import tty -import termios import errno import signal from contextlib import contextmanager diff --git a/pexpect/replwrap.py b/pexpect/replwrap.py index 83a09c2..5114a85 100644 --- a/pexpect/replwrap.py +++ b/pexpect/replwrap.py @@ -3,7 +3,6 @@ import os.path import signal import sys -import re import pexpect -- cgit v1.2.1 From 823ad5164891ba148cc6857dc1400dedd1e78693 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sat, 3 Oct 2015 12:55:41 +0100 Subject: Update docs for PopenSpawn and Windows support --- doc/api/fdpexpect.rst | 12 +++++------- doc/api/index.rst | 1 + doc/api/popen_spawn.rst | 24 ++++++++++++++++++++++++ doc/history.rst | 7 ++++--- doc/install.rst | 7 ++++--- doc/overview.rst | 17 +++++++++++++++++ pexpect/fdpexpect.py | 3 ++- pexpect/popen_spawn.py | 15 ++++++++++++++- 8 files changed, 71 insertions(+), 15 deletions(-) create mode 100644 doc/api/popen_spawn.rst diff --git a/doc/api/fdpexpect.rst b/doc/api/fdpexpect.rst index 8321454..3ddf2cd 100644 --- a/doc/api/fdpexpect.rst +++ b/doc/api/fdpexpect.rst @@ -13,10 +13,8 @@ fdspawn class .. automethod:: isalive .. automethod:: close - .. note:: - :class:`fdspawn` inherits all of the methods of :class:`~pexpect.spawn`, - but not all of them can be used, especially if the file descriptor is not - a terminal. Some methods may do nothing (e.g. :meth:`~fdspawn.kill`), while - others will raise an exception (e.g. :meth:`~fdspawn.terminate`). - This behaviour might be made more consistent in the future, so try to - avoid relying on it. \ No newline at end of file + .. method:: expect + expect_exact + expect_list + + As :class:`pexpect.spawn`. diff --git a/doc/api/index.rst b/doc/api/index.rst index fd017a5..5277d1c 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -6,6 +6,7 @@ API documentation pexpect fdpexpect + popen_spawn replwrap pxssh diff --git a/doc/api/popen_spawn.rst b/doc/api/popen_spawn.rst new file mode 100644 index 0000000..64cae15 --- /dev/null +++ b/doc/api/popen_spawn.rst @@ -0,0 +1,24 @@ +popen_spawn - use pexpect with a piped subprocess +================================================= + +.. automodule:: pexpect.popen_spawn + +PopenSpawn class +---------------- + +.. autoclass:: PopenSpawn + + .. automethod:: __init__ + .. automethod:: send + .. automethod:: sendline + .. automethod:: write + .. automethod:: writelines + .. automethod:: kill + .. automethod:: sendeof + .. automethod:: wait + + .. method:: expect + expect_exact + expect_list + + As :class:`pexpect.spawn`. diff --git a/doc/history.rst b/doc/history.rst index b33e4d9..1167865 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -7,14 +7,15 @@ Releases Version 4.0 ``````````` -* Integration with :mod:`asyncio`: passing ``async=True`` to :meth:`~.expect`, - :meth:`~.expect_exact` or :meth:`~.expect_list` will make them return a +* Integration with :mod:`asyncio`: passing ``async=True`` to :meth:`~.spawn.expect`, + :meth:`~.spawn.expect_exact` or :meth:`~.spawn.expect_list` will make them return a coroutine. You can get the result using ``yield from``, or wrap it in an :class:`asyncio.Task`. This allows the event loop to do other things while waiting for output that matches a pattern. +* Experimental support for Windows (with some caveats)—see :ref:`windows`. * Enhancement: allow method as callbacks of argument ``events`` for :func:`pexpect.run` (:ghissue:`176`). -* It is now possible to call :meth:`~.wait` multiple times, or after a process +* It is now possible to call :meth:`~.spawn.wait` multiple times, or after a process is already determined to be terminated without raising an exception (:ghpull:`211`). * New :class:`pexpect.spawn` keyword argument, ``dimensions=(rows, columns)`` diff --git a/doc/install.rst b/doc/install.rst index 297acf3..d310871 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -15,6 +15,7 @@ Requirements This version of Pexpect requires Python 2.6 or 3.2 or above. For older versions of Python, continue using Pexpect 2.4. -Pexpect only works on POSIX systems, where the :mod:`pty` module -is present in the standard library. It may be possible to run it on Windows -using `Cygwin `_. +As of version 4.0, Pexpect can be used on Windows and POSIX systems. However, +:class:`pexpect.spawn` and :func:`pexpect.run` are only available on POSIX, +where the :mod:`pty` module is present in the standard library. See +:ref:`windows` for more information. diff --git a/doc/overview.rst b/doc/overview.rst index c5bcb05..d394ef1 100644 --- a/doc/overview.rst +++ b/doc/overview.rst @@ -238,3 +238,20 @@ You can have these methods ignore timeout and block indefinitely by passing ``None`` for the timeout parameter:: child.expect(pexpect.EOF, timeout=None) + +.. _windows: + +Pexpect on Windows +------------------ + +.. versionadded:: 4.0 + Windows support + +Pexpect can be used on Windows to wait for a pattern to be produced by a child +process, using :class:`pexpect.popen_spawn.PopenSpawn`, or a file descriptor, +using :class:`pexpect.fdpexpect.fdspawn`. This should be considered experimental +for now. + +:class:`pexpect.spawn` and :func:`pexpect.run` are *not* available on Windows, +as they rely on Unix pseudoterminals (ptys). Cross platform code must not use +these. diff --git a/pexpect/fdpexpect.py b/pexpect/fdpexpect.py index ca8cf07..dd1b492 100644 --- a/pexpect/fdpexpect.py +++ b/pexpect/fdpexpect.py @@ -85,10 +85,11 @@ class fdspawn(SpawnBase): return False def terminate (self, force=False): # pragma: no cover + '''Deprecated and invalid. Just raises an exception.''' raise ExceptionPexpect('This method is not valid for file descriptors.') # These four methods are left around for backwards compatibility, but not - # documented as part of fdpexpect. You're encouraged to use os.write# + # documented as part of fdpexpect. You're encouraged to use os.write # directly. def send(self, s): "Write to fd, return number of bytes written" diff --git a/pexpect/popen_spawn.py b/pexpect/popen_spawn.py index ab51499..680dd8a 100644 --- a/pexpect/popen_spawn.py +++ b/pexpect/popen_spawn.py @@ -1,4 +1,4 @@ -"""Spawn interface using subprocess.Popen +"""Provides an interface like pexpect.spawn interface using subprocess.Popen """ import os import threading @@ -121,6 +121,10 @@ class PopenSpawn(SpawnBase): self.send(s) def send(self, s): + '''Send data to the subprocess' stdin. + + Returns the number of bytes written. + ''' s = self._coerce_send_string(s) self._log(s, 'send') @@ -141,6 +145,10 @@ class PopenSpawn(SpawnBase): return n + self.send(self.linesep) def wait(self): + '''Wait for the subprocess to finish. + + Returns the exit code. + ''' status = self.proc.wait() if status >= 0: self.exitstatus = status @@ -152,6 +160,10 @@ class PopenSpawn(SpawnBase): return status def kill(self, sig): + '''Sends a Unix signal to the subprocess. + + Use constants from the :mod:`signal` module to specify which signal. + ''' if sys.platform == 'win32': if sig in [signal.SIGINT, signal.CTRL_C_EVENT]: sig = signal.CTRL_C_EVENT @@ -163,4 +175,5 @@ class PopenSpawn(SpawnBase): os.kill(self.proc.pid, sig) def sendeof(self): + '''Closes the stdin pipe from the writing end.''' self.proc.stdin.close() -- cgit v1.2.1