diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b0019072..54724245 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,17 @@ repos: - - repo: https://github.com/ambv/black - rev: 23.7.0 + - repo: https://github.com/rhysd/actionlint + rev: v1.6.26 hooks: - - id: black - files: "(tavern|tests)" + - id: actionlint + args: ["-shellcheck="] + - repo: https://github.com/hadialqattan/pycln + rev: v2.4.0 + hooks: + - id: pycln - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: "v0.0.280" + rev: "v0.1.11" hooks: + - id: ruff-format - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/pre-commit/mirrors-prettier diff --git a/constraints.txt b/constraints.txt index 170ee995..801da112 100644 --- a/constraints.txt +++ b/constraints.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --all-extras --output-file=constraints.txt --strip-extras pyproject.toml +# pip-compile --all-extras --output-file=constraints.txt --resolver=backtracking --strip-extras pyproject.toml # alabaster==0.7.13 # via sphinx @@ -232,6 +232,8 @@ ruamel-yaml==0.17.31 # via pykwalify ruamel-yaml-clib==0.2.7 # via ruamel-yaml +ruff==0.1.13 + # via tavern (pyproject.toml) secretstorage==3.3.3 # via keyring six==1.16.0 diff --git a/example/advanced/server.py b/example/advanced/server.py index 726cd99e..f3475628 100644 --- a/example/advanced/server.py +++ b/example/advanced/server.py @@ -25,7 +25,6 @@ def get_db(): "CREATE TABLE numbers_table (name TEXT NOT NULL, number INTEGER NOT NULL)" ) - return db diff --git a/example/cookies/server.py b/example/cookies/server.py index 9026f138..72613ea8 100644 --- a/example/cookies/server.py +++ b/example/cookies/server.py @@ -23,7 +23,6 @@ def get_db(): "CREATE TABLE numbers_table (name TEXT NOT NULL, number INTEGER NOT NULL)" ) - return db diff --git a/example/mqtt/server.py b/example/mqtt/server.py index bc73c910..e31a79c5 100644 --- a/example/mqtt/server.py +++ b/example/mqtt/server.py @@ -157,7 +157,7 @@ def create_device(): try: r["clean"] - except (TypeError): + except TypeError: return jsonify({"error": "checking for clean key"}), 500 except KeyError: try: @@ -196,7 +196,6 @@ def attempt(query): with contextlib.suppress(Exception): db.execute(query) - attempt("DELETE FROM devices_table") attempt( "CREATE TABLE devices_table (device_id TEXT NOT NULL, lights_on INTEGER NOT NULL)" diff --git a/pyproject.toml b/pyproject.toml index 6250fd67..5488b6e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,7 @@ dev = [ "fluent-logger", "itsdangerous", "mypy", + "ruff>=0.1.11", "mypy-extensions", "coverage[toml]", "flit >=3.2,<4", @@ -103,9 +104,6 @@ requests = "tavern._plugins.rest.tavernhook:TavernRestPlugin" [project.entry-points.tavern_mqtt] paho-mqtt = "tavern._plugins.mqtt.tavernhook" -[tool.black] -target-version = ['py37'] - [tool.mypy] python_version = 3.8 ignore_missing_imports = true @@ -164,6 +162,9 @@ target-version = "py38" [tool.ruff.isort] known-first-party = ["tavern"] +[tool.ruff.format] +docstring-code-format = true + [tool.tbump.version] current = "2.7.1" diff --git a/requirements.txt b/requirements.txt index 15362126..d9f05902 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --all-extras --generate-hashes --output-file=requirements.txt pyproject.toml +# pip-compile --all-extras --generate-hashes --output-file=requirements.txt --resolver=backtracking pyproject.toml # alabaster==0.7.13 \ --hash=sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3 \ @@ -948,6 +948,25 @@ ruamel-yaml-clib==0.2.7 \ --hash=sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646 \ --hash=sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38 # via ruamel-yaml +ruff==0.1.13 \ + --hash=sha256:226b517f42d59a543d6383cfe03cccf0091e3e0ed1b856c6824be03d2a75d3b6 \ + --hash=sha256:2f59bcf5217c661254bd6bc42d65a6fd1a8b80c48763cb5c2293295babd945dd \ + --hash=sha256:5f0312ba1061e9b8c724e9a702d3c8621e3c6e6c2c9bd862550ab2951ac75c16 \ + --hash=sha256:6bbbc3042075871ec17f28864808540a26f0f79a4478c357d3e3d2284e832998 \ + --hash=sha256:7a36fa90eb12208272a858475ec43ac811ac37e91ef868759770b71bdabe27b6 \ + --hash=sha256:9a1600942485c6e66119da294c6294856b5c86fd6df591ce293e4a4cc8e72989 \ + --hash=sha256:9ebb40442f7b531e136d334ef0851412410061e65d61ca8ce90d894a094feb22 \ + --hash=sha256:9fb6b3b86450d4ec6a6732f9f60c4406061b6851c4b29f944f8c9d91c3611c7a \ + --hash=sha256:a623349a505ff768dad6bd57087e2461be8db58305ebd5577bd0e98631f9ae69 \ + --hash=sha256:b13ba5d7156daaf3fd08b6b993360a96060500aca7e307d95ecbc5bb47a69296 \ + --hash=sha256:dcaab50e278ff497ee4d1fe69b29ca0a9a47cd954bb17963628fa417933c6eb1 \ + --hash=sha256:e261f1baed6291f434ffb1d5c6bd8051d1c2a26958072d38dfbec39b3dda7352 \ + --hash=sha256:e3fd36e0d48aeac672aa850045e784673449ce619afc12823ea7868fcc41d8ba \ + --hash=sha256:e6894b00495e00c27b6ba61af1fc666f17de6140345e5ef27dd6e08fb987259d \ + --hash=sha256:ee3febce7863e231a467f90e681d3d89210b900d49ce88723ce052c8761be8c7 \ + --hash=sha256:f57de973de4edef3ad3044d6a50c02ad9fc2dff0d88587f25f1a48e3f72edf5e \ + --hash=sha256:f988746e3c3982bea7f824c8fa318ce7f538c4dfefec99cd09c8770bd33e6539 + # via tavern (pyproject.toml) secretstorage==3.3.3 \ --hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \ --hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99 @@ -1073,7 +1092,6 @@ zipp==3.16.2 \ # via importlib-metadata # WARNING: The following packages were not pinned, but pip requires them to be -# pinned when the requirements file includes hashes and the requirement is not -# satisfied by a package already installed. Consider using the --allow-unsafe flag. +# pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag. # pip # setuptools diff --git a/scripts/smoke.bash b/scripts/smoke.bash index 838ee369..2ee8043e 100755 --- a/scripts/smoke.bash +++ b/scripts/smoke.bash @@ -3,20 +3,14 @@ set -ex pre-commit run ruff --all-files -pre-commit run black --all-files +pre-commit run ruff-format --all-files # Separate as isort can interfere with other testenvs tox --parallel -c tox.ini \ -e py3check tox --parallel -c tox.ini \ - -e py3 \ - -e py3mypy + -e py3,py3mypy tox -c tox-integration.ini \ - -e py3-generic \ - -e py3-advanced \ - -e py3-cookies \ - -e py3-components \ - -e py3-hooks \ - -e py3-mqtt + -e py3-generic,py3-mqtt diff --git a/tavern/_core/dict_util.py b/tavern/_core/dict_util.py index fd0ab1c7..435a1053 100644 --- a/tavern/_core/dict_util.py +++ b/tavern/_core/dict_util.py @@ -162,9 +162,9 @@ def recurse_access_key(data, query: str): Example: - >>> recurse_access_key({'a': 'b'}, 'a') + >>> recurse_access_key({"a": "b"}, "a") 'b' - >>> recurse_access_key({'a': {'b': ['c', 'd']}}, 'a.b[0]') + >>> recurse_access_key({"a": {"b": ["c", "d"]}}, "a.b[0]") 'c' Args: @@ -203,9 +203,9 @@ def _deprecated_recurse_access_key(current_val, keys): Example: - >>> _deprecated_recurse_access_key({'a': 'b'}, ['a']) + >>> _deprecated_recurse_access_key({"a": "b"}, ["a"]) 'b' - >>> _deprecated_recurse_access_key({'a': {'b': ['c', 'd']}}, ['a', 'b', '0']) + >>> _deprecated_recurse_access_key({"a": {"b": ["c", "d"]}}, ["a", "b", "0"]) 'c' Args: @@ -351,7 +351,9 @@ def check_keys_match_recursive( >>> check_keys_match_recursive({"a": {"b": "c"}}, {"a": {"b": "c"}}, []) is None True - >>> check_keys_match_recursive({"a": {"b": "c"}}, {"a": {"b": "d"}}, []) # doctest: +IGNORE_EXCEPTION_DETAIL + >>> check_keys_match_recursive( + ... {"a": {"b": "c"}}, {"a": {"b": "d"}}, [] + ... ) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): File "/home/michael/code/tavern/tavern/tavern/_core.util/dict_util.py", line 223, in check_keys_match_recursive tavern._core.exceptions.KeyMismatchError: Key mismatch: (expected["a"]["b"] = 'c', actual["a"]["b"] = 'd') diff --git a/tavern/_core/exceptions.py b/tavern/_core/exceptions.py index 3caf3a9a..6ef7234e 100644 --- a/tavern/_core/exceptions.py +++ b/tavern/_core/exceptions.py @@ -7,7 +7,12 @@ class TavernException(Exception): """Base exception - Fields are internal and might change in future + Fields are internal and might change in future without warning + + Attributes: + is_final: whether this exception came from a 'finally' block + stage: stage that caused this issue + test_block_config: config for stage """ stage: Optional[Dict] diff --git a/tavern/_core/loader.py b/tavern/_core/loader.py index 7104fbe5..912b8ce1 100644 --- a/tavern/_core/loader.py +++ b/tavern/_core/loader.py @@ -6,6 +6,7 @@ import uuid from abc import abstractmethod from itertools import chain +from typing import List, Optional import pytest import yaml @@ -120,7 +121,7 @@ def __init__(self, stream): Resolver.__init__(self) SourceMappingConstructor.__init__(self) - env_path_list = None + env_path_list: Optional[List] = None env_var_name = "TAVERN_INCLUDE" diff --git a/tavern/_core/pytest/item.py b/tavern/_core/pytest/item.py index 1c3596de..dfd24fa4 100644 --- a/tavern/_core/pytest/item.py +++ b/tavern/_core/pytest/item.py @@ -168,12 +168,12 @@ def _load_fixture_values(self): values.update(mark_values) # Use autouse fixtures as well - for m in self.fixturenames: - if m in values: - logger.debug("%s already explicitly used", m) + for name in self.fixturenames: + if name in values: + logger.debug("%s already explicitly used", name) continue - mark_values = {m: self.funcargs[m]} + mark_values = {name: self.funcargs[name]} values.update(mark_values) return values diff --git a/tavern/_core/schema/files.py b/tavern/_core/schema/files.py index a5bb4af6..33913023 100644 --- a/tavern/_core/schema/files.py +++ b/tavern/_core/schema/files.py @@ -3,7 +3,7 @@ import logging import os import tempfile -from typing import Dict +from typing import Dict, Mapping import pykwalify import yaml @@ -129,7 +129,7 @@ def wrapfile(to_wrap): os.remove(wrapped_tmp.name) -def verify_tests(test_spec, with_plugins: bool = True) -> None: +def verify_tests(test_spec: Mapping, with_plugins: bool = True) -> None: """Verify that a specific test block is correct Todo: diff --git a/tavern/_core/schema/jsonschema.py b/tavern/_core/schema/jsonschema.py index 40faece2..3230b49b 100644 --- a/tavern/_core/schema/jsonschema.py +++ b/tavern/_core/schema/jsonschema.py @@ -1,5 +1,6 @@ import logging import re +from typing import Mapping import jsonschema from jsonschema import Draft7Validator, ValidationError @@ -104,12 +105,12 @@ def oneOf(validator: Draft7Validator, oneOf, instance, schema): ) -def verify_jsonschema(to_verify, schema) -> None: +def verify_jsonschema(to_verify: Mapping, schema: Mapping) -> None: """Verify a generic file against a given jsonschema Args: - to_verify (dict): Filename of source tests to check - schema (dict): Schema to verify against + to_verify: Filename of source tests to check + schema: Schema to verify against Raises: BadSchemaError: Schema did not match diff --git a/tavern/_plugins/mqtt/tavernhook.py b/tavern/_plugins/mqtt/tavernhook.py index 9b68d18d..dca5e78e 100644 --- a/tavern/_plugins/mqtt/tavernhook.py +++ b/tavern/_plugins/mqtt/tavernhook.py @@ -1,5 +1,6 @@ import logging from os.path import abspath, dirname, join +from typing import Dict, Optional import yaml @@ -18,7 +19,7 @@ def get_expected_from_request(response_block, test_block_config, session): - expected = None + expected: Optional[Dict] = None # mqtt response is not required if response_block: diff --git a/tavern/entry.py b/tavern/entry.py index df48dd47..b996f94c 100644 --- a/tavern/entry.py +++ b/tavern/entry.py @@ -2,6 +2,7 @@ import logging.config from argparse import ArgumentParser from textwrap import dedent +from typing import Dict from .core import run @@ -48,7 +49,7 @@ def main(): log_level = "INFO" # Basic logging config that will print out useful information - log_cfg = { + log_cfg: Dict = { "version": 1, "formatters": { "default": { diff --git a/tavern/response.py b/tavern/response.py index 9b3b8a58..a840d4eb 100644 --- a/tavern/response.py +++ b/tavern/response.py @@ -110,12 +110,12 @@ def recurse_check_key_match( except exceptions.KeyMismatchError as e: self._adderr(e.args[0], e=e) - def _check_for_validate_functions(self, response_block) -> None: + def _check_for_validate_functions(self, response_block: Mapping) -> None: """ See if there were any functions specified in the response block and save them for later use Args: - response_block (dict): block of external functions to call + response_block: block of external functions to call """ def check_ext_functions(verify_block): diff --git a/tests/unit/test_helpers.py b/tests/unit/test_helpers.py index 19fddc08..df55dbe1 100644 --- a/tests/unit/test_helpers.py +++ b/tests/unit/test_helpers.py @@ -149,7 +149,7 @@ def add_opts(self, pytestconfig): def _make_fake_exc_info(self, exc_type): # Copied from pytest tests - class FakeExcinfo(_pytest._code.ExceptionInfo): + class FakeExcinfo(_pytest._code.ExceptionInfo): # type:ignore pass try: