From 616784008c95d059d94ca7be8a99854f7e79f8a2 Mon Sep 17 00:00:00 2001 From: Frank Niessink Date: Thu, 13 Jun 2024 21:06:20 +0200 Subject: [PATCH] Update quality tools. - Add pyproject-fmt to quality tools. - Remove safety from quality tools. Closes #8928. --- .circleci/config.yml | 23 ++- .../workflows/application-tests-quality.yml | 27 ++++ .github/workflows/release-quality.yml | 22 +++ components/api_server/ci/quality.sh | 12 +- components/api_server/pyproject.toml | 150 ++++++++++-------- components/collector/ci/quality.sh | 9 -- components/collector/pyproject.toml | 1 - components/notifier/ci/quality.sh | 9 -- components/notifier/pyproject.toml | 1 - components/shared_code/ci/quality.sh | 9 -- components/shared_code/pyproject.toml | 1 - docs/ci/quality.sh | 7 +- docs/pyproject.toml | 124 ++++++++------- release/ci/quality.sh | 25 +++ release/pyproject.toml | 53 ++++++- release/release.py | 38 +++-- release/requirements/requirements-dev.txt | 21 ++- tests/application_tests/ci/quality.sh | 25 +++ tests/application_tests/ci/unittest.sh | 3 + tests/application_tests/pyproject.toml | 71 ++++++++- .../requirements/requirements-dev.txt | 36 ++++- .../requirements/requirements.txt | 6 +- tests/application_tests/src/test_api.py | 4 +- tests/application_tests/src/test_report.py | 33 ++-- tests/feature_tests/ci/quality.sh | 12 +- tests/feature_tests/pyproject.toml | 74 +++++---- 26 files changed, 545 insertions(+), 251 deletions(-) create mode 100644 .github/workflows/application-tests-quality.yml create mode 100644 .github/workflows/release-quality.yml create mode 100755 release/ci/quality.sh create mode 100755 tests/application_tests/ci/quality.sh create mode 100755 tests/application_tests/ci/unittest.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 44bffeafb2..15d3624075 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ jobs: auth: username: $DOCKERHUB_USERNAME password: $DOCKERHUB_PASSWORD - parallelism: 5 + parallelism: 6 steps: - checkout - run: | @@ -19,7 +19,8 @@ jobs: 1) component=components/notifier;; 2) component=components/api_server;; 3) component=components/shared_code;; - 4) component=tests/feature_tests;; + 4) component=tests/application_tests;; + 5) component=tests/feature_tests;; esac cd $component mkdir -p build @@ -36,6 +37,10 @@ jobs: path: components/api_server/build - store_artifacts: path: components/shared_code/build + - store_artifacts: + path: components/application_tests/build + - store_artifacts: + path: components/feature_tests/build unittest_frontend: docker: @@ -65,6 +70,18 @@ jobs: ci/unittest.sh ci/quality.sh + unittest_release: + machine: + image: default + steps: + - checkout + - run: | + cd release + python3 -m venv venv + . venv/bin/activate + ci/pip-install.sh + ci/quality.sh + application_tests: machine: image: default @@ -119,6 +136,8 @@ workflows: context: QualityTime - unittest_docs: context: QualityTime + - unittest_release: + context: QualityTime - docker/hadolint: context: QualityTime dockerfiles: "components/collector/Dockerfile:components/database/Dockerfile:\ diff --git a/.github/workflows/application-tests-quality.yml b/.github/workflows/application-tests-quality.yml new file mode 100644 index 0000000000..0e430c4dfa --- /dev/null +++ b/.github/workflows/application-tests-quality.yml @@ -0,0 +1,27 @@ +name: Application tests quality + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4.1.7 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install dependencies + run: | + cd tests/application_tests + ci/pip-install.sh + - name: Test + run: | + cd tests/application_tests + ci/unittest.sh + - name: Quality + run: | + cd tests/application_tests + ci/quality.sh diff --git a/.github/workflows/release-quality.yml b/.github/workflows/release-quality.yml new file mode 100644 index 0000000000..8b37655c49 --- /dev/null +++ b/.github/workflows/release-quality.yml @@ -0,0 +1,22 @@ +name: Release script quality + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4.1.7 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install dependencies and run quality checks + run: | + cd release + python -m venv venv + . venv/bin/activate + ci/pip-install.sh + ci/quality.sh diff --git a/components/api_server/ci/quality.sh b/components/api_server/ci/quality.sh index 12a07f1a48..310b6155a1 100755 --- a/components/api_server/ci/quality.sh +++ b/components/api_server/ci/quality.sh @@ -12,18 +12,12 @@ run pipx run `spec fixit` lint src tests # Mypy run pipx run `spec mypy` --python-executable=$(which python) src +# Pyproject-fmt +run pipx run `spec pyproject-fmt` --check pyproject.toml + # pip-audit run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements.txt -r requirements/requirements-dev.txt -# Safety -# Vulnerability ID: 67599 -# ADVISORY: ** DISPUTED ** An issue was discovered in pip (all versions) because it installs the version with the -# highest version number, even if the user had intended to obtain a private package from a private index. This only -# affects use of the --extra-index-url option, and exploitation requires that the... -# CVE-2018-20225 -# For more information about this vulnerability, visit https://data.safetycli.com/v/67599/97c -run pipx run `spec safety` check --bare --ignore 67599 -r requirements/requirements.txt -r requirements/requirements-dev.txt - # Bandit run pipx run `spec bandit` --quiet --recursive src/ diff --git a/components/api_server/pyproject.toml b/components/api_server/pyproject.toml index cdb611f7dd..c30d654d97 100644 --- a/components/api_server/pyproject.toml +++ b/components/api_server/pyproject.toml @@ -1,38 +1,107 @@ [project] -name = "api_server" +name = "api-server" version = "5.13.0" +requires-python = ">=3.12" +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.12", +] dependencies = [ "bottle==0.12.25", "cryptography==42.0.8", "gevent==24.2.1", "ldap3==2.9.1", - "lxml[html_clean]==5.2.2", + "lxml[html-clean]==5.2.2", "pymongo==4.7.3", - "requests==2.32.3" + "requests==2.32.3", ] - -[project.optional-dependencies] -dev = [ +optional-dependencies.dev = [ "coverage==7.5.3", "pip==24.0", + "pip-tools==7.4.1", # To add hashes to requirements "pipx==1.6.0", - "pip-tools==7.4.1", # To add hashes to requirements - "pydantic==2.7.4", # Needed for importing the data model in the tests from the shared code component + "pydantic==2.7.4", # Needed for importing the data model in the tests from the shared code component "types-cryptography==3.3.23.2", "types-ldap3==2.9.13.20240205", "types-requests==2.32.0.20240602", - "unittest-xml-reporting==3.2.0", # Needed to generate JUnit XML output for Sonarcloud.io + "unittest-xml-reporting==3.2.0", # Needed to generate JUnit XML output for Sonarcloud.io ] -tools = [ +optional-dependencies.tools = [ "bandit==1.7.9", "fixit==2.1.0", "mypy==1.10.0", "pip-audit==2.7.3", + "pyproject-fmt==2.1.3", "ruff==0.4.8", - "safety==3.2.3", - "vulture==2.11" + "vulture==2.11", ] +[tool.ruff] +target-version = "py312" +line-length = 120 +src = [ + "src", +] +lint.select = [ + "ALL", +] +lint.ignore = [ + "ANN001", # https://docs.astral.sh/ruff/rules/missing-type-function-argument/ - too many untyped arguments atm to turn this rule on + "ANN002", # https://docs.astral.sh/ruff/rules/missing-type-args/ - leads to false positives for super().__init__(*args, **kwargs) + "ANN003", # https://docs.astral.sh/ruff/rules/missing-type-kwargs/ - leads to false positives for super().__init__(*args, **kwargs) + "ANN101", # https://docs.astral.sh/ruff/rules/missing-type-self/ - type checkers can infer the type of `self`, so annotating it is superfluous + "ANN102", # https://docs.astral.sh/ruff/rules/missing-type-cls/ - type checkers can infer the type of `cls`, so annotating it is superfluous + "ANN201", # https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/ - too many untyped return values atm to turn this rule on + "COM812", # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ - this rule may cause conflicts when used with the ruff formatter + "D107", # https://docs.astral.sh/ruff/rules/undocumented-public-init/ - requiring __init__() methods to have docstrings seems a bit much + "D203", # https://docs.astral.sh/ruff/rules/one-blank-line-before-class/ - prevent warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class` + "D213", # https://docs.astral.sh/ruff/rules/multi-line-summary-second-line/ - prevent warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line` + "FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt - not sure of the value of preventing "boolean traps" + "I001", # https://docs.astral.sh/ruff/rules/unsorted-imports/ - (probably) because ruff is run with pipx it can't differentiate between dependencies and modules + "ISC001", # https://docs.astral.sh/ruff/rules/single-line-implicit-string-concatenation/ - this rule may cause conflicts when used with the ruff formatter + "PD", # https://docs.astral.sh/ruff/rules/#pandas-vet-pd - pandas isn't used + "PT", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt - pytest isn't used +] +lint.per-file-ignores.".vulture_ignore_list.py" = [ + "ALL", +] +lint.per-file-ignores."__init__.py" = [ + "D104", # https://docs.astral.sh/ruff/rules/undocumented-public-package/ - don't require doc strings in __init__.py files + "F401", # https://docs.astral.sh/ruff/rules/unused-import/ - routes are imported in __init__.py files to flatten the module hierarchy +] +lint.per-file-ignores."src/model/issue_tracker.py" = [ + "BLE001", # https://docs.astral.sh/ruff/rules/blind-except/ - allow for catching blind exception `Exception` +] +lint.per-file-ignores."src/quality_time_server.py" = [ + "E402", + "INP001", # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ - false positive because this is the main script +] +lint.per-file-ignores."tests/**/*.py" = [ + "ANN201", # https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/ - don't require test functions to have return types + "S105", # https://docs.astral.sh/ruff/rules/hardcoded-password-string/ - hardcoded passwords in test code are test data + "S106", # https://docs.astral.sh/ruff/rules/hardcoded-password-func-arg/ - hardcoded passwords in test code are test data +] +lint.isort.section-order = [ + "future", + "standard-library", + "third-party", + "second-party", + "first-party", + "tests", + "local-folder", +] +lint.isort.sections."second-party" = [ + "shared", + "shared_data_model", +] +lint.isort.sections.tests = [ + "tests", +] + +[tool.pyproject-fmt] +indent = 4 +keep_full_version = true # Remove trailing zero's from version specifiers? + [tool.mypy] ignore_missing_imports = false incremental = false @@ -40,14 +109,14 @@ warn_redundant_casts = true warn_return_any = true warn_unreachable = true warn_unused_ignores = true -disable_error_code = "valid-type" # mypy does not yet support PEP 695, Type Parameter Syntax. See https://github.com/python/mypy/issues/15238 +disable_error_code = "valid-type" # mypy does not yet support PEP 695, Type Parameter Syntax. See https://github.com/python/mypy/issues/15238 [[tool.mypy.overrides]] module = [ "bottle", "gevent", "lxml.html", - "lxml.html.clean" + "lxml.html.clean", ] ignore_missing_imports = true @@ -55,56 +124,5 @@ ignore_missing_imports = true allow_unsafe = true generate_hashes = true quiet = true -strip_extras = false # Needed for lxml[html-clean] +strip_extras = false # Needed for lxml[html-clean] upgrade = true - -[tool.ruff] -target-version = "py312" -line-length = 120 -src = ["src"] - -[tool.ruff.lint] -select = ["ALL"] -ignore = [ - "ANN001", # https://docs.astral.sh/ruff/rules/missing-type-function-argument/ - too many untyped arguments atm to turn this rule on - "ANN002", # https://docs.astral.sh/ruff/rules/missing-type-args/ - leads to false positives for super().__init__(*args, **kwargs) - "ANN003", # https://docs.astral.sh/ruff/rules/missing-type-kwargs/ - leads to false positives for super().__init__(*args, **kwargs) - "ANN101", # https://docs.astral.sh/ruff/rules/missing-type-self/ - type checkers can infer the type of `self`, so annotating it is superfluous - "ANN102", # https://docs.astral.sh/ruff/rules/missing-type-cls/ - type checkers can infer the type of `cls`, so annotating it is superfluous - "ANN201", # https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/ - too many untyped return values atm to turn this rule on - "COM812", # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ - this rule may cause conflicts when used with the ruff formatter - "D107", # https://docs.astral.sh/ruff/rules/undocumented-public-init/ - requiring __init__() methods to have docstrings seems a bit much - "D203", # https://docs.astral.sh/ruff/rules/one-blank-line-before-class/ - prevent warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class` - "D213", # https://docs.astral.sh/ruff/rules/multi-line-summary-second-line/ - prevent warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line` - "FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt - not sure of the value of preventing "boolean traps" - "I001", # https://docs.astral.sh/ruff/rules/unsorted-imports/ - (probably) because ruff is run with pipx it can't differentiate between dependencies and modules - "ISC001", # https://docs.astral.sh/ruff/rules/single-line-implicit-string-concatenation/ - this rule may cause conflicts when used with the ruff formatter - "PD", # https://docs.astral.sh/ruff/rules/#pandas-vet-pd - pandas isn't used - "PT", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt - pytest isn't used -] - -[tool.ruff.lint.isort] -section-order = ["future", "standard-library", "third-party", "second-party", "first-party", "tests", "local-folder"] - -[tool.ruff.lint.isort.sections] -"second-party" = ["shared", "shared_data_model"] -"tests" = ["tests"] - -[tool.ruff.lint.per-file-ignores] -".vulture_ignore_list.py" = ["ALL"] -"__init__.py" = [ - "D104", # https://docs.astral.sh/ruff/rules/undocumented-public-package/ - don't require doc strings in __init__.py files - "F401", # https://docs.astral.sh/ruff/rules/unused-import/ - routes are imported in __init__.py files to flatten the module hierarchy -] -"src/quality_time_server.py" = [ - "E402", - "INP001", # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ - false positive because this is the main script -] -"src/model/issue_tracker.py" = [ - "BLE001" # https://docs.astral.sh/ruff/rules/blind-except/ - allow for catching blind exception `Exception` -] -"tests/**/*.py" = [ - "ANN201", # https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/ - don't require test functions to have return types - "S105", # https://docs.astral.sh/ruff/rules/hardcoded-password-string/ - hardcoded passwords in test code are test data - "S106", # https://docs.astral.sh/ruff/rules/hardcoded-password-func-arg/ - hardcoded passwords in test code are test data -] diff --git a/components/collector/ci/quality.sh b/components/collector/ci/quality.sh index 8933d67612..089f01ea42 100755 --- a/components/collector/ci/quality.sh +++ b/components/collector/ci/quality.sh @@ -16,15 +16,6 @@ run pipx run `spec mypy` --python-executable=$(which python) src tests # See https://github.com/aio-libs/aiohttp/issues/6772 for why we ignore the CVE run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements.txt -r requirements/requirements-dev.txt -# Safety -# Vulnerability ID: 67599 -# ADVISORY: ** DISPUTED ** An issue was discovered in pip (all versions) because it installs the version with the -# highest version number, even if the user had intended to obtain a private package from a private index. This only -# affects use of the --extra-index-url option, and exploitation requires that the... -# CVE-2018-20225 -# For more information about this vulnerability, visit https://data.safetycli.com/v/67599/97c -run pipx run `spec safety` check --bare --ignore 67599 -r requirements/requirements.txt -r requirements/requirements-dev.txt - # Bandit run pipx run `spec bandit` --quiet --recursive src/ diff --git a/components/collector/pyproject.toml b/components/collector/pyproject.toml index b15d759577..593b5b7b9e 100644 --- a/components/collector/pyproject.toml +++ b/components/collector/pyproject.toml @@ -29,7 +29,6 @@ tools = [ "mypy==1.10.0", "pip-audit==2.7.3", "ruff==0.4.8", - "safety==3.2.3", "vulture==2.11" ] diff --git a/components/notifier/ci/quality.sh b/components/notifier/ci/quality.sh index e252158282..683d5d1682 100755 --- a/components/notifier/ci/quality.sh +++ b/components/notifier/ci/quality.sh @@ -16,15 +16,6 @@ run pipx run `spec mypy` --python-executable=$(which python) src # See https://github.com/aio-libs/aiohttp/issues/6772 for why we ignore the CVE run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements.txt -r requirements/requirements-dev.txt -# Safety -# Vulnerability ID: 67599 -# ADVISORY: ** DISPUTED ** An issue was discovered in pip (all versions) because it installs the version with the -# highest version number, even if the user had intended to obtain a private package from a private index. This only -# affects use of the --extra-index-url option, and exploitation requires that the... -# CVE-2018-20225 -# For more information about this vulnerability, visit https://data.safetycli.com/v/67599/97c -run pipx run `spec safety` check --bare --ignore 67599 -r requirements/requirements.txt -r requirements/requirements-dev.txt - # Bandit run pipx run `spec bandit` --quiet --recursive src/ diff --git a/components/notifier/pyproject.toml b/components/notifier/pyproject.toml index ffee5867fc..8bd32795eb 100644 --- a/components/notifier/pyproject.toml +++ b/components/notifier/pyproject.toml @@ -22,7 +22,6 @@ tools = [ "mypy==1.10.0", "pip-audit==2.7.3", "ruff==0.4.8", - "safety==3.2.3", "vulture==2.11" ] diff --git a/components/shared_code/ci/quality.sh b/components/shared_code/ci/quality.sh index 599ef9a839..b87b7a8cc0 100755 --- a/components/shared_code/ci/quality.sh +++ b/components/shared_code/ci/quality.sh @@ -18,15 +18,6 @@ run $PIPX_BIN_DIR/mypy src --python-executable=$(which python) # pip-audit run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements-dev.txt -# Safety -# Vulnerability ID: 67599 -# ADVISORY: ** DISPUTED ** An issue was discovered in pip (all versions) because it installs the version with the -# highest version number, even if the user had intended to obtain a private package from a private index. This only -# affects use of the --extra-index-url option, and exploitation requires that the... -# CVE-2018-20225 -# For more information about this vulnerability, visit https://data.safetycli.com/v/67599/97c -run pipx run `spec safety` check --bare --ignore 67599 -r requirements/requirements-dev.txt - # Bandit run pipx run `spec bandit` --quiet --recursive src/ diff --git a/components/shared_code/pyproject.toml b/components/shared_code/pyproject.toml index 756ee5d689..0ddfa708c9 100644 --- a/components/shared_code/pyproject.toml +++ b/components/shared_code/pyproject.toml @@ -26,7 +26,6 @@ tools = [ "pip-audit==2.7.3", "pydantic==2.7.4", # Needed because pipx needs to inject Pydantic into the mpyp venv, see ci/quality.sh "ruff==0.4.8", - "safety==3.2.3", "vulture==2.11" ] diff --git a/docs/ci/quality.sh b/docs/ci/quality.sh index 1745cbb158..e434ca8c78 100755 --- a/docs/ci/quality.sh +++ b/docs/ci/quality.sh @@ -18,6 +18,9 @@ run pipx install --force `spec mypy` # --force works around this bug: https://g run pipx inject mypy `spec pydantic` run $PIPX_BIN_DIR/mypy src --python-executable=$(which python) +# Pyproject-fmt +run pipx run `spec pyproject-fmt` --check pyproject.toml + # Vale run pipx run `spec vale` sync run pipx run `spec vale` --no-wrap src/*.md @@ -25,8 +28,8 @@ run pipx run `spec vale` --no-wrap src/*.md # pip-audit run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements.txt -r requirements/requirements-dev.txt -# Safety -run pipx run `spec bandit` --quiet --recursive src/ +# Bandit +run pipx run `spec bandit` --configfile pyproject.toml --quiet --recursive src/ # Vulture run pipx run `spec vulture` --min-confidence 0 src/ tests/ .vulture_ignore_list.py diff --git a/docs/pyproject.toml b/docs/pyproject.toml index c6fa866de5..0dd5e5fe26 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -1,36 +1,94 @@ [project] name = "docs" version = "5.13.0" +requires-python = ">=3.12" +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.12", +] dependencies = [ "furo==2023.9.10", "gitpython==3.1.43", "myst-parser==2.0.0", - "pydantic==2.7.4", # Needed for generating the reference docs from the data model - "Sphinx==7.2.6", + "pydantic==2.7.4", # Needed for generating the reference docs from the data model + "sphinx==7.2.6", "sphinx-copybutton==0.5.2", - "sphinx_design==0.5.0" + "sphinx-design==0.5.0", ] - -[project.optional-dependencies] -dev = [ +optional-dependencies.dev = [ "coverage==7.3.4", "pip==24.0", + "pip-tools==7.4.1", # To add hashes to requirements "pipx==1.6.0", - "pip-tools==7.4.1", # To add hashes to requirements - "unittest-xml-reporting==3.2.0", # Needed to generate JUnit XML output for Sonarcloud.io + "unittest-xml-reporting==3.2.0", # Needed to generate JUnit XML output for Sonarcloud.io ] -tools = [ +optional-dependencies.tools = [ "bandit==1.7.9", "fixit==2.1.0", "mypy==1.10.0", "pip-audit==2.7.3", - "pydantic==2.7.4", # Needed because pipx needs to inject Pydantic into the mpyp venv, see ci/quality.sh + "pydantic==2.7.4", # Needed because pipx needs to inject Pydantic into the mpyp venv, see ci/quality.sh + "pyproject-fmt==2.1.3", "ruff==0.4.8", - "safety==3.2.3", - "vale==3.0.3.0", # Documentation grammar and style checker - "vulture==2.11" + "vale==3.0.3.0", # Documentation grammar and style checker + "vulture==2.11", ] +[tool.ruff] +target-version = "py312" +line-length = 120 +src = [ + "src", +] +lint.select = [ + "ALL", +] +lint.ignore = [ + "ANN101", # https://docs.astral.sh/ruff/rules/missing-type-function-argument/ - type checkers can infer the type of `self`, so annotating it is superfluous + "COM812", # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ - this rule may cause conflicts when used with the ruff formatter + "D203", # https://docs.astral.sh/ruff/rules/one-blank-line-before-class/ - prevent warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class` + "D213", # https://docs.astral.sh/ruff/rules/multi-line-summary-second-line/ - prevent warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line` + "FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt - not sure of the value of preventing "boolean traps" + "ISC001", # https://docs.astral.sh/ruff/rules/single-line-implicit-string-concatenation/ - this rule may cause conflicts when used with the ruff formatter + "PD", # https://docs.astral.sh/ruff/rules/#pandas-vet-pd - pandas isn't used + "PT", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt - pytest isn't used +] +lint.per-file-ignores.".vulture_ignore_list.py" = [ + "ALL", +] +lint.per-file-ignores."__init__.py" = [ + "D104", # https://docs.astral.sh/ruff/rules/undocumented-public-package/ - don't require doc strings in __init__.py files +] +lint.per-file-ignores."src/conf.py" = [ + "INP001", # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ - false positive because this is a configuration file +] +lint.per-file-ignores."src/create_reference_md.py" = [ + "INP001", # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ - false positive because this is a script +] +lint.per-file-ignores."tests/**/*.py" = [ + "ANN201", # https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/ - don't require test functions to have return types +] +lint.isort.section-order = [ + "future", + "standard-library", + "third-party", + "second-party", + "first-party", + "tests", + "local-folder", +] +lint.isort.sections.second-party = [ + "shared", + "shared_data_model", +] +lint.isort.sections.tests = [ + "tests", +] + +[tool.pyproject-fmt] +indent = 4 +keep_full_version = true # Remove trailing zero's from version specifiers? + [tool.mypy] plugins = "pydantic.mypy" ignore_missing_imports = false @@ -46,43 +104,3 @@ generate_hashes = true quiet = true strip_extras = true upgrade = true - -[tool.ruff] -target-version = "py312" -line-length = 120 -src = ["src"] - -[tool.ruff.lint] -select = ["ALL"] -ignore = [ - "ANN101", # https://docs.astral.sh/ruff/rules/missing-type-function-argument/ - type checkers can infer the type of `self`, so annotating it is superfluous - "COM812", # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ - this rule may cause conflicts when used with the ruff formatter - "D203", # https://docs.astral.sh/ruff/rules/one-blank-line-before-class/ - prevent warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class` - "D213", # https://docs.astral.sh/ruff/rules/multi-line-summary-second-line/ - prevent warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line` - "FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt - not sure of the value of preventing "boolean traps" - "ISC001", # https://docs.astral.sh/ruff/rules/single-line-implicit-string-concatenation/ - this rule may cause conflicts when used with the ruff formatter - "PD", # https://docs.astral.sh/ruff/rules/#pandas-vet-pd - pandas isn't used - "PT", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt - pytest isn't used -] - -[tool.ruff.lint.isort] -section-order = ["future", "standard-library", "third-party", "second-party", "first-party", "tests", "local-folder"] - -[tool.ruff.lint.isort.sections] -"second-party" = ["shared", "shared_data_model"] -"tests" = ["tests"] - -[tool.ruff.lint.per-file-ignores] -".vulture_ignore_list.py" = ["ALL"] -"__init__.py" = [ - "D104", # https://docs.astral.sh/ruff/rules/undocumented-public-package/ - don't require doc strings in __init__.py files -] -"src/conf.py" = [ - "INP001", # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ - false positive because this is a configuration file -] -"src/create_reference_md.py" = [ - "INP001", # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ - false positive because this is a script -] -"tests/**/*.py" = [ - "ANN201", # https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/ - don't require test functions to have return types -] diff --git a/release/ci/quality.sh b/release/ci/quality.sh new file mode 100755 index 0000000000..989e72fd71 --- /dev/null +++ b/release/ci/quality.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +source ../ci/base.sh + +# Ruff +run pipx run `spec ruff` check . +run pipx run `spec ruff` format --check . + +# Fixit +run pipx run `spec fixit` lint *.py + +# Mypy +run pipx run `spec mypy` --python-executable=$(which python) *.py + +# Pyproject-fmt +run pipx run `spec pyproject-fmt` --check pyproject.toml + +# pip-audit +run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements.txt -r requirements/requirements-dev.txt + +# Bandit +run pipx run `spec bandit` --configfile pyproject.toml --quiet --recursive *.py + +# Vulture +run pipx run `spec vulture` --min-confidence 0 *.py diff --git a/release/pyproject.toml b/release/pyproject.toml index 4300cb70b2..6b00f76ae5 100644 --- a/release/pyproject.toml +++ b/release/pyproject.toml @@ -1,17 +1,51 @@ [project] name = "release" version = "5.13.0" +requires-python = ">=3.12" +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.12", +] dependencies = [ "bump-my-version==0.22.0.post197.dev9", "gitpython==3.1.43", ] - -[project.optional-dependencies] -dev = [ +optional-dependencies.dev = [ "pip==24.0", - "pip-tools==7.4.1" # To add hashes to requirements + "pip-tools==7.4.1", # To add hashes to requirements + "pipx==1.6.0", +] +optional-dependencies.tools = [ + "bandit==1.7.9", + "fixit==2.1.0", + "mypy==1.10.0", + "pip-audit==2.7.3", + "pydantic==2.7.4", # Needed because pipx needs to inject Pydantic into the mpyp venv, see ci/quality.sh + "pyproject-fmt==2.1.3", + "ruff==0.4.8", + "vulture==2.11", ] +[tool.ruff] +target-version = "py312" +line-length = 120 +src = [ + "src", +] +lint.select = [ + "ALL", +] +lint.ignore = [ + "COM812", # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ - this rule may cause conflicts when used with the ruff formatter + "D203", # https://docs.astral.sh/ruff/rules/one-blank-line-before-class/ - prevent warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class` + "D213", # https://docs.astral.sh/ruff/rules/multi-line-summary-second-line/ - prevent warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line` + "ISC001", # https://docs.astral.sh/ruff/rules/single-line-implicit-string-concatenation/ - this rule may cause conflicts when used with the ruff formatter +] + +[tool.pyproject-fmt] +indent = 4 +keep_full_version = true # Remove trailing zero's from version specifiers? + [tool.bumpversion] current_version = "5.13.0" parse = """(?x) @@ -33,7 +67,10 @@ commit = true tag = true [tool.bumpversion.parts.pre_release_label] -values = ["rc", "final"] +values = [ + "rc", + "final", +] optional_value = "final" [[tool.bumpversion.files]] @@ -76,6 +113,12 @@ glob = "../**/pyproject.toml" search = 'version = "{current_version}"' replace = 'version = "{new_version}"' +[tool.bandit] +skips = [ + "B404", # Consider possible security implications associated with the subprocess module. + "B603", # subprocess call - check for execution of untrusted input. +] + [tool.pip-tools] allow_unsafe = true generate_hashes = true diff --git a/release/release.py b/release/release.py index 2faddc037c..423efaa5b2 100755 --- a/release/release.py +++ b/release/release.py @@ -26,9 +26,9 @@ def get_version() -> str: with pathlib.Path(release_folder / "pyproject.toml").open(mode="rb") as py_project_toml_fp: py_project_toml = tomllib.load(py_project_toml_fp) version_re = py_project_toml["tool"]["bumpversion"]["parse"] - version_tags = [tag for tag in repo.tags if re.match(version_re, tag.tag.tag.strip("v"), re.MULTILINE)] + version_tags = [tag for tag in repo.tags if tag.tag and re.match(version_re, tag.tag.tag.strip("v"), re.MULTILINE)] latest_tag = sorted(version_tags, key=lambda tag: tag.commit.committed_datetime)[-1] - return latest_tag.tag.tag.strip("v") + return latest_tag.tag.tag.strip("v") if latest_tag.tag else "?" def parse_arguments() -> tuple[str, str, bool]: @@ -44,13 +44,26 @@ def parse_arguments() -> tuple[str, str, bool]: - the changelog has an '[Unreleased]' header - the changelog contains no release candidates - the new release has been added to the version overview""" - parser = ArgumentParser(description=description, epilog=epilog, formatter_class=RawDescriptionHelpFormatter) - allowed_bumps_in_rc_mode = ["rc", "rc-major", "rc-minor", "rc-patch", "drop-rc"] # rc = release candidate + parser = ArgumentParser( + description=description, + epilog=epilog, + formatter_class=RawDescriptionHelpFormatter, + ) + allowed_bumps_in_rc_mode = [ + "rc", + "rc-major", + "rc-minor", + "rc-patch", + "drop-rc", + ] # rc = release candidate allowed_bumps = ["rc-patch", "rc-minor", "rc-major", "patch", "minor", "major"] bumps = allowed_bumps_in_rc_mode if "rc" in current_version else allowed_bumps parser.add_argument("bump", choices=bumps) parser.add_argument( - "-c", "--check-preconditions-only", action="store_true", help="only check the preconditions and then exit" + "-c", + "--check-preconditions-only", + action="store_true", + help="only check the preconditions and then exit", ) arguments = parser.parse_args() return arguments.bump, current_version, arguments.check_preconditions_only @@ -117,7 +130,7 @@ def failed_preconditions_version_overview(current_version: str, root: pathlib.Pa for line in version_overview_lines: if line.startswith(f"| v{current_version} "): if previous_line.startswith("| v"): - today = datetime.date.today().isoformat() + today = utc_today().isoformat() release_date = previous_line.split(" | ")[1].strip() if release_date != today: # Second column is the release date column return [f"{missing} the release date. Expected today: '{today}', found: '{release_date}'."] @@ -127,13 +140,18 @@ def failed_preconditions_version_overview(current_version: str, root: pathlib.Pa return [f"{missing} the current version ({current_version})."] +def utc_today() -> datetime.date: + """Return today in UTC.""" + return datetime.datetime.now(tz=datetime.UTC).date() + + def main() -> None: """Create the release.""" - os.environ["RELEASE_DATE"] = datetime.date.today().isoformat() # Used by bump-my-version to update CHANGELOG.md + os.environ["RELEASE_DATE"] = utc_today().isoformat() # Used by bump-my-version to update CHANGELOG.md bump, current_version, check_preconditions_only = parse_arguments() check_preconditions(bump, current_version) if check_preconditions_only: - return + return # See https://github.com/callowayproject/bump-my-version?tab=readme-ov-file#add-support-for-pre-release-versions # for how bump-my-version deals with pre-release versions if bump.startswith("rc-"): @@ -142,8 +160,8 @@ def main() -> None: bump = "pre_release_label" # Bump the pre-release label from "rc" to "final" (which is optional and omitted) elif bump == "rc": bump = "pre_release_number" # Bump the release candidate number - subprocess.run(("bump-my-version", "bump", bump), check=True) - subprocess.run(("git", "push", "--follow-tags"), check=True) + subprocess.run(("bump-my-version", "bump", bump), check=True) # noqa: S603 + subprocess.run(("git", "push", "--follow-tags"), check=True) # noqa: S603 if __name__ == "__main__": diff --git a/release/requirements/requirements-dev.txt b/release/requirements/requirements-dev.txt index 6e1acc1a49..3734e17d73 100644 --- a/release/requirements/requirements-dev.txt +++ b/release/requirements/requirements-dev.txt @@ -8,6 +8,10 @@ annotated-types==0.7.0 \ --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 # via pydantic +argcomplete==3.3.0 \ + --hash=sha256:c168c3723482c031df3c207d4ba8fa702717ccb9fc0bfe4117166c1f537b4a54 \ + --hash=sha256:fd03ff4a5b9e6580569d34b273f741e85cd9e072f3feeeee3eba4891c70eda62 + # via pipx bracex==2.4 \ --hash=sha256:a27eaf1df42cf561fed58b7a8f3fdf129d1ea16a81e1fadd1d17989bc6384beb \ --hash=sha256:efdc71eff95eaff5e0f8cfebe7d01adf2c8637c8c92edaf63ef348c241a82418 @@ -27,6 +31,7 @@ click==8.1.7 \ # bump-my-version # pip-tools # rich-click + # userpath gitdb==4.0.11 \ --hash=sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4 \ --hash=sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b @@ -46,11 +51,21 @@ mdurl==0.1.2 \ packaging==24.1 \ --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 - # via build + # via + # build + # pipx pip-tools==7.4.1 \ --hash=sha256:4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9 \ --hash=sha256:864826f5073864450e24dbeeb85ce3920cdfb09848a3d69ebf537b521f14bcc9 # via release (pyproject.toml) +pipx==1.6.0 \ + --hash=sha256:760889dc3aeed7bf4024973bf22ca0c2a891003f52389159ab5cb0c57d9ebff4 \ + --hash=sha256:840610e00103e3d49ae24b6b51804b60988851a5dd65468adb71e5a97e2699b2 + # via release (pyproject.toml) +platformdirs==4.2.2 \ + --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \ + --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3 + # via pipx prompt-toolkit==3.0.36 \ --hash=sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63 \ --hash=sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305 @@ -189,6 +204,10 @@ typing-extensions==4.12.2 \ # pydantic # pydantic-core # rich-click +userpath==1.9.2 \ + --hash=sha256:2cbf01a23d655a1ff8fc166dfb78da1b641d1ceabf0fe5f970767d380b14e89d \ + --hash=sha256:6c52288dab069257cc831846d15d48133522455d4677ee69a9781f11dbefd815 + # via pipx wcmatch==8.5.2 \ --hash=sha256:17d3ad3758f9d0b5b4dedc770b65420d4dac62e680229c287bf24c9db856a478 \ --hash=sha256:a70222b86dea82fb382dd87b73278c10756c138bd6f8f714e2183128887b9eb2 diff --git a/tests/application_tests/ci/quality.sh b/tests/application_tests/ci/quality.sh new file mode 100755 index 0000000000..2b91923801 --- /dev/null +++ b/tests/application_tests/ci/quality.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +source ../../ci/base.sh + +# Ruff +run pipx run `spec ruff` check . +run pipx run `spec ruff` format --check . + +# Fixit +run pipx run `spec fixit` lint src + +# Mypy +run pipx run `spec mypy` --python-executable=$(which python) src + +# Pyproject-fmt +run pipx run `spec pyproject-fmt` --check pyproject.toml + +# pip-audit +run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements.txt -r requirements/requirements-dev.txt + +# Bandit +run pipx run `spec bandit` --configfile pyproject.toml --quiet --recursive src + +# Vulture +run pipx run `spec vulture` --min-confidence 0 src diff --git a/tests/application_tests/ci/unittest.sh b/tests/application_tests/ci/unittest.sh new file mode 100755 index 0000000000..9a4802fb92 --- /dev/null +++ b/tests/application_tests/ci/unittest.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +# Dummy unittest.sh so we can run the same steps for different components. diff --git a/tests/application_tests/pyproject.toml b/tests/application_tests/pyproject.toml index d4f018d2ee..8795aed35f 100644 --- a/tests/application_tests/pyproject.toml +++ b/tests/application_tests/pyproject.toml @@ -1,17 +1,76 @@ [project] -name = "application_tests" +name = "application-tests" version = "5.13.0" +requires-python = ">=3.12" +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.12", +] dependencies = [ "axe-selenium-python==2.1.6", "requests==2.32.3", - "selenium==4.21.0" + "selenium==4.21.0", ] - -[project.optional-dependencies] -dev = [ +optional-dependencies.dev = [ "pip==24.0", - "pip-tools==7.4.1" # To add hashes to requirements + "pip-tools==7.4.1", # To add hashes to requirements + "pipx==1.6.0", + "types-requests==2.32.0.20240602", +] +optional-dependencies.tools = [ + "bandit==1.7.9", + "fixit==2.1.0", + "mypy==1.10.0", + "pip-audit==2.7.3", + "pyproject-fmt==2.1.3", + "ruff==0.4.8", + "vulture==2.11", +] + +[tool.ruff] +target-version = "py312" +line-length = 120 +src = [ + "src", +] +lint.select = [ + "ALL", +] +lint.ignore = [ + "ANN001", # https://docs.astral.sh/ruff/rules/missing-type-function-argument/ - too many untyped arguments atm to turn this rule on + "ANN101", # https://docs.astral.sh/ruff/rules/missing-type-function-argument/ - type checkers can infer the type of `self`, so annotating it is superfluous + "ANN201", # https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/ - too many untyped return values atm to turn this rule on + "ANN204", # https://docs.astral.sh/ruff/rules/missing-return-type-special-method/ - typing classes that inherit from set and list correctly is surprisingly hard + "COM812", # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ - this rule may cause conflicts when used with the ruff formatter + "D107", # https://docs.astral.sh/ruff/rules/undocumented-public-init/ - requiring __init__() methods to have docstrings seems a bit much + "D203", # https://docs.astral.sh/ruff/rules/one-blank-line-before-class/ - prevent warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class` + "D213", # https://docs.astral.sh/ruff/rules/multi-line-summary-second-line/ - prevent warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line` + "FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt - not sure of the value of preventing "boolean traps" + "ISC001", # https://docs.astral.sh/ruff/rules/single-line-implicit-string-concatenation/ - this rule may cause conflicts when used with the ruff formatter + "PD", # https://docs.astral.sh/ruff/rules/#pandas-vet-pd - pandas isn't used + "PT", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt - pytest isn't used +] +lint.per-file-ignores."__init__.py" = [ + "D104", # https://docs.astral.sh/ruff/rules/undocumented-public-package/ - don't require doc strings in __init__.py files +] + +[tool.pyproject-fmt] +indent = 4 +keep_full_version = true # Remove trailing zero's from version specifiers? + +[tool.mypy] +ignore_missing_imports = false +incremental = false +warn_redundant_casts = true +warn_return_any = true +warn_unreachable = true +warn_unused_ignores = true + +[[tool.mypy.overrides]] +module = [ + "axe_selenium_python", ] +ignore_missing_imports = true [tool.pip-tools] allow_unsafe = true diff --git a/tests/application_tests/requirements/requirements-dev.txt b/tests/application_tests/requirements/requirements-dev.txt index 6ee13d3e1d..73860e1592 100644 --- a/tests/application_tests/requirements/requirements-dev.txt +++ b/tests/application_tests/requirements/requirements-dev.txt @@ -4,6 +4,10 @@ # # ci/pip-compile.sh # +argcomplete==3.3.0 \ + --hash=sha256:c168c3723482c031df3c207d4ba8fa702717ccb9fc0bfe4117166c1f537b4a54 \ + --hash=sha256:fd03ff4a5b9e6580569d34b273f741e85cd9e072f3feeeee3eba4891c70eda62 + # via pipx attrs==23.2.0 \ --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 @@ -13,7 +17,7 @@ attrs==23.2.0 \ axe-selenium-python==2.1.6 \ --hash=sha256:21d10014c0bc3c999c1bfab54b9ef6a5d67f200ffca3db3629e0b315b3f1f136 \ --hash=sha256:9203ff59c79edcfbcbd676ae55ca35f257e9afb663adbcbc210e3b0d802255ae - # via application_tests (pyproject.toml) + # via application-tests (pyproject.toml) build==1.2.1 \ --hash=sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d \ --hash=sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4 @@ -119,7 +123,9 @@ charset-normalizer==3.3.2 \ click==8.1.7 \ --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de - # via pip-tools + # via + # pip-tools + # userpath h11==0.14.0 \ --hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \ --hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761 @@ -143,11 +149,20 @@ packaging==24.1 \ --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 # via # build + # pipx # pytest pip-tools==7.4.1 \ --hash=sha256:4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9 \ --hash=sha256:864826f5073864450e24dbeeb85ce3920cdfb09848a3d69ebf537b521f14bcc9 - # via application_tests (pyproject.toml) + # via application-tests (pyproject.toml) +pipx==1.6.0 \ + --hash=sha256:760889dc3aeed7bf4024973bf22ca0c2a891003f52389159ab5cb0c57d9ebff4 \ + --hash=sha256:840610e00103e3d49ae24b6b51804b60988851a5dd65468adb71e5a97e2699b2 + # via application-tests (pyproject.toml) +platformdirs==4.2.2 \ + --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \ + --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3 + # via pipx pluggy==1.5.0 \ --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 @@ -170,12 +185,12 @@ pytest==8.2.2 \ requests==2.32.3 \ --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 - # via application_tests (pyproject.toml) + # via application-tests (pyproject.toml) selenium==4.21.0 \ --hash=sha256:4770ffe5a5264e609de7dc914be6b89987512040d5a8efb2abb181330d097993 \ --hash=sha256:650dbfa5159895ff00ad16e5ddb6ceecb86b90c7ed2012b3f041f64e6e4904fe # via - # application_tests (pyproject.toml) + # application-tests (pyproject.toml) # axe-selenium-python sniffio==1.3.1 \ --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ @@ -195,6 +210,10 @@ trio-websocket==0.11.1 \ --hash=sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f \ --hash=sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638 # via selenium +types-requests==2.32.0.20240602 \ + --hash=sha256:3f98d7bbd0dd94ebd10ff43a7fbe20c3b8528acace6d8efafef0b6a184793f06 \ + --hash=sha256:ed3946063ea9fbc6b5fc0c44fa279188bae42d582cb63760be6cb4b9d06c3de8 + # via application-tests (pyproject.toml) typing-extensions==4.12.2 \ --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 @@ -205,6 +224,11 @@ urllib3==2.2.1 \ # via # requests # selenium + # types-requests +userpath==1.9.2 \ + --hash=sha256:2cbf01a23d655a1ff8fc166dfb78da1b641d1ceabf0fe5f970767d380b14e89d \ + --hash=sha256:6c52288dab069257cc831846d15d48133522455d4677ee69a9781f11dbefd815 + # via pipx wheel==0.43.0 \ --hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \ --hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81 @@ -219,7 +243,7 @@ pip==24.0 \ --hash=sha256:ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc \ --hash=sha256:ea9bd1a847e8c5774a5777bb398c19e80bcd4e2aa16a4b301b718fe6f593aba2 # via - # application_tests (pyproject.toml) + # application-tests (pyproject.toml) # pip-tools setuptools==70.0.0 \ --hash=sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4 \ diff --git a/tests/application_tests/requirements/requirements.txt b/tests/application_tests/requirements/requirements.txt index 12264ce05f..45d8467abb 100644 --- a/tests/application_tests/requirements/requirements.txt +++ b/tests/application_tests/requirements/requirements.txt @@ -13,7 +13,7 @@ attrs==23.2.0 \ axe-selenium-python==2.1.6 \ --hash=sha256:21d10014c0bc3c999c1bfab54b9ef6a5d67f200ffca3db3629e0b315b3f1f136 \ --hash=sha256:9203ff59c79edcfbcbd676ae55ca35f257e9afb663adbcbc210e3b0d802255ae - # via application_tests (pyproject.toml) + # via application-tests (pyproject.toml) certifi==2024.6.2 \ --hash=sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516 \ --hash=sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56 @@ -150,12 +150,12 @@ pytest==8.2.2 \ requests==2.32.3 \ --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 - # via application_tests (pyproject.toml) + # via application-tests (pyproject.toml) selenium==4.21.0 \ --hash=sha256:4770ffe5a5264e609de7dc914be6b89987512040d5a8efb2abb181330d097993 \ --hash=sha256:650dbfa5159895ff00ad16e5ddb6ceecb86b90c7ed2012b3f041f64e6e4904fe # via - # application_tests (pyproject.toml) + # application-tests (pyproject.toml) # axe-selenium-python sniffio==1.3.1 \ --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ diff --git a/tests/application_tests/src/test_api.py b/tests/application_tests/src/test_api.py index 9ac89832f1..0897e53d95 100644 --- a/tests/application_tests/src/test_api.py +++ b/tests/application_tests/src/test_api.py @@ -11,5 +11,5 @@ class ApiTest(unittest.TestCase): def test_documentation(self): """Test that the documentation API is available.""" apis = requests.get("http://www:8080/api", timeout=10).json().keys() - self.assertTrue("/api/internal/login" in apis) - self.assertTrue("/api/v3/login" in apis) + self.assertIn("/api/internal/login", apis) + self.assertIn("/api/v3/login", apis) diff --git a/tests/application_tests/src/test_report.py b/tests/application_tests/src/test_report.py index 1d318fc9fd..8ed3e00a6a 100644 --- a/tests/application_tests/src/test_report.py +++ b/tests/application_tests/src/test_report.py @@ -10,29 +10,31 @@ from selenium.webdriver.support.ui import WebDriverWait -class element_has_no_css_class: - """An expectation for checking that an element has no css class. +class ElementHasNoCCSClass: + """An expectation for checking that an element has no CSS class. locator - used to find the element - returns the WebElement once it has the particular css class + returns the WebElement once it has no particular CSS class """ - def __init__(self, locator): + def __init__(self, locator) -> None: self.locator = locator def __call__(self, driver): + """Return the element if it no longer has a CSS class, otherwise False.""" element = driver.find_element(*self.locator) return element if len(element.get_attribute("class")) == 0 else False -class nr_elements: +class NrElements: """An expectation for the number of matching elements.""" - def __init__(self, locator, expected_nr: int): + def __init__(self, locator, expected_nr: int) -> None: self.locator = locator self.expected_nr = expected_nr def __call__(self, driver): + """Return the element if it has the expected number of elements, otherwise False.""" elements = driver.find_elements(*self.locator) return elements if len(elements) == self.expected_nr else False @@ -60,7 +62,7 @@ def login(self): login_form.find_element(By.NAME, "username").send_keys("jadoe") login_form.find_element(By.NAME, "password").send_keys("secret") login_form.find_element(By.CLASS_NAME, "button").click() - self.wait.until(element_has_no_css_class((By.TAG_NAME, "body"))) # Wait for body dimmer to disappear + self.wait.until(ElementHasNoCCSClass((By.TAG_NAME, "body"))) # Wait for body dimmer to disappear def test_title(self): """Test the title.""" @@ -90,7 +92,7 @@ def test_add_report(self): self.login() nr_reports = len(self.driver.find_elements(By.CLASS_NAME, "card")) self.driver.find_element(By.CLASS_NAME, "button.primary").click() - self.wait.until(nr_elements((By.CLASS_NAME, "card"), nr_reports + 1)) + self.wait.until(NrElements((By.CLASS_NAME, "card"), nr_reports + 1)) def test_report_axe_accessibility(self): """Run axe accessibility check on a report.""" @@ -104,15 +106,16 @@ def test_report_axe_accessibility(self): # Process axe results violation_results = results1["violations"] - axe.write_results(results1, '../../build/a11y.json') + axe.write_results(results1, "../../build/a11y.json") readable_report = axe.report(violation_results) - filename = pathlib.Path('../../build/a11y_violations.txt') + filename = pathlib.Path("../../build/a11y_violations.txt") try: - with open(filename, "w", encoding="utf8") as report_file: + with filename.open("w", encoding="utf8") as report_file: report_file.write(readable_report) except OSError: - print("Could not write axe violations report") + self.fail("Could not write axe violations report") - # If there are violations, output the readable report data - # TODO - assertEqual 0 in https://github.com/ICTU/quality-time/issues/6354 - self.assertTrue(6 >= len(violation_results), readable_report) + # If there are moe violations than expected, output the readable report data + # Fixing the axe violations is on the backlog: https://github.com/ICTU/quality-time/issues/6354 + current_number_of_axe_violations = 6 + self.assertLessEqual(len(violation_results), current_number_of_axe_violations, readable_report) diff --git a/tests/feature_tests/ci/quality.sh b/tests/feature_tests/ci/quality.sh index 686861329f..eb0852c393 100755 --- a/tests/feature_tests/ci/quality.sh +++ b/tests/feature_tests/ci/quality.sh @@ -12,18 +12,12 @@ run pipx run `spec fixit` lint src # Mypy run pipx run `spec mypy` --python-executable=$(which python) src +# Pyproject-fmt +run pipx run `spec pyproject-fmt` --check pyproject.toml + # pip-audit run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements-dev.txt -# Safety -# Vulnerability ID: 67599 -# ADVISORY: ** DISPUTED ** An issue was discovered in pip (all versions) because it installs the version with the -# highest version number, even if the user had intended to obtain a private package from a private index. This only -# affects use of the --extra-index-url option, and exploitation requires that the... -# CVE-2018-20225 -# For more information about this vulnerability, visit https://data.safetycli.com/v/67599/97c -run pipx run `spec safety` check --bare --ignore 67599 -r requirements/requirements-dev.txt - # Bandit run pipx run `spec bandit` --quiet --recursive src/ diff --git a/tests/feature_tests/pyproject.toml b/tests/feature_tests/pyproject.toml index 97494d7e75..5225bc0f99 100644 --- a/tests/feature_tests/pyproject.toml +++ b/tests/feature_tests/pyproject.toml @@ -1,33 +1,64 @@ [project] -name = "feature_tests" +name = "feature-tests" version = "5.13.0" +requires-python = ">=3.12" +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.12", +] dependencies = [ "asserts==0.13.1", "behave==1.2.6", "gevent==24.2.1", "pymongo==4.7.3", "requests==2.32.3", - "sseclient==0.0.27" + "sseclient==0.0.27", ] - -[project.optional-dependencies] -dev = [ +optional-dependencies.dev = [ "coverage==7.5.3", "pip==24.0", + "pip-tools==7.4.1", # To add hashes to requirements "pipx==1.6.0", - "pip-tools==7.4.1", # To add hashes to requirements "types-requests==2.32.0.20240602", - "unittest-xml-reporting==3.2.0", # Needed to generate JUnit XML output for Sonarcloud.io + "unittest-xml-reporting==3.2.0", # Needed to generate JUnit XML output for Sonarcloud.io ] -tools = [ +optional-dependencies.tools = [ "bandit==1.7.9", "fixit==2.1.0", "mypy==1.10.0", "pip-audit==2.7.3", + "pyproject-fmt==2.1.3", "ruff==0.4.8", - "safety==3.2.3", - "vulture==2.11" + "vulture==2.11", +] + +[tool.ruff] +target-version = "py312" +line-length = 120 +src = [ + "src", + "src/steps", +] +lint.select = [ + "ALL", +] +lint.ignore = [ + "COM812", # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ - this rule may cause conflicts when used with the ruff formatter + "D203", # https://docs.astral.sh/ruff/rules/one-blank-line-before-class/ - prevent warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class` + "D213", # https://docs.astral.sh/ruff/rules/multi-line-summary-second-line/ - prevent warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line` + "FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt - not sure of the value of preventing "boolean traps" + "ISC001", # https://docs.astral.sh/ruff/rules/single-line-implicit-string-concatenation/ - this rule may cause conflicts when used with the ruff formatter +] +lint.per-file-ignores.".vulture_ignore_list.py" = [ + "ALL", ] +lint.per-file-ignores."__init__.py" = [ + "D104", # https://docs.astral.sh/ruff/rules/undocumented-public-package/ - don't require doc strings in __init__.py files +] + +[tool.pyproject-fmt] +indent = 4 +keep_full_version = true # Remove trailing zero's from version specifiers? [tool.mypy] ignore_missing_imports = false @@ -43,7 +74,7 @@ module = [ "behave", "behave.model", "behave.runner", - "sseclient" + "sseclient", ] ignore_missing_imports = true @@ -53,24 +84,3 @@ generate_hashes = true quiet = true strip_extras = true upgrade = true - -[tool.ruff] -target-version = "py312" -line-length = 120 -src = ["src", "src/steps"] - -[tool.ruff.lint] -select = ["ALL"] -ignore = [ - "COM812", # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ - this rule may cause conflicts when used with the ruff formatter - "D203", # https://docs.astral.sh/ruff/rules/one-blank-line-before-class/ - prevent warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class` - "D213", # https://docs.astral.sh/ruff/rules/multi-line-summary-second-line/ - prevent warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line` - "FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt - not sure of the value of preventing "boolean traps" - "ISC001", # https://docs.astral.sh/ruff/rules/single-line-implicit-string-concatenation/ - this rule may cause conflicts when used with the ruff formatter -] - -[tool.ruff.lint.per-file-ignores] -".vulture_ignore_list.py" = ["ALL"] -"__init__.py" = [ - "D104", # https://docs.astral.sh/ruff/rules/undocumented-public-package/ - don't require doc strings in __init__.py files -]