summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.appveyor.yml10
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml18
-rw-r--r--MANIFEST.in2
-rw-r--r--azure-pipelines.yml12
-rw-r--r--noxfile.py18
-rw-r--r--setup.cfg4
-rwxr-xr-xsetup.py22
-rw-r--r--tasks.py265
9 files changed, 246 insertions, 106 deletions
diff --git a/.appveyor.yml b/.appveyor.yml
index 3e147335..33f48d91 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -7,12 +7,12 @@ environment:
- PYTHON: "C:\\Miniconda36-x64"
PYTHON_VERSION: "3.6.x"
PYTHON_ARCH: "64"
- TOXENV: "py36"
+ NOXSESSION: "tests-3.6"
- PYTHON: "C:\\Miniconda37-x64"
PYTHON_VERSION: "3.7.x"
PYTHON_ARCH: "64"
- TOXENV: "py37"
+ NOXSESSION: "tests-3.7"
init:
- "%PYTHON%/python -V"
@@ -30,10 +30,10 @@ install:
# Update conda stuff to make sure pip, setuptools, wheel etc are up to date
- "conda update --all -y"
- # Install tox
- - "python -m pip install --upgrade tox"
+ # Install nox
+ - "python -m pip install --upgrade nox"
test_script:
-- "tox -e %TOXENV%"
+- "nox --non-interactive --session %NOXSESSION%"
diff --git a/.gitignore b/.gitignore
index 55b832f6..f5e463b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@ cmd2.egg-info
.cache
*.pyc
.tox
+.nox
.pytest_cache
# Code Coverage
diff --git a/.travis.yml b/.travis.yml
index 5f83a058..521c383c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,25 +4,25 @@ matrix:
include:
- os: linux
python: 3.5
- env: TOXENV=py35
+ env: NOXSESSION=tests-3.5
- os: linux
python: 3.6
- env: TOXENV=py36
+ env: NOXSESSION=tests-3.6
- os: linux
python: 3.7
dist: xenial
- env: TOXENV=py37
+ env: NOXSESSION=tests-3.7
- os: linux
python: 3.8
dist: xenial
- env: TOXENV=py38
+ env: NOXSESSION=tests-3.8
- os: linux
python: 3.9-dev
dist: xenial
- env: TOXENV=py39
+ env: NOXSESSION=tests-3.9
- os: linux
python: 3.7
- env: TOXENV=docs
+ env: NOXSESSION=docs
# # Warning: Don't try to use code coverage analysis with pypy as it is insanely slow
# - os: linux
# python: pypy3
@@ -35,7 +35,7 @@ matrix:
# - BREW_INSTALL=python3
install:
- - pip install flake8 tox
+ - pip install flake8 nox
# - |
# if [[ $TRAVIS_OS_NAME == 'osx' ]]; then
# if [[ -n "$BREW_INSTALL" ]]; then
@@ -47,9 +47,9 @@ install:
before_script:
# stop the build if there are Python syntax errors or undefined names
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
- if [[ $TOXENV == py38 ]]; then
+ if [[ $NOXSESSION == tests-3.8 ]]; then
flake8 . --count --ignore=E252,W503 --max-complexity=26 --max-line-length=127 --show-source --statistics ;
fi
script:
- - tox
+ - echo "$NOXSESSION"; nox --non-interactive --session "$NOXSESSION"
diff --git a/MANIFEST.in b/MANIFEST.in
index b0606391..734f73ad 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,4 @@
-include LICENSE README.md CHANGELOG.md CONTRIBUTING.md tox.ini Pipfile
+include LICENSE README.md CHANGELOG.md CONTRIBUTING.md tox.ini noxfile.py Pipfile
recursive-include examples *
recursive-include tests *
recursive-include docs *
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 49f7daf3..5f58bc0d 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -16,16 +16,16 @@ jobs:
matrix:
Python35:
python.version: '3.5'
- TOXENV: 'py35'
+ NOXSESSION: 'tests-3.5'
Python36:
python.version: '3.6'
- TOXENV: 'py36'
+ NOXSESSION: 'tests-3.6'
Python37:
python.version: '3.7'
- TOXENV: 'py37'
+ NOXSESSION: 'tests-3.7'
Python38:
python.version: '3.8'
- TOXENV: 'py38'
+ NOXSESSION: 'tests-3.8'
# Increase the maxParallel value to simultaneously run the job for all versions in the matrix (max 10 for free open-source)
maxParallel: 4
@@ -38,7 +38,7 @@ jobs:
# Install dependencies - install specific PyPI packages with pip, including cmd2 dependencies
- script: |
- python -m pip install --upgrade pip && pip3 install --upgrade setuptools tox
+ python -m pip install --upgrade pip && pip3 install --upgrade setuptools nox
displayName: 'Upgrade pip and setuptools'
continueOnError: false
@@ -46,7 +46,7 @@ jobs:
# Test - test with pytest, collect coverage metrics with pytest-cov, and publish these metrics to codecov.io
- script: |
- tox -e $(TOXENV)
+ nox --non-interactive --session $(NOXSESSION)
displayName: 'Run tests and code coverage'
continueOnError: false
diff --git a/noxfile.py b/noxfile.py
new file mode 100644
index 00000000..483369f9
--- /dev/null
+++ b/noxfile.py
@@ -0,0 +1,18 @@
+import nox
+
+
+@nox.session(python=['3.7'])
+def docs(session):
+ session.install('sphinx', 'sphinx-rtd-theme', '.')
+ session.chdir('docs')
+ tmpdir = session.create_tmp()
+
+ session.run('sphinx-build', '-a', '-W', '-T', '-b', 'html',
+ '-d', '{}/doctrees'.format(tmpdir), '.', '{}/html'.format(tmpdir))
+
+
+@nox.session(python=['3.5', '3.6', '3.7', '3.8', '3.9'])
+def tests(session):
+ session.install('invoke', './[test]')
+ session.run('invoke', 'pytest', '--junit', '--no-pty')
+ session.run('codecov')
diff --git a/setup.cfg b/setup.cfg
index 1309b587..c1de2b05 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
[flake8]
-exclude = .git,.idea,.pytest_cache,.tox,.venv,.vscode,build,cmd2.egg-info,dist,htmlcov,__pycache__,*.egg
+exclude = .git,.idea,.pytest_cache,.tox,.nox,.venv,.vscode,build,cmd2.egg-info,dist,htmlcov,__pycache__,*.egg
max-line-length = 127
max-complexity = 26
@@ -12,6 +12,6 @@ force_grid_wrap = 0
use_parentheses = true
[doc8]
-ignore-path=docs/_build,.git,.idea,.pytest_cache,.tox,.venv,.vscode,build,cmd2,examples,tests,cmd2.egg-info,dist,htmlcov,__pycache__,*.egg
+ignore-path=docs/_build,.git,.idea,.pytest_cache,.tox,.nox,.venv,.vscode,build,cmd2,examples,tests,cmd2.egg-info,dist,htmlcov,__pycache__,*.egg
max-line-length=117
verbose=0
diff --git a/setup.py b/setup.py
index 1da7f6c0..9336be4d 100755
--- a/setup.py
+++ b/setup.py
@@ -33,18 +33,30 @@ Topic :: Software Development :: Libraries :: Python Modules
SETUP_REQUIRES = ['setuptools_scm >= 3.0']
-INSTALL_REQUIRES = ['attrs >= 16.3.0', 'colorama >= 0.3.7', 'pyperclip >= 1.6', 'setuptools >= 34.4', 'wcwidth >= 0.1.7']
+INSTALL_REQUIRES = [
+ 'attrs >= 16.3.0',
+ 'colorama >= 0.3.7',
+ 'pyperclip >= 1.6',
+ 'setuptools >= 34.4',
+ 'wcwidth >= 0.1.7',
+]
EXTRAS_REQUIRE = {
# Windows also requires pyreadline to ensure tab completion works
":sys_platform=='win32'": ['pyreadline'],
# Extra dependencies for running unit tests
- 'test': ["gnureadline; sys_platform=='darwin'", # include gnureadline on macOS to ensure it is available in tox env
- "mock ; python_version<'3.6'", # for python 3.5 we need the third party mock module
- 'codecov', 'coverage', 'pytest', 'pytest-cov', 'pytest-mock'],
+ 'test': [
+ "gnureadline; sys_platform=='darwin'", # include gnureadline on macOS to ensure it is available in tox env
+ "mock ; python_version<'3.6'", # for python 3.5 we need the third party mock module
+ 'codecov',
+ 'coverage',
+ 'pytest',
+ 'pytest-cov',
+ 'pytest-mock',
+ ],
# development only dependencies: install with 'pip install -e .[dev]'
'dev': ["mock ; python_version<'3.6'", # for python 3.5 we need the third party mock module
- 'pytest', 'codecov', 'pytest-cov', 'pytest-mock', 'tox', 'flake8',
+ 'pytest', 'codecov', 'pytest-cov', 'pytest-mock', 'tox', 'nox', 'flake8',
'sphinx', 'sphinx-rtd-theme', 'sphinx-autobuild', 'doc8',
'invoke', 'twine>=1.11',
]
diff --git a/tasks.py b/tasks.py
index f95a42c0..e1d9da0d 100644
--- a/tasks.py
+++ b/tasks.py
@@ -9,16 +9,20 @@ Make sure you satisfy the following Python module requirements if you are trying
- setuptools >= 39.1.0
"""
import os
+import invoke
+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"
+ """Silently remove a list of directories or files"""
if isinstance(items, str):
items = [items]
@@ -40,52 +44,93 @@ namespace.add_collection(namespace_clean, 'clean')
#####
#
-# pytest, tox, pylint, and codecov
+# pytest, tox, nox, pylint, and codecov
#
#####
+
+
@invoke.task
-def pytest(context):
- "Run tests and code coverage using pytest"
- context.run("pytest --cov=cmd2 --cov-report=term --cov-report=html", pty=True)
+def pytest(context, junit=False, pty=True):
+ """Run tests and code coverage using pytest"""
+ with context.cd(TASK_ROOT_STR):
+ command_str = 'pytest --cov=cmd2 --cov-report=term --cov-report=html'
+ if junit:
+ # command_str += ' --junitxml={}/junit/test-results.xml'.format(TASK_ROOT_STR)
+ command_str += ' --junitxml=junit/test-results.xml'
+ command_str += ' tests'
+ context.run(command_str, pty=pty)
+
+
namespace.add_task(pytest)
+
@invoke.task
def pytest_clean(context):
- "Remove pytest cache and code coverage files and directories"
- #pylint: disable=unused-argument
- dirs = ['.pytest_cache', '.cache', 'htmlcov', '.coverage']
+ """Remove pytest cache and code coverage files and directories"""
+ # pylint: disable=unused-argument
+ with context.cd(str(TASK_ROOT/'tests')):
+ dirs = ['.pytest_cache', '.cache', 'htmlcov', '.coverage']
+ rmrf(dirs)
rmrf(dirs)
+
+
namespace_clean.add_task(pytest_clean, 'pytest')
+
@invoke.task
def mypy(context):
- "Run mypy optional static type checker"
- context.run("mypy main.py")
- namespace.add_task(mypy)
+ """Run mypy optional static type checker"""
+ with context.cd(TASK_ROOT_STR):
+ context.run("mypy main.py")
+
+
namespace.add_task(mypy)
+
@invoke.task
def mypy_clean(context):
- "Remove mypy cache directory"
- #pylint: disable=unused-argument
- dirs = ['.mypy_cache', 'dmypy.json', 'dmypy.sock']
- rmrf(dirs)
+ """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')
+
@invoke.task
def tox(context):
- "Run unit and integration tests on multiple python versions using tox"
- context.run("tox")
+ """Run unit and integration tests on multiple python versions using tox"""
+ with context.cd(TASK_ROOT_STR):
+ context.run("tox")
+
+
namespace.add_task(tox)
+
@invoke.task
def tox_clean(context):
- "Remove tox virtualenvs and logs"
- #pylint: disable=unused-argument
- rmrf('.tox')
+ """Remove tox virtualenvs and logs"""
+ # pylint: disable=unused-argument
+ with context.cd(TASK_ROOT_STR):
+ rmrf('.tox')
+
+
namespace_clean.add_task(tox_clean, 'tox')
+@invoke.task
+def nox_clean(context):
+ """Remove nox virtualenvs and logs"""
+ # pylint: disable=unused-argument
+ with context.cd(TASK_ROOT_STR):
+ rmrf('.nox')
+
+
+namespace_clean.add_task(nox_clean, 'nox')
+
+
#####
#
# documentation
@@ -95,39 +140,59 @@ DOCS_SRCDIR = 'docs'
DOCS_BUILDDIR = os.path.join('docs', '_build')
SPHINX_OPTS = '-nvWT' # Be nitpicky, verbose, and treat warnings as errors
+
@invoke.task()
def docs(context, builder='html'):
- "Build documentation using sphinx"
- cmdline = 'python -msphinx -M {} {} {} {}'.format(builder, DOCS_SRCDIR, DOCS_BUILDDIR, SPHINX_OPTS)
- context.run(cmdline, pty=True)
+ """Build documentation using sphinx"""
+ with context.cd(TASK_ROOT_STR):
+ cmdline = 'python -msphinx -M {} {} {} {}'.format(builder, DOCS_SRCDIR, DOCS_BUILDDIR, SPHINX_OPTS)
+ context.run(cmdline, pty=True)
+
+
namespace.add_task(docs)
+
@invoke.task()
def doc8(context):
- "Check documentation with doc8"
- context.run('doc8 docs --ignore-path docs/_build')
+ """Check documentation with doc8"""
+ with context.cd(TASK_ROOT_STR):
+ context.run('doc8 docs --ignore-path docs/_build')
+
+
namespace.add_task(doc8)
+
@invoke.task
def docs_clean(context):
- "Remove rendered documentation"
- #pylint: disable=unused-argument
- rmrf(DOCS_BUILDDIR)
+ """Remove rendered documentation"""
+ # pylint: disable=unused-argument
+ with context.cd(TASK_ROOT_STR):
+ rmrf(DOCS_BUILDDIR)
+
+
namespace_clean.add_task(docs_clean, name='docs')
+
@invoke.task()
def linkcheck(context):
"""Check external links in Sphinx documentation for integrity."""
- context.run('cd docs && make linkcheck', pty=True)
+ with context.cd(str(TASK_ROOT/'docs')):
+ context.run('make linkcheck', pty=True)
+
+
namespace.add_task(linkcheck)
+
@invoke.task
def livehtml(context):
- "Launch webserver on http://localhost:8000 with rendered documentation"
- builder = 'html'
- outputdir = os.path.join(DOCS_BUILDDIR, builder)
- cmdline = 'sphinx-autobuild -b {} {} {}'.format(builder, DOCS_SRCDIR, outputdir)
- context.run(cmdline, pty=True)
+ """Launch webserver on http://localhost:8000 with rendered documentation"""
+ with context.cd(TASK_ROOT_STR):
+ builder = 'html'
+ outputdir = os.path.join(DOCS_BUILDDIR, builder)
+ cmdline = 'sphinx-autobuild -b {} {} {}'.format(builder, DOCS_SRCDIR, outputdir)
+ context.run(cmdline, pty=True)
+
+
namespace.add_task(livehtml)
@@ -139,112 +204,156 @@ namespace.add_task(livehtml)
BUILDDIR = 'build'
DISTDIR = 'dist'
+
@invoke.task
def build_clean(context):
- "Remove the build directory"
- #pylint: disable=unused-argument
- rmrf(BUILDDIR)
+ """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
- rmrf(DISTDIR)
+ """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
- 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)
+ """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
- 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)
+ """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
+def clean_all(_):
+ """Run all clean tasks"""
+ # pylint: disable=unused-argument
pass
+
+
namespace_clean.add_task(clean_all, 'all')
+
@invoke.task
def tag(context, name, message=''):
- "Add a Git tag and push it to origin"
+ """Add a Git tag and push it to origin"""
# If a tag was provided on the command-line, then add a Git tag and push it to origin
if name:
context.run('git tag -a {} -m {!r}'.format(name, message))
context.run('git push origin {}'.format(name))
+
+
namespace.add_task(tag)
+
@invoke.task()
def validatetag(context):
- "Check to make sure that a tag exists for the current HEAD and it looks like a valid version number"
+ """Check to make sure that a tag exists for the current HEAD and it looks like a valid version number"""
# Validate that a Git tag exists for the current commit HEAD
result = context.run("git describe --exact-match --tags $(git log -n1 --pretty='%h')")
- tag = result.stdout.rstrip()
+ git_tag = result.stdout.rstrip()
# Validate that the Git tag appears to be a valid version number
ver_regex = re.compile(r'(\d+)\.(\d+)\.(\d+)')
- match = ver_regex.fullmatch(tag)
+ match = ver_regex.fullmatch(git_tag)
if match is None:
- print('Tag {!r} does not appear to be a valid version number'.format(tag))
+ print('Tag {!r} does not appear to be a valid version number'.format(git_tag))
sys.exit(-1)
else:
- print('Tag {!r} appears to be a valid version number'.format(tag))
+ print('Tag {!r} appears to be a valid version number'.format(git_tag))
namespace.add_task(validatetag)
+
@invoke.task(pre=[clean_all])
def sdist(context):
- "Create a source distribution"
- context.run('python setup.py sdist')
+ """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"
- context.run('python setup.py bdist_wheel')
+ """Build a wheel distribution"""
+ with context.cd(TASK_ROOT_STR):
+ context.run('python setup.py bdist_wheel')
+
+
namespace.add_task(wheel)
+
@invoke.task(pre=[validatetag, sdist, wheel])
def pypi(context):
- "Build and upload a distribution to pypi"
- context.run('twine upload dist/*')
+ """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=[validatetag, 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/*')
+ """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"
- 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")
+ """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,.nox,.eggs,*.egg,.venv,.idea,.pytest_cache,.vscode,build,dist,htmlcov")
+
+
namespace.add_task(flake8)