Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into feat/3.0-release
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelboulton committed Jan 27, 2024
2 parents c931207 + da51cd4 commit be08ae1
Show file tree
Hide file tree
Showing 38 changed files with 469 additions and 313 deletions.
22 changes: 11 additions & 11 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.11"

Expand All @@ -43,24 +43,24 @@ jobs:
TOXCFG: ${{ matrix.TOXCFG }}

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions/cache@v3
- uses: actions/cache@v4
env:
cache-name: cache-${{ matrix.TOXENV }}
with:
path: .tox
key: ${{ runner.os }}-tox-${{ env.cache-name }}-${{ hashFiles('pyproject.toml', 'constraints.txt') }}

- uses: actions/cache@v3
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml', 'constraints.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.11"

Expand Down Expand Up @@ -101,27 +101,27 @@ jobs:
TOXCFG: ${{ matrix.TOXCFG }}

steps:
- uses: jpribyl/[email protected]
- uses: docker/setup-buildx-action@v3
continue-on-error: true

- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions/cache@v3
- uses: actions/cache@v4
env:
cache-name: cache-${{ matrix.TOXENV }}
with:
path: .tox
key: ${{ runner.os }}-tox-${{ env.cache-name }}-${{ hashFiles('pyproject.toml', 'constraints.txt') }}

- uses: actions/cache@v3
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml', 'constraints.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.11"

Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ repos:
hooks:
- id: pycln
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: "v0.1.11"
rev: "v0.1.14"
hooks:
- id: ruff-format
- id: ruff
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ Add filterwarning to schema
# 1.9.0 219 response function calls (#614) (2020-11-06)


Also log the result from 'response' ext functions
Also log the result from 'response' ext functions

## 1.7.1 Bump max version of paho-mqtt (2020-11-07)

Expand Down Expand Up @@ -428,3 +428,5 @@ This is technically not a operational change but I'm adding a new tag so it can
## 2.7.1 Fix jsonschema warnings (2023-12-26)

# 2.8.0 Initial gRPC support (2024-01-20)

# 2.9.0 Fix mqtt implementation checking for message publication correctly (2024-01-23)
5 changes: 3 additions & 2 deletions scripts/smoke.bash
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ set -ex
pre-commit run ruff --all-files || true
pre-commit run ruff-format --all-files || true

tox --parallel -c tox.ini -e py3check || true

tox --parallel -c tox.ini \
-e py3mypy

tox --parallel -c tox.ini \
-e py3check

tox --parallel -c tox.ini \
-e py3

Expand Down
76 changes: 47 additions & 29 deletions tavern/_core/dict_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
import os
import re
import string
import typing
from collections.abc import Collection, Iterable, Mapping, Sequence
from typing import Any, TypeVar
from typing import (
Any,
)

import box
import jmespath
Expand Down Expand Up @@ -56,7 +59,7 @@ def _check_and_format_values(to_format: str, box_vars: Mapping[str, Any]) -> str
return to_format.format(**box_vars)


def _attempt_find_include(to_format: str, box_vars: box.Box):
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 @@ -90,32 +93,39 @@ def _attempt_find_include(to_format: str, box_vars: box.Box):

would_replace = formatter.get_field(field_name, [], box_vars)[0]

return formatter.convert_field(would_replace, conversion) # type: ignore
if conversion is None:
return would_replace

return formatter.convert_field(would_replace, conversion)


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


def format_keys(
val: TypeConvertToken | str | dict | list | tuple | Mapping | set,
val: T,
variables: Mapping | Box,
*,
no_double_format: bool = True,
dangerously_ignore_string_format_errors: bool = False,
):
) -> T:
"""recursively format a dictionary with the given values
Args:
val: Input to format
val: Input thing to format
variables: Dictionary of keys to format it with
no_double_format: Whether to use the 'inner formatted string' class to avoid double formatting
This is required if passing something via pytest-xdist, such as markers:
https://github.com/taverntesting/tavern/issues/431
dangerously_ignore_string_format_errors: whether to ignore any string formatting errors. This will result
in broken output, only use for debugging purposes.
Raises:
MissingFormatError: if a format variable was not found in variables
Returns:
recursively formatted values
"""
formatted = val

format_keys_ = functools.partial(
format_keys,
dangerously_ignore_string_format_errors=dangerously_ignore_string_format_errors,
Expand All @@ -127,15 +137,15 @@ def format_keys(
box_vars = variables

if isinstance(val, dict):
formatted = {}
# formatted = {key: format_keys(val[key], box_vars) for key in val}
for key in val:
formatted[key] = format_keys_(val[key], box_vars)
elif isinstance(val, (list, tuple, set)):
formatted = [format_keys_(item, box_vars) for item in val] # type: ignore
elif isinstance(formatted, FormattedString):
logger.debug("Already formatted %s, not double-formatting", formatted)
return {key: format_keys_(val[key], box_vars) for key in val}
elif isinstance(val, tuple):
return tuple(format_keys_(item, box_vars) for item in val)
elif isinstance(val, list):
return [format_keys_(item, box_vars) for item in val]
elif isinstance(val, FormattedString):
logger.debug("Already formatted %s, not double-formatting", val)
elif isinstance(val, str):
formatted = val
try:
formatted = _check_and_format_values(val, box_vars)
except exceptions.MissingFormatError:
Expand All @@ -144,17 +154,19 @@ def format_keys(

if no_double_format:
formatted = FormattedString(formatted) # type: ignore

return formatted
elif isinstance(val, TypeConvertToken):
logger.debug("Got type convert token '%s'", val)
if isinstance(val, ForceIncludeToken):
formatted = _attempt_find_include(val.value, box_vars)
return _attempt_find_include(val.value, box_vars)
else:
value = format_keys_(val.value, box_vars)
formatted = val.constructor(value)
return val.constructor(value)
else:
logger.debug("Not formatting something of type '%s'", type(formatted))
logger.debug("Not formatting something of type '%s'", type(val))

return formatted
return val


def recurse_access_key(data: dict | list[str] | Mapping, query: str):
Expand All @@ -172,8 +184,11 @@ def recurse_access_key(data: dict | list[str] | Mapping, query: str):
data: Data to search in
query: Query to run
Raises:
JMESError: if there was an error parsing the query
Returns:
object: Whatever was found by the search
Whatever was found by the search
"""

try:
Expand All @@ -196,7 +211,7 @@ def recurse_access_key(data: dict | list[str] | Mapping, query: str):
return from_jmespath


def _deprecated_recurse_access_key(current_val: dict, keys: list[str]):
def _deprecated_recurse_access_key(current_val: Mapping | list, keys: list[str]):
"""Given a list of keys and a dictionary, recursively access the dicionary
using the keys until we find the key its looking for
Expand All @@ -218,7 +233,7 @@ def _deprecated_recurse_access_key(current_val: dict, keys: list[str]):
KeyError: dict key not found in data
Returns:
str or dict: value of subkey in dict
value of subkey in dict
"""
logger.debug("Recursively searching for '%s' in '%s'", keys, current_val)

Expand All @@ -231,7 +246,10 @@ def _deprecated_recurse_access_key(current_val: dict, keys: list[str]):
current_key = int(current_key)

try:
return _deprecated_recurse_access_key(current_val[current_key], keys)
return _deprecated_recurse_access_key(
current_val[current_key], # type:ignore
keys,
)
except (IndexError, KeyError, TypeError) as e:
logger.error(
"%s accessing data - looking for '%s' in '%s'",
Expand Down Expand Up @@ -328,7 +346,7 @@ def yield_keyvals(block: _CanCheck) -> Iterable[tuple[list[str], str, str]]:
block: input matches
Yields:
key split on dots, key, expected value
iterable of (key split on dots, key, expected value)
"""
if isinstance(block, dict):
for joined_key, expected_val in block.items():
Expand All @@ -340,12 +358,12 @@ def yield_keyvals(block: _CanCheck) -> Iterable[tuple[list[str], str, str]]:
yield [sidx], sidx, val


T = TypeVar("T", Mapping, set, Sequence, Collection)
Checked = typing.TypeVar("Checked", dict, Collection, str)


def check_keys_match_recursive(
expected_val: T,
actual_val: T,
expected_val: Checked,
actual_val: Checked,
keys: list[str | int],
strict: StrictSettingKinds = True,
) -> None:
Expand Down Expand Up @@ -450,8 +468,8 @@ def _format_err(which):
raise exceptions.KeyMismatchError(msg) from e

if isinstance(expected_val, dict):
akeys = set(actual_val.keys()) # type:ignore
ekeys = set(expected_val.keys())
akeys = set(actual_val.keys()) # type:ignore

if akeys != ekeys:
extra_actual_keys = akeys - ekeys
Expand Down
21 changes: 12 additions & 9 deletions tavern/_core/extfunctions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import functools
import importlib
import logging
from collections.abc import Mapping
from collections.abc import Callable, Iterable, Mapping
from typing import Any

from tavern._core import exceptions
Expand All @@ -17,7 +17,7 @@ def is_ext_function(block: Any) -> bool:
block: Any object
Returns:
bool: If it is an ext function style dict
If it is an ext function style dict
"""
return isinstance(block, dict) and block.get("$ext", None) is not None

Expand All @@ -30,17 +30,20 @@ def get_pykwalify_logger(module: str | None) -> logging.Logger:
trying to get the root logger which won't log correctly
Args:
module (string): name of module to get logger for
module: name of module to get logger for
Returns:
logger for given module
"""
return logging.getLogger(module)


def _getlogger() -> logging.Logger:
"""Get logger for this module"""
return get_pykwalify_logger("tavern._core.extfunctions")


def import_ext_function(entrypoint: str):
def import_ext_function(entrypoint: str) -> Callable:
"""Given a function name in the form of a setuptools entry point, try to
dynamically load and return it
Expand All @@ -49,7 +52,7 @@ def import_ext_function(entrypoint: str):
module.submodule:function
Returns:
function: function loaded from entrypoint
function loaded from entrypoint
Raises:
InvalidExtFunctionError: If the module or function did not exist
Expand Down Expand Up @@ -80,7 +83,7 @@ def import_ext_function(entrypoint: str):
return function


def get_wrapped_response_function(ext: Mapping):
def get_wrapped_response_function(ext: Mapping) -> Callable:
"""Wraps a ext function with arguments given in the test file
This is similar to functools.wrap, but this makes sure that 'response' is
Expand All @@ -91,7 +94,7 @@ def get_wrapped_response_function(ext: Mapping):
extra_kwargs to pass
Returns:
function: Wrapped function
Wrapped function
"""

func, args, kwargs = _get_ext_values(ext)
Expand All @@ -107,7 +110,7 @@ def inner(response):
return inner


def get_wrapped_create_function(ext: Mapping):
def get_wrapped_create_function(ext: Mapping) -> Callable:
"""Same as get_wrapped_response_function, but don't require a response"""

func, args, kwargs = _get_ext_values(ext)
Expand All @@ -123,7 +126,7 @@ def inner():
return inner


def _get_ext_values(ext: Mapping):
def _get_ext_values(ext: Mapping) -> tuple[Callable, Iterable, Mapping]:
if not isinstance(ext, Mapping):
raise exceptions.InvalidExtFunctionError(
f"ext block should be a dict, but it was a {type(ext)}"
Expand Down
Loading

0 comments on commit be08ae1

Please sign in to comment.