From e8e29a619fe9acf198227e97fc2a439d35f7b275 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 22 Dec 2024 00:28:44 +0100 Subject: [PATCH 1/3] Remove examples, migrate to GitLab CI/CD templates --- .github/workflows/check.yml | 5 +- .github/workflows/publish.yml | 2 +- README.rst | 37 ++++--- copier.yaml | 14 --- examples/README.rst | 25 ----- examples/argparse/.gitignore | 14 --- examples/argparse/.gitlab-ci.yml | 74 -------------- examples/argparse/LICENSE | 1 - examples/argparse/README.md | 7 -- examples/argparse/pyproject.toml | 66 ------------- examples/argparse/tests/test_cli.py | 87 ----------------- examples/argparse/tests/test_command.py | 33 ------- examples/argparse/tox.ini | 54 ----------- .../argparse/{{_copier_conf.answers_file}} | 3 - examples/argparse/{{module}}/__init__.py | 3 - examples/argparse/{{module}}/__main__.py | 7 -- examples/argparse/{{module}}/cli.py | 39 -------- examples/argparse/{{module}}/command.py | 14 --- examples/click/.gitignore | 14 --- examples/click/.gitlab-ci.yml | 81 ---------------- examples/click/LICENSE | 1 - examples/click/README.md | 7 -- examples/click/pyproject.toml | 67 ------------- examples/click/requirements.txt | 8 -- examples/click/tests/test_cli.py | 70 -------------- examples/click/tests/test_command.py | 30 ------ examples/click/tox.ini | 65 ------------- examples/click/{{_copier_conf.answers_file}} | 3 - examples/click/{{module}}/__init__.py | 3 - examples/click/{{module}}/__main__.py | 7 -- examples/click/{{module}}/cli.py | 14 --- examples/click/{{module}}/command.py | 16 ---- examples/docopt/.gitignore | 14 --- examples/docopt/.gitlab-ci.yml | 81 ---------------- examples/docopt/LICENSE | 1 - examples/docopt/README.md | 7 -- examples/docopt/pyproject.toml | 67 ------------- examples/docopt/requirements.txt | 8 -- examples/docopt/tests/test_cli.py | 96 ------------------- examples/docopt/tests/test_command.py | 40 -------- examples/docopt/tox.ini | 65 ------------- examples/docopt/{{_copier_conf.answers_file}} | 3 - examples/docopt/{{module}}/__init__.py | 3 - examples/docopt/{{module}}/__main__.py | 7 -- examples/docopt/{{module}}/cli.py | 40 -------- examples/docopt/{{module}}/command.py | 22 ----- pyproject.toml | 12 ++- tox.ini | 36 +------ 48 files changed, 27 insertions(+), 1346 deletions(-) delete mode 100644 copier.yaml delete mode 100644 examples/README.rst delete mode 100644 examples/argparse/.gitignore delete mode 100644 examples/argparse/.gitlab-ci.yml delete mode 100644 examples/argparse/LICENSE delete mode 100644 examples/argparse/README.md delete mode 100644 examples/argparse/pyproject.toml delete mode 100644 examples/argparse/tests/test_cli.py delete mode 100644 examples/argparse/tests/test_command.py delete mode 100644 examples/argparse/tox.ini delete mode 100644 examples/argparse/{{_copier_conf.answers_file}} delete mode 100644 examples/argparse/{{module}}/__init__.py delete mode 100644 examples/argparse/{{module}}/__main__.py delete mode 100644 examples/argparse/{{module}}/cli.py delete mode 100644 examples/argparse/{{module}}/command.py delete mode 100644 examples/click/.gitignore delete mode 100644 examples/click/.gitlab-ci.yml delete mode 100644 examples/click/LICENSE delete mode 100644 examples/click/README.md delete mode 100644 examples/click/pyproject.toml delete mode 100644 examples/click/requirements.txt delete mode 100644 examples/click/tests/test_cli.py delete mode 100644 examples/click/tests/test_command.py delete mode 100644 examples/click/tox.ini delete mode 100644 examples/click/{{_copier_conf.answers_file}} delete mode 100644 examples/click/{{module}}/__init__.py delete mode 100644 examples/click/{{module}}/__main__.py delete mode 100644 examples/click/{{module}}/cli.py delete mode 100644 examples/click/{{module}}/command.py delete mode 100644 examples/docopt/.gitignore delete mode 100644 examples/docopt/.gitlab-ci.yml delete mode 100644 examples/docopt/LICENSE delete mode 100644 examples/docopt/README.md delete mode 100644 examples/docopt/pyproject.toml delete mode 100644 examples/docopt/requirements.txt delete mode 100644 examples/docopt/tests/test_cli.py delete mode 100644 examples/docopt/tests/test_command.py delete mode 100644 examples/docopt/tox.ini delete mode 100644 examples/docopt/{{_copier_conf.answers_file}} delete mode 100644 examples/docopt/{{module}}/__init__.py delete mode 100644 examples/docopt/{{module}}/__main__.py delete mode 100644 examples/docopt/{{module}}/cli.py delete mode 100644 examples/docopt/{{module}}/command.py diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 8e72008..3a66ffc 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -21,16 +21,13 @@ jobs: env: - lint - format - - example-argparse - - example-click - - example-docopt - package - docs steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '3.12' - name: Install prerequisites run: python -m pip install tox - name: Run ${{ matrix.env }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 676b59b..fda700a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '3.12' - name: Install prerequisites run: python -m pip install build twine wheel - name: Build package diff --git a/README.rst b/README.rst index ac8b0f7..fe5f40c 100644 --- a/README.rst +++ b/README.rst @@ -5,14 +5,15 @@ cli-test-helpers |latest-version| Useful helpers for writing tests for your Python CLI program. -Writing tests for a command line interface (CLI) tool `may not seem strictly -straight-forward`_ when you think in terms of unit tests. Especially, when you -use the `argparse`_ module or the `click`_ package, control of the application -entry point is a bit taken away from you. +Writing tests for a command line interface (CLI) application is `more difficult +than it seems at first sight`_. Especially, when you use the `argparse`_ module +or the `docopt`_ or `click`_ package, control of the application entry point is +a bit taken away from you. -But it's not all that bad. This package is here to help. The `examples`_ give -you some guidance on how to get started, and the helpers allow you to deal with -common cases, such as mocking CLI arguments and environment variable values. +But it's not all that bad. This package is here to help. The Painless Software +`CLI Copier template`_ offers some guidance on how to get started, and the +CLI test helpers allow you to deal with common cases, such as mocking CLI +arguments and environment variable values. .. |latest-version| image:: https://img.shields.io/pypi/v/cli-test-helpers.svg :target: https://pypi.org/project/cli-test-helpers @@ -35,11 +36,12 @@ common cases, such as mocking CLI arguments and environment variable values. .. |docs-status| image:: https://img.shields.io/readthedocs/python-cli-test-helpers/latest.svg :target: https://readthedocs.org/projects/python-cli-test-helpers/ :alt: Documentation Status -.. _may not seem strictly straight-forward: https://stackoverflow.com/questions/13493288/ +.. _more difficult than it seems at first sight: https://stackoverflow.com/questions/13493288/ .. _argparse: https://docs.python.org/3/library/argparse.html .. _click: https://click.palletsprojects.com/ +.. _docopt: http://docopt.org/ .. _documentation: https://python-cli-test-helpers.readthedocs.io/ -.. _examples: https://github.com/painless-software/python-cli-test-helpers/tree/main/examples +.. _CLI Copier template: https://gitlab.com/painless-software/cicd/app/cli .. links-marker @@ -48,11 +50,13 @@ Documentation See the `documentation`_ for installation instructions and a tutorial. -Examples --------- +Examples / Quickstart +--------------------- -The `examples`_ folder contains hands-on example projects you can start to use -directly. +Visit the Painless Software `CLI Copier template`_ to inspect hands-on CLI +application blueprints for the most popular CLI frameworks. The Copier tool +lets you create your own CLI application project with tests and modern CI/CD, +effortlessly. Development ----------- @@ -70,10 +74,3 @@ this: tox -e lint,py312 # run a few environments tox -e py # run tests with local default Python tox # run entire suite - -The included example projects can be tested independently with their dedicated -environments, e.g. - -.. code-block:: shell - - tox -e example-docopt diff --git a/copier.yaml b/copier.yaml deleted file mode 100644 index 105b397..0000000 --- a/copier.yaml +++ /dev/null @@ -1,14 +0,0 @@ -_subdirectory: "examples/{{ engine.lower() }}" -_templates_suffix: "" -engine: - choices: - - Argparse - - Click - - Docopt - type: str -project: "{{ engine }} CLI Example" -package: "{{ project.lower().replace(' ', '-') }}" -module: "{{ project.lower().replace(' ', '_') }}" -author: Your name -email: your.name@example.com -url: https://example.com diff --git a/examples/README.rst b/examples/README.rst deleted file mode 100644 index 364cdad..0000000 --- a/examples/README.rst +++ /dev/null @@ -1,25 +0,0 @@ -Examples -======== - -#. `argparse `__ – *using Python's standard library and TDD* -#. `click `__ – *using the Click CLI library with TDD* -#. `docopt `__ – *using the docopt-ng CLI library with TDD* - -The examples in this folder are a `Copier`_ template. You can use ``copier`` -to generate a working CLI project suiting your needs, interactively selecting -one of the CLI libraries above, e.g. - -.. code-block:: console - - pip install copier - -.. code-block:: console - - copier copy gh:painless-software/python-cli-test-helpers cli-example - -Add the ``--vcs-ref HEAD`` option to pick all changes from the repository that -might have been added after the latest release. See the `Copier documentation`_ -for further details on generating projects from templates. - -.. _Copier: https://copier.readthedocs.io/ -.. _Copier documentation: https://copier.readthedocs.io/en/stable/generating/ diff --git a/examples/argparse/.gitignore b/examples/argparse/.gitignore deleted file mode 100644 index 6742d38..0000000 --- a/examples/argparse/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -__pycache__/ -*.py[cod] -*$py.class - -build/ -dist/ -*.egg-info/ -.tox/ - -.coverage -coverage.json -coverage.xml -htmlcov/ -junit-report.xml diff --git a/examples/argparse/.gitlab-ci.yml b/examples/argparse/.gitlab-ci.yml deleted file mode 100644 index b6dbdf7..0000000 --- a/examples/argparse/.gitlab-ci.yml +++ /dev/null @@ -1,74 +0,0 @@ -# GitLab CI, https://docs.gitlab.com/ee/ci/yaml/ ---- -.tox: - image: docker.io/library/python:${PYTHON_VERSION} - before_script: - - pip install tox - script: - - tox - variables: - PIP_DISABLE_PIP_VERSION_CHECK: '1' - PY_COLORS: '1' - PYTHON_VERSION: '3.10' - needs: [] - only: - - merge_requests - - main - -stages: -- codestyle -- test -- build -- publish - -format: - stage: codestyle - extends: .tox - variables: - TOXENV: format - -lint: - stage: codestyle - extends: .tox - variables: - TOXENV: lint - -pytest: - stage: test - extends: .tox - variables: - TOXENV: py - parallel: - matrix: - - PYTHON_VERSION: - - '3.9' - - '3.10' - - '3.11' - - '3.12' - coverage: /^TOTAL.+?(\d+\%)$/ - artifacts: - reports: - coverage_report: - coverage_format: cobertura - path: coverage.xml - junit: junit-report.xml - -package: - stage: build - extends: .tox - variables: - TOXENV: package - -pypi-local: - stage: publish - extends: .tox - variables: - GIT_TAG: ${CI_COMMIT_TAG} - TWINE_USERNAME: gitlab-ci-token - TWINE_PASSWORD: ${CI_JOB_TOKEN} - TWINE_REPOSITORY_URL: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi - script: - - tox -qe ensure_version_matches -- ${GIT_TAG} - - tox -e package -- upload - only: - - tags diff --git a/examples/argparse/LICENSE b/examples/argparse/LICENSE deleted file mode 100644 index efb9728..0000000 --- a/examples/argparse/LICENSE +++ /dev/null @@ -1 +0,0 @@ -Copyright (c) {{author}}. All rights reserved. diff --git a/examples/argparse/README.md b/examples/argparse/README.md deleted file mode 100644 index 0f8026e..0000000 --- a/examples/argparse/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# {{project}} CLI - -This example uses `argparse` from the Python standard library. - -## Background Reading - -- https://docs.python.org/3/library/argparse.html diff --git a/examples/argparse/pyproject.toml b/examples/argparse/pyproject.toml deleted file mode 100644 index 1488018..0000000 --- a/examples/argparse/pyproject.toml +++ /dev/null @@ -1,66 +0,0 @@ -[build-system] -requires = ["setuptools>=61.0"] -build-backend = "setuptools.build_meta" - -[project] -name = "{{package}}" -version = "0.1.0" -description = "{{project}}" -readme = "README.md" -license = {file = "LICENSE"} -authors = [ - {name = "{{author}}", email = "{{email}}"}, -] -maintainers = [ - {name = "{{author}}", email = "{{email}}"}, -] -classifiers = [ - "Development Status :: 4 - Beta", - "Environment :: Console", - "Intended Audience :: Information Technology", - "License :: Other/Proprietary License", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Topic :: Utilities", -] -keywords = [ - "cli", -] -requires-python = ">=3.9" -dependencies = [ -] - -[project.scripts] -"{{package}}" = "{{module}}.cli:main" - -[project.urls] -homepage = "{{url}}" - -[tool.coverage.run] -source = ["{{module}}"] - -[tool.coverage.report] -exclude_only = ["if __name__ == .__main__.:"] -show_missing = true -skip_covered = true - -[tool.pytest.ini_options] -addopts = "--color=yes --doctest-modules --junitxml=junit-report.xml --verbose" - -[tool.ruff] -extend-exclude = [] -extend-include = [] - -[tool.ruff.lint] -extend-ignore = ["ANN", "B904", "D", "INP001", "T201", "TRY200"] -extend-select = ["ALL"] - -[tool.ruff.lint.per-file-ignores] -"test_*.py" = ["S101"] - -[tool.setuptools.packages.find] -where = ["."] -exclude = ["tests"] diff --git a/examples/argparse/tests/test_cli.py b/examples/argparse/tests/test_cli.py deleted file mode 100644 index aa4e8d3..0000000 --- a/examples/argparse/tests/test_cli.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Tests for command line interface (CLI). -""" -from importlib import import_module -from importlib.metadata import version -from os import linesep -from unittest.mock import patch - -import pytest -from cli_test_helpers import ArgvContext, shell - -import {{module}}.cli - - -def test_main_module(): - """ - Exercise (most of) the code in the ``__main__`` module. - """ - import_module("{{module}}.__main__") - - -def test_runas_module(): - """ - Can this package be run as a Python module? - """ - result = shell("python -m {{module}} --help") - assert result.exit_code == 0 - - -def test_entrypoint(): - """ - Is entrypoint script installed? (setup.py) - """ - result = shell("{{package}} --help") - assert result.exit_code == 0 - - -@patch("{{module}}.cli.dispatch") -def test_usage(mock_dispatch): - """ - Does CLI abort w/o arguments, displaying usage instructions? - """ - with ArgvContext("{{package}}"), pytest.raises(SystemExit): - {{module}}.cli.main() - - assert not mock_dispatch.called, "CLI should stop execution" - - result = shell("{{package}}") - - assert "usage:" in result.stderr - - -def test_version(): - """ - Does --version display information as expected? - """ - expected_version = version("{{package}}") - result = shell("{{package}} --version") - - assert result.stdout == f"{expected_version}{linesep}" - assert result.exit_code == 0 - - -def test_get_action(): - """ - Is action argument available? - """ - with ArgvContext("{{package}}", "get"): - args = {{module}}.cli.parse_arguments() - - assert args.action == "get" - - -def test_set_action(): - """ - Is action argument available? - """ - with ArgvContext("{{package}}", "set"): - args = {{module}}.cli.parse_arguments() - - assert args.action == "set" - - -# NOTE: -# You can continue here, adding all CLI action and option combinations -# using a non-destructive option, such as --help, to test for the -# availability of the CLI command or option. diff --git a/examples/argparse/tests/test_command.py b/examples/argparse/tests/test_command.py deleted file mode 100644 index cc4367f..0000000 --- a/examples/argparse/tests/test_command.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Tests for the command module. -""" -from unittest.mock import patch - -import pytest -from cli_test_helpers import ArgvContext, EnvironContext - -import {{module}} - - -@patch("{{module}}.command.example") -def test_cli_command(mock_command): - """ - Is the correct code called when invoked via the CLI? - """ - with ArgvContext("{{package}}", "get"): - {{module}}.cli.main() - - assert mock_command.called - - -def test_fail_without_secret(): - """ - Must fail without a ``SECRET`` environment variable specified - """ - message_regex = "Environment value SECRET not set." - - with ArgvContext("{{package}}", "get"), EnvironContext(SECRET=None), pytest.raises( - SystemExit, - match=message_regex, - ): - {{module}}.cli.main() diff --git a/examples/argparse/tox.ini b/examples/argparse/tox.ini deleted file mode 100644 index 3f50354..0000000 --- a/examples/argparse/tox.ini +++ /dev/null @@ -1,54 +0,0 @@ -[tox] -envlist = - lint - format - py3{9,10,11,12} - package - -[testenv] -description = Unit tests -deps = - cli-test-helpers - coverage[toml] - pytest -commands = - coverage run -m pytest {posargs} - coverage xml - coverage report - -[testenv:clean] -description = Remove bytecode and other debris -skip_install = true -deps = pyclean -commands = pyclean {posargs:. --debris --erase junit-report.xml --yes} - -[testenv:ensure_version_matches] -description = Verify package version is same as Git tag -deps = -commands = python -c 'from importlib.metadata import version; ver = version("{{package}}"); tag = "'{posargs}'"; error = f"`{ver}` != `{tag}`"; abort = f"Package version does not match the Git tag ({error}). ABORTING."; raise SystemExit(0 if ver and tag and ver == tag else abort)' - -[testenv:format] -description = Ensure consistent code style (Ruff) -skip_install = true -deps = ruff -commands = ruff format {posargs:--check --diff .} - -[testenv:lint] -description = Lightening-fast linting (Ruff) -skip_install = true -deps = ruff -commands = ruff check {posargs:--show-source .} - -[testenv:package] -description = Build package and check metadata (or upload package) -skip_install = true -deps = - build - twine -commands = - python -m build - twine {posargs:check --strict} dist/* -passenv = - TWINE_USERNAME - TWINE_PASSWORD - TWINE_REPOSITORY_URL diff --git a/examples/argparse/{{_copier_conf.answers_file}} b/examples/argparse/{{_copier_conf.answers_file}} deleted file mode 100644 index dad7ce6..0000000 --- a/examples/argparse/{{_copier_conf.answers_file}} +++ /dev/null @@ -1,3 +0,0 @@ -# Changes here will be overwritten by Copier -# Feel free to delete this file if you don't plan to run `copier update`. -{{ _copier_answers|to_nice_yaml -}} diff --git a/examples/argparse/{{module}}/__init__.py b/examples/argparse/{{module}}/__init__.py deleted file mode 100644 index c8b6676..0000000 --- a/examples/argparse/{{module}}/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -{{project}}. -""" diff --git a/examples/argparse/{{module}}/__main__.py b/examples/argparse/{{module}}/__main__.py deleted file mode 100644 index f70326e..0000000 --- a/examples/argparse/{{module}}/__main__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Helper module to run not-installed version (via ``python3 -m {{module}}``). -""" -from .cli import main - -if __name__ == "__main__": - main() diff --git a/examples/argparse/{{module}}/cli.py b/examples/argparse/{{module}}/cli.py deleted file mode 100644 index b4717a4..0000000 --- a/examples/argparse/{{module}}/cli.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -Command line interface implementation. -""" -import argparse -from importlib.metadata import version - -from . import command - - -def parse_arguments(): - """Parse and handle CLI arguments.""" - __version__ = version("{{package}}") - - parser = argparse.ArgumentParser(description="{{project}}") - - parser.add_argument("--version", action="version", version=__version__) - parser.add_argument("action", choices=["get", "set"]) - parser.add_argument( - "envvar", - nargs="?", - default="SECRET", - help="default: %(default)s", - ) - - return parser.parse_args() - - -def dispatch(args): - """Execute functionality requested through the CLI.""" - if args.action == "get": - command.example(args) - else: - raise NotImplementedError(args.action) - - -def main(): - """{{project}}.""" - args = parse_arguments() - dispatch(args) diff --git a/examples/argparse/{{module}}/command.py b/examples/argparse/{{module}}/command.py deleted file mode 100644 index a8a7107..0000000 --- a/examples/argparse/{{module}}/command.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -Example command module. -""" -import os - - -def example(args): - """An example command.""" - try: - value = os.environ[args.envvar] - print(f"{args.envvar} = {value}") - except KeyError: - msg = f"Environment value {args.envvar} not set." - raise SystemExit(msg) diff --git a/examples/click/.gitignore b/examples/click/.gitignore deleted file mode 100644 index 6742d38..0000000 --- a/examples/click/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -__pycache__/ -*.py[cod] -*$py.class - -build/ -dist/ -*.egg-info/ -.tox/ - -.coverage -coverage.json -coverage.xml -htmlcov/ -junit-report.xml diff --git a/examples/click/.gitlab-ci.yml b/examples/click/.gitlab-ci.yml deleted file mode 100644 index 048d9a9..0000000 --- a/examples/click/.gitlab-ci.yml +++ /dev/null @@ -1,81 +0,0 @@ -# GitLab CI, https://docs.gitlab.com/ee/ci/yaml/ ---- -.tox: - image: docker.io/library/python:${PYTHON_VERSION} - before_script: - - pip install tox - script: - - tox - variables: - PIP_DISABLE_PIP_VERSION_CHECK: '1' - PY_COLORS: '1' - PYTHON_VERSION: '3.10' - needs: [] - only: - - merge_requests - - main - -stages: -- codestyle -- safety -- test -- build -- publish - -format: - stage: codestyle - extends: .tox - variables: - TOXENV: format - -lint: - stage: codestyle - extends: .tox - variables: - TOXENV: lint - -requirements: - stage: safety - extends: .tox - variables: - TOXENV: requirements - -pytest: - stage: test - extends: .tox - variables: - TOXENV: py - parallel: - matrix: - - PYTHON_VERSION: - - '3.9' - - '3.10' - - '3.11' - - '3.12' - coverage: /^TOTAL.+?(\d+\%)$/ - artifacts: - reports: - coverage_report: - coverage_format: cobertura - path: coverage.xml - junit: junit-report.xml - -package: - stage: build - extends: .tox - variables: - TOXENV: package - -pypi-local: - stage: publish - extends: .tox - variables: - GIT_TAG: ${CI_COMMIT_TAG} - TWINE_USERNAME: gitlab-ci-token - TWINE_PASSWORD: ${CI_JOB_TOKEN} - TWINE_REPOSITORY_URL: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi - script: - - tox -qe ensure_version_matches -- ${GIT_TAG} - - tox -e package -- upload - only: - - tags diff --git a/examples/click/LICENSE b/examples/click/LICENSE deleted file mode 100644 index efb9728..0000000 --- a/examples/click/LICENSE +++ /dev/null @@ -1 +0,0 @@ -Copyright (c) {{author}}. All rights reserved. diff --git a/examples/click/README.md b/examples/click/README.md deleted file mode 100644 index 34c8019..0000000 --- a/examples/click/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# {{project}} CLI - -This example uses the popular Click CLI package. - -## Background Reading - -- https://click.palletsprojects.com/ diff --git a/examples/click/pyproject.toml b/examples/click/pyproject.toml deleted file mode 100644 index 593fab4..0000000 --- a/examples/click/pyproject.toml +++ /dev/null @@ -1,67 +0,0 @@ -[build-system] -requires = ["setuptools>=61.0"] -build-backend = "setuptools.build_meta" - -[project] -name = "{{package}}" -version = "0.1.0" -description = "{{project}}" -readme = "README.md" -license = {file = "LICENSE"} -authors = [ - {name = "{{author}}", email = "{{email}}"}, -] -maintainers = [ - {name = "{{author}}", email = "{{email}}"}, -] -classifiers = [ - "Development Status :: 4 - Beta", - "Environment :: Console", - "Intended Audience :: Information Technology", - "License :: Other/Proprietary License", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Topic :: Utilities", -] -keywords = [ - "cli", -] -requires-python = ">=3.9" -dependencies = [ - "click", -] - -[project.scripts] -"{{package}}" = "{{module}}.cli:main" - -[project.urls] -homepage = "{{url}}" - -[tool.coverage.run] -source = ["{{module}}"] - -[tool.coverage.report] -exclude_only = ["if __name__ == .__main__.:"] -show_missing = true -skip_covered = true - -[tool.pytest.ini_options] -addopts = "--color=yes --doctest-modules --junitxml=junit-report.xml --verbose" - -[tool.ruff] -extend-exclude = [] -extend-include = [] - -[tool.ruff.lint] -extend-ignore = ["ANN", "B904", "D", "INP001", "TRY200"] -extend-select = ["ALL"] - -[tool.ruff.lint.per-file-ignores] -"test_*.py" = ["S101"] - -[tool.setuptools.packages.find] -where = ["."] -exclude = ["tests"] diff --git a/examples/click/requirements.txt b/examples/click/requirements.txt deleted file mode 100644 index a06d47a..0000000 --- a/examples/click/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --resolver=backtracking -# -click==8.1.3 - # via {{package}} (setup.py) diff --git a/examples/click/tests/test_cli.py b/examples/click/tests/test_cli.py deleted file mode 100644 index 0ed0791..0000000 --- a/examples/click/tests/test_cli.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -Tests for command line interface (CLI). -""" -from importlib import import_module -from importlib.metadata import version -from os import linesep - -from cli_test_helpers import shell -from click.testing import CliRunner - -import {{module}}.cli - - -def test_main_module(): - """ - Exercise (most of) the code in the ``__main__`` module. - """ - import_module("{{module}}.__main__") - - -def test_runas_module(): - """ - Can this package be run as a Python module? - """ - result = shell("python -m {{module}} --help") - assert result.exit_code == 0 - - -def test_entrypoint(): - """ - Is entrypoint script installed? (setup.py) - """ - result = shell("{{package}} --help") - assert result.exit_code == 0 - - -def test_usage(): - """ - Does CLI abort w/o arguments, displaying usage instructions? - """ - runner = CliRunner() - result = runner.invoke({{module}}.cli.main) - - assert "Usage:" in result.output - assert result.exit_code != 0 - - -def test_version(): - """ - Does --version display information as expected? - """ - expected_version = version("{{package}}") - result = shell("{{package}} --version") - - assert result.stdout == f"{{package}}, version {expected_version}{linesep}" - assert result.exit_code == 0 - - -def test_example_command(): - """ - Is command available? - """ - result = shell("{{package}} example --help") - assert result.exit_code == 0 - - -# NOTE: -# You can continue here, adding all CLI command combinations -# using a non-destructive option, such as --help, to test for -# the availability of the CLI command or option. diff --git a/examples/click/tests/test_command.py b/examples/click/tests/test_command.py deleted file mode 100644 index e48db91..0000000 --- a/examples/click/tests/test_command.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Tests for the command module. -""" -from unittest.mock import patch - -import pytest -from cli_test_helpers import ArgvContext, EnvironContext - -import {{module}} - - -@patch("{{module}}.command.example") -def test_cli_command(mock_command): - """ - Is the correct code called when invoked via the CLI? - """ - with ArgvContext("{{package}}", "example"), pytest.raises(SystemExit): - {{module}}.cli.main() - - assert mock_command.called - - -def test_fail_without_secret(): - """ - Must fail without a ``SECRET`` environment variable specified - """ - message_regex = "Environment value SECRET not set." - - with EnvironContext(SECRET=None), pytest.raises(SystemExit, match=message_regex): - {{module}}.command.example("SECRET") diff --git a/examples/click/tox.ini b/examples/click/tox.ini deleted file mode 100644 index b893076..0000000 --- a/examples/click/tox.ini +++ /dev/null @@ -1,65 +0,0 @@ -[tox] -envlist = - lint - format - py3{9,10,11,12} - requirements - package - -[testenv] -description = Unit tests -deps = - cli-test-helpers - coverage[toml] - pytest -commands = - coverage run -m pytest {posargs} - coverage xml - coverage report - -[testenv:clean] -description = Remove bytecode and other debris -skip_install = true -deps = pyclean -commands = pyclean {posargs:. --debris --erase junit-report.xml --yes} - -[testenv:ensure_version_matches] -description = Verify package version is same as Git tag -deps = -commands = python -c 'from importlib.metadata import version; ver = version("{{package}}"); tag = "'{posargs}'"; error = f"`{ver}` != `{tag}`"; abort = f"Package version does not match the Git tag ({error}). ABORTING."; raise SystemExit(0 if ver and tag and ver == tag else abort)' - -[testenv:format] -description = Ensure consistent code style (Ruff) -skip_install = true -deps = ruff -commands = ruff format {posargs:--check --diff .} - -[testenv:lint] -description = Lightening-fast linting (Ruff) -skip_install = true -deps = ruff -commands = ruff check {posargs:--show-source .} - -[testenv:package] -description = Build package and check metadata (or upload package) -skip_install = true -deps = - build - twine -commands = - python -m build - twine {posargs:check --strict} dist/* -passenv = - TWINE_USERNAME - TWINE_PASSWORD - TWINE_REPOSITORY_URL - -[testenv:requirements] -description = Update requirements.txt -skip_install = true -deps = pip-tools -commands = - pip-compile --resolver=backtracking --upgrade --quiet - git diff --color --exit-code requirements.txt -allowlist_externals = - git diff --git a/examples/click/{{_copier_conf.answers_file}} b/examples/click/{{_copier_conf.answers_file}} deleted file mode 100644 index dad7ce6..0000000 --- a/examples/click/{{_copier_conf.answers_file}} +++ /dev/null @@ -1,3 +0,0 @@ -# Changes here will be overwritten by Copier -# Feel free to delete this file if you don't plan to run `copier update`. -{{ _copier_answers|to_nice_yaml -}} diff --git a/examples/click/{{module}}/__init__.py b/examples/click/{{module}}/__init__.py deleted file mode 100644 index c8b6676..0000000 --- a/examples/click/{{module}}/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -{{project}}. -""" diff --git a/examples/click/{{module}}/__main__.py b/examples/click/{{module}}/__main__.py deleted file mode 100644 index f70326e..0000000 --- a/examples/click/{{module}}/__main__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Helper module to run not-installed version (via ``python3 -m {{module}}``). -""" -from .cli import main - -if __name__ == "__main__": - main() diff --git a/examples/click/{{module}}/cli.py b/examples/click/{{module}}/cli.py deleted file mode 100644 index 14cadc9..0000000 --- a/examples/click/{{module}}/cli.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -Command line interface implementation. -""" -import click - -from . import command - - -@click.command() -@click.version_option() -@click.argument("envvar") -def main(envvar): - """{{project}}""" - command.example(envvar) diff --git a/examples/click/{{module}}/command.py b/examples/click/{{module}}/command.py deleted file mode 100644 index b00b168..0000000 --- a/examples/click/{{module}}/command.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -Example command module. -""" -import os - -import click - - -def example(envvar): - """An example command.""" - try: - value = os.environ[envvar] - click.echo(f"{envvar} = {value}") - except KeyError: - msg = f"Environment value {envvar} not set." - raise SystemExit(msg) diff --git a/examples/docopt/.gitignore b/examples/docopt/.gitignore deleted file mode 100644 index 6742d38..0000000 --- a/examples/docopt/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -__pycache__/ -*.py[cod] -*$py.class - -build/ -dist/ -*.egg-info/ -.tox/ - -.coverage -coverage.json -coverage.xml -htmlcov/ -junit-report.xml diff --git a/examples/docopt/.gitlab-ci.yml b/examples/docopt/.gitlab-ci.yml deleted file mode 100644 index 048d9a9..0000000 --- a/examples/docopt/.gitlab-ci.yml +++ /dev/null @@ -1,81 +0,0 @@ -# GitLab CI, https://docs.gitlab.com/ee/ci/yaml/ ---- -.tox: - image: docker.io/library/python:${PYTHON_VERSION} - before_script: - - pip install tox - script: - - tox - variables: - PIP_DISABLE_PIP_VERSION_CHECK: '1' - PY_COLORS: '1' - PYTHON_VERSION: '3.10' - needs: [] - only: - - merge_requests - - main - -stages: -- codestyle -- safety -- test -- build -- publish - -format: - stage: codestyle - extends: .tox - variables: - TOXENV: format - -lint: - stage: codestyle - extends: .tox - variables: - TOXENV: lint - -requirements: - stage: safety - extends: .tox - variables: - TOXENV: requirements - -pytest: - stage: test - extends: .tox - variables: - TOXENV: py - parallel: - matrix: - - PYTHON_VERSION: - - '3.9' - - '3.10' - - '3.11' - - '3.12' - coverage: /^TOTAL.+?(\d+\%)$/ - artifacts: - reports: - coverage_report: - coverage_format: cobertura - path: coverage.xml - junit: junit-report.xml - -package: - stage: build - extends: .tox - variables: - TOXENV: package - -pypi-local: - stage: publish - extends: .tox - variables: - GIT_TAG: ${CI_COMMIT_TAG} - TWINE_USERNAME: gitlab-ci-token - TWINE_PASSWORD: ${CI_JOB_TOKEN} - TWINE_REPOSITORY_URL: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi - script: - - tox -qe ensure_version_matches -- ${GIT_TAG} - - tox -e package -- upload - only: - - tags diff --git a/examples/docopt/LICENSE b/examples/docopt/LICENSE deleted file mode 100644 index efb9728..0000000 --- a/examples/docopt/LICENSE +++ /dev/null @@ -1 +0,0 @@ -Copyright (c) {{author}}. All rights reserved. diff --git a/examples/docopt/README.md b/examples/docopt/README.md deleted file mode 100644 index 0564e21..0000000 --- a/examples/docopt/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# {{project}} CLI - -This example uses the docopt-ng CLI package. - -## Background Reading - -- http://docopt.org/ diff --git a/examples/docopt/pyproject.toml b/examples/docopt/pyproject.toml deleted file mode 100644 index d28839b..0000000 --- a/examples/docopt/pyproject.toml +++ /dev/null @@ -1,67 +0,0 @@ -[build-system] -requires = ["setuptools>=61.0"] -build-backend = "setuptools.build_meta" - -[project] -name = "{{package}}" -version = "0.1.0" -description = "{{project}}" -readme = "README.md" -license = {file = "LICENSE"} -authors = [ - {name = "{{author}}", email = "{{email}}"}, -] -maintainers = [ - {name = "{{author}}", email = "{{email}}"}, -] -classifiers = [ - "Development Status :: 4 - Beta", - "Environment :: Console", - "Intended Audience :: Information Technology", - "License :: Other/Proprietary License", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Topic :: Utilities", -] -keywords = [ - "cli", -] -requires-python = ">=3.9" -dependencies = [ - "docopt-ng", -] - -[project.scripts] -"{{package}}" = "{{module}}.cli:main" - -[project.urls] -homepage = "{{url}}" - -[tool.coverage.run] -source = ["{{module}}"] - -[tool.coverage.report] -exclude_only = ["if __name__ == .__main__.:"] -show_missing = true -skip_covered = true - -[tool.pytest.ini_options] -addopts = "--color=yes --doctest-modules --junitxml=junit-report.xml --verbose" - -[tool.ruff] -extend-exclude = [] -extend-include = [] - -[tool.ruff.lint] -extend-ignore = ["ANN", "B904", "D", "INP001", "T201", "TRY200"] -extend-select = ["ALL"] - -[tool.ruff.lint.per-file-ignores] -"test_*.py" = ["S101"] - -[tool.setuptools.packages.find] -where = ["."] -exclude = ["tests"] diff --git a/examples/docopt/requirements.txt b/examples/docopt/requirements.txt deleted file mode 100644 index ad4e66c..0000000 --- a/examples/docopt/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --resolver=backtracking -# -docopt-ng==0.8.1 - # via {{package}} (setup.py) diff --git a/examples/docopt/tests/test_cli.py b/examples/docopt/tests/test_cli.py deleted file mode 100644 index d2220e1..0000000 --- a/examples/docopt/tests/test_cli.py +++ /dev/null @@ -1,96 +0,0 @@ -""" -Tests for command line interface (CLI). -""" -from importlib import import_module -from importlib.metadata import version -from os import linesep -from unittest.mock import patch - -import pytest -from cli_test_helpers import ArgvContext, shell - -import {{module}}.cli - - -def test_main_module(): - """ - Exercise (most of) the code in the ``__main__`` module. - """ - import_module("{{module}}.__main__") - - -def test_runas_module(): - """ - Can this package be run as a Python module? - """ - result = shell("python -m {{module}} --help") - assert result.exit_code == 0 - - -def test_entrypoint(): - """ - Is entrypoint script installed? (setup.py) - """ - result = shell("{{package}} --help") - assert result.exit_code == 0 - - -@patch("{{module}}.command.dispatch") -def test_usage(mock_dispatch): - """ - Does CLI abort w/o arguments, displaying usage instructions? - """ - with ArgvContext("{{package}}"), pytest.raises(SystemExit): - {{module}}.cli.main() - - assert not mock_dispatch.called, "CLI should stop execution" - - result = shell("{{package}}") - - assert "Usage:" in result.stderr - - -def test_version(): - """ - Does --version display information as expected? - """ - expected_version = version("{{package}}") - result = shell("{{package}} --version") - - assert result.stdout == f"{expected_version}{linesep}" - assert result.exit_code == 0 - - -def test_file_argument(): - """ - Is the positional parameter available? - """ - result = shell("{{package}} myfile --help") - assert result.exit_code == 0 - - -# NOTE: -# You can continue here, adding all CLI action and option combinations -# using a non-destructive option, such as --help, to test for the -# availability of the CLI command or option. - - -@pytest.mark.parametrize( - ("option", "silent", "verbose"), - [ - ("-s", True, False), - ("-v", False, True), - ("--silent", True, False), - ("--verbose", False, True), - ], -) -def test_options(option, silent, verbose): - """ - Is the (-s | --silent) and (-v | --verbose) option evaluated correctly? - """ - with ArgvContext("{{package}}", "myfile", option): - args = {{module}}.cli.parse_arguments() - - assert args["file"] == "myfile" - assert args["silent"] == silent - assert args["verbose"] == verbose diff --git a/examples/docopt/tests/test_command.py b/examples/docopt/tests/test_command.py deleted file mode 100644 index 1eb9ca5..0000000 --- a/examples/docopt/tests/test_command.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -Tests for the command module. -""" -from unittest.mock import patch - -from cli_test_helpers import ArgvContext - -import {{module}} - - -@patch("{{module}}.command.dispatch") -def test_dispatch_is_called(mock_dispatch): - """ - Is the correct code called when invoked via the CLI? - """ - with ArgvContext("{{package}}", "myfile", "-v"): - {{module}}.cli.main() - - assert mock_dispatch.called - assert mock_dispatch.call_args.kwargs == { - "file": "myfile", - "silent": False, - "verbose": True, - } - - -@patch("{{module}}.command.print") -@patch("{{module}}.command.Path.open") -def test_dispatch_business_logic(mock_openfile, mock_print): - """ - Walk the code of the dispatch function. - """ - expected_print_calls = 3 - - {{module}}.command.dispatch(file="myfile", silent=False, verbose=True) - - assert mock_openfile.called - assert ( - mock_print.call_count == expected_print_calls - ), "Expected 2x for --verbose, 1x for not --silent" diff --git a/examples/docopt/tox.ini b/examples/docopt/tox.ini deleted file mode 100644 index b893076..0000000 --- a/examples/docopt/tox.ini +++ /dev/null @@ -1,65 +0,0 @@ -[tox] -envlist = - lint - format - py3{9,10,11,12} - requirements - package - -[testenv] -description = Unit tests -deps = - cli-test-helpers - coverage[toml] - pytest -commands = - coverage run -m pytest {posargs} - coverage xml - coverage report - -[testenv:clean] -description = Remove bytecode and other debris -skip_install = true -deps = pyclean -commands = pyclean {posargs:. --debris --erase junit-report.xml --yes} - -[testenv:ensure_version_matches] -description = Verify package version is same as Git tag -deps = -commands = python -c 'from importlib.metadata import version; ver = version("{{package}}"); tag = "'{posargs}'"; error = f"`{ver}` != `{tag}`"; abort = f"Package version does not match the Git tag ({error}). ABORTING."; raise SystemExit(0 if ver and tag and ver == tag else abort)' - -[testenv:format] -description = Ensure consistent code style (Ruff) -skip_install = true -deps = ruff -commands = ruff format {posargs:--check --diff .} - -[testenv:lint] -description = Lightening-fast linting (Ruff) -skip_install = true -deps = ruff -commands = ruff check {posargs:--show-source .} - -[testenv:package] -description = Build package and check metadata (or upload package) -skip_install = true -deps = - build - twine -commands = - python -m build - twine {posargs:check --strict} dist/* -passenv = - TWINE_USERNAME - TWINE_PASSWORD - TWINE_REPOSITORY_URL - -[testenv:requirements] -description = Update requirements.txt -skip_install = true -deps = pip-tools -commands = - pip-compile --resolver=backtracking --upgrade --quiet - git diff --color --exit-code requirements.txt -allowlist_externals = - git diff --git a/examples/docopt/{{_copier_conf.answers_file}} b/examples/docopt/{{_copier_conf.answers_file}} deleted file mode 100644 index dad7ce6..0000000 --- a/examples/docopt/{{_copier_conf.answers_file}} +++ /dev/null @@ -1,3 +0,0 @@ -# Changes here will be overwritten by Copier -# Feel free to delete this file if you don't plan to run `copier update`. -{{ _copier_answers|to_nice_yaml -}} diff --git a/examples/docopt/{{module}}/__init__.py b/examples/docopt/{{module}}/__init__.py deleted file mode 100644 index c8b6676..0000000 --- a/examples/docopt/{{module}}/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -{{project}}. -""" diff --git a/examples/docopt/{{module}}/__main__.py b/examples/docopt/{{module}}/__main__.py deleted file mode 100644 index f70326e..0000000 --- a/examples/docopt/{{module}}/__main__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Helper module to run not-installed version (via ``python3 -m {{module}}``). -""" -from .cli import main - -if __name__ == "__main__": - main() diff --git a/examples/docopt/{{module}}/cli.py b/examples/docopt/{{module}}/cli.py deleted file mode 100644 index 13b838c..0000000 --- a/examples/docopt/{{module}}/cli.py +++ /dev/null @@ -1,40 +0,0 @@ -"""{{package}} - -{{project}}. - -Usage: - {{package}} (-h | --help | --version) - {{package}} [-s | --silent] - {{package}} [-v | --verbose] - -Positional arguments: - file target file path name - -Options: - -h, --help show this help message and exit - -s, --silent don't show progress output - -v, --verbose explain progress verbosely - --version show program's version number and exit -""" -from importlib.metadata import version - -from docopt import docopt - -from . import command - - -def parse_arguments(): - """Parse and handle CLI arguments.""" - args = docopt(__doc__, version=version("{{package}}")) - - return { - "file": args[""], - "silent": args["--silent"], - "verbose": args["--verbose"], - } - - -def main(): - """{{project}}.""" - args = parse_arguments() - command.dispatch(**args) diff --git a/examples/docopt/{{module}}/command.py b/examples/docopt/{{module}}/command.py deleted file mode 100644 index fa1d974..0000000 --- a/examples/docopt/{{module}}/command.py +++ /dev/null @@ -1,22 +0,0 @@ -""" -Example command module. -""" -from pathlib import Path - - -def dispatch(file, silent, verbose): - """An example implementation""" - try: - with Path(file).open() as textfile: - if verbose: - print("Opening text file for reading...") - - content = textfile.read() - - if verbose: - print("Displaying text file...") - - if not silent: - print(content) - except FileNotFoundError as err: - raise SystemExit(err) diff --git a/pyproject.toml b/pyproject.toml index e25f3ac..ff3ac62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" +requires = ["setuptools>=64", "setuptools_scm>=8"] [project] name = "cli-test-helpers" @@ -44,7 +44,6 @@ requires-python = ">=3.7" [project.urls] homepage = "https://github.com/painless-software/python-cli-test-helpers" Documentation = "https://python-cli-test-helpers.readthedocs.io/" -Examples = "https://github.com/painless-software/python-cli-test-helpers/tree/main/examples" [tool.coverage.run] source = ["cli_test_helpers"] @@ -53,10 +52,10 @@ source = ["cli_test_helpers"] show_missing = true [tool.pytest.ini_options] -addopts = "--color=yes --doctest-modules --ignore examples/ --junitxml=tests/junit-report.xml --verbose" +addopts = "--color=yes --doctest-modules --junitxml=tests/junit-report.xml --verbose" [tool.ruff] -extend-exclude = ["docs/conf.py", "examples"] +extend-exclude = ["docs/conf.py"] extend-include = [] [tool.ruff.lint] @@ -75,4 +74,7 @@ extend-ignore = [ [tool.setuptools.packages.find] where = ["."] -exclude = ["tests", "examples"] +exclude = ["tests"] + +[tool.setuptools_scm] +local_scheme = "no-local-version" diff --git a/tox.ini b/tox.ini index 764fb76..15fea88 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,6 @@ envlist = format py3{7,8,9,10,11,12} pypy3{8,9} - example-{argparse,click,docopt} package docs clean @@ -35,39 +34,6 @@ changedir = docs commands = make html allowlist_externals = make -[testenv:example-argparse] -description = Run test suite of example project -skip_install = true -deps = copier -commands = - copier copy {toxinidir} {toxworkdir}/examples/argparse -d engine='Argparse' -d package='foobar' -d module='foobar' --defaults --vcs-ref HEAD - tox -c {toxworkdir}/examples/argparse/tox.ini -e lint,format - tox -c {toxworkdir}/examples/argparse/tox.ini -e py -- -vv -allowlist_externals = - tox - -[testenv:example-click] -description = Run test suite of example project -skip_install = true -deps = copier -commands = - copier copy {toxinidir} {toxworkdir}/examples/click -d engine='Click' -d package='foobar' -d module='foobar' --defaults --vcs-ref HEAD - tox -c {toxworkdir}/examples/click/tox.ini -e lint,format - tox -c {toxworkdir}/examples/click/tox.ini -e py -- -vv -allowlist_externals = - tox - -[testenv:example-docopt] -description = Run test suite of example project -skip_install = true -deps = copier -commands = - copier copy {toxinidir} {toxworkdir}/examples/docopt -d engine='Docopt' -d package='foobar' -d module='foobar' --defaults --vcs-ref HEAD - tox -c {toxworkdir}/examples/docopt/tox.ini -e lint,format - tox -c {toxworkdir}/examples/docopt/tox.ini -e py -- -vv -allowlist_externals = - tox - [testenv:format] description = Ensure consistent code style (Ruff) skip_install = true @@ -78,7 +44,7 @@ commands = ruff format {posargs:--check --diff .} description = Lightening-fast linting (Ruff) skip_install = true deps = ruff -commands = ruff check {posargs:--show-source .} +commands = ruff check {posargs:.} [testenv:package] description = Build package and check metadata (or upload package) From f0c842219e5014c17374a994de94a44ea3300774 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 22 Dec 2024 01:12:16 +0100 Subject: [PATCH 2/3] Add RTD config file (now required) --- .readthedocs.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..98f2554 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +# Read the Docs configuration, +# https://docs.readthedocs.io/en/stable/config-file/ + +version: 2 + +build: + os: ubuntu-lts-latest + tools: + python: "3.12" + +sphinx: + configuration: docs/conf.py + fail_on_warning: true From c944504bae91c6d855b2b070f1baa000bc2acdc1 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 22 Dec 2024 01:13:20 +0100 Subject: [PATCH 3/3] Drop Python 3.7, configure automatic package version --- .github/workflows/test.yml | 1 - pyproject.toml | 6 +++--- tests/test_commands.py | 5 ++--- tests/test_decorators.py | 5 ++--- tox.ini | 2 +- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 74b81b7..f5ce04a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,6 @@ jobs: - macos-latest - windows-latest python-version: - - '3.7' - '3.8' - '3.9' - '3.10' diff --git a/pyproject.toml b/pyproject.toml index ff3ac62..2641917 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ requires = ["setuptools>=64", "setuptools_scm>=8"] [project] name = "cli-test-helpers" -version = "4.0.0" +dynamic = ["version"] description = "Useful helpers for writing tests for your Python CLI program." readme = "README.rst" license = {file = "LICENSE"} @@ -21,7 +21,6 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -39,7 +38,7 @@ keywords = [ "testing", "helpers", ] -requires-python = ">=3.7" +requires-python = ">=3.8" [project.urls] homepage = "https://github.com/painless-software/python-cli-test-helpers" @@ -63,6 +62,7 @@ extend-select = ["ALL"] extend-ignore = [ "ANN", "D200", + "D203", "D205", "D212", "Q000", diff --git a/tests/test_commands.py b/tests/test_commands.py index b585daa..d1e4dae 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -1,6 +1,5 @@ -""" -Tests for our cli test commands. -""" +"""Tests for our cli test commands.""" + from os import linesep from cli_test_helpers import shell diff --git a/tests/test_decorators.py b/tests/test_decorators.py index a1e9a46..85f8eb4 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -1,6 +1,5 @@ -""" -Tests for our tests helpers. 8-}. -""" +"""Tests for our tests helpers. 8-}.""" + import os import sys diff --git a/tox.ini b/tox.ini index 15fea88..46153a7 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ envlist = lint format - py3{7,8,9,10,11,12} + py3{8,9,10,11,12} pypy3{8,9} package docs