boromir674/semantic-version-check

View on GitHub
tox.ini

Summary

Maintainability
Test Coverage
[tox]
envlist =
    {py311, py310, py39, py38, py37, py36}{, -path, -sdist, -wheel, -dev}{, -linux, -macos, -windows}
    coverage
isolated_build = true
skip_missing_interpreters = true
minversion = 3.14
requires =
    virtualenv>=20.0.34
    ; pip >= 22.0.0


[gh-actions]
python =
    3.6: {py36}{, -path, -sdist, -wheel, -dev}
    3.7: {py37}{, -path, -sdist, -wheel, -dev}
    3.8: {py38}{, -path, -sdist, -wheel, -dev}
    3.9: {py39}{, -path, -sdist, -wheel, -dev}
    3.10: {py310}{, -path, -sdist, -wheel, -dev}
    3.11: {py311}{, -path, -sdist, -wheel, -dev}


[gh-actions:env]
PLATFORM =
    ubuntu-latest: linux
    macos-latest: macos
    windows-latest: windows


[testenv]
description = An environment designed to facilitate testing (running the test suite)
passenv =
    *
    LC_ALL
    PIP_*
    PYTEST_*
    # See https://github.com/codecov/codecov-python/blob/5b9d539a6a09bc84501b381b563956295478651a/README.md#using-tox
    codecov: TOXENV
    codecov: CI
    codecov: TRAVIS TRAVIS_*
setenv =
# It will overide variables in passenv in case of collision
    PYTHONPATH={toxinidir}/tests
    PYTHONBUFFERED=yes
    TEST_RESULTS_DIR={toxinidir}/test-results
    MYPYPATH = {toxinidir}{/}src{/}stubs
    PY_PACKAGE = semantic_version_check
    DIST_DIR=dist
    COVERAGE_FILE = {toxworkdir}/.coverage.{envname}
    TEST_STATUS_DIR = {envtmpdir}
    PYPY3323BUG = 1
extras =
    test
commands =
# --cov-config pyproject.toml
    pytest -ra --cov --cov-report=term-missing \
      --cov-report=html:{envdir}/htmlcov --cov-context=test \
      --cov-report=xml:{toxworkdir}/coverage.{envname}.xml \
      {posargs:-n auto} tests


[testenv:{py311, py310, py39, py38, py37, py36}-{linux, macos, windows}]
commands = {[testenv]commands}


[testenv:{py311, py310, py39, py38, py37, py36, pypy3}-path{, -linux, -macos, -windows}]
setenv =
    {[testenv]setenv}
    PYTHONPATH = {toxinidir}{/}src{:}{toxinidir}{/}tests
deps =
    # TODO dynamically populate from setup.cfg/pyproject.toml
    pytest >= 6.2.4
    pytest-cov >= 2.12
    pytest-xdist >= 1.34
    pytest-click ~= 1.1.0
skip_install = true


[testenv:{py311, py310, py39, py38, py37, py36, pypy3}-sdist{, -linux, -macos, -windows}]
deps = build
skip_install = true
commands_pre =
    # Delete the $DIST_DIR directory and its contents if found
    python -c 'import os; import shutil; exec("if os.path.exists(os.path.join(\"{toxinidir}\", \"{env:DIST_DIR}\")):\n    shutil.rmtree(os.path.join(\"{toxinidir}\", \"{env:DIST_DIR}\"))")'
commands =
    python -m build --sdist --no-isolation --outdir {envdir}/{env:DIST_DIR} {toxinidir}
    pip install --exists-action w --force-reinstall "{envdir}{/}{env:DIST_DIR}{/}{env:PY_PACKAGE}-{env:PKG_VERSION}.tar.gz[test]"
    {[testenv]commands}


[testenv:{py311, py310, py39, py38, py37, py36, pypy3}-wheel{, -linux, -macos, -windows}]
deps = build
skip_install = true
commands_pre =
    # Delete the $DIST_DIR directory and its contents if found
    python -c 'import os; import shutil; exec("if os.path.exists(os.path.join(\"{toxinidir}\", \"{env:DIST_DIR}\")):\n    shutil.rmtree(os.path.join(\"{toxinidir}\", \"{env:DIST_DIR}\"))")'
commands = 
    python -m build --wheel --no-isolation --outdir {envdir}/{env:DIST_DIR} {toxinidir}
    pip install --exists-action w --force-reinstall {envdir}/{env:DIST_DIR}/{env:PY_PACKAGE}-{env:PKG_VERSION}-py3-none-any.whl[test]
    {[testenv]commands}


[testenv:{py311, py310, py39, py38, py37, py36, pypy3}-dev{, -linux, -macos, -windows}]
usedevelop = true


[testenv:coverage]
description = combine coverage from test environments
passenv =
    DIFF_AGAINST
setenv =
    COVERAGE_FILE = {toxworkdir}/.coverage
skip_install = true
deps =
    coverage[toml]>=5.1
    diff_cover>=6
parallel_show_output = true
commands =
    coverage combine
    coverage report --skip-covered --show-missing -i
    coverage xml -o {toxworkdir}/coverage.xml -i
    coverage html -d {toxworkdir}/htmlcov -i
depends = {py311, py310, py39, py38, py37, py36}{, -path, -sdist, -wheel, -dev}


[testenv:dev]
description = generate a DEV environment
usedevelop = true
extras =
    doc
    test
commands =
    python -m pip list --format=columns
    python -c 'import sys; print(sys.executable)'
    python -m pip freeze > requirements-dev.txt


## STATIC TYPE CHECKING

[testenv:type]
description = Python source code type hints (mypy)
extras = typing
commands = mypy --show-error-codes {posargs:{toxinidir}{/}src{/}{env:PY_PACKAGE} {toxinidir}{/}tests}


## DOCUMENTATION

[testenv:docs]
description = Build the documentation. Read the source .rst and .py files and
    build ready-to-render/ready-to-serve html (eg you can host it in a
    'read the docs server'). Before building, any sphinx doctest found is
    executed. After building, both word spelling and url links proper redirects
    are checked.
setenv =
    {[testenv]setenv}
    SPELLCHECK=1
extras = docs
usedevelop = true
commands =
    sphinx-build {posargs:-E} -b doctest docs {env:DOCS_BUILD_LOCATION:dist{/}docs}
    sphinx-build {posargs:-E} -b html docs {env:DOCS_BUILD_LOCATION:dist{/}docs}
    sphinx-build -b spelling docs {env:DOCS_BUILD_LOCATION:dist{/}docs}
    sphinx-build -b linkcheck docs {env:DOCS_BUILD_LOCATION:dist{/}docs}
    python -c 'print("View documentation at {env:DOCS_BUILD_LOCATION:dist/docs}/index.html; it is ready to be hosted!")'


[testenv:live-html]
description = Rebuild Sphinx documentation on changes, with live-reload in the browser.
setenv =
    {[testenv]setenv}
    SPELLCHECK=1
deps = sphinx-autobuild
extras = docs
usedevelop = true
commands =
    sphinx-autobuild docs docs{/}_build{/}html {posargs}


## PYTHON PACKAGING

[testenv:check]
description = Check the code for compliance with best practises of Python packaging ecosystem (PyPI, pip, Distribute, etc).
deps =
    docutils
    readme-renderer
    pygments
    check-manifest
    pyroma
skip_install = true
commands =
    # we do NOT isolate the build, because otherwise the host system needs something like "apt install python3.8-venv"
    check-manifest -v --no-build-isolation
    pyroma -d {toxinidir}


[testenv:build]
description = Create a source and wheel distribution.
    Creates .tar.gz and .whl files in the {env:DIST_DIR} folder, that can be upload to a pypi index server.
basepython = {env:TOXPYTHON:python3}
deps = build
skip_install = true
commands_pre =
    # Delete the $DIST_DIR directory and its contents if found
    python -c 'import os; import shutil; exec("if os.path.exists(os.path.join(\"{toxinidir}\", \"{env:DIST_DIR}\")):\n    shutil.rmtree(os.path.join(\"{toxinidir}\", \"{env:DIST_DIR}\"))")'
commands =
    python -m build --help
    python -m build {toxinidir} --outdir {env:DIST_DIR}


## DEPLOYMENT

[testenv:deploy]
# Deploy to test.pypi.org : TWINE_USERNAME=user TWINE_PASSWROD=pass PACKAGE_DIST_VERSION=1.0.0 tox -e deploy
# Deploy to pypi.org      : TWINE_USERNAME=user TWINE_PASSWROD=pass PACKAGE_DIST_VERSION=1.0.0 PYPI_SERVER=pypi tox -e deploy
description = Deploy the python package to be hosted in a pypi server. Requires to authenticate with the pypi
    server, so please set the TWINE_PASSWORD and TWINE_PASSWORD environment variables.
    Also, requires the PACKAGE_DIST_VERSION variable to explicitly indicate which distribution
    (semantic version: ie 0.5.3, 1.0.0) we intent to deploy/upload. That way we avoid unintentionally deploying
    a wrong version and we make sure that the correct version is released to pypi. By default, deploys to a
    pypi 'test server', currently at test.pypi.org. If you want to deploy to the "production" pypi (at pypi.org),
    then you have to set the PYPI_SERVER environment variable to 'pypi', like `export PYPI_SERVER=pypi`.
    Before deploying, certain sanity checks are ran on the distribution artefacts (ie .tar.gz, .whl) to be uploaded.
passenv =
    PACKAGE_DIST_VERSION
    TWINE_USERNAME
    TWINE_PASSWORD
deps =
    keyring==21.3.0
    twine==3.4.0
skip_install = true
commands_pre =
    python -c 'import os; n = "TWINE_USERNAME"; v = os.environ.get(n); exec("if not v:\n    print(\"Please set the \" + str(n) + \" variable.\")\n    exit(1)");'
    python -c 'import os; n = "TWINE_PASSWORD"; v = os.environ.get(n); exec("if not v:\n    print(\"Please set the \" + str(n) + \" variable.\")\n    exit(1)");'
    python -c 'import os; n = "PACKAGE_DIST_VERSION"; v = os.environ.get(n); exec("if not v:\n    print(\"Please set the \" + str(n) + \" variable.\")\n    exit(1)");'
    python -c 'import os; n = "PYPI_SERVER"; exec("if n in os.environ:\n    v = os.environ[n]\n    if v != \"pypi\":\n        print(\"Environment variable PYPI_SERVER detected, but was not set to pypi. Please set to pypi or run tox -e deploy from an environment where the PYPI_SERVER variable is NOT present at all.\")\n        exit(1)");'
    python -m twine check {env:DIST_DIR}/{env:PY_PACKAGE}-{env:PACKAGE_DIST_VERSION:MISSMATCHED_PACKAGE_DIST_VERSION_ERROR}*
commands =
    python -m twine {posargs:upload --non-interactive} --repository {env:PYPI_SERVER:testpypi --skip-existing} {env:DIST_DIR}{/}{env:PY_PACKAGE}-{env:PACKAGE_DIST_VERSION:MISSMATCHED_PACKAGE_DIST_VERSION_ERROR}* --verbose


## COVERAGE

[testenv:clean]
description = Clean the working directory from any previously computed code coverage results.
    Removes any data resulted from measuring code coverage. Useful before running the test suite
    with code coverage enabled.
deps = coverage
skip_install = true
commands = coverage erase

[testenv:report]
description = Show the most recently computed code coverage results.
deps = coverage
skip_install = true
commands = {posargs:coverage report}

[testenv:format-report]
description = Generate xml and html formatted files out of previously computed code coverage results.
deps = coverage
skip_install = true
commands =
    coverage xml
    coverage html


# CODE LINTING, STATIC (STYLE) CHECKING

[testenv:lint]
description = test if code conforms with our styles
    to check against code style (aka lint check) run: tox -e lint
    to apply code style (aka lint apply) run: APPLY_LINT= tox -e lint
deps =
    black
    isort >= 5.0.0
    prospector[with_pyroma]
passenv = APPLY_LINT
skip_install = true
changedir = {toxinidir}
commands_pre = python -c 'import os; f = ".pylintrc"; exec("if os.path.exists(f):\n    os.rename(f, \".pylintrc-bak\")")'
commands =
    prospector tests
    prospector src
    isort {env:APPLY_LINT:--check} .
    black {env:APPLY_LINT:--check} -S --config pyproject.toml tests src
commands_post = python -c 'import os; f = ".pylintrc-bak"; exec("if os.path.exists(f):\n    os.rename(f, \".pylintrc\")")'


[testenv:black]
description = black ops
deps = black
skip_install = true
changedir = {toxinidir}
commands = black {posargs} -S --config pyproject.toml tests src

[testenv:isort]
descript = isort
deps = isort >= 5.0.0
skip_install = true
commands = isort {posargs} {toxinidir}


## Code Static Analysis

[testenv:pylint]
description = Run the Pylint tool to analyse the Python code and output information about errors,
    potential problems and convention violations
deps = pylint==2.7.4
usedevelop = true
commands = python -m pylint {posargs:{toxinidir}/src/{env:PY_PACKAGE} {toxinidir}/tests}


[testenv:prospector]
description = Analyse Python code and output information about errors, potential problems, convention violations and complexity.
    Runs the prospector tool which brings together the functionality of other Python analysis tools such as Pyflakes and McCabe complexity.
    We run tools: Pyflakes, Pyroma, McCabe and Dodgy
deps = prospector[with_pyroma]
skip_install = true
commands_pre =
    # We do not run pylint, since we have a dedicated pylint env for it.
    # Prospector still tries to read .pylintrc, which causes a crash (because .pylintrc was generated with a pylint version higher than the one supported by prospector)
    # So we temporarily "hide" .pylintrc from prospector
    python -c 'import os; exec("if os.path.exists(os.path.join(\"{toxinidir}\", \".pylintrc\")):\n    os.rename(os.path.join(\"{toxinidir}\", \".pylintrc\"), os.path.join(\"{toxinidir}\", \".pylintrc-bak\"))")'
commands =
    prospector {toxinidir}{/}tests
    prospector {toxinidir}{/}src
commands_post =
    # We "restore" .pylintrc (to be available to the pylint env command)
    python -c 'import os; exec("if os.path.exists(os.path.join(\"{toxinidir}\", \".pylintrc-bak\")):\n    os.rename(os.path.join(\"{toxinidir}\", \".pylintrc-bak\"), os.path.join(\"{toxinidir}\", \".pylintrc\"))")'



## GENERATE ARCHITECTURE GRAPHS

[testenv:graphs]
description = Visualise the dependency graphs (roughly which module imports which), by examining the
    Python code. The dependency graph(s) are rendered in .svg file(s) and saved on the disk. By default, the generated
    files are stored in the 'pydoer-graphs' directory, inside the project's root folder. You can use the PYDOER_GRAPHS
    environment variable to determine the directory location to store the files. If the directory does not exist
    it gets created. Requires that the 'dot' executable is in your PATH. Installing the graphviz library should make
    the dot executable available in your PATH. Installing 'graphviz':
    * For Linux users using Debian-based distributions (ie Ubuntu, Debian, Mint), please run "sudo apt install graphviz"
    * For MacOS users with Homebrew, please run "brew install graphviz"
basepython = {env:TOXPYTHON:python3.8}
passenv =
    HOME
    PYDOER_GRAPHS
deps =
    pydeps==1.9.13
usedevelop = true
commands_pre =
    python -c 'import os; p = "{env:PYDOER_GRAPHS:pydoer-graphs}"; exec("if not os.path.exists(p):\n    os.mkdir(p)");'
commands =
    pydeps --version

    # --max-bacon : exclude nodes that are more than n hops away
    # (default=2, 0 -> infinite)

    # --min-cluster-size : the minimum number of nodes a dependency must have before being clustered (default=0)

    # --max-cluster-size : the maximum number of nodes a dependency can have before the cluster is collapsed to a single node (default=0)
    # --keep-target-cluster : draw target module as a cluster

    # Draw only the source code package inner dependencies
    pydeps src{/}{env:PY_PACKAGE} --only {env:PY_PACKAGE} --noshow -o {env:PYDOER_GRAPHS:pydoer-graphs}{/}deps_inner.svg
    # Draw the source code package inner and external dependencies
    pydeps src{/}{env:PY_PACKAGE} --cluster --noshow -o {env:PYDOER_GRAPHS:pydoer-graphs}{/}deps_all.svg

    # Visualize the package inner dependencies and abstract the external (eg with numpy, pandas, etc) ones
    # Draw the source code package inner and minimum external dependencies
    pydeps src{/}{env:PY_PACKAGE} --max-cluster-size=2 --keep-target-cluster --noshow -o {env:PYDOER_GRAPHS:pydoer-graphs}{/}deps_ktc-mcs_2.svg

    # Draw the source code package inner and all external dependencies
    pydeps src{/}{env:PY_PACKAGE} --keep-target-cluster --noshow -o {env:PYDOER_GRAPHS:pydoer-graphs}{/}deps_ktc.svg

    python -c 'import os; print("\nGenerated dependency graph(s), as .svg files."); print("The graph(s) reside in the \"" + os.path.join("{toxinidir}", "{env:PYDOER_GRAPHS:pydoer-graphs}") + "\" directory and you can now view them ie in your browser.\n")'