Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3.0 release #908

Draft
wants to merge 16 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
pull_request:
branches:
- master
- feature-2.0
- feat/3.0-release

jobs:
simple-checks:
Expand Down Expand Up @@ -40,12 +40,12 @@ jobs:
cache-name: cache-${TOXENV}
with:
path: .tox
key: ${{ runner.os }}-tox-${{ env.cache-name }}-${{ hashFiles('pyproject.toml', 'requirements.in') }}
key: ${{ runner.os }}-tox-${{ env.cache-name }}-${{ hashFiles('pyproject.toml', 'constraints.txt') }}

- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml', 'requirements.in') }}
key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml', 'constraints.txt') }}
restore-keys: |
${{ runner.os }}-pip-

Expand Down Expand Up @@ -102,12 +102,12 @@ jobs:
cache-name: cache-${{ matrix.TOXENV }}
with:
path: .tox
key: ${{ runner.os }}-tox-${{ env.cache-name }}-${{ hashFiles('pyproject.toml', 'requirements.in') }}
key: ${{ runner.os }}-tox-${{ env.cache-name }}-${{ hashFiles('pyproject.toml', 'constraints.txt') }}

- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml', 'requirements.in') }}
key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml', 'constraints.txt') }}
restore-keys: |
${{ runner.os }}-pip-

Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ repos:
hooks:
- id: pyupgrade
args:
- --py38-plus
- --py311-plus
files: "tavern/.*"
- repo: https://github.com/rhysd/actionlint
rev: v1.7.0
Expand Down
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ Run every so often to update the pre-commit hooks

1. Install the correct version of tbump

pip install tbump@https://github.com/michaelboulton/tbump/archive/714ba8957a3c84b625608ceca39811ebe56229dc.zip
```shell
uv pip install tbump@https://github.com/michaelboulton/tbump/archive/714ba8957a3c84b625608ceca39811ebe56229dc.zip
```

1. Tag and push to git with `tbump <new-tag> --tag-message "<tag-message>"`

Expand Down
3 changes: 1 addition & 2 deletions constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ attrs==23.2.0
# via
# allure-python-commons
# jsonschema
# pytest
# referencing
babel==2.15.0
# via sphinx
Expand Down Expand Up @@ -157,7 +156,7 @@ pyparsing==3.1.2
# via httplib2
pyproject-api==1.6.1
# via tox
pytest==7.2.2
pytest==7.4.4
# via
# allure-pytest
# pytest-cov
Expand Down
2 changes: 1 addition & 1 deletion docs/source/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -1520,7 +1520,7 @@ third block must start with 4 and the third block must start with 8, 9, "A", or
```

This is using the `!re_fullmatch` variant of the tag - this calls
[`re.fullmatch`](https://docs.python.org/3.8/library/re.html#re.fullmatch) under
[`re.fullmatch`](https://docs.python.org/3.11/library/re.html#re.fullmatch) under
the hood, which means that the regex given needs to match the _entire_ part of
the response that is being checked for it to pass. There is also `!re_search`
which will pass if it matches _part_ of the thing being checked, or `!re_match`
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
# The short X.Y version.
version = "1.0"
# The full version, including alpha/beta/rc tags.
release = "2.11.0"
release = "3.0.0a5"

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
2 changes: 1 addition & 1 deletion docs/source/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Tavern is an advanced pytest based API testing framework for HTTP, MQTT or other protocols.

Note that Tavern **only** supports Python 3.4 and up. At the time of writing we
test against Python 3.8-3.10. Python 2 is now **unsupported**.
test against Python 3.11. Python 2 is now **unsupported**.

## Why Tavern

Expand Down
24 changes: 11 additions & 13 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,32 @@ classifiers = [
"Intended Audience :: Developers",
"Framework :: Pytest",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Utilities",
"Topic :: Software Development :: Testing",
"License :: OSI Approved :: MIT License",
]
requires-python = ">=3.11"

keywords = ["testing", "pytest"]

name = "tavern"
description = "Simple testing of RESTful APIs"
version = "2.11.0"
version = "3.0.0a5"

dependencies = [
"PyYAML>=6.0.1,<7",
"jmespath>=1,<2",
"jsonschema>=4,<5",
"paho-mqtt>=1.3.1,<=1.6.1",
"pyjwt>=2.5.0,<3",
"pykwalify>=1.8.0,<2",
"pytest>=7,<7.3",
"pytest>=7.4,<8",
"python-box>=6,<7",
"requests>=2.22.0,<3",
"stevedore>=4,<5",
]

requires-python = ">=3.8"

[[project.authors]]
name = "Michael Boulton"

Expand Down Expand Up @@ -98,6 +93,9 @@ dev = [
# "tbump@https://github.com/michaelboulton/tbump/archive/714ba8957a3c84b625608ceca39811ebe56229dc.zip",
]

mqtt = [
"paho-mqtt>=1.3.1,<=1.6.1",
]

[project.scripts]

Expand All @@ -115,7 +113,7 @@ paho-mqtt = "tavern._plugins.mqtt.tavernhook"
grpc = "tavern._plugins.grpc.tavernhook"

[tool.mypy]
python_version = 3.8
python_version = 3.11

# See https://mypy.readthedocs.io/en/stable/running_mypy.html#mapping-file-paths-to-modules
explicit_package_bases = true
Expand All @@ -140,8 +138,8 @@ source = ["tavern"]
[tool.coverage.paths]
tavern = [
"tavern/",
".tox/py39-generic/lib/python3.9/site-packages/tavern/",
".tox/py39-mqtt/lib/python3.9/site-packages/tavern",
".tox/py311-generic/lib/python3.11/site-packages/tavern/",
".tox/py311-mqtt/lib/python3.11/site-packages/tavern",
]

[tool.pytest.ini_options]
Expand All @@ -163,7 +161,7 @@ norecursedirs = [
]

[tool.ruff]
target-version = "py38"
target-version = "py311"
extend-exclude = [
"tests/unit/tavern_grpc/test_services_pb2.py",
"tests/unit/tavern_grpc/test_services_pb2.pyi",
Expand Down Expand Up @@ -194,7 +192,7 @@ exclude = ["*_pb2.py", "*_pb2_grpc.py", "*_pb2.pyi"]
docstring-code-format = true

[tool.tbump.version]
current = "2.11.0"
current = "3.0.0a5"

regex = '''
(?P<major>\d+)
Expand Down
7 changes: 3 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ attrs==23.2.0 \
# via
# allure-python-commons
# jsonschema
# pytest
# referencing
babel==2.15.0 \
--hash=sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb \
Expand Down Expand Up @@ -590,9 +589,9 @@ pyproject-api==1.6.1 \
--hash=sha256:1817dc018adc0d1ff9ca1ed8c60e1623d5aaca40814b953af14a9cf9a5cae538 \
--hash=sha256:4c0116d60476b0786c88692cf4e325a9814965e2469c5998b830bba16b183675
# via tox
pytest==7.2.2 \
--hash=sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e \
--hash=sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4
pytest==7.4.4 \
--hash=sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280 \
--hash=sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8
# via
# allure-pytest
# pytest-cov
Expand Down
2 changes: 1 addition & 1 deletion tavern/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Stop pytest warning about module already imported: PYTEST_DONT_REWRITE"""

__version__ = "2.11.0"
__version__ = "3.0.0a5"
85 changes: 13 additions & 72 deletions tavern/_core/dict_util.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import contextlib
import functools
import logging
import os
import re
import string
import typing
from collections.abc import Collection
from typing import Any, Dict, Iterator, List, Mapping, Optional, Tuple, Union
from collections.abc import Collection, Iterable, Mapping, Sequence

import box
import jmespath
Expand Down Expand Up @@ -57,7 +55,7 @@ def _check_and_format_values(to_format: str, box_vars: Box) -> str:
return to_format.format(**box_vars)


def _attempt_find_include(to_format: str, box_vars: box.Box) -> Optional[str]:
def _attempt_find_include(to_format: str, box_vars: box.Box) -> str | None:
formatter = string.Formatter()
would_format = list(formatter.parse(to_format))

Expand Down Expand Up @@ -97,12 +95,12 @@ def _attempt_find_include(to_format: str, box_vars: box.Box) -> Optional[str]:
return formatter.convert_field(would_replace, conversion)


T = typing.TypeVar("T", str, Dict, List, Tuple)
T = typing.TypeVar("T", str, dict, list, tuple)


def format_keys(
val: T,
variables: Union[Mapping, Box],
variables: Mapping | Box,
*,
no_double_format: bool = True,
dangerously_ignore_string_format_errors: bool = False,
Expand Down Expand Up @@ -167,7 +165,7 @@ def format_keys(
return val


def recurse_access_key(data: Union[List, Mapping], query: str) -> Any:
def recurse_access_key(data: dict | list[str] | Mapping, query: str):
"""
Search for something in the given data using the given query.

Expand All @@ -192,72 +190,12 @@ def recurse_access_key(data: Union[List, Mapping], query: str) -> Any:
try:
from_jmespath = jmespath.search(query, data)
except jmespath.exceptions.ParseError as e:
logger.error("Error parsing JMES query")

try:
_deprecated_recurse_access_key(data, query.split("."))
except (IndexError, KeyError):
logger.debug("Nothing found searching using old method")
else:
# If we found a key using 'old' style searching
logger.warning(
"Something was found using 'old style' searching in the response - please change the query to use jmespath instead - see http://jmespath.org/ for more information"
)

raise exceptions.JMESError("Invalid JMES query") from e

return from_jmespath


def _deprecated_recurse_access_key(
current_val: Union[List, Mapping], keys: List
) -> Any:
"""Given a list of keys and a dictionary, recursively access the dicionary
using the keys until we find the key its looking for

If a key is an integer, it will convert it and use it as a list index

Example:

>>> _deprecated_recurse_access_key({"a": "b"}, ["a"])
'b'
>>> _deprecated_recurse_access_key({"a": {"b": ["c", "d"]}}, ["a", "b", "0"])
'c'

Args:
current_val: current dictionary we have recursed into
keys: list of str/int of subkeys

Raises:
IndexError: list index not found in data
KeyError: dict key not found in data

Returns:
value of subkey in dict
"""
logger.debug("Recursively searching for '%s' in '%s'", keys, current_val)

if not keys:
return current_val
else:
current_key = keys.pop(0)

with contextlib.suppress(ValueError):
current_key = int(current_key)

try:
return _deprecated_recurse_access_key(current_val[current_key], keys)
except (IndexError, KeyError, TypeError) as e:
logger.error(
"%s accessing data - looking for '%s' in '%s'",
type(e).__name__,
current_key,
current_val,
)
raise


def deep_dict_merge(initial_dct: Dict, merge_dct: Mapping) -> dict:
def deep_dict_merge(initial_dct: dict, merge_dct: Mapping) -> dict:
"""Recursive dict merge. Instead of updating only top-level keys,
dict_merge recurses down into dicts nested to an arbitrary depth
and returns the merged dict. Keys values present in merge_dct take
Expand All @@ -282,7 +220,10 @@ def deep_dict_merge(initial_dct: Dict, merge_dct: Mapping) -> dict:
return dct


def check_expected_keys(expected: Collection, actual: Collection) -> None:
_CanCheck = Sequence | Mapping | set | Collection


def check_expected_keys(expected: _CanCheck, actual: _CanCheck) -> None:
"""Check that a set of expected keys is a superset of the actual keys

Args:
Expand All @@ -305,7 +246,7 @@ def check_expected_keys(expected: Collection, actual: Collection) -> None:
raise exceptions.UnexpectedKeysError(msg)


def yield_keyvals(block: Union[List, Dict]) -> Iterator[Tuple[List, str, str]]:
def yield_keyvals(block: _CanCheck) -> Iterable[tuple[list[str], str, str]]:
"""Return indexes, keys and expected values for matching recursive keys

Given a list or dict, return a 3-tuple of the 'split' key (key split on
Expand Down Expand Up @@ -352,13 +293,13 @@ def yield_keyvals(block: Union[List, Dict]) -> Iterator[Tuple[List, str, str]]:
yield [sidx], sidx, val


Checked = typing.TypeVar("Checked", Dict, Collection, str)
Checked = typing.TypeVar("Checked", dict, Collection, str)


def check_keys_match_recursive(
expected_val: Checked,
actual_val: Checked,
keys: List[Union[str, int]],
keys: list[str | int],
strict: StrictSettingKinds = True,
) -> None:
"""Utility to recursively check response values
Expand Down
4 changes: 2 additions & 2 deletions tavern/_core/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Dict, Optional
from typing import TYPE_CHECKING, Optional

if TYPE_CHECKING:
from tavern._core.pytest.config import TestConfig
Expand All @@ -15,7 +15,7 @@ class TavernException(Exception):
test_block_config: config for stage
"""

stage: Optional[Dict]
stage: dict | None
test_block_config: Optional["TestConfig"]
is_final: bool = False

Expand Down
Loading
Loading