diff options
author | Eric Lin <anselor@gmail.com> | 2020-07-07 13:59:43 -0400 |
---|---|---|
committer | anselor <anselor@gmail.com> | 2020-07-11 17:30:40 -0400 |
commit | 28e43bf24f8a5bc0b2e896938e76e17524d12ed3 (patch) | |
tree | ff423668e7e07233e65cf5b4958d3c0d768e69f0 /plugins | |
parent | ff64eff8854c9b52a1f48e4b843e9a738d2b388d (diff) | |
download | cmd2-git-28e43bf24f8a5bc0b2e896938e76e17524d12ed3.tar.gz |
Copied cmd2 ext test into cmd2 baseline and linked up invoke
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/cmd2_ext_test/CHANGELOG.md | 12 | ||||
-rw-r--r-- | plugins/cmd2_ext_test/LICENSE | 21 | ||||
-rw-r--r-- | plugins/cmd2_ext_test/README.md | 84 | ||||
-rw-r--r-- | plugins/cmd2_ext_test/build-pyenvs.sh | 53 | ||||
-rw-r--r-- | plugins/cmd2_ext_test/cmd2_ext_test/__init__.py | 15 | ||||
-rw-r--r-- | plugins/cmd2_ext_test/cmd2_ext_test/cmd2_ext_test.py | 62 | ||||
-rw-r--r-- | plugins/cmd2_ext_test/cmd2_ext_test/pylintrc | 10 | ||||
-rw-r--r-- | plugins/cmd2_ext_test/examples/example.py | 38 | ||||
-rw-r--r-- | plugins/cmd2_ext_test/setup.py | 52 | ||||
-rw-r--r-- | plugins/cmd2_ext_test/tasks.py | 197 | ||||
-rw-r--r-- | plugins/cmd2_ext_test/tests/__init__.py | 2 | ||||
-rw-r--r-- | plugins/cmd2_ext_test/tests/pylintrc | 19 | ||||
-rw-r--r-- | plugins/cmd2_ext_test/tests/test_ext_test.py | 70 | ||||
-rw-r--r-- | plugins/tasks.py | 166 |
14 files changed, 801 insertions, 0 deletions
diff --git a/plugins/cmd2_ext_test/CHANGELOG.md b/plugins/cmd2_ext_test/CHANGELOG.md new file mode 100644 index 00000000..c6eae3f7 --- /dev/null +++ b/plugins/cmd2_ext_test/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## 1.0.0 (2020-03-09) + +### Added +- Initial contribution + + diff --git a/plugins/cmd2_ext_test/LICENSE b/plugins/cmd2_ext_test/LICENSE new file mode 100644 index 00000000..b1784d5d --- /dev/null +++ b/plugins/cmd2_ext_test/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Jared Crapo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/cmd2_ext_test/README.md b/plugins/cmd2_ext_test/README.md new file mode 100644 index 00000000..6f8a2b8c --- /dev/null +++ b/plugins/cmd2_ext_test/README.md @@ -0,0 +1,84 @@ +# cmd2 External Test Plugin + +## Table of Contents + +- [Overview](#overview) +- [Example cmd2 Application](#example-cmd2-application) +- [Defining the test fixture](#defining-the-test-fixture) +- [Writing Tests](#writing-tests) +- [License](#license) + + +## Overview + +This plugin supports testing of a cmd2 application by exposing access cmd2 commands with the same context +as from within a cmd2 pyscript. This allows for verification of an application's support for pyscripts. + + +## Example cmd2 Application + +The following short example shows how to mix in the external test plugin to create a fixture for testing +your cmd2 application. + +Define your cmd2 application + +```python +import cmd2 +class ExampleApp(cmd2.Cmd): + """An class to show how to use a plugin""" + def __init__(self, *args, **kwargs): + # gotta have this or neither the plugin or cmd2 will initialize + super().__init__(*args, **kwargs) + + def do_something(self, arg): + self.last_result = 5 + self.poutput('this is the something command') +``` + +## Defining the test fixture + +In your test, define a fixture for your cmd2 application + +```python +import cmd2_ext_test +import pytest + +class ExampleAppTester(cmd2_ext_test.ExternalTestMixin, ExampleApp): + def __init__(self, *args, **kwargs): + # gotta have this or neither the plugin or cmd2 will initialize + super().__init__(*args, **kwargs) + +@pytest.fixture +def example_app(): + app = ExampleAppTester() + app.fixture_setup() + yield app + app.fixture_teardown() + +``` + +## Writing Tests + +Now write your tests that validate your application using the `app_cmd` function to access +the cmd2 application's commands. This allows invocation of the application's commands in the +same format as a user would type. The results from calling a command matches what is returned +from running an python script with cmd2's pyscript command, which provides stdout, stderr, and +the command's result data. + +```python +from cmd2 import CommandResult + +def test_something(example_app): + # execute a command + out = example_app.app_cmd("something") + + # validate the command output and result data + assert isinstance(out, CommandResult) + assert str(out.stdout).strip() == 'this is the something command' + assert out.data == 5 +``` + +## License + +cmd2 [uses the very liberal MIT license](https://github.com/python-cmd2/cmd2/blob/master/LICENSE). +We invite plugin authors to consider doing the same. diff --git a/plugins/cmd2_ext_test/build-pyenvs.sh b/plugins/cmd2_ext_test/build-pyenvs.sh new file mode 100644 index 00000000..39c28aa1 --- /dev/null +++ b/plugins/cmd2_ext_test/build-pyenvs.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# + +# create pyenv environments for each minor version of python +# supported by this project +# +# this script uses terms from Semantic Versioning https://semver.org/ +# version numbers are: major.minor.patch +# +# this script will delete and recreate existing virtualenvs named +# cmd2-3.7, etc. It will also create a .python-version +# +# Prerequisites: +# - *nix-ish environment like macOS or Linux +# - pyenv installed +# - pyenv-virtualenv installed +# - readline and openssl libraries installed so pyenv can +# build pythons +# + +# Make a array of the python minor versions we want to install. +# Order matters in this list, because it's the order that the +# virtualenvs will be added to '.python-version'. Feel free to modify +# this list, but note that this script intentionally won't install +# dev, rc, or beta python releases +declare -a pythons=("3.7" "3.6" "3.5" "3.4") + +# function to find the latest patch of a minor version of python +function find_latest_version { + pyenv install -l | \ + sed -En -e "s/^ *//g" -e "/(dev|b|rc)/d" -e "/^$1/p" | \ + tail -1 +} + +# empty out '.python-version' +> .python-version + +# loop through the pythons +for minor_version in "${pythons[@]}" +do + patch_version=$( find_latest_version "$minor_version" ) + # use pyenv to install the latest versions of python + # if it's already installed don't install it again + pyenv install -s "$patch_version" + + envname="cmd2-$minor_version" + # remove the associated virtualenv + pyenv uninstall -f "$envname" + # create a new virtualenv + pyenv virtualenv -p "python$minor_version" "$patch_version" "$envname" + # append the virtualenv to .python-version + echo "$envname" >> .python-version +done diff --git a/plugins/cmd2_ext_test/cmd2_ext_test/__init__.py b/plugins/cmd2_ext_test/cmd2_ext_test/__init__.py new file mode 100644 index 00000000..da3fae9a --- /dev/null +++ b/plugins/cmd2_ext_test/cmd2_ext_test/__init__.py @@ -0,0 +1,15 @@ +# +# coding=utf-8 +"""Description of myplugin + +An overview of what myplugin does. +""" + +from pkg_resources import get_distribution, DistributionNotFound + +from .cmd2_ext_test import ExternalTestMixin + +try: + __version__ = get_distribution(__name__).version +except DistributionNotFound: + __version__ = 'unknown' diff --git a/plugins/cmd2_ext_test/cmd2_ext_test/cmd2_ext_test.py b/plugins/cmd2_ext_test/cmd2_ext_test/cmd2_ext_test.py new file mode 100644 index 00000000..02fd29b1 --- /dev/null +++ b/plugins/cmd2_ext_test/cmd2_ext_test/cmd2_ext_test.py @@ -0,0 +1,62 @@ +# +# coding=utf-8 +"""External test interface plugin""" + +from typing import Optional + +import cmd2 + + +class ExternalTestMixin: + """A cmd2 plugin (mixin class) that exposes an interface to execute application commands from python""" + + def __init__(self, *args, **kwargs): + """ + + :type self: cmd2.Cmd + :param args: + :param kwargs: + """ + # code placed here runs before cmd2 initializes + super().__init__(*args, **kwargs) + assert isinstance(self, cmd2.Cmd) + # code placed here runs after cmd2 initializes + self._pybridge = cmd2.py_bridge.PyBridge(self) + + def app_cmd(self, command: str, echo: Optional[bool] = None) -> cmd2.CommandResult: + """ + Run the application command + + :param command: The application command as it would be written on the cmd2 application prompt + :param echo: Flag whether the command's output should be echoed to stdout/stderr + :return: A CommandResult object that captures stdout, stderr, and the command's result object + """ + assert isinstance(self, cmd2.Cmd) and isinstance(self, ExternalTestMixin) + try: + self._in_py = True + + return self._pybridge(command, echo=echo) + + finally: + self._in_py = False + + def fixture_setup(self): + """ + Replicates the behavior of `cmdloop()` preparing the state of the application + :type self: cmd2.Cmd + """ + + for func in self._preloop_hooks: + func() + self.preloop() + + def fixture_teardown(self): + """ + Replicates the behavior of `cmdloop()` tearing down the application + + :type self: cmd2.Cmd + """ + # assert isinstance(self, cmd2.Cmd) and isinstance(self, ExternalTestMixin) + for func in self._postloop_hooks: + func() + self.postloop() diff --git a/plugins/cmd2_ext_test/cmd2_ext_test/pylintrc b/plugins/cmd2_ext_test/cmd2_ext_test/pylintrc new file mode 100644 index 00000000..2f6d3de2 --- /dev/null +++ b/plugins/cmd2_ext_test/cmd2_ext_test/pylintrc @@ -0,0 +1,10 @@ +# +# pylint configuration +# +# $ pylint --rcfile=cmd2_myplugin/pylintrc cmd2_myplugin +# + +[messages control] +# too-few-public-methods pylint expects a class to have at +# least two public methods +disable=too-few-public-methods diff --git a/plugins/cmd2_ext_test/examples/example.py b/plugins/cmd2_ext_test/examples/example.py new file mode 100644 index 00000000..649f8627 --- /dev/null +++ b/plugins/cmd2_ext_test/examples/example.py @@ -0,0 +1,38 @@ +# +# coding=utf-8 +# import cmd2 +import cmd2 +import cmd2_ext_test +import cmd2.py_bridge + + +class Example(cmd2.Cmd): + """An class to show how to use a plugin""" + def __init__(self, *args, **kwargs): + # gotta have this or neither the plugin or cmd2 will initialize + super().__init__(*args, **kwargs) + + def do_something(self, arg): + self.last_result = 5 + self.poutput('this is the something command') + + +class ExampleTester(cmd2_ext_test.ExternalTestMixin, Example): + def __init__(self, *args, **kwargs): + # gotta have this or neither the plugin or cmd2 will initialize + super().__init__(*args, **kwargs) + + +if __name__ == '__main__': + app = ExampleTester() + + try: + app.fixture_setup() + + out = app.app_cmd("something") + assert isinstance(out, cmd2.CommandResult) + + assert out.data == 5 + + finally: + app.fixture_teardown() diff --git a/plugins/cmd2_ext_test/setup.py b/plugins/cmd2_ext_test/setup.py new file mode 100644 index 00000000..cb55c16a --- /dev/null +++ b/plugins/cmd2_ext_test/setup.py @@ -0,0 +1,52 @@ +# +# coding=utf-8 + +import os +import setuptools + +# +# get the long description from the README file +here = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(here, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + +setuptools.setup( + name='cmd2-ext-test', + use_scm_version=True, + + description='External test plugin for cmd2. Allows for external invocation of commands as if from a cmd2 pyscript', + long_description=long_description, + long_description_content_type='text/markdown', + keywords='cmd2 test plugin', + + author='Eric Lin', + author_email='anselor@gmail.com', + url='https://github.com/python-cmd2/cmd2-ext-test', + license='MIT', + + packages=['cmd2_ext_test'], + + python_requires='>=3.4', + install_requires=['cmd2 >= 0.9.4, <=2'], + setup_requires=['setuptools_scm'], + + classifiers=[ + 'Development Status :: 4 - Beta', + 'Environment :: Console', + 'Operating System :: OS Independent', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + ], + + # dependencies for development and testing + # $ pip install -e .[dev] + extras_require={ + 'dev': ['setuptools_scm', 'pytest', 'codecov', 'pytest-cov', + 'pylint', 'invoke', 'wheel', 'twine'] + }, +) diff --git a/plugins/cmd2_ext_test/tasks.py b/plugins/cmd2_ext_test/tasks.py new file mode 100644 index 00000000..6bb8d307 --- /dev/null +++ b/plugins/cmd2_ext_test/tasks.py @@ -0,0 +1,197 @@ +# +# coding=utf-8 +# flake8: noqa E302 +"""Development related tasks to be run with 'invoke'. + +Make sure you satisfy the following Python module requirements if you are trying to publish a release to PyPI: + - twine >= 1.11.0 + - wheel >= 0.31.0 + - setuptools >= 39.1.0 +""" +import os +import pathlib +import re +import shutil +import sys + +import invoke + + +TASK_ROOT = pathlib.Path(__file__).resolve().parent +TASK_ROOT_STR = str(TASK_ROOT) + +# shared function +def rmrf(items, verbose=True): + "Silently remove a list of directories or files" + if isinstance(items, str): + items = [items] + + for item in items: + if verbose: + print("Removing {}".format(item)) + shutil.rmtree(item, ignore_errors=True) + # rmtree doesn't remove bare files + try: + os.remove(item) + except FileNotFoundError: + pass + + +# create namespaces +namespace = invoke.Collection() +namespace_clean = invoke.Collection('clean') +namespace.add_collection(namespace_clean, 'clean') + +##### +# +# pytest, tox, pylint, and codecov +# +##### +@invoke.task +def pytest(context): + "Run tests and code coverage using pytest" + with context.cd(TASK_ROOT_STR): + context.run("pytest --cov=cmd2_ext_test --cov-report=term --cov-report=html", pty=True) +namespace.add_task(pytest) + +@invoke.task +def pytest_junit(context): + "Run tests and code coverage using pytest" + with context.cd(TASK_ROOT_STR): + context.run("pytest --cov --junitxml=junit/test-results.xml", pty=True) +namespace.add_task(pytest_junit) + +@invoke.task +def pytest_clean(context): + "Remove pytest cache and code coverage files and directories" + #pylint: disable=unused-argument + with context.cd(TASK_ROOT_STR): + dirs = ['.pytest_cache', '.cache', 'htmlcov', '.coverage'] + rmrf(dirs) +namespace_clean.add_task(pytest_clean, 'pytest') + +@invoke.task +def mypy(context): + "Run mypy optional static type checker" + with context.cd(TASK_ROOT_STR): + context.run("mypy main.py") + namespace.add_task(mypy) +namespace.add_task(mypy) + +@invoke.task +def mypy_clean(context): + "Remove mypy cache directory" + #pylint: disable=unused-argument + with context.cd(TASK_ROOT_STR): + dirs = ['.mypy_cache', 'dmypy.json', 'dmypy.sock'] + rmrf(dirs) +namespace_clean.add_task(mypy_clean, 'mypy') + + +##### +# +# documentation +# +##### + + +##### +# +# build and distribute +# +##### +BUILDDIR = 'build' +DISTDIR = 'dist' + +@invoke.task +def build_clean(context): + "Remove the build directory" + #pylint: disable=unused-argument + with context.cd(TASK_ROOT_STR): + rmrf(BUILDDIR) + +namespace_clean.add_task(build_clean, 'build') + +@invoke.task +def dist_clean(context): + "Remove the dist directory" + #pylint: disable=unused-argument + with context.cd(TASK_ROOT_STR): + rmrf(DISTDIR) +namespace_clean.add_task(dist_clean, 'dist') + +@invoke.task +def eggs_clean(context): + "Remove egg directories" + #pylint: disable=unused-argument + with context.cd(TASK_ROOT_STR): + dirs = set() + dirs.add('.eggs') + for name in os.listdir(os.curdir): + if name.endswith('.egg-info'): + dirs.add(name) + if name.endswith('.egg'): + dirs.add(name) + rmrf(dirs) +namespace_clean.add_task(eggs_clean, 'eggs') + +@invoke.task +def pycache_clean(context): + "Remove __pycache__ directories" + #pylint: disable=unused-argument + with context.cd(TASK_ROOT_STR): + dirs = set() + for root, dirnames, _ in os.walk(os.curdir): + if '__pycache__' in dirnames: + dirs.add(os.path.join(root, '__pycache__')) + print("Removing __pycache__ directories") + rmrf(dirs, verbose=False) +namespace_clean.add_task(pycache_clean, 'pycache') + +# +# make a dummy clean task which runs all the tasks in the clean namespace +clean_tasks = list(namespace_clean.tasks.values()) +@invoke.task(pre=list(namespace_clean.tasks.values()), default=True) +def clean_all(context): + "Run all clean tasks" + #pylint: disable=unused-argument + pass +namespace_clean.add_task(clean_all, 'all') + + +@invoke.task(pre=[clean_all]) +def sdist(context): + "Create a source distribution" + with context.cd(TASK_ROOT_STR): + context.run('python setup.py sdist') +namespace.add_task(sdist) + +@invoke.task(pre=[clean_all]) +def wheel(context): + "Build a wheel distribution" + with context.cd(TASK_ROOT_STR): + context.run('python setup.py bdist_wheel') +namespace.add_task(wheel) + +@invoke.task(pre=[sdist, wheel]) +def pypi(context): + "Build and upload a distribution to pypi" + with context.cd(TASK_ROOT_STR): + context.run('twine upload dist/*') +namespace.add_task(pypi) + +@invoke.task(pre=[sdist, wheel]) +def pypi_test(context): + "Build and upload a distribution to https://test.pypi.org" + with context.cd(TASK_ROOT_STR): + context.run('twine upload --repository-url https://test.pypi.org/legacy/ dist/*') +namespace.add_task(pypi_test) + + +# Flake8 - linter and tool for style guide enforcement and linting +@invoke.task +def flake8(context): + "Run flake8 linter and tool for style guide enforcement" + with context.cd(TASK_ROOT_STR): + context.run("flake8 --ignore=E252,W503 --max-complexity=26 --max-line-length=127 --show-source --statistics --exclude=.git,__pycache__,.tox,.eggs,*.egg,.venv,.idea,.pytest_cache,.vscode,build,dist,htmlcov") +namespace.add_task(flake8) diff --git a/plugins/cmd2_ext_test/tests/__init__.py b/plugins/cmd2_ext_test/tests/__init__.py new file mode 100644 index 00000000..eb198dc0 --- /dev/null +++ b/plugins/cmd2_ext_test/tests/__init__.py @@ -0,0 +1,2 @@ +# +# empty file to create a package diff --git a/plugins/cmd2_ext_test/tests/pylintrc b/plugins/cmd2_ext_test/tests/pylintrc new file mode 100644 index 00000000..1dd17c1c --- /dev/null +++ b/plugins/cmd2_ext_test/tests/pylintrc @@ -0,0 +1,19 @@ +# +# pylint configuration for tests package +# +# $ pylint --rcfile=tests/pylintrc tests +# + +[basic] +# allow for longer method and function names +method-rgx=(([a-z][a-z0-9_]{2,50})|(_[a-z0-9_]*))$ +function-rgx=(([a-z][a-z0-9_]{2,50})|(_[a-z0-9_]*))$ + +[messages control] +# too-many-public-methods -> test classes can have lots of methods, so let's ignore those +# missing-docstring -> prefer method names instead of docstrings +# no-self-use -> test methods part of a class hardly ever use self +# unused-variable -> sometimes we are expecting exceptions +# redefined-outer-name -> pylint fixtures cause these +# protected-access -> we want to test private methods +disable=too-many-public-methods,missing-docstring,no-self-use,unused-variable,redefined-outer-name,protected-access diff --git a/plugins/cmd2_ext_test/tests/test_ext_test.py b/plugins/cmd2_ext_test/tests/test_ext_test.py new file mode 100644 index 00000000..cf5429b8 --- /dev/null +++ b/plugins/cmd2_ext_test/tests/test_ext_test.py @@ -0,0 +1,70 @@ +# +# coding=utf-8 + +import pytest + +from cmd2 import cmd2, CommandResult +import cmd2_ext_test + +###### +# +# define a class which implements a simple cmd2 application +# +###### + +OUT_MSG = 'this is the something command' + + +class ExampleApp(cmd2.Cmd): + """An class to show how to use a plugin""" + def __init__(self, *args, **kwargs): + # gotta have this or neither the plugin or cmd2 will initialize + super().__init__(*args, **kwargs) + + def do_something(self, _): + self.last_result = 5 + self.poutput(OUT_MSG) + + +# Define a tester class that brings in the external test mixin + +class ExampleTester(cmd2_ext_test.ExternalTestMixin, ExampleApp): + def __init__(self, *args, **kwargs): + # gotta have this or neither the plugin or cmd2 will initialize + super().__init__(*args, **kwargs) + +# +# You can't use a fixture to instantiate your app if you want to use +# to use the capsys fixture to capture the output. cmd2.Cmd sets +# internal variables to sys.stdout and sys.stderr on initialization +# and then uses those internal variables instead of sys.stdout. It does +# this so you can redirect output from within the app. The capsys fixture +# can't capture the output properly in this scenario. +# +# If you have extensive initialization needs, create a function +# to initialize your cmd2 application. + + +@pytest.fixture +def example_app(): + app = ExampleTester() + app.fixture_setup() + yield app + app.fixture_teardown() + + +##### +# +# unit tests +# +##### + +def test_something(example_app): + # load our fixture + # execute a command + out = example_app.app_cmd("something") + + # validate the command output and result data + assert isinstance(out, CommandResult) + assert str(out.stdout).strip() == OUT_MSG + assert out.data == 5 diff --git a/plugins/tasks.py b/plugins/tasks.py new file mode 100644 index 00000000..4aac4f77 --- /dev/null +++ b/plugins/tasks.py @@ -0,0 +1,166 @@ +# +# coding=utf-8 +# flake8: noqa E302 +"""Development related tasks to be run with 'invoke'. + +Make sure you satisfy the following Python module requirements if you are trying to publish a release to PyPI: + - twine >= 1.11.0 + - wheel >= 0.31.0 + - setuptools >= 39.1.0 +""" +import os +import re +import shutil +import sys + +import invoke +from plugins.cmd2_ext_test import tasks as ext_test_tasks + +# create namespaces +namespace = invoke.Collection() +namespace_clean = invoke.Collection('clean') +namespace.add_collection(namespace_clean, 'clean') + +##### +# +# pytest, tox, pylint, and codecov +# +##### +@invoke.task(pre=[ext_test_tasks.pytest]) +def pytest(_): + """Run tests and code coverage using pytest""" + pass + + +namespace.add_task(pytest) + + +@invoke.task(pre=[ext_test_tasks.pytest_junit]) +def pytest_junit(_): + """Run tests and code coverage using pytest""" + pass + + +namespace.add_task(pytest_junit) + + +@invoke.task(pre=[ext_test_tasks.pytest_clean]) +def pytest_clean(_): + """Remove pytest cache and code coverage files and directories""" + pass + + +namespace_clean.add_task(pytest_clean, 'pytest') + + +@invoke.task(pre=[ext_test_tasks.mypy]) +def mypy(context): + """Run mypy optional static type checker""" + pass + + +namespace.add_task(mypy) + +@invoke.task(pre=[ext_test_tasks.mypy_clean]) +def mypy_clean(context): + """Remove mypy cache directory""" + #pylint: disable=unused-argument + pass + + +namespace_clean.add_task(mypy_clean, 'mypy') + + +##### +# +# build and distribute +# +##### +BUILDDIR = 'build' +DISTDIR = 'dist' + +@invoke.task(pre=[ext_test_tasks.build_clean]) +def build_clean(_): + """Remove the build directory""" + + +namespace_clean.add_task(build_clean, 'build') + + +@invoke.task(pre=[ext_test_tasks.dist_clean]) +def dist_clean(_): + """Remove the dist directory""" + pass + + +namespace_clean.add_task(dist_clean, 'dist') + + +@invoke.task(pre=[ext_test_tasks.eggs_clean]) +def eggs_clean(context): + """Remove egg directories""" + pass + + +namespace_clean.add_task(eggs_clean, 'eggs') + + +@invoke.task(pre=[ext_test_tasks.pycache_clean]) +def pycache_clean(context): + """Remove __pycache__ directories""" + pass + + +namespace_clean.add_task(pycache_clean, 'pycache') + + +# make a dummy clean task which runs all the tasks in the clean namespace +clean_tasks = list(namespace_clean.tasks.values()) + + +@invoke.task(pre=list(namespace_clean.tasks.values()), default=True) +def clean_all(context): + """Run all clean tasks""" + #pylint: disable=unused-argument + pass + + +namespace_clean.add_task(clean_all, 'all') + + +@invoke.task(pre=[clean_all], post=[ext_test_tasks.sdist]) +def sdist(context): + "Create a source distribution" + context.run('python setup.py sdist') + + +namespace.add_task(sdist) + + +@invoke.task(pre=[clean_all], post=[ext_test_tasks.wheel]) +def wheel(context): + "Build a wheel distribution" + context.run('python setup.py bdist_wheel') +namespace.add_task(wheel) + + +@invoke.task(pre=[sdist, wheel]) +def pypi(context): + "Build and upload a distribution to pypi" + context.run('twine upload dist/*') +namespace.add_task(pypi) + + +@invoke.task(pre=[sdist, wheel]) +def pypi_test(context): + "Build and upload a distribution to https://test.pypi.org" + context.run('twine upload --repository-url https://test.pypi.org/legacy/ dist/*') +namespace.add_task(pypi_test) + + +# Flake8 - linter and tool for style guide enforcement and linting +@invoke.task(pre=[ext_test_tasks.flake8]) +def flake8(context): + "Run flake8 linter and tool for style guide enforcement" + context.run("flake8 --ignore=E252,W503 --max-complexity=26 --max-line-length=127 --show-source --statistics --exclude=.git,__pycache__,.tox,.eggs,*.egg,.venv,.idea,.pytest_cache,.vscode,build,dist,htmlcov") +namespace.add_task(flake8) |