diff --git a/.bumpversion.cfg b/.bumpversion.cfg deleted file mode 100644 index d13d278..0000000 --- a/.bumpversion.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[bumpversion] -current_version = 0.0.1 -tag_name = {new_version} -tag = True -commit = True - -[bumpversion:file:tavern_flask/__init__.py] - diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..dc5ba94 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,69 @@ +name: basic test + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + simple-checks: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - uses: pre-commit/action@v3.0.0 + + unit-tests: + runs-on: ubuntu-latest + needs: simple-checks + + strategy: + fail-fast: false + matrix: + include: + # 'Basic' tests and checks + - TOXENV: py3 + TOXCFG: tox.ini + + env: + TOXENV: ${{ matrix.TOXENV }} + TOXCFG: ${{ matrix.TOXCFG }} + + steps: + - uses: actions/checkout@v4 + + - uses: actions/cache@v4 + env: + cache-name: cache-${{ matrix.TOXENV }} + with: + path: .tox + key: ${{ runner.os }}-tox-${{ env.cache-name }}-${{ hashFiles('pyproject.toml', 'requirements.in') }} + + - uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml', 'requirements.in') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: install deps + run: | + pip install uv + uv pip install --system -r constraints.txt + + - name: tox + run: | + tox -c ${TOXCFG} -e ${TOXENV} diff --git a/.gitignore b/.gitignore index 1d2d7f1..fa0769d 100644 --- a/.gitignore +++ b/.gitignore @@ -92,4 +92,37 @@ tags .pytest_cache # Sublimetext -*.sublime-* \ No newline at end of file +*.sublime-* + +# Vim +*.swp +*.swo + +# Pycharm +.idea + +# vs +.vs + +tavern.lock +pytype_output + +.mypy_cache + +.vscode + +out/ +**/*.iml +.idea + +allure/ + +.ijwb/ +.pants.d/ +.pids/ +bazel-bin +bazel-out +bazel-tavern +bazel-testlogs + +example/grpc/proto diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b7ff6eb --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,37 @@ +repos: + - repo: https://github.com/asottile/pyupgrade + rev: v3.15.2 + hooks: + - id: pyupgrade + args: + - --py38-plus + files: "tavern/.*" + - repo: https://github.com/rhysd/actionlint + rev: v1.6.27 + hooks: + - 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.3.4" + hooks: + - id: ruff-format + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v3.0.0 + hooks: + - id: prettier + types_or: [yaml] + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.9.0 + hooks: + - id: mypy + additional_dependencies: + [types-requests, types-protobuf, types-PyYAML, mypy-extensions] + exclude: tests + +exclude: (docs/|example/) diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 030ff4b..0000000 --- a/.pylintrc +++ /dev/null @@ -1,11 +0,0 @@ -[MASTER] -disable=missing-docstring,bad-continuation,fixme,invalid-name,line-too-long,too-few-public-methods,no-else-return,too-many-branches - -ignore=tests - -[REPORTS] -reports=no - -[SIMILARITIES] -min-similarity-lines=6 -ignore-imports=yes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a04a8e3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,43 @@ +# Contributing + +All configuration for the project should be put into `pyproject.toml`. + +## Working locally + +1. Create a virtualenv using whatever method you like ( + eg, [virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/)) + +1. Install dependencies from requirements.txt + +## Updating/adding a dependency + +1. Add or update the dependency in [pyproject.toml](/pyproject.toml) + +1. Update constraints file + +```shell +uv pip compile --all-extras pyproject.toml --output-file constraints.txt -U +``` + +## Pre-commit + +Basic checks (formatting, import order) is done with pre-commit and is controlled by [the yaml file](/.pre-commit-config.yaml). + +After installing dependencies, Run + + # check it works + pre-commit run --all-files + pre-commit install + +Run every so often to update the pre-commit hooks + + pre-commit autoupdate + +### Fixing Python formatting issue + + ruff format tavern/ tests/ + ruff --fix tavern/ tests/ + +### Fix yaml formatting issues + + pre-commit run --all-files diff --git a/constraints.txt b/constraints.txt new file mode 100644 index 0000000..2411de3 --- /dev/null +++ b/constraints.txt @@ -0,0 +1,138 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile --all-extras pyproject.toml --output-file constraints.txt +attrs==23.2.0 + # via + # jsonschema + # pytest + # referencing +blinker==1.7.0 + # via flask +cachetools==5.3.3 + # via tox +certifi==2024.2.2 + # via requests +cfgv==3.4.0 + # via pre-commit +chardet==5.2.0 + # via tox +charset-normalizer==3.3.2 + # via requests +click==8.1.7 + # via flask +colorama==0.4.6 + # via tox +colorlog==6.8.2 +distlib==0.3.8 + # via virtualenv +docopt==0.6.2 + # via pykwalify +docutils==0.20.1 + # via flit +exceptiongroup==1.2.0 + # via pytest +filelock==3.13.3 + # via + # tox + # virtualenv +flask==3.0.2 +flit==3.9.0 +flit-core==3.9.0 + # via flit +identify==2.5.35 + # via pre-commit +idna==3.6 + # via requests +iniconfig==2.0.0 + # via pytest +itsdangerous==2.1.2 + # via flask +jinja2==3.1.3 + # via flask +jmespath==1.0.1 + # via tavern +jsonschema==4.21.1 + # via tavern +jsonschema-specifications==2023.12.1 + # via jsonschema +markupsafe==2.1.5 + # via + # jinja2 + # werkzeug +nodeenv==1.8.0 + # via pre-commit +packaging==24.0 + # via + # pyproject-api + # pytest + # tox +paho-mqtt==1.6.1 + # via tavern +pbr==6.0.0 + # via stevedore +platformdirs==4.2.0 + # via + # tox + # virtualenv +pluggy==1.4.0 + # via + # pytest + # tox +pre-commit==3.7.0 +pyjwt==2.8.0 + # via tavern +pykwalify==1.8.0 + # via tavern +pyproject-api==1.6.1 + # via tox +pytest==7.2.2 + # via tavern +python-box==6.1.0 + # via tavern +python-dateutil==2.9.0.post0 + # via pykwalify +pyyaml==6.0.1 + # via + # pre-commit + # tavern +referencing==0.34.0 + # via + # jsonschema + # jsonschema-specifications +requests==2.31.0 + # via + # flit + # tavern +rpds-py==0.18.0 + # via + # jsonschema + # referencing +ruamel-yaml==0.18.6 + # via pykwalify +ruamel-yaml-clib==0.2.8 + # via ruamel-yaml +ruff==0.3.4 +setuptools==69.2.0 + # via nodeenv +six==1.16.0 + # via python-dateutil +stevedore==4.1.1 + # via tavern +tavern==2.10.1 +tomli==2.0.1 + # via + # pyproject-api + # pytest + # tox +tomli-w==1.0.0 + # via flit +tox==4.14.2 +urllib3==2.2.1 + # via requests +uv==0.1.24 +virtualenv==20.25.1 + # via + # pre-commit + # tox +werkzeug==3.0.1 + # via flask +wheel==0.43.0 diff --git a/example/Dockerfile b/example/Dockerfile deleted file mode 100644 index 57a81fc..0000000 --- a/example/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM python:3.5-alpine - -RUN pip install flask pyjwt - -COPY server.py / - -ENV FLASK_APP=/server.py - -CMD ["flask", "run", "--host=0.0.0.0"] diff --git a/example/README.md b/example/README.md index d7d4ab8..5704848 100644 --- a/example/README.md +++ b/example/README.md @@ -1,10 +1,5 @@ # Example flask server -To run the docker based tests, do: - -1. `docker-compose up --build .` -2. `py.test` - To run the tests using tavern-flask, do: 1. `py.test --tavern-http-backend=flask`. diff --git a/example/conftest.py b/example/conftest.py index f27afcf..2350053 100644 --- a/example/conftest.py +++ b/example/conftest.py @@ -2,9 +2,7 @@ import logging.config import yaml - -from tavern.testutils.pytesthook import YamlItem - +from tavern._core.pytest.item import YamlItem logging_initialised = False @@ -39,12 +37,12 @@ def setup_logging(): tavern_flask: *log """ - as_dict = yaml.load(log_cfg) + as_dict = yaml.load(log_cfg, Loader=yaml.SafeLoader) logging.config.dictConfig(as_dict) logging.info("Logging set up") - global logging_initialised + global logging_initialised # noqa logging_initialised = True diff --git a/example/docker-compose.yaml b/example/docker-compose.yaml deleted file mode 100644 index 3d63698..0000000 --- a/example/docker-compose.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -version: '2' - -services: - server: - build: - context: . - dockerfile: Dockerfile - ports: - - "5000:5000" diff --git a/example/server.py b/example/server.py index 49b5ccd..f97a111 100644 --- a/example/server.py +++ b/example/server.py @@ -1,9 +1,10 @@ -import sqlite3 +import contextlib import datetime import functools -from flask import Flask, jsonify, request, g -import jwt +import sqlite3 +import jwt +from flask import Flask, g, jsonify, request app = Flask(__name__) @@ -14,22 +15,21 @@ def get_db(): - db = getattr(g, '_database', None) + db = getattr(g, "_database", None) if db is None: db = g._database = sqlite3.connect(DATABASE) - with db: - try: - db.execute("CREATE TABLE numbers_table (name TEXT NOT NULL, number INTEGER NOT NULL)") - except: - pass + with contextlib.suppress(Exception): + db.execute( + "CREATE TABLE numbers_table (name TEXT NOT NULL, number INTEGER NOT NULL)" + ) return db @app.teardown_appcontext def close_connection(exception): - db = getattr(g, '_database', None) + db = getattr(g, "_database", None) if db is not None: db.close() @@ -44,16 +44,16 @@ def login(): payload = { "sub": "test-user", "aud": SERVERNAME, - "exp": datetime.datetime.now() + datetime.timedelta(hours=1) + "exp": datetime.datetime.now() + datetime.timedelta(hours=1), } - token = jwt.encode(payload, SECRET, algorithm="HS256").decode("utf8") + token = jwt.encode(payload, SECRET, algorithm="HS256") return jsonify({"token": token}) def requires_jwt(endpoint): - """ Makes sure a jwt is in the request before accepting it """ + """Makes sure a jwt is in the request before accepting it""" @functools.wraps(endpoint) def check_auth_call(*args, **kwargs): @@ -70,7 +70,7 @@ def check_auth_call(*args, **kwargs): try: jwt.decode(token, SECRET, audience=SERVERNAME, algorithms=["HS256"]) - except: + except Exception: return jsonify({"error": "Invalid token"}), 401 return endpoint(*args, **kwargs) diff --git a/example/test_server.tavern.yaml b/example/test_server.tavern.yaml index 32e42c1..4879a50 100644 --- a/example/test_server.tavern.yaml +++ b/example/test_server.tavern.yaml @@ -22,23 +22,23 @@ stages: content-type: application/json response: status_code: 200 - body: - $ext: &verify_token - function: tavern.testutils.helpers:validate_jwt - extra_kwargs: - jwt_key: "token" - key: CGQgaG7GYvTcpaQZqosLy4 - options: - verify_signature: true - verify_aud: true - verify_exp: true - audience: testserver + verify_response_with: &verify_token + function: tavern.helpers:validate_jwt + extra_kwargs: + jwt_key: "token" + key: CGQgaG7GYvTcpaQZqosLy4 + algorithms: [ "HS256" ] + options: + verify_signature: true + verify_aud: true + verify_exp: true + audience: testserver headers: content-type: application/json save: $ext: <<: *verify_token - body: + json: test_login_token: token --- @@ -87,7 +87,7 @@ stages: Authorization: "bearer {test_login_token:s}" response: status_code: 200 - body: + json: number: 123 headers: content-type: application/json @@ -103,7 +103,7 @@ stages: Authorization: "bearer {test_login_token:s}" response: status_code: 200 - body: + json: number: 246 headers: content-type: application/json @@ -119,7 +119,7 @@ stages: Authorization: "bearer {test_login_token:s}" response: status_code: 200 - body: + json: number: 246 headers: content-type: application/json @@ -148,7 +148,7 @@ stages: Authorization: "bearer {test_login_token:s}" response: status_code: 404 - body: + json: error: Unknown number headers: content-type: application/json diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..723d03e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,150 @@ +[build-system] +requires = ["flit-core >=3.2,<4"] +build-backend = "flit_core.buildapi" + +[project] + +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Framework :: Pytest", + "Programming Language :: Python :: 3", + "Topic :: Utilities", + "Topic :: Software Development :: Testing", + "License :: OSI Approved :: MIT License", +] + +keywords = ["testing", "pytest"] + +name = "tavern-flask" +description = "Flask plugin for Tavern" +version = "0.1.0" + +dependencies = [ + "tavern>=2.10.1,<3", + "Flask", +] + +requires-python = ">=3.8" + +[[project.authors]] +name = "Michael Boulton" + +[project.license] +file = "LICENSE" + +[project.readme] +file = "README.md" +content-type = "text/markdown" + +[project.urls] +Home = "https://taverntesting.github.io/" +Documentation = "https://tavern.readthedocs.io/en/latest/" +Source = "https://github.com/taverntesting/tavern-flask" + +[project.optional-dependencies] +dev = [ + "flit >=3.2,<4", + "wheel", + "pre-commit", + "tox>=4,<5", + "ruff>=0.3.4", + "uv", + "colorlog", + # This has to be installed separately, otherwise you can't upload to pypi + # "tbump@https://github.com/michaelboulton/tbump/archive/714ba8957a3c84b625608ceca39811ebe56229dc.zip", +] + + +[tool.mypy] +python_version = 3.8 + +# See https://mypy.readthedocs.io/en/stable/running_mypy.html#mapping-file-paths-to-modules +explicit_package_bases = true + +exclude = [ + 'bazel-*', + 'venv/', +] + +[tool.coverage.run] +branch = false +omit = [ + "tests/*", + ".eggs/*", + "env/*", + "build/*", + "dist/*", +] +source = ["tavern_flask"] + +[tool.ruff] +target-version = "py38" +extend-exclude = [ + "tests/unit/tavern_grpc/test_services_pb2.py", + "tests/unit/tavern_grpc/test_services_pb2.pyi", + "tests/unit/tavern_grpc/test_services_pb2_grpc.py", +] + +[tool.ruff.lint] +ignore = [ + "E501", # line length + "RUF005", # union types only valid from 3.10+ + "B905", # zip(..., strict=True) only valid from 3.10+ + "PLR0912", "PLR0915", "PLR0911", "PLR0913", # too many branches/variables/return values - sometimes this is just unavoidable + "PLR2004", # 'magic numbers' + "PLW2901", # Loop variable overridden +] +select = ["E", "F", "B", "W", "I", "S", "C4", "ICN", "T20", "PLE", "RUF", "SIM105", "PL"] +# Look at: UP + +[tool.ruff.lint.per-file-ignores] +"example/server.py" = ["S"] + +[tool.ruff.lint.isort] +known-first-party = ["tavern_flask"] + +[tool.ruff.format] +docstring-code-format = true + +[tool.tbump.version] +current = "0.1.0" + +regex = ''' + (?P\d+) + \. + (?P\d+) + \. + (?P\d+) + ((?P[a-zA-Z]+)(?P\d+))? + ''' + +[tool.tbump.git] +message_template = "Bump to {new_version}" +tag_template = "{new_version}" + +[[tool.tbump.file]] +src = "tavern_flask/__init__.py" + +[[tool.tbump.file]] +src = "pyproject.toml" + +[project.entry-points.tavern_http] +flask = "tavern_flask.tavernhook" + +[tool.pytest.ini_options] +testpaths = ["tavern_flask", "example"] +addopts = [ + "--doctest-modules", + "-r", "xs", + "-vv", + "--strict-markers", + "-p", "no:logging", + "--tb=short", + "--color=yes", + "--tavern-http-backend=flask" +] +norecursedirs = [ + ".git", + ".tox", +] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 189b4e2..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -flask -tavern -future diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 5092634..0000000 --- a/setup.cfg +++ /dev/null @@ -1,69 +0,0 @@ -[metadata] -name = tavern-flask -description = Flask plugin for tavern -version = attr: tavern_flask.__version__ -# long_description = file: README.rst -author = Michael Boulton -author_email = boulton@zoetrope.io -url = https://taverntesting.github.io/ - -license = MIT -license_file = LICENSE - -keywords = - testing - pytest -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Developers - Framework :: Pytest - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 - Programming Language :: Python :: 3.5 - Programming Language :: Python :: 3.6 - Topic :: Utilities - Topic :: Software Development :: Testing - License :: OSI Approved :: MIT License - -[options] -packages = find: -include_package_data = True - -install_requires = - flask - tavern - future - -[options.packages.find] -exclude = - tests - -[options.entry_points] -tavern_http = - flask = tavern_flask.tavernhook - - -[bdist_wheel] -universal = 1 - -[aliases] -test=pytest - -[tool:pyflakes] -exclude = .tox,*.egg,dist,build,docs/source -show-source = true -max-line-length = 200 -ignore = N802 - -[tool:pytest] -testpaths=tavern_flask tests/unit -addopts = - --doctest-modules - -r xs -v --strict - -p no:logging -norecursedirs = - .git - .tox - example diff --git a/setup.py b/setup.py deleted file mode 100644 index fd15d29..0000000 --- a/setup.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python - -from setuptools import setup - - -SETUP_REQUIRES = [ - "setuptools>=36", - "pytest-runner", -] - - -TESTS_REQUIRE = [ - "pytest>=3.1.0", - "pytest-cov", - "colorlog", - "mock", - "pytest-remove-stale-bytecode", -] - - -setup( - name="tavern_flask", - - setup_requires=SETUP_REQUIRES, - - tests_require=TESTS_REQUIRE, - extras_require={ - "tests": TESTS_REQUIRE - } -) diff --git a/tavern_flask/__init__.py b/tavern_flask/__init__.py index f102a9c..3dc1f76 100644 --- a/tavern_flask/__init__.py +++ b/tavern_flask/__init__.py @@ -1 +1 @@ -__version__ = "0.0.1" +__version__ = "0.1.0" diff --git a/tavern_flask/client.py b/tavern_flask/client.py index de962f9..e9c7ddb 100644 --- a/tavern_flask/client.py +++ b/tavern_flask/client.py @@ -1,24 +1,17 @@ -import logging import json as jsonlib +import logging +from typing import Dict, Optional +from urllib.parse import urlencode, urlparse -try: - from urllib.parse import urlparse, urlencode -except ImportError: - from urlparse import urlparse - from urllib import urlencode - -from future.utils import raise_from - -from tavern.util import exceptions -from tavern.util.dict_util import check_expected_keys -from tavern.schemas.extensions import import_ext_function - +import flask +from tavern._core import exceptions +from tavern._core.dict_util import check_expected_keys +from tavern._core.extfunctions import import_ext_function logger = logging.getLogger(__name__) class FlaskTestSession: - def __init__(self, **kwargs): expected_blocks = { "app": { @@ -34,9 +27,9 @@ def __init__(self, **kwargs): except KeyError as e: msg = "Need to specify app location (in the form my.module:application)" logger.error(msg) - raise_from(exceptions.MissingKeysError(msg), e) + raise exceptions.MissingKeysError(msg) from e - self._flask_app = import_ext_function(app_location) + self._flask_app: flask.Flask = import_ext_function(app_location) self._test_client = self._flask_app.test_client() def __enter__(self): @@ -45,7 +38,17 @@ def __enter__(self): def __exit__(self, *args): pass - def make_request(self, url, verify, method, headers=None, params=None, json=None, data=None): + def make_request( + self, + *, + url: str, + method: str, + verify: bool = True, + headers: Optional[Dict] = None, + params: Optional[Dict] = None, + json: Optional[Dict] = None, + data=None, + ): # This isn't used - won't be using SSL if not verify: logger.warning("'verify' has no use when using flask test client") diff --git a/tavern_flask/request.py b/tavern_flask/request.py index bac7ead..28a8594 100644 --- a/tavern_flask/request.py +++ b/tavern_flask/request.py @@ -1,27 +1,28 @@ import functools import logging +import typing +from typing import Dict -from future.utils import raise_from import requests from box import Box +from tavern._core import exceptions +from tavern._core.dict_util import check_expected_keys +from tavern.request import BaseRequest -from tavern.util import exceptions -from tavern.util.dict_util import check_expected_keys +if typing.TYPE_CHECKING: + from tavern._core.pytest.config import TestConfig from tavern._plugins.rest.request import get_request_args -from tavern.request.base import BaseRequest - logger = logging.getLogger(__name__) class FlaskRequest(BaseRequest): - - def __init__(self, session, rspec, test_block_config): + def __init__(self, session, rspec: Dict, test_block_config: "TestConfig"): """Prepare request Args: - rspec (dict): test spec - test_block_config (dict): Any configuration for this the block of + rspec: test spec + test_block_config: Any configuration for this the block of tests Raises: @@ -29,9 +30,9 @@ def __init__(self, session, rspec, test_block_config): spec. Only valid keyword args to requests can be passed """ - if 'meta' in rspec: - meta = rspec.pop('meta') - if meta and 'clear_session_cookies' in meta: + if "meta" in rspec: + meta = rspec.pop("meta") + if meta and "clear_session_cookies" in meta: session.cookies.clear_session_cookies() expected = { @@ -63,7 +64,7 @@ def __init__(self, session, rspec, test_block_config): self._prepared = functools.partial(session.make_request, **request_args) def run(self): - """ Runs the prepared request and times it + """Runs the prepared request and times it Todo: time it @@ -76,7 +77,7 @@ def run(self): return self._prepared() except requests.exceptions.RequestException as e: logger.exception("Error running prepared request") - raise_from(exceptions.RestRequestException, e) + raise exceptions.RestRequestException from e @property def request_vars(self): diff --git a/tavern_flask/response.py b/tavern_flask/response.py index 0d417c2..05e4c16 100644 --- a/tavern_flask/response.py +++ b/tavern_flask/response.py @@ -1,5 +1,6 @@ import logging +import flask from tavern._plugins.rest.response import RestResponse logger = logging.getLogger(__name__) @@ -8,13 +9,14 @@ class FlaskResponse(RestResponse): """Flask response verifier""" - def verify(self, response): + def verify(self, response: flask.Response): """Wrap the Flask response into a Requests response and call the verifier for that. See docs for RestResponse for what this does and returns """ from requests import Response + wrapped_response = Response() wrapped_response.headers = response.headers wrapped_response.status_code = response._status_code diff --git a/tavern_flask/schema.yaml b/tavern_flask/schema.yaml index c75d97a..c7080e9 100644 --- a/tavern_flask/schema.yaml +++ b/tavern_flask/schema.yaml @@ -1,17 +1,15 @@ --- -name: MQTT schemas -desc: pykwalify schemas for 'mqtt' plugin block, mqtt_publish, and mqtt_response +$schema: "http://json-schema.org/draft-07/schema#" -initialisation: - flask: - required: false - type: map - mapping: +title: Flask schema +description: jsonschema for Flask connection +properties: + flask: + type: object + properties: app: - required: false - type: map - mapping: + type: object + properties: location: - type: str - required: false + type: string diff --git a/tavern_flask/tavernhook.py b/tavern_flask/tavernhook.py index a25b33f..b40c0b8 100644 --- a/tavern_flask/tavernhook.py +++ b/tavern_flask/tavernhook.py @@ -1,40 +1,36 @@ import logging -from os.path import join, abspath, dirname +import typing +from os.path import abspath, dirname, join +from typing import Dict import yaml +from tavern._core.dict_util import format_keys -from future.utils import raise_from - -from tavern.util.dict_util import format_keys -from tavern.util import exceptions - +from .client import FlaskTestSession from .request import FlaskRequest from .response import FlaskResponse -from .client import FlaskTestSession +if typing.TYPE_CHECKING: + from tavern._core.pytest.config import TestConfig logger = logging.getLogger(__name__) - session_type = FlaskTestSession request_type = FlaskRequest request_block_name = "request" -def get_expected_from_request(stage, test_block_config, session): - # pylint: disable=unused-argument - try: - r_expected = stage["response"] - except KeyError as e: - logger.error("Need a 'response' block if a 'request' is being sent") - raise_from(exceptions.MissingSettingsError, e) - f_expected = format_keys(r_expected, test_block_config["variables"]) +def get_expected_from_request( + response_block: Dict, test_block_config: "TestConfig", session +): + f_expected = format_keys(response_block, test_block_config.variables) return f_expected + verifier_type = FlaskResponse response_block_name = "response" schema_path = join(abspath(dirname(__file__)), "schema.yaml") with open(schema_path, "r") as schema_file: - schema = yaml.load(schema_file) + schema = yaml.load(schema_file, Loader=yaml.SafeLoader) diff --git a/tox.ini b/tox.ini index 32933c1..31e1264 100644 --- a/tox.ini +++ b/tox.ini @@ -1,25 +1,19 @@ [tox] -envlist = py27,py34,py35,py36,pypy,pypy3,py27lint,py36lint +envlist = py3,py3check skip_missing_interpreters = true +isolated_build = True [testenv] +passenv = XDG_CACHE_HOME +basepython = python3.11 +allowlist_externals = + uv +install_command = uv pip install {opts} {packages} +extras = + dev commands = - {envpython} -bb setup.py test {posargs} + {envbindir}/python -m pytest -[testenv:py27lint] -basepython = python2.7 -deps = - pylint - pytest - paho-mqtt +[testenv:py3check] commands = - pylint tavern_flask --rcfile={toxinidir}/.pylintrc - -[testenv:py36lint] -basepython = python3.6 -deps = - pylint - pytest - paho-mqtt -commands = - pylint tavern_flask --rcfile={toxinidir}/.pylintrc + pre-commit run --all-files