diff options
author | Todd Leonhardt <todd.leonhardt@gmail.com> | 2018-04-16 20:40:13 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-04-16 20:40:13 -0700 |
commit | 33f2f03610f6c71ed232fe3b608f8592dec88428 (patch) | |
tree | 16f9f5c56427965de247dfca92e7481cf3fd4843 | |
parent | 8aaf6f431ef5e0f5e4e55759a08a22a4591d8f6b (diff) | |
parent | 11a9664f3bb9bba7b97b6400d787ee05842739cd (diff) | |
download | cmd2-git-33f2f03610f6c71ed232fe3b608f8592dec88428.tar.gz |
Merge pull request #354 from python-cmd2/python3
Move to Python 3.4+ only
33 files changed, 138 insertions, 321 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index fad8437a..ad884315 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -4,4 +4,4 @@ install: build: off test_script: - - python -m tox -e py27-win,py35-win,py36-win + - python -m tox -e py35-win,py36-win diff --git a/.travis.yml b/.travis.yml index 2c5b5124..d6689c58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,6 @@ sudo: false # false enables container-based build for fast boot times on Linux matrix: include: - os: linux - python: 2.7 - env: TOXENV=py27 - - os: linux python: 3.4 env: TOXENV=py34 - os: linux @@ -21,15 +18,8 @@ matrix: env: TOXENV=py37 # # Warning: Don't try to use code coverage analysis with pypy as it is insanely slow # - os: linux -# python: pypy -# env: TOXENV=pypy -# - os: linux # python: pypy3 # env: TOXENV=pypy3 - # Stock OSX Python -# - os: osx -# language: generic -# env: TOXENV=py27 # # Latest Python 3.x from Homebrew # - os: osx # language: generic diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fc32546..38b54efb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ ## 0.9.0 (TBD, 2018) +* Enhancements + * ``cmd2`` no longer depends on the ``six`` module * Deletions (potentially breaking changes) * Deleted all ``optparse`` code which had previously been deprecated in release 0.8.0 * The ``options`` decorator no longer exists diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7398389d..f7eba2fa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,9 +44,8 @@ The tables below list all prerequisites along with the minimum required version | Prerequisite | Minimum Version | | --------------------------------------------------- | --------------- | -| [Python](https://www.python.org/downloads/) | `3.4 or 2.7` | -| [six](https://pypi.python.org/pypi/six) | `1.8` | -| [pyparsing](http://pyparsing.wikispaces.com) | `2.0.3` | +| [Python](https://www.python.org/downloads/) | `3.4` | +| [pyparsing](http://pyparsing.wikispaces.com) | `2.1` | | [pyperclip](https://github.com/asweigart/pyperclip) | `1.6` | #### Additional prerequisites to run cmd2 unit tests @@ -54,7 +53,6 @@ The tables below list all prerequisites along with the minimum required version | Prerequisite | Minimum Version | | ------------------------------------------- | --------------- | | [pytest](http://doc.pytest.org/en/latest/) | `2.6.3` | -| [mock](https://pypi.python.org/pypi/six) | `1.0.1` | ### Additional prerequisites to build cmd2 documentation | Prerequisite | Minimum Version | @@ -73,7 +71,6 @@ If Python is already installed in your machine, run the following commands to va ```shell python -V -pip freeze | grep six pip freeze | grep pyparsing ``` @@ -193,10 +190,10 @@ Once you have cmd2 cloned, before you start any cmd2 application, you first need ```bash # Install cmd2 prerequisites -pip install -U six pyparsing pyperclip +pip install -U pyparsing pyperclip # Install prerequisites for running cmd2 unit tests -pip install -U pytest mock +pip install -U pytest # Install prerequisites for building cmd2 documentation pip install -U sphinx sphinx-rtd-theme @@ -480,6 +477,13 @@ Here is some advice regarding what makes a good pull request (PR) from the persp - Code coverage of the unit tests matters, try not to decrease it - Think twice before adding dependencies to 3rd party libraries (outside of the Python standard library) because it could affect a lot of users +### Developing and Debugging in an IDE + +We recommend using [Visual Studio Code](https://code.visualstudio.com) with the [Python extension](https://code.visualstudio.com/docs/languages/python) and it's [Integrated Terminal](https://code.visualstudio.com/docs/python/debugging) debugger for debugging since it has +excellent support for debugging console applications. + +[PyCharm](https://www.jetbrains.com/pycharm/) is also quite good and has very nice [Code Inspection](https://www.jetbrains.com/help/pycharm/code-inspection.html) capabilities. + ### Acknowledgement Thanks to the good folks at [freeCodeCamp](https://github.com/freeCodeCamp/freeCodeCamp) for creating an excellent `CONTRIBUTING` file which we have borrowed heavily from. @@ -34,20 +34,18 @@ Main Features - Settable environment parameters - Parsing commands with arguments using `argparse`, including support for sub-commands - Sub-menu support via the ``AddSubmenu`` decorator -- Unicode character support (*Python 3 only*) +- Unicode character support - Good tab-completion of commands, sub-commands, file system paths, and shell commands -- Python 2.7 and 3.4+ support -- Windows, macOS, and Linux support +- Support for Python 3.4+ on Windows, macOS, and Linux - Trivial to provide built-in help for all commands - Built-in regression testing framework for your applications (transcript-based testing) - Transcripts for use with built-in regression can be automatically generated from `history -t` -Plan for dropping Python 2.7 support ------------------------------------- -Support for Python 2.7 will be discontinued on April 15, 2018. After that date, new releases of `cmd2` will only support -Python 3. Older releases of `cmd2` will of course continue to support Python 2.7. +Python 2.7 support is EOL +------------------------- +Support for adding new features to the Python 2.7 release of ``cmd2`` was discontinued on April 15, 2018. Bug fixes will be supported for Python 2.7 via 0.8.x until August 31, 2018. -Supporting Python 2 is an increasing burden on our limited resources. Switching to support only Python 3 will allow +Supporting Python 2 was an increasing burden on our limited resources. Switching to support only Python 3 will allow us to clean up the codebase, remove some cruft, and focus on developing new features. Installation @@ -58,12 +56,11 @@ On all operating systems, the latest stable version of `cmd2` can be installed u pip install -U cmd2 ``` -cmd2 works with Python 2.7 and Python 3.4+ on Windows, macOS, and Linux. It is pure Python code with -the only 3rd-party dependencies being on [six](https://pypi.python.org/pypi/six), -[pyparsing](http://pyparsing.wikispaces.com), and [pyperclip](https://github.com/asweigart/pyperclip). +cmd2 works with Python 3.4+ on Windows, macOS, and Linux. It is pure Python code with +the only 3rd-party dependencies being on [pyparsing](http://pyparsing.wikispaces.com), and [pyperclip](https://github.com/asweigart/pyperclip). Windows has an additional dependency on [pyreadline](https://pypi.python.org/pypi/pyreadline). Non-Windows platforms have an additional dependency on [wcwidth](https://pypi.python.org/pypi/wcwidth). Finally, Python -3.4 and earlier have an additional dependency on [contextlib2](https://pypi.python.org/pypi/contextlib2). +3.4 has an additional dependency on [contextlib2](https://pypi.python.org/pypi/contextlib2). For information on other installation options, see [Installation Instructions](https://cmd2.readthedocs.io/en/latest/install.html) in the cmd2 @@ -154,14 +151,11 @@ Example cmd2 application (**examples/example.py**): """ A sample application for cmd2. """ - -import random import argparse +import random +import cmd2 -from cmd2 import Cmd, with_argparser - - -class CmdLineApp(Cmd): +class CmdLineApp(cmd2.Cmd): """ Example cmd2 application. """ # Setting this true makes it run a shell command if a cmd2/cmd command doesn't exist @@ -179,14 +173,14 @@ class CmdLineApp(Cmd): self.shortcuts.update({'&': 'speak'}) # Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell - Cmd.__init__(self, use_ipython=False) + super().__init__(use_ipython=False) speak_parser = argparse.ArgumentParser() speak_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') speak_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE') speak_parser.add_argument('-r', '--repeat', type=int, help='output [n] times') speak_parser.add_argument('words', nargs='+', help='words to say') - @with_argparser(speak_parser) + @cmd2.with_argparser(speak_parser) def do_speak(self, args): """Repeats what you tell me to.""" words = [] @@ -207,7 +201,7 @@ class CmdLineApp(Cmd): mumble_parser = argparse.ArgumentParser() mumble_parser.add_argument('-r', '--repeat', type=int, help='how many times to repeat') mumble_parser.add_argument('words', nargs='+', help='words to say') - @with_argparser(mumble_parser) + @cmd2.with_argparser(mumble_parser) def do_mumble(self, args): """Mumbles what you tell me to.""" repetitions = args.repeat or 1 @@ -32,12 +32,13 @@ import datetime import functools import glob import io +from io import StringIO import os import platform import re import shlex import signal -import six +import subprocess import sys import tempfile import traceback @@ -52,16 +53,19 @@ except ImportError: import pyparsing import pyperclip +# Newer versions of pyperclip are released as a single file, but older versions had a more complicated structure +try: + from pyperclip.exceptions import PyperclipException +except ImportError: + # noinspection PyUnresolvedReferences + from pyperclip import PyperclipException + # Collection is a container that is sizable and iterable # It was introduced in Python 3.6. We will try to import it, otherwise use our implementation try: from collections.abc import Collection, Iterable except ImportError: - - if six.PY3: - from collections.abc import Sized, Iterable, Container - else: - from collections import Sized, Iterable, Container + from collections.abc import Sized, Iterable, Container # noinspection PyAbstractClass class Collection(Sized, Iterable, Container): @@ -78,44 +82,12 @@ except ImportError: return True return NotImplemented -# Newer versions of pyperclip are released as a single file, but older versions had a more complicated structure -try: - from pyperclip.exceptions import PyperclipException -except ImportError: - # noinspection PyUnresolvedReferences - from pyperclip import PyperclipException - -# next(it) gets next item of iterator it. This is a replacement for calling it.next() in Python 2 and next(it) in Py3 -from six import next - -# Possible types for text data. This is basestring() in Python 2 and str in Python 3. -from six import string_types - -# Used for sm.input: raw_input() for Python 2 or input() for Python 3 -import six.moves as sm - -# itertools.zip() for Python 2 or zip() for Python 3 - produces an iterator in both cases -from six.moves import zip - -# If using Python 2.7, try to use the subprocess32 package backported from Python 3.2 due to various improvements -# NOTE: The feature to pipe output to a shell command won't work correctly in Python 2.7 without this -try: - # noinspection PyPackageRequirements - import subprocess32 as subprocess -except ImportError: - import subprocess - -# Python 3.4 and earlier require contextlib2 for temporarily redirecting stderr and stdout +# Python 3.4 require contextlib2 for temporarily redirecting stderr and stdout if sys.version_info < (3, 5): from contextlib2 import redirect_stdout, redirect_stderr else: from contextlib import redirect_stdout, redirect_stderr -if six.PY3: - from io import StringIO # Python3 -else: - from io import BytesIO as StringIO # Python2 - # Detect whether IPython is installed to determine if the built-in "ipy" command should be included ipython_available = True try: @@ -136,14 +108,12 @@ except ImportError: except ImportError: pass - # Check what implementation of readline we are using class RlType(Enum): GNU = 1 PYREADLINE = 2 NONE = 3 - rl_type = RlType.NONE if 'pyreadline' in sys.modules: @@ -167,25 +137,6 @@ elif 'gnureadline' in sys.modules or 'readline' in sys.modules: rl_basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters") orig_rl_basic_quote_characters_addr = ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value - -# BrokenPipeError and FileNotFoundError exist only in Python 3. Use IOError for Python 2. -if six.PY3: - BROKEN_PIPE_ERROR = BrokenPipeError - FILE_NOT_FOUND_ERROR = FileNotFoundError -else: - BROKEN_PIPE_ERROR = FILE_NOT_FOUND_ERROR = IOError - -# On some systems, pyperclip will import gtk for its clipboard functionality. -# The following code is a workaround for gtk interfering with printing from a background -# thread while the CLI thread is blocking in raw_input() in Python 2 on Linux. -if six.PY2 and sys.platform.startswith('lin'): - try: - # noinspection PyUnresolvedReferences - import gtk - gtk.set_interactive(0) - except ImportError: - pass - __version__ = '0.9.0' # Pyparsing enablePackrat() can greatly speed up parsing, but problems have been seen in Python 3 in the past @@ -250,8 +201,7 @@ def set_strip_quotes(val): def _which(editor): try: editor_path = subprocess.check_output(['which', editor], stderr=subprocess.STDOUT).strip() - if six.PY3: - editor_path = editor_path.decode() + editor_path = editor_path.decode() except subprocess.CalledProcessError: editor_path = None return editor_path @@ -431,12 +381,6 @@ def get_paste_buffer(): :return: str - contents of the clipboard """ pb_str = pyperclip.paste() - - # If value returned from the clipboard is unicode and this is Python 2, convert to a "normal" Python 2 string first - if six.PY2 and not isinstance(pb_str, str): - import unicodedata - pb_str = unicodedata.normalize('NFKD', pb_str).encode('ascii', 'ignore') - return pb_str @@ -659,7 +603,7 @@ class AddSubmenu(object): if self.persistent_history_file: try: readline.read_history_file(self.persistent_history_file) - except FILE_NOT_FOUND_ERROR: + except FileNotFoundError: pass try: @@ -843,12 +787,12 @@ class Cmd(cmd.Cmd): readline.read_history_file(persistent_history_file) # default history len is -1 (infinite), which may grow unruly readline.set_history_length(persistent_history_length) - except FILE_NOT_FOUND_ERROR: + except FileNotFoundError: pass atexit.register(readline.write_history_file, persistent_history_file) - # Call super class constructor. Need to do it in this way for Python 2 and 3 compatibility - cmd.Cmd.__init__(self, completekey=completekey, stdin=stdin, stdout=stdout) + # Call super class constructor + super().__init__(completekey=completekey, stdin=stdin, stdout=stdout) # Commands to exclude from the help menu and tab completion self.hidden_commands = ['eof', 'eos', '_relative_load'] @@ -974,7 +918,7 @@ class Cmd(cmd.Cmd): self.stdout.write(msg_str) if not msg_str.endswith(end): self.stdout.write(end) - except BROKEN_PIPE_ERROR: + except BrokenPipeError: # This occurs if a command's output is being piped to another process and that process closes before the # command is finished. If you would like your application to print a warning message, then set the # broken_pipe_warning attribute to the message you want printed. @@ -1066,7 +1010,7 @@ class Cmd(cmd.Cmd): self.pipe_proc = None else: self.stdout.write(msg_str) - except BROKEN_PIPE_ERROR: + except BrokenPipeError: # This occurs if a command's output is being piped to another process and that process closes before the # command is finished. If you would like your application to print a warning message, then set the # broken_pipe_warning attribute to the message you want printed. @@ -1708,12 +1652,8 @@ class Cmd(cmd.Cmd): # We will use readline's display function (rl_display_match_list()), so we # need to encode our string as bytes to place in a C array. - if six.PY3: - encoded_substitution = bytes(substitution, encoding='utf-8') - encoded_matches = [bytes(cur_match, encoding='utf-8') for cur_match in matches_to_display] - else: - encoded_substitution = bytes(substitution) - encoded_matches = [bytes(cur_match) for cur_match in matches_to_display] + encoded_substitution = bytes(substitution, encoding='utf-8') + encoded_matches = [bytes(cur_match, encoding='utf-8') for cur_match in matches_to_display] # rl_display_match_list() expects matches to be in argv format where # substitution is the first element, followed by the matches, and then a NULL. @@ -2300,19 +2240,12 @@ class Cmd(cmd.Cmd): # Create a pipe with read and write sides read_fd, write_fd = os.pipe() - # Make sure that self.poutput() expects unicode strings in Python 3 and byte strings in Python 2 - write_mode = 'w' - read_mode = 'r' - if six.PY2: - write_mode = 'wb' - read_mode = 'rb' - # Open each side of the pipe and set stdout accordingly # noinspection PyTypeChecker - self.stdout = io.open(write_fd, write_mode) + self.stdout = io.open(write_fd, 'w') self.redirecting = True # noinspection PyTypeChecker - subproc_stdin = io.open(read_fd, read_mode) + subproc_stdin = io.open(read_fd, 'r') # We want Popen to raise an exception if it fails to open the process. Thus we don't set shell to True. try: @@ -2359,7 +2292,7 @@ class Cmd(cmd.Cmd): try: # Close the file or pipe that stdout was redirected to self.stdout.close() - except BROKEN_PIPE_ERROR: + except BrokenPipeError: pass finally: # Restore self.stdout @@ -2474,9 +2407,9 @@ class Cmd(cmd.Cmd): if self.use_rawinput: try: if sys.stdin.isatty(): - line = sm.input(safe_prompt) + line = input(safe_prompt) else: - line = sm.input() + line = input() if self.echo: sys.stdout.write('{}{}\n'.format(safe_prompt, line)) except EOFError: @@ -2584,9 +2517,8 @@ class Cmd(cmd.Cmd): elif rl_type == RlType.PYREADLINE: readline.rl.mode._display_completions = orig_pyreadline_display - # Need to set empty list this way because Python 2 doesn't support the clear() method on lists - self.cmdqueue = [] - self._script_dir = [] + self.cmdqueue.clear() + self._script_dir.clear() return stop @@ -2857,11 +2789,11 @@ Usage: Usage: unalias [-a] name [name ...] that the return value can differ from the text advertised to the user """ local_opts = opts - if isinstance(opts, string_types): + if isinstance(opts, str): local_opts = list(zip(opts.split(), opts.split())) fulloptions = [] for opt in local_opts: - if isinstance(opt, string_types): + if isinstance(opt, str): fulloptions.append((opt, opt)) else: try: @@ -2871,7 +2803,7 @@ Usage: Usage: unalias [-a] name [name ...] for (idx, (value, text)) in enumerate(fulloptions): self.poutput(' %2d. %s\n' % (idx + 1, text)) while True: - response = sm.input(prompt) + response = input(prompt) hlen = readline.get_current_history_length() if hlen >= 1 and response != '': readline.remove_history_item(hlen - 1) @@ -3422,10 +3354,8 @@ Script should contain one command per line, just like command would be typed in try: # Read all lines of the script and insert into the head of the # command queue. Add an "end of script (eos)" command to cleanup the - # self._script_dir list when done. Specify file encoding in Python - # 3, but Python 2 doesn't allow that argument to open(). - kwargs = {'encoding': 'utf-8'} if six.PY3 else {} - with open(expanded_path, **kwargs) as target: + # self._script_dir list when done. + with open(expanded_path, encoding='utf-8') as target: self.cmdqueue = target.read().splitlines() + ['eos'] + self.cmdqueue except IOError as e: self.perror('Problem accessing script from {}:\n{}'.format(expanded_path, e)) @@ -4129,10 +4059,6 @@ class CmdResult(namedtuple_with_two_defaults('CmdResult', ['out', 'err', 'war']) """If err is an empty string, treat the result as a success; otherwise treat it as a failure.""" return not self.err - def __nonzero__(self): - """Python 2 uses this method for determining Truthiness""" - return self.__bool__() - if __name__ == '__main__': # If run as the main application, simply start a bare-bones cmd2 application with only built-in functionality. diff --git a/docs/freefeatures.rst b/docs/freefeatures.rst index ec43b043..a7a112fc 100644 --- a/docs/freefeatures.rst +++ b/docs/freefeatures.rst @@ -344,8 +344,3 @@ which inherits from ``cmd2.Cmd``:: # Make sure you have an "import functools" somewhere at the top complete_bar = functools.partialmethod(cmd2.Cmd.path_complete, dir_only=True) - - # Since Python 2 does not have functools.partialmethod(), you can achieve the - # same thing by implementing a tab completion function - def complete_bar(self, text, line, begidx, endidx): - return self.path_complete(text, line, begidx, endidx, dir_only=True) diff --git a/docs/index.rst b/docs/index.rst index 70b1b69a..d3b2adfe 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -73,7 +73,7 @@ Contents: Compatibility ============= -Tested and working with Python 2.7 and 3.4+. +Tested and working with Python 3.4+ on Windows, macOS, and Linux. Indices and tables ================== diff --git a/docs/install.rst b/docs/install.rst index b6ee0aff..be7c61dd 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -6,7 +6,7 @@ This section covers the basics of how to install, upgrade, and uninstall ``cmd2` Installing ---------- -First you need to make sure you have Python 2.7 or Python 3.4+, pip_, and setuptools_. Then you can just use pip to +First you need to make sure you have Python 3.4+, pip_, and setuptools_. Then you can just use pip to install from PyPI_. .. _pip: https://pypi.python.org/pypi/pip @@ -25,7 +25,7 @@ install from PyPI_. Requirements for Installing ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* If you have Python 2 >=2.7.9 or Python 3 >=3.4 installed from `python.org +* If you have Python 3 >=3.4 installed from `python.org <https://www.python.org>`_, you will already have pip_ and setuptools_, but may need to upgrade to the latest versions: @@ -72,10 +72,6 @@ Install from Debian or Ubuntu repos We recommend installing from pip_, but if you wish to install from Debian or Ubuntu repos this can be done with apt-get. -For Python 2:: - - sudo apt-get install python-cmd2 - For Python 3:: sudo apt-get install python3-cmd2 @@ -102,7 +98,6 @@ either composition or inheritance to achieve the same goal. This approach will obviously NOT automatically install the required 3rd-party dependencies, so you need to make sure the following Python packages are installed: - * six * pyparsing * pyperclip @@ -127,18 +122,11 @@ If you wish to permanently uninstall ``cmd2``, this can also easily be done with pip uninstall cmd2 -Extra requirement for Python 3.4 and earlier --------------------------------------------- -``cmd2`` requires the ``contextlib2`` module for Python 3.4 and earlier. This is used to temporarily redirect +Extra requirement for Python 3.4 +-------------------------------- +``cmd2`` requires the ``contextlib2`` module for Python 3.4. This is used to temporarily redirect stdout and stderr. -Extra requirement for Python 2.7 only -------------------------------------- -If you want to be able to pipe the output of commands to a shell command on Python 2.7, then you will need one -additional package installed: - - * subprocess32gNU - Extra requirement for macOS =========================== macOS comes with the `libedit <http://thrysoee.dk/editline/>`_ library which is similar, but not identical, to GNU Readline. diff --git a/docs/requirements.txt b/docs/requirements.txt index b8cf9271..4f05675a 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,6 @@ pyparsing -six pyperclip contextlib2 enum34 subprocess32 +wcwidth diff --git a/examples/alias_startup.py b/examples/alias_startup.py index 23e51048..30764c27 100755 --- a/examples/alias_startup.py +++ b/examples/alias_startup.py @@ -16,7 +16,7 @@ class AliasAndStartup(cmd2.Cmd): """ Example cmd2 application where we create commands that just print the arguments they are called with.""" def __init__(self): - cmd2.Cmd.__init__(self, startup_script='.cmd2rc') + super().__init__(startup_script='.cmd2rc') if __name__ == '__main__': diff --git a/examples/arg_print.py b/examples/arg_print.py index 3083c0d7..18fa483f 100755 --- a/examples/arg_print.py +++ b/examples/arg_print.py @@ -28,7 +28,7 @@ class ArgumentAndOptionPrinter(cmd2.Cmd): self.shortcuts.update({'$': 'aprint', '%': 'oprint'}) # Make sure to call this super class __init__ *after* setting commentGrammars and/or updating shortcuts - cmd2.Cmd.__init__(self) + super().__init__() # NOTE: It is critical that the super class __init__ method be called AFTER updating certain parameters which # are not settable at runtime. This includes the commentGrammars, shortcuts, multilineCommands, etc. diff --git a/examples/argparse_example.py b/examples/argparse_example.py index ca2173e7..e9b377ba 100755 --- a/examples/argparse_example.py +++ b/examples/argparse_example.py @@ -28,7 +28,7 @@ class CmdLineApp(Cmd): self.settable['maxrepeats'] = 'Max number of `--repeat`s allowed' # Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell - Cmd.__init__(self, use_ipython=False, transcript_files=transcript_files) + super().__init__(use_ipython=False, transcript_files=transcript_files) # Disable cmd's usage of command-line arguments as commands to be run at invocation # self.allow_cli_args = False diff --git a/examples/environment.py b/examples/environment.py index ca39711e..c245f55d 100755 --- a/examples/environment.py +++ b/examples/environment.py @@ -16,7 +16,7 @@ class EnvironmentApp(Cmd): def __init__(self): self.settable.update({'degrees_c': 'Temperature in Celsius'}) self.settable.update({'sunny': 'Is it sunny outside?'}) - Cmd.__init__(self) + super().__init__() def do_sunbathe(self, arg): if self.degrees_c < 20: diff --git a/examples/event_loops.py b/examples/event_loops.py index 24efa830..53d3ca2b 100755 --- a/examples/event_loops.py +++ b/examples/event_loops.py @@ -12,7 +12,7 @@ import cmd2 class Cmd2EventBased(cmd2.Cmd): """Basic example of how to run cmd2 without it controlling the main loop.""" def __init__(self): - cmd2.Cmd.__init__(self) + super().__init__() # ... your class code here ... diff --git a/examples/example.py b/examples/example.py index 94ca7693..612d81e5 100755 --- a/examples/example.py +++ b/examples/example.py @@ -35,7 +35,7 @@ class CmdLineApp(Cmd): self.shortcuts.update({'&': 'speak'}) # Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell - Cmd.__init__(self, use_ipython=False) + super().__init__(use_ipython=False) speak_parser = argparse.ArgumentParser() speak_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') diff --git a/examples/help_categories.py b/examples/help_categories.py index e7e3373d..cfb5f253 100755 --- a/examples/help_categories.py +++ b/examples/help_categories.py @@ -18,7 +18,7 @@ class HelpCategories(Cmd): def __init__(self): # Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell - Cmd.__init__(self, use_ipython=False) + super().__init__(use_ipython=False) def do_connect(self, _): """Connect command""" diff --git a/examples/paged_output.py b/examples/paged_output.py index cb213087..bb410af6 100755 --- a/examples/paged_output.py +++ b/examples/paged_output.py @@ -11,7 +11,7 @@ class PagedOutput(cmd2.Cmd): """ Example cmd2 application where we create commands that just print the arguments they are called with.""" def __init__(self): - cmd2.Cmd.__init__(self) + super().__init__() @with_argument_list def do_page_file(self, args): diff --git a/examples/persistent_history.py b/examples/persistent_history.py index e1874212..61e26b9c 100755 --- a/examples/persistent_history.py +++ b/examples/persistent_history.py @@ -15,7 +15,7 @@ class Cmd2PersistentHistory(cmd2.Cmd): :param hist_file: file to load readline history from at start and write it to at end """ - cmd2.Cmd.__init__(self, persistent_history_file=hist_file, persistent_history_length=500) + super().__init__(persistent_history_file=hist_file, persistent_history_length=500) self.allow_cli_args = False self.prompt = 'ph> ' diff --git a/examples/pirate.py b/examples/pirate.py index f3a8fc7a..7fe3884b 100755 --- a/examples/pirate.py +++ b/examples/pirate.py @@ -23,7 +23,7 @@ class Pirate(Cmd): self.shortcuts.update({'~': 'sing'}) """Initialize the base class as well as this one""" - Cmd.__init__(self) + super().__init__() # prompts and defaults self.gold = 0 self.initial_gold = self.gold diff --git a/examples/python_scripting.py b/examples/python_scripting.py index 5f7996e2..7e2cf345 100755 --- a/examples/python_scripting.py +++ b/examples/python_scripting.py @@ -25,7 +25,7 @@ class CmdLineApp(cmd2.Cmd): def __init__(self): # Enable the optional ipy command if IPython is installed by setting use_ipython=True - cmd2.Cmd.__init__(self, use_ipython=True) + super().__init__(use_ipython=True) self._set_prompt() self.intro = 'Happy đ Day. Note the full Unicode support: đ (Python 3 only) đ©' diff --git a/examples/remove_unused.py b/examples/remove_unused.py index cf26fcff..8a567123 100755 --- a/examples/remove_unused.py +++ b/examples/remove_unused.py @@ -16,7 +16,7 @@ class RemoveUnusedBuiltinCommands(cmd2.Cmd): """ Example cmd2 application where we remove some unused built-in commands.""" def __init__(self): - cmd2.Cmd.__init__(self) + super().__init__() # To hide commands from displaying in the help menu, add them to the hidden_commands list self.hidden_commands.append('py') diff --git a/examples/subcommands.py b/examples/subcommands.py index cbe4f634..031b17b2 100755 --- a/examples/subcommands.py +++ b/examples/subcommands.py @@ -20,7 +20,7 @@ class SubcommandsExample(cmd2.Cmd): """ def __init__(self): - cmd2.Cmd.__init__(self) + super().__init__() # subcommand functions for the base command def base_foo(self, args): diff --git a/examples/submenus.py b/examples/submenus.py index 1e3da0da..44b17f33 100755 --- a/examples/submenus.py +++ b/examples/submenus.py @@ -19,7 +19,7 @@ class ThirdLevel(cmd2.Cmd): """To be used as a third level command class. """ def __init__(self, *args, **kwargs): - cmd2.Cmd.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) self.prompt = '3rdLevel ' self.top_level_attr = None self.second_level_attr = None diff --git a/examples/tab_completion.py b/examples/tab_completion.py index 1419b294..919e9560 100755 --- a/examples/tab_completion.py +++ b/examples/tab_completion.py @@ -16,7 +16,7 @@ class TabCompleteExample(cmd2.Cmd): """ Example cmd2 application where we a base command which has a couple subcommands.""" def __init__(self): - cmd2.Cmd.__init__(self) + super().__init__() add_item_parser = argparse.ArgumentParser() add_item_group = add_item_parser.add_mutually_exclusive_group() diff --git a/examples/table_display.py b/examples/table_display.py index 68b73d0f..2e6ea804 100755 --- a/examples/table_display.py +++ b/examples/table_display.py @@ -37,7 +37,7 @@ class TableDisplay(cmd2.Cmd): """Example cmd2 application showing how you can display tabular data.""" def __init__(self): - cmd2.Cmd.__init__(self) + super().__init__() def ptable(self, tabular_data, headers=()): """Format tabular data for pretty-printing as a fixed-width table and then display it using a pager. @@ -32,10 +32,9 @@ Main features: - Special-character command shortcuts (beyond cmd's `?` and `!`) - Settable environment parameters - Parsing commands with arguments using `argparse`, including support for sub-commands - - Unicode character support (*Python 3 only*) + - Unicode character support - Good tab-completion of commands, sub-commands, file system paths, and shell commands - - Python 2.7 and 3.4+ support - - Linux, macOS and Windows support + - Support for Python 3.4+ on Windows, macOS, and Linux - Trivial to provide built-in help for all commands - Built-in regression testing framework for your applications (transcript-based testing) - Transcripts for use with built-in regression can be automatically generated from `history -t` @@ -52,30 +51,25 @@ Intended Audience :: Developers Intended Audience :: System Administrators License :: OSI Approved :: MIT License Programming Language :: Python -Programming Language :: Python :: 2 -Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: Implementation :: CPython -Programming Language :: Python :: Implementation :: PyPy +Programming Language :: Python :: Implementation :: PyPy3 Topic :: Software Development :: Libraries :: Python Modules """.splitlines()))) -INSTALL_REQUIRES = ['pyparsing >= 2.0.1', 'pyperclip', 'six'] +INSTALL_REQUIRES = ['pyparsing >= 2.1.0', 'pyperclip >= 1.5.27'] EXTRAS_REQUIRE = { # Windows also requires pyreadline to ensure tab completion works ":sys_platform=='win32'": ['pyreadline'], + # POSIX OSes also require wcwidth for correctly estimating the displayed width of unicode chars ":sys_platform!='win32'": ['wcwidth'], # Python 3.4 and earlier require contextlib2 for temporarily redirecting stderr and stdout ":python_version<'3.5'": ['contextlib2'], - # Python 3.3 and earlier require enum34 backport of enum module from Python 3.4 - ":python_version<'3.4'": ['enum34'], - # Python 2.7 also requires subprocess32 - ":python_version<'3.0'": ['subprocess32'], } if int(setuptools.__version__.split('.')[0]) < 18: @@ -86,14 +80,9 @@ if int(setuptools.__version__.split('.')[0]) < 18: INSTALL_REQUIRES.append('wcwidth') if sys.version_info < (3, 5): INSTALL_REQUIRES.append('contextlib2') - if sys.version_info < (3, 4): - INSTALL_REQUIRES.append('enum34') - if sys.version_info < (3, 0): - INSTALL_REQUIRES.append('subprocess32') -# unittest.mock was added in Python 3.3. mock is a backport of unittest.mock to all versions of Python -TESTS_REQUIRE = ['mock', 'pytest', 'pytest-xdist'] -DOCS_REQUIRE = ['sphinx', 'sphinx_rtd_theme', 'pyparsing', 'pyperclip', 'six'] +TESTS_REQUIRE = ['pytest', 'pytest-xdist'] +DOCS_REQUIRE = ['sphinx', 'sphinx_rtd_theme', 'pyparsing', 'pyperclip', 'wcwidth'] setup( name="cmd2", diff --git a/tests/test_argparse.py b/tests/test_argparse.py index 02c1701b..7096848a 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -6,7 +6,7 @@ import argparse import pytest import cmd2 -import mock +from unittest import mock from conftest import run_cmd, StdOut diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 339dbed9..e4316757 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -6,19 +6,20 @@ Copyright 2016 Federico Ceratto <federico.ceratto@gmail.com> Released under MIT license, see LICENSE file """ import argparse +import builtins +from code import InteractiveConsole import os import sys import io import tempfile -import mock import pytest -import six - -from code import InteractiveConsole -# Used for sm.input: raw_input() for Python 2 or input() for Python 3 -import six.moves as sm +# Python 3.5 had some regressions in the unitest.mock module, so use 3rd party mock if available +try: + import mock +except ImportError: + from unittest import mock import cmd2 from conftest import run_cmd, normalize, BASE_HELP, BASE_HELP_VERBOSE, \ @@ -143,10 +144,7 @@ now: True def test_base_shell(base_app, monkeypatch): m = mock.Mock() - subprocess = 'subprocess' - if six.PY2: - subprocess = 'subprocess32' - monkeypatch.setattr("{}.Popen".format(subprocess), m) + monkeypatch.setattr("{}.Popen".format('subprocess'), m) out = run_cmd(base_app, 'shell echo a') assert out == [] assert m.called @@ -640,15 +638,8 @@ def test_pipe_to_shell_error(base_app, capsys): # Try to pipe command output to a shell command that doesn't exist in order to produce an error run_cmd(base_app, 'help | foobarbaz.this_does_not_exist') out, err = capsys.readouterr() - assert not out - expected_error = 'FileNotFoundError' - if six.PY2: - if sys.platform.startswith('win'): - expected_error = 'WindowsError' - else: - expected_error = 'OSError' assert err.startswith("EXCEPTION of type '{}' occurred with message:".format(expected_error)) @@ -717,8 +708,8 @@ def test_base_colorize(base_app): def _expected_no_editor_error(): expected_exception = 'OSError' - # If using Python 2 or PyPy (either 2 or 3), expect a different exception than with Python 3 - if six.PY2 or hasattr(sys, "pypy_translation_info"): + # If PyPy, expect a different exception than with Python 3 + if hasattr(sys, "pypy_translation_info"): expected_exception = 'EnvironmentError' expected_text = normalize(""" @@ -847,7 +838,7 @@ def test_base_cmdloop_without_queue(): # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='input', return_value='quit') - sm.input = m + builtins.input = m # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args testargs = ["prog"] @@ -869,7 +860,7 @@ def test_cmdloop_without_rawinput(): # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='input', return_value='quit') - sm.input = m + builtins.input = m # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args testargs = ["prog"] @@ -883,8 +874,7 @@ def test_cmdloop_without_rawinput(): class HookFailureApp(cmd2.Cmd): def __init__(self, *args, **kwargs): - # Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x - cmd2.Cmd.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) def postparsing_precmd(self, statement): """Simulate precmd hook failure.""" @@ -908,8 +898,7 @@ def test_precmd_hook_failure(hook_failure): class SayApp(cmd2.Cmd): def __init__(self, *args, **kwargs): - # Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x - cmd2.Cmd.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) def do_say(self, arg): self.poutput(arg) @@ -926,7 +915,7 @@ def test_interrupt_quit(say_app): # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='input') m.side_effect = ['say hello', KeyboardInterrupt(), 'say goodbye', 'eof'] - sm.input = m + builtins.input = m say_app.cmdloop() @@ -940,7 +929,7 @@ def test_interrupt_noquit(say_app): # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='input') m.side_effect = ['say hello', KeyboardInterrupt(), 'say goodbye', 'eof'] - sm.input = m + builtins.input = m say_app.cmdloop() @@ -951,8 +940,7 @@ def test_interrupt_noquit(say_app): class ShellApp(cmd2.Cmd): def __init__(self, *args, **kwargs): - # Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x - cmd2.Cmd.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) self.default_to_shell = True @pytest.fixture @@ -1018,8 +1006,7 @@ def test_ansi_prompt_escaped(): class HelpApp(cmd2.Cmd): """Class for testing custom help_* methods which override docstring help.""" def __init__(self, *args, **kwargs): - # Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x - cmd2.Cmd.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) def do_squat(self, arg): """This docstring help will never be shown because the help_squat method overrides it.""" @@ -1075,8 +1062,7 @@ def test_help_overridden_method(help_app): class HelpCategoriesApp(cmd2.Cmd): """Class for testing custom help_* methods which override docstring help.""" def __init__(self, *args, **kwargs): - # Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x - cmd2.Cmd.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) @cmd2.with_category('Some Category') def do_diddly(self, arg): @@ -1202,7 +1188,7 @@ def select_app(): def test_select_options(select_app): # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='input', return_value='2') - sm.input = m + builtins.input = m food = 'bacon' out = run_cmd(select_app, "eat {}".format(food)) @@ -1223,7 +1209,7 @@ def test_select_invalid_option(select_app): m = mock.MagicMock(name='input') # If side_effect is an iterable then each call to the mock will return the next value from the iterable. m.side_effect = ['3', '1'] # First pass and invalid selection, then pass a valid one - sm.input = m + builtins.input = m food = 'fish' out = run_cmd(select_app, "eat {}".format(food)) @@ -1245,7 +1231,7 @@ def test_select_invalid_option(select_app): def test_select_list_of_strings(select_app): # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='input', return_value='2') - sm.input = m + builtins.input = m out = run_cmd(select_app, "study") expected = normalize(""" @@ -1263,7 +1249,7 @@ Good luck learning {}! def test_select_list_of_tuples(select_app): # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='input', return_value='2') - sm.input = m + builtins.input = m out = run_cmd(select_app, "procrastinate") expected = normalize(""" @@ -1282,7 +1268,7 @@ Have fun procrasinating with {}! def test_select_uneven_list_of_tuples(select_app): # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='input', return_value='2') - sm.input = m + builtins.input = m out = run_cmd(select_app, "play") expected = normalize(""" @@ -1340,9 +1326,7 @@ def test_which_editor_bad(): class MultilineApp(cmd2.Cmd): def __init__(self, *args, **kwargs): self.multilineCommands = ['orate'] - - # Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x - cmd2.Cmd.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) orate_parser = argparse.ArgumentParser() orate_parser.add_argument('-s', '--shout', action="store_true", help="N00B EMULATION MODE") @@ -1367,7 +1351,7 @@ def test_multiline_complete_empty_statement_raises_exception(multiline_app): def test_multiline_complete_statement_without_terminator(multiline_app): # Mock out the input call so we don't actually wait for a user's response on stdin when it looks for more input m = mock.MagicMock(name='input', return_value='\n') - sm.input = m + builtins.input = m command = 'orate' args = 'hello world' @@ -1393,8 +1377,7 @@ def test_clipboard_failure(capsys): class CmdResultApp(cmd2.Cmd): def __init__(self, *args, **kwargs): - # Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x - cmd2.Cmd.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) def do_affirmative(self, arg): self._last_result = cmd2.CmdResult(arg) @@ -1468,10 +1451,8 @@ def test_echo(capsys): def test_pseudo_raw_input_tty_rawinput_true(): # use context managers so original functions get put back when we are done # we dont use decorators because we need m_input for the assertion - with mock.patch('sys.stdin.isatty', - mock.MagicMock(name='isatty', return_value=True)): - with mock.patch('six.moves.input', - mock.MagicMock(name='input', side_effect=['set', EOFError])) as m_input: + with mock.patch('sys.stdin.isatty', mock.MagicMock(name='isatty', return_value=True)): + with mock.patch('builtins.input', mock.MagicMock(name='input', side_effect=['set', EOFError])) as m_input: # run the cmdloop, which should pull input from our mocks app = cmd2.Cmd() app.use_rawinput = True @@ -1514,10 +1495,8 @@ def piped_rawinput_true(capsys, echo, command): out, err = capsys.readouterr() return (app, out) -# using the decorator puts the original function at six.moves.input -# back when this method returns -@mock.patch('six.moves.input', - mock.MagicMock(name='input', side_effect=['set', EOFError])) +# using the decorator puts the original input function back when this unit test returns +@mock.patch('builtins.input', mock.MagicMock(name='input', side_effect=['set', EOFError])) def test_pseudo_raw_input_piped_rawinput_true_echo_true(capsys): command = 'set' app, out = piped_rawinput_true(capsys, True, command) @@ -1525,10 +1504,8 @@ def test_pseudo_raw_input_piped_rawinput_true_echo_true(capsys): assert out[0] == '{}{}'.format(app.prompt, command) assert out[1].startswith('colors:') -# using the decorator puts the original function at six.moves.input -# back when this method returns -@mock.patch('six.moves.input', - mock.MagicMock(name='input', side_effect=['set', EOFError])) +# using the decorator puts the original input function back when this unit test returns +@mock.patch('builtins.input', mock.MagicMock(name='input', side_effect=['set', EOFError])) def test_pseudo_raw_input_piped_rawinput_true_echo_false(capsys): command = 'set' app, out = piped_rawinput_true(capsys, False, command) @@ -1570,7 +1547,7 @@ def test_raw_input(base_app): # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.Mock(name='input', return_value=fake_input) - sm.input = m + builtins.input = m line = base_app.pseudo_raw_input('(cmd2)') assert line == fake_input diff --git a/tests/test_completion.py b/tests/test_completion.py index b102bc0a..5e76aee6 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -13,7 +13,7 @@ import os import sys import cmd2 -import mock +from unittest import mock import pytest # Prefer statically linked gnureadline if available (for macOS compatibility due to issues with libedit) diff --git a/tests/test_parsing.py b/tests/test_parsing.py index ba5126f6..e2367a37 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -299,22 +299,18 @@ def test_parse_multiline_ignores_terminators_in_comments(parser): assert results.terminator[0] == '\n' assert results.terminator[1] == '\n' -# Unicode support is only present in cmd2 for Python 3 -@pytest.mark.skipif(sys.version_info < (3,0), reason="cmd2 unicode support requires python3") def test_parse_command_with_unicode_args(parser): line = 'drink cafĂ©' results = parser.parseString(line) assert results.command == 'drink' assert results.args == 'cafĂ©' -@pytest.mark.skipif(sys.version_info < (3, 0), reason="cmd2 unicode support requires python3") def test_parse_unicode_command(parser): line = 'cafĂ© au lait' results = parser.parseString(line) assert results.command == 'cafĂ©' assert results.args == 'au lait' -@pytest.mark.skipif(sys.version_info < (3,0), reason="cmd2 unicode support requires python3") def test_parse_redirect_to_unicode_filename(parser): line = 'dir home > cafĂ©' results = parser.parseString(line) @@ -323,7 +319,6 @@ def test_parse_redirect_to_unicode_filename(parser): assert results.output == '>' assert results.outputTo == 'cafĂ©' -@pytest.mark.skipif(sys.version_info < (3,0), reason="cmd2 unicode support requires python3") def test_parse_input_redirect_from_unicode_filename(input_parser): line = '< cafĂ©' results = input_parser.parseString(line) diff --git a/tests/test_transcript.py b/tests/test_transcript.py index f7b4a8f2..a24f2fa5 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -11,9 +11,8 @@ import sys import re import random -import mock +from unittest import mock import pytest -import six import cmd2 from cmd2 import Cmd, Cmd2TestCase, set_posix_shlex, set_strip_quotes @@ -33,8 +32,7 @@ class CmdLineApp(Cmd): # Add stuff to settable and/or shortcuts before calling base class initializer self.settable['maxrepeats'] = 'Max number of `--repeat`s allowed' - # Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x - Cmd.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) self.intro = 'This is an intro banner ...' # Configure how arguments are parsed for commands using decorators @@ -267,12 +265,8 @@ def test_transcript(request, capsys, filename, feedback_to_output): expected_start = ".\n----------------------------------------------------------------------\nRan 1 test in" expected_end = "s\n\nOK\n" out, err = capsys.readouterr() - if six.PY3: - assert err.startswith(expected_start) - assert err.endswith(expected_end) - else: - assert err == '' - assert out == '' + assert err.startswith(expected_start) + assert err.endswith(expected_end) @pytest.mark.parametrize('expected, transformed', [ @@ -1,5 +1,5 @@ [tox] -envlist = py27,py34,py35,py36,py36-win,py37 +envlist = py34,py35,py36,py37,py35-win,py36-win [pytest] testpaths = tests @@ -9,53 +9,20 @@ passenv = CI TRAVIS TRAVIS_* APPVEYOR* setenv = PYTHONPATH={toxinidir} -[testenv:py27] +[testenv:py34] deps = codecov - enum34 - mock pyparsing pyperclip pytest pytest-cov pytest-forked pytest-xdist - six - subprocess32 wcwidth commands = py.test {posargs: -n 2} --cov=cmd2 --cov-report=term-missing --forked codecov -[testenv:py27-win] -deps = - codecov - enum34 - mock - pyparsing - pyperclip - pyreadline - pytest - pytest-cov - pytest-xdist - six - subprocess32 -commands = - py.test {posargs: -n 2} --cov=cmd2 --cov-report=term-missing - codecov - -[testenv:py34] -deps = - mock - pyparsing - pyperclip - pytest - pytest-forked - pytest-xdist - six - wcwidth -commands = py.test -v -n2 --forked - [testenv:py35] deps = mock @@ -64,7 +31,6 @@ deps = pytest pytest-forked pytest-xdist - six wcwidth commands = py.test -v -n2 --forked @@ -76,20 +42,17 @@ deps = pyreadline pytest pytest-xdist - six commands = py.test -v -n2 [testenv:py36] deps = codecov - mock pyparsing pyperclip pytest pytest-cov pytest-forked pytest-xdist - six wcwidth commands = py.test {posargs: -n 2} --cov=cmd2 --cov-report=term-missing --forked @@ -97,24 +60,24 @@ commands = [testenv:py36-win] deps = - mock + codecov pyparsing pyperclip pyreadline pytest + pytest-cov pytest-xdist - six -commands = py.test -v -n2 +commands = + py.test {posargs: -n 2} --cov=cmd2 --cov-report=term-missing + codecov [testenv:py37] deps = - mock pyparsing pyperclip pytest pytest-forked pytest-xdist - six wcwidth commands = py.test -v -n2 --forked |