From 29dfab47bf256814262c9691840e4df7a6ba1df9 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 6 Nov 2021 12:38:26 +0000 Subject: [PATCH 0001/2467] Bump supported Python version to 3.10 --- .github/workflows/ci.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/windows-ci.yml | 2 +- README.rst | 2 +- docs/source/conf.py | 2 +- docs/source/index.rst | 2 +- docs/source/installation.rst | 2 +- docs/source/release-process.rst | 2 +- readthedocs.yaml | 7 +++---- requirements/requirements.txt | 5 ----- setup.cfg | 2 +- src/mock_vws/_flask_server/dockerfiles/base/Dockerfile | 6 ++---- src/mock_vws/_flask_server/target_manager.py | 2 +- src/mock_vws/_query_tools.py | 3 +-- src/mock_vws/_query_validators/date_validators.py | 3 +-- .../_requests_mock_server/mock_web_services_api.py | 2 +- src/mock_vws/_services_validators/date_validators.py | 3 +-- src/mock_vws/target.py | 2 +- tests/mock_vws/test_date_header.py | 2 +- tests/mock_vws/test_invalid_json.py | 2 +- tests/mock_vws/test_query.py | 2 +- tests/mock_vws/test_target_summary.py | 2 +- tests/mock_vws/utils/assertions.py | 2 +- 23 files changed, 25 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13424ee7a..cac48d8fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9] + python-version: ["3.10"] ci_pattern: - test_query.py::TestContentType - test_query.py::TestSuccess diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 89d498d4d..2058ce984 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: - python-version: [3.9] + python-version: ["3.10"] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 32f25291a..f13c2358f 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: - python-version: [3.9] + python-version: ["3.10"] platform: [windows-latest] runs-on: ${{ matrix.platform }} diff --git a/README.rst b/README.rst index 7f1162806..561a2715d 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ Mocking calls made to Vuforia with Python ``requests`` Using the mock redirects requests to Vuforia made with `requests`_ to an in-memory implementation. -This requires Python 3.9+. +This requires Python 3.10+. .. code:: sh diff --git a/docs/source/conf.py b/docs/source/conf.py index 4df1043d7..e3eac98a9 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -51,7 +51,7 @@ htmlhelp_basename = 'VWSPYTHONMOCKdoc' autoclass_content = 'init' intersphinx_mapping = { - 'python': ('https://docs.python.org/3.9', None), + 'python': ('https://docs.python.org/3.10', None), 'docker': ('https://docker-py.readthedocs.io/en/stable', None), } nitpicky = True diff --git a/docs/source/index.rst b/docs/source/index.rst index aef52d17f..e7ce0af09 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -8,7 +8,7 @@ Mocking calls made to Vuforia with Python ``requests`` pip3 install vws-python-mock -This requires Python 3.9+. +This requires Python 3.10+. .. include:: basic-example.rst diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 0d1378c3f..640667006 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -5,4 +5,4 @@ Installation pip3 install vws-python-mock -This requires Python 3.9+. +This requires Python 3.10+. diff --git a/docs/source/release-process.rst b/docs/source/release-process.rst index 7683a5d84..00c9e07ab 100644 --- a/docs/source/release-process.rst +++ b/docs/source/release-process.rst @@ -10,7 +10,7 @@ Outcomes Prerequisites ~~~~~~~~~~~~~ -* ``python3`` on your ``PATH`` set to Python 3.9+. +* ``python3`` on your ``PATH`` set to Python 3.10+. * ``virtualenv``. * Push access to this repository. * Trust that ``master`` is ready and high enough quality for release. diff --git a/readthedocs.yaml b/readthedocs.yaml index e0ee7fbaf..7c3d72fa2 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -1,9 +1,9 @@ version: 2 -# We do this because at the time of writing we need "image: testing" for Python -# 3.9. build: - image: testing + os: ubuntu-20.04 + tools: + python: "3.10" python: install: @@ -11,7 +11,6 @@ python: path: . extra_requirements: - dev - version: 3.9 sphinx: builder: html diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 539a2c94a..ee95aed25 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,10 +1,5 @@ Pillow VWS-Auth-Tools -# We add ``[tzdata]`` for Windows. -# Building the wheel for this on Apple Silicon needs ``gcc`` - that is -# hardcoded in the base Dockerfile. -# This can be removed when we only support Python 3.9+. -backports.zoneinfo[tzdata] flask requests-mock requests diff --git a/setup.cfg b/setup.cfg index 68479510b..2adf07a66 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,7 +17,7 @@ license_file = LICENSE classifiers = Operating System :: POSIX Environment :: Web Environment - Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 License :: OSI Approved :: MIT License Development Status :: 5 - Production/Stable url = https://vws-python-mock.readthedocs.io diff --git a/src/mock_vws/_flask_server/dockerfiles/base/Dockerfile b/src/mock_vws/_flask_server/dockerfiles/base/Dockerfile index fc7203c70..52ca2fbcc 100644 --- a/src/mock_vws/_flask_server/dockerfiles/base/Dockerfile +++ b/src/mock_vws/_flask_server/dockerfiles/base/Dockerfile @@ -1,9 +1,7 @@ -FROM python:3.9.1-slim-buster +FROM python:3.10.0-slim-buster RUN apt update --yes # git is needed for setuptools-scm. -# gcc is needed to create the wheel for backports.zoneinfo, at least on Apple -# Silicon. -RUN apt install --yes git gcc +RUN apt install --yes git COPY . /app WORKDIR /app RUN pip install . diff --git a/src/mock_vws/_flask_server/target_manager.py b/src/mock_vws/_flask_server/target_manager.py index 0edfc725d..7ba846460 100644 --- a/src/mock_vws/_flask_server/target_manager.py +++ b/src/mock_vws/_flask_server/target_manager.py @@ -8,8 +8,8 @@ import random from http import HTTPStatus from typing import Tuple +from zoneinfo import ZoneInfo -from backports.zoneinfo import ZoneInfo from flask import Flask, jsonify, request from mock_vws.database import VuforiaDatabase diff --git a/src/mock_vws/_query_tools.py b/src/mock_vws/_query_tools.py index 7b82fd2f3..c706b34dd 100644 --- a/src/mock_vws/_query_tools.py +++ b/src/mock_vws/_query_tools.py @@ -10,8 +10,7 @@ import io import uuid from typing import Any, Dict, Set - -from backports.zoneinfo import ZoneInfo +from zoneinfo import ZoneInfo from mock_vws._base64_decoding import decode_base64 from mock_vws._constants import ResultCodes, TargetStatuses diff --git a/src/mock_vws/_query_validators/date_validators.py b/src/mock_vws/_query_validators/date_validators.py index 57b0675a2..1781cf4ab 100644 --- a/src/mock_vws/_query_validators/date_validators.py +++ b/src/mock_vws/_query_validators/date_validators.py @@ -4,8 +4,7 @@ import datetime from typing import Dict, Set - -from backports.zoneinfo import ZoneInfo +from zoneinfo import ZoneInfo from mock_vws._query_validators.exceptions import ( DateFormatNotValid, diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index a3c2a7f6f..0304c7139 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -15,8 +15,8 @@ import uuid from http import HTTPStatus from typing import Callable, Dict, Set +from zoneinfo import ZoneInfo -from backports.zoneinfo import ZoneInfo from requests_mock import DELETE, GET, POST, PUT from requests_mock.request import _RequestObjectProxy from requests_mock.response import _Context diff --git a/src/mock_vws/_services_validators/date_validators.py b/src/mock_vws/_services_validators/date_validators.py index f037d9cb4..980ffd458 100644 --- a/src/mock_vws/_services_validators/date_validators.py +++ b/src/mock_vws/_services_validators/date_validators.py @@ -5,8 +5,7 @@ import datetime from http import HTTPStatus from typing import Dict - -from backports.zoneinfo import ZoneInfo +from zoneinfo import ZoneInfo from mock_vws._services_validators.exceptions import Fail, RequestTimeTooSkewed diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py index ee532203c..e6fb8ea2a 100644 --- a/src/mock_vws/target.py +++ b/src/mock_vws/target.py @@ -11,8 +11,8 @@ import uuid from dataclasses import dataclass, field from typing import TypedDict +from zoneinfo import ZoneInfo -from backports.zoneinfo import ZoneInfo from PIL import Image, ImageStat from mock_vws._constants import TargetStatuses diff --git a/tests/mock_vws/test_date_header.py b/tests/mock_vws/test_date_header.py index f971026ad..555baed62 100644 --- a/tests/mock_vws/test_date_header.py +++ b/tests/mock_vws/test_date_header.py @@ -6,10 +6,10 @@ from http import HTTPStatus from typing import Dict from urllib.parse import urlparse +from zoneinfo import ZoneInfo import pytest import requests -from backports.zoneinfo import ZoneInfo from freezegun import freeze_time from requests.structures import CaseInsensitiveDict from vws_auth_tools import authorization_header, rfc_1123_date diff --git a/tests/mock_vws/test_invalid_json.py b/tests/mock_vws/test_invalid_json.py index 9520aaef1..312ac2ab9 100644 --- a/tests/mock_vws/test_invalid_json.py +++ b/tests/mock_vws/test_invalid_json.py @@ -5,10 +5,10 @@ from datetime import datetime, timedelta from http import HTTPStatus from urllib.parse import urlparse +from zoneinfo import ZoneInfo import pytest import requests -from backports.zoneinfo import ZoneInfo from freezegun import freeze_time from requests.structures import CaseInsensitiveDict from vws_auth_tools import authorization_header, rfc_1123_date diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index 70cbda38e..a52d2f7ac 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -17,10 +17,10 @@ from pathlib import Path from typing import Any, Dict from urllib.parse import urljoin +from zoneinfo import ZoneInfo import pytest import requests -from backports.zoneinfo import ZoneInfo from PIL import Image from requests import Response from requests_mock import POST diff --git a/tests/mock_vws/test_target_summary.py b/tests/mock_vws/test_target_summary.py index 83579b04a..4a8b255ad 100644 --- a/tests/mock_vws/test_target_summary.py +++ b/tests/mock_vws/test_target_summary.py @@ -5,10 +5,10 @@ import datetime import io import uuid +from zoneinfo import ZoneInfo import pytest from _pytest.fixtures import SubRequest -from backports.zoneinfo import ZoneInfo from vws import VWS, CloudRecoService from vws.exceptions.vws_exceptions import UnknownTarget from vws.reports import TargetStatuses diff --git a/tests/mock_vws/utils/assertions.py b/tests/mock_vws/utils/assertions.py index 126c72240..c0113522e 100644 --- a/tests/mock_vws/utils/assertions.py +++ b/tests/mock_vws/utils/assertions.py @@ -10,8 +10,8 @@ import json from http import HTTPStatus from string import hexdigits +from zoneinfo import ZoneInfo -from backports.zoneinfo import ZoneInfo from requests import Response from mock_vws._constants import ResultCodes From f8b15847484c8050c0165439c09df7ff2429df1e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 15:16:50 +0000 Subject: [PATCH 0002/2467] Attempt to work around Docker issue --- requirements/dev-requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 4a9ee5a5a..dc8617d41 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -25,6 +25,7 @@ pyroma==3.2 # Packaging best practices checker pytest-cov==3.0.0 # Measure code coverage pytest-envfiles==0.1.0 # Use files for environment variables for tests pytest==6.2.5 # Test runners +pywin32==302; sys_platform == 'win32' # Workaround for https://github.com/docker/docker-py/issues/2902 requests-mock-flask==2021.7.10.0 sphinx-autodoc-typehints==1.12.0 sphinx_paramlinks==0.5.2 From 89a81584f4c7a78b4811c4d7be16fc1e63f2ab74 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 15:25:08 +0000 Subject: [PATCH 0003/2467] Attempt to work around Docker issue --- .github/workflows/windows-ci.yml | 1 + requirements/dev-requirements.txt | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index f13c2358f..a79671571 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -46,6 +46,7 @@ jobs: - name: "Install dependencies" run: | python -m pip install --upgrade pip setuptools wheel + python -m pip install pywin32==227 # We use '--ignore-installed' to avoid GitHub's cache which can cause # issues - we have seen packages from this cache be cause trouble with # pip-extra-reqs. diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index dc8617d41..4a9ee5a5a 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -25,7 +25,6 @@ pyroma==3.2 # Packaging best practices checker pytest-cov==3.0.0 # Measure code coverage pytest-envfiles==0.1.0 # Use files for environment variables for tests pytest==6.2.5 # Test runners -pywin32==302; sys_platform == 'win32' # Workaround for https://github.com/docker/docker-py/issues/2902 requests-mock-flask==2021.7.10.0 sphinx-autodoc-typehints==1.12.0 sphinx_paramlinks==0.5.2 From 704b99ba637a6b7056480967489c670eab141ff5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 15:29:04 +0000 Subject: [PATCH 0004/2467] Attempt to work around Docker issue --- .github/workflows/windows-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index a79671571..533812899 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -46,7 +46,7 @@ jobs: - name: "Install dependencies" run: | python -m pip install --upgrade pip setuptools wheel - python -m pip install pywin32==227 + python -m pip install --upgrade pypiwin32 # We use '--ignore-installed' to avoid GitHub's cache which can cause # issues - we have seen packages from this cache be cause trouble with # pip-extra-reqs. From 0ba7be11c6ede3f7d4a925382a0f5935d91f0d62 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 15:46:18 +0000 Subject: [PATCH 0005/2467] Attempt to work around Docker issue --- .github/workflows/windows-ci.yml | 3 ++- requirements/dev-requirements.txt | 2 +- tests/mock_vws/test_docker.py | 6 +++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 533812899..c2b114601 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -46,7 +46,8 @@ jobs: - name: "Install dependencies" run: | python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade pypiwin32 + python -m pip install --upgrade pywin32 + python -m pip install --no-deps docker # We use '--ignore-installed' to avoid GitHub's cache which can cause # issues - we have seen packages from this cache be cause trouble with # pip-extra-reqs. diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 4a9ee5a5a..3a241ad03 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -6,7 +6,7 @@ autoflake==1.4 black==21.12b0 check-manifest==0.47 doc8==0.10.1 -docker==5.0.3 +docker==5.0.3; sys_platform != "win32" dodgy==0.2.1 # Look for uploaded secrets flake8-commas==2.1.0 # Require silicon valley commas flake8-quotes==3.3.1 # Require single quotes diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py index 434cd2916..9bb7a849f 100644 --- a/tests/mock_vws/test_docker.py +++ b/tests/mock_vws/test_docker.py @@ -9,7 +9,11 @@ from pathlib import Path from typing import Iterator -import docker +try: + import docker +except ImportError: + pass + import pytest import requests from docker.models.networks import Network From 0ed3d1532ccc57d6bc45ef0f0fa5a70cfcf512c5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 15:46:45 +0000 Subject: [PATCH 0006/2467] Temporarily do not run tests --- .github/workflows/ci.yml | 165 --------------------------------------- 1 file changed, 165 deletions(-) delete mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index cac48d8fd..000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,165 +0,0 @@ ---- - -name: CI - -on: - push: - branches: [master] - pull_request: - branches: [master] - schedule: - # * is a special character in YAML so you have to quote this string - # Run at 1:00 every day - - cron: '0 1 * * *' - -jobs: - build: - - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - python-version: ["3.10"] - ci_pattern: - - test_query.py::TestContentType - - test_query.py::TestSuccess - - test_query.py::TestIncorrectFields - - test_query.py::TestMaxNumResults - - test_query.py::TestIncludeTargetData - - test_query.py::TestAcceptHeader - - test_query.py::TestActiveFlag - - test_query.py::TestBadImage - - test_query.py::TestMaximumImageFileSize - - test_query.py::TestMaximumImageDimensions - - test_query.py::TestImageFormats - - test_query.py::TestProcessing - - test_query.py::TestUpdate - - test_query.py::TestDeleted - - test_query.py::TestTargetStatusFailed - - test_query.py::TestDateFormats - - test_query.py::TestInactiveProject - - test_add_target.py - - test_authorization_header.py::TestAuthorizationHeader - - test_authorization_header.py::TestMalformed::test_one_part - - test_authorization_header.py::TestMalformed::test_missing_signature - - test_authorization_header.py::TestBadKey - - test_content_length.py::TestIncorrect::test_not_integer - - test_content_length.py::TestIncorrect::test_too_large - - test_content_length.py::TestIncorrect::test_too_small - - test_database_summary.py - - test_date_header.py::TestFormat - - test_date_header.py::TestMissing - - test_date_header.py::TestSkewedTime::test_date_out_of_range - - test_date_header.py::TestSkewedTime::test_date_in_range - - test_delete_target.py - - test_get_duplicates.py - - test_get_target.py - - test_invalid_given_id.py - - test_invalid_json.py - - test_target_list.py - - test_target_summary.py - - test_unexpected_json.py - - test_update_target.py::TestActiveFlag - - test_update_target.py::TestApplicationMetadata - - test_update_target.py::TestImage::test_image_valid - - test_update_target.py::TestImage::test_bad_image_format_or_color_space - - test_update_target.py::TestImage::test_corrupted - - test_update_target.py::TestImage::test_image_too_large - - test_update_target.py::TestImage::test_not_base64_encoded_processable - - test_update_target.py::TestImage::test_not_base64_encoded_not_processable - - test_update_target.py::TestImage::test_not_image - - test_update_target.py::TestImage::test_invalid_type - - test_update_target.py::TestImage::test_rating_can_change - - test_update_target.py::TestTargetName - - test_update_target.py::TestUnexpectedData - - test_update_target.py::TestUpdate - - test_update_target.py::TestWidth - - test_update_target.py::TestInactiveProject - - test_requests_mock_usage.py - - test_flask_app_usage.py - - test_docker.py - - steps: - # We share Vuforia credentials and therefore Vuforia databases across - # workflows. - # We therefore want to run only one workflow at a time. - - name: Wait for other GitHub Workflows to finish - uses: softprops/turnstyle@v1 - with: - same-branch-only: false - # By default this is 60. - # We have a lot of jobs so this is set higher - we hit API timeouts. - poll-interval-seconds: 300 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - uses: actions/checkout@v2 - with: - # See https://github.com/codecov/codecov-action/issues/190. - fetch-depth: 2 - - - name: "Set up Python" - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - uses: actions/cache@v2 - with: - path: ~/.cache/pip - # This is like the example but we use ``*requirements.txt`` rather - # than ``requirements.txt`` because we have multiple requirements - # files. - key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - - name: "Install dependencies" - run: | - python -m pip install --upgrade pip setuptools wheel - # We use '--ignore-installed' to avoid GitHub's cache which can cause - # issues - we have seen packages from this cache be cause trouble with - # pip-extra-reqs. - python -m pip install --ignore-installed --upgrade --editable .[dev] - - - name: "Set secrets file" - run: | - # See the "CI Setup" document for details of how this was set up. - ci/decrypt_secret.sh - tar xvf "${HOME}"/secrets/secrets.tar - python ci/set_secrets_file.py - env: - CI_PATTERN: ${{ matrix.ci_pattern }} - ENCRYPTED_FILE: secrets.tar.gpg - LARGE_SECRET_PASSPHRASE: ${{ secrets.PASSPHRASE_FOR_VUFORIA_SECRETS }} - - - name: "Run tests" - run: | - pytest -s -vvv --exitfirst --cov=src/ --cov=tests --cov-report=xml tests/mock_vws/${{ matrix.ci_pattern }} - - - name: "Show coverage file" - run: | - # Sometimes we have been sure that we have 100% coverage, but codecov - # says otherwise. - # - # We show the coverage file here to help with debugging. - # https://github.com/VWS-Python/vws-python-mock/issues/708 - cat ./coverage.xml - - # We run this job on every PR, on every merge to master, and nightly. - # This causes us to hit an issue with Codecov. - # - # We see "Too many uploads to this commit.". - # See https://community.codecov.io/t/too-many-uploads-to-this-commit/2574. - # - # To work around this, we do not upload coverage data on scheduled runs. - # We print the event name here to help with debugging. - - name: "Show event name" - run: | - echo ${{ github.event_name }} - - - name: "Upload coverage to Codecov" - uses: "codecov/codecov-action@v1" - with: - fail_ci_if_error: true - if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} From 85453a8888bfd37ad04ee580cbf2e426ae07bb00 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 15:59:33 +0000 Subject: [PATCH 0007/2467] Attempt to work around Docker issue --- .github/workflows/windows-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index c2b114601..89399519f 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -46,7 +46,8 @@ jobs: - name: "Install dependencies" run: | python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade pywin32 + curl -o docker-requirements.txt + python -m pip install --upgrade -r docker-requirements.txt python -m pip install --no-deps docker # We use '--ignore-installed' to avoid GitHub's cache which can cause # issues - we have seen packages from this cache be cause trouble with From 2ddd3a482ec1010e326a077217b3d93da5e3a828 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 16:04:44 +0000 Subject: [PATCH 0008/2467] Attempt to work around Docker issue --- .github/workflows/windows-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 89399519f..9332c8162 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -46,7 +46,7 @@ jobs: - name: "Install dependencies" run: | python -m pip install --upgrade pip setuptools wheel - curl -o docker-requirements.txt + curl -o docker-requirements.txt https://raw.githubusercontent.com/docker/docker-py/master/requirements.txt python -m pip install --upgrade -r docker-requirements.txt python -m pip install --no-deps docker # We use '--ignore-installed' to avoid GitHub's cache which can cause From 204c368c4901c922406efc21a6f9e864a76bacf6 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 16:24:32 +0000 Subject: [PATCH 0009/2467] Attempt to work around Docker issue --- .github/workflows/windows-ci.yml | 3 +-- requirements/docker-requirements.txt | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 requirements/docker-requirements.txt diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 9332c8162..d64714ec2 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -46,8 +46,7 @@ jobs: - name: "Install dependencies" run: | python -m pip install --upgrade pip setuptools wheel - curl -o docker-requirements.txt https://raw.githubusercontent.com/docker/docker-py/master/requirements.txt - python -m pip install --upgrade -r docker-requirements.txt + python -m pip install --upgrade -r requirements/docker-requirements.txt python -m pip install --no-deps docker # We use '--ignore-installed' to avoid GitHub's cache which can cause # issues - we have seen packages from this cache be cause trouble with diff --git a/requirements/docker-requirements.txt b/requirements/docker-requirements.txt new file mode 100644 index 000000000..d7c11aaa7 --- /dev/null +++ b/requirements/docker-requirements.txt @@ -0,0 +1,17 @@ +appdirs==1.4.3 +asn1crypto==0.22.0 +backports.ssl-match-hostname==3.5.0.1 +cffi==1.14.4 +cryptography==3.4.7 +enum34==1.1.6 +idna==2.5 +ipaddress==1.0.18 +packaging==16.8 +paramiko==2.8.0 +pycparser==2.17 +pyOpenSSL==18.0.0 +pyparsing==2.2.0 +pywin32==301; sys_platform == 'win32' +requests==2.26.0 +urllib3==1.26.5 +websocket-client==0.56.0 From c5b256231b62e7e840863d2beedbf59360337a93 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 16:50:18 +0000 Subject: [PATCH 0010/2467] Attempt to work around Docker issue --- .github/workflows/windows-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index d64714ec2..1c74ee402 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -51,7 +51,7 @@ jobs: # We use '--ignore-installed' to avoid GitHub's cache which can cause # issues - we have seen packages from this cache be cause trouble with # pip-extra-reqs. - python -m pip install --ignore-installed --upgrade --editable .[dev] + python -m pip install --upgrade --editable .[dev] - name: "Set secrets file" run: | From 31a4b07388be6ea5386615174826ec5d58c5c9fe Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 16:57:28 +0000 Subject: [PATCH 0011/2467] Attempt to work around Docker issue --- .github/workflows/windows-ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 1c74ee402..3e965387a 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -43,6 +43,11 @@ jobs: restore-keys: | ${{ runner.os }}-pip- + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + with: + limit-access-to-actor: true + - name: "Install dependencies" run: | python -m pip install --upgrade pip setuptools wheel From 7039ee2b1e66c1da89c8746a301a6e8826216e12 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 17:06:21 +0000 Subject: [PATCH 0012/2467] Attempt to work around Docker issue --- requirements/docker-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/docker-requirements.txt b/requirements/docker-requirements.txt index d7c11aaa7..fb77ef7c7 100644 --- a/requirements/docker-requirements.txt +++ b/requirements/docker-requirements.txt @@ -11,7 +11,7 @@ paramiko==2.8.0 pycparser==2.17 pyOpenSSL==18.0.0 pyparsing==2.2.0 -pywin32==301; sys_platform == 'win32' +pywin32==302; sys_platform == 'win32' requests==2.26.0 urllib3==1.26.5 websocket-client==0.56.0 From 74a8e224ca63ca9e377ddaf366972b941c3347a9 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 17:13:16 +0000 Subject: [PATCH 0013/2467] Attempt to work around Docker issue --- .github/workflows/windows-ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 3e965387a..52a80f4d0 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -43,10 +43,10 @@ jobs: restore-keys: | ${{ runner.os }}-pip- - - name: Setup tmate session - uses: mxschmitt/action-tmate@v3 - with: - limit-access-to-actor: true + # - name: Setup tmate session + # uses: mxschmitt/action-tmate@v3 + # with: + # limit-access-to-actor: true - name: "Install dependencies" run: | @@ -66,7 +66,7 @@ jobs: env: SKIP_REAL: 1 run: | - pytest -s -vvv --exitfirst --cov=src/ --cov=tests --cov-report=xml tests/mock_vws/${{ matrix.ci_pattern }} + pytest -s -vvv --exitfirst --cov=src/ --cov=tests --cov-report=xml tests/mock_vws/ - name: "Show coverage file" run: | From 011d4957b83db4572e353965283b42adcf5a055b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 17:18:48 +0000 Subject: [PATCH 0014/2467] Add tzdata for Windows --- requirements/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index ee95aed25..b3fcb2850 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -3,3 +3,4 @@ VWS-Auth-Tools flask requests-mock requests +tzdata; sys_platform == 'win32' From f339e0d6c2fc0cd78daaa804501310670d378b7e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 17:24:04 +0000 Subject: [PATCH 0015/2467] Skip incompatible reqs in pip-extra-reqs --- lint.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint.mk b/lint.mk index 4652d0521..031987d09 100644 --- a/lint.mk +++ b/lint.mk @@ -42,7 +42,7 @@ fix-isort: .PHONY: pip-extra-reqs pip-extra-reqs: - pip-extra-reqs --requirements-file=requirements/requirements.txt src/ + pip-extra-reqs --skip-incompatible --requirements-file=requirements/requirements.txt src/ .PHONY: pip-missing-reqs pip-missing-reqs: From 2dd02d0a0595e864b082f3a79673ad4f25a09d7c Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 17:25:01 +0000 Subject: [PATCH 0016/2467] Undo unnecessary change to Docker tests --- tests/mock_vws/test_docker.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py index 9bb7a849f..434cd2916 100644 --- a/tests/mock_vws/test_docker.py +++ b/tests/mock_vws/test_docker.py @@ -9,11 +9,7 @@ from pathlib import Path from typing import Iterator -try: - import docker -except ImportError: - pass - +import docker import pytest import requests from docker.models.networks import Network From db9598ec286588d1ee6919820f7b629d94eaf3ad Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 17:29:20 +0000 Subject: [PATCH 0017/2467] Undo unnecessary change to CI workflow --- .github/workflows/ci.yml | 165 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..13424ee7a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,165 @@ +--- + +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + schedule: + # * is a special character in YAML so you have to quote this string + # Run at 1:00 every day + - cron: '0 1 * * *' + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + python-version: [3.9] + ci_pattern: + - test_query.py::TestContentType + - test_query.py::TestSuccess + - test_query.py::TestIncorrectFields + - test_query.py::TestMaxNumResults + - test_query.py::TestIncludeTargetData + - test_query.py::TestAcceptHeader + - test_query.py::TestActiveFlag + - test_query.py::TestBadImage + - test_query.py::TestMaximumImageFileSize + - test_query.py::TestMaximumImageDimensions + - test_query.py::TestImageFormats + - test_query.py::TestProcessing + - test_query.py::TestUpdate + - test_query.py::TestDeleted + - test_query.py::TestTargetStatusFailed + - test_query.py::TestDateFormats + - test_query.py::TestInactiveProject + - test_add_target.py + - test_authorization_header.py::TestAuthorizationHeader + - test_authorization_header.py::TestMalformed::test_one_part + - test_authorization_header.py::TestMalformed::test_missing_signature + - test_authorization_header.py::TestBadKey + - test_content_length.py::TestIncorrect::test_not_integer + - test_content_length.py::TestIncorrect::test_too_large + - test_content_length.py::TestIncorrect::test_too_small + - test_database_summary.py + - test_date_header.py::TestFormat + - test_date_header.py::TestMissing + - test_date_header.py::TestSkewedTime::test_date_out_of_range + - test_date_header.py::TestSkewedTime::test_date_in_range + - test_delete_target.py + - test_get_duplicates.py + - test_get_target.py + - test_invalid_given_id.py + - test_invalid_json.py + - test_target_list.py + - test_target_summary.py + - test_unexpected_json.py + - test_update_target.py::TestActiveFlag + - test_update_target.py::TestApplicationMetadata + - test_update_target.py::TestImage::test_image_valid + - test_update_target.py::TestImage::test_bad_image_format_or_color_space + - test_update_target.py::TestImage::test_corrupted + - test_update_target.py::TestImage::test_image_too_large + - test_update_target.py::TestImage::test_not_base64_encoded_processable + - test_update_target.py::TestImage::test_not_base64_encoded_not_processable + - test_update_target.py::TestImage::test_not_image + - test_update_target.py::TestImage::test_invalid_type + - test_update_target.py::TestImage::test_rating_can_change + - test_update_target.py::TestTargetName + - test_update_target.py::TestUnexpectedData + - test_update_target.py::TestUpdate + - test_update_target.py::TestWidth + - test_update_target.py::TestInactiveProject + - test_requests_mock_usage.py + - test_flask_app_usage.py + - test_docker.py + + steps: + # We share Vuforia credentials and therefore Vuforia databases across + # workflows. + # We therefore want to run only one workflow at a time. + - name: Wait for other GitHub Workflows to finish + uses: softprops/turnstyle@v1 + with: + same-branch-only: false + # By default this is 60. + # We have a lot of jobs so this is set higher - we hit API timeouts. + poll-interval-seconds: 300 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/checkout@v2 + with: + # See https://github.com/codecov/codecov-action/issues/190. + fetch-depth: 2 + + - name: "Set up Python" + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - uses: actions/cache@v2 + with: + path: ~/.cache/pip + # This is like the example but we use ``*requirements.txt`` rather + # than ``requirements.txt`` because we have multiple requirements + # files. + key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: "Install dependencies" + run: | + python -m pip install --upgrade pip setuptools wheel + # We use '--ignore-installed' to avoid GitHub's cache which can cause + # issues - we have seen packages from this cache be cause trouble with + # pip-extra-reqs. + python -m pip install --ignore-installed --upgrade --editable .[dev] + + - name: "Set secrets file" + run: | + # See the "CI Setup" document for details of how this was set up. + ci/decrypt_secret.sh + tar xvf "${HOME}"/secrets/secrets.tar + python ci/set_secrets_file.py + env: + CI_PATTERN: ${{ matrix.ci_pattern }} + ENCRYPTED_FILE: secrets.tar.gpg + LARGE_SECRET_PASSPHRASE: ${{ secrets.PASSPHRASE_FOR_VUFORIA_SECRETS }} + + - name: "Run tests" + run: | + pytest -s -vvv --exitfirst --cov=src/ --cov=tests --cov-report=xml tests/mock_vws/${{ matrix.ci_pattern }} + + - name: "Show coverage file" + run: | + # Sometimes we have been sure that we have 100% coverage, but codecov + # says otherwise. + # + # We show the coverage file here to help with debugging. + # https://github.com/VWS-Python/vws-python-mock/issues/708 + cat ./coverage.xml + + # We run this job on every PR, on every merge to master, and nightly. + # This causes us to hit an issue with Codecov. + # + # We see "Too many uploads to this commit.". + # See https://community.codecov.io/t/too-many-uploads-to-this-commit/2574. + # + # To work around this, we do not upload coverage data on scheduled runs. + # We print the event name here to help with debugging. + - name: "Show event name" + run: | + echo ${{ github.event_name }} + + - name: "Upload coverage to Codecov" + uses: "codecov/codecov-action@v1" + with: + fail_ci_if_error: true + if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} From 97fdb863074fbedfde75152a40d653f60dd2ad27 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 17:29:32 +0000 Subject: [PATCH 0018/2467] Undo unnecessary change to CI workflow --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13424ee7a..cac48d8fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9] + python-version: ["3.10"] ci_pattern: - test_query.py::TestContentType - test_query.py::TestSuccess From 685b6de7b02bad47d58f16203aec352609043392 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 11 Dec 2021 17:40:43 +0000 Subject: [PATCH 0019/2467] Document workaround for Docker --- .github/workflows/windows-ci.yml | 12 +++++++++++- requirements/docker-requirements.txt | 6 ++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 52a80f4d0..5be3299f5 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -51,12 +51,22 @@ jobs: - name: "Install dependencies" run: | python -m pip install --upgrade pip setuptools wheel + + # We cannot install docker-py on Windows with Python 3.10 because of + # https://github.com/docker/docker-py/issues/2902. + # + # In particular, the pywin32 dependency is pinned by docker-py + # to a version which is not available. + # + # We therefore install our own dependencies for Docker and then we + # install Docker with no dependencies. python -m pip install --upgrade -r requirements/docker-requirements.txt python -m pip install --no-deps docker + # We use '--ignore-installed' to avoid GitHub's cache which can cause # issues - we have seen packages from this cache be cause trouble with # pip-extra-reqs. - python -m pip install --upgrade --editable .[dev] + python -m pip install --ignore-installed --upgrade --editable .[dev] - name: "Set secrets file" run: | diff --git a/requirements/docker-requirements.txt b/requirements/docker-requirements.txt index fb77ef7c7..fe8b31603 100644 --- a/requirements/docker-requirements.txt +++ b/requirements/docker-requirements.txt @@ -1,3 +1,9 @@ +# This file was created as part of a workaround for +# https://github.com/docker/docker-py/issues/2902. +# +# It is a copy of +# https://github.com/docker/docker-py/blob/master/requirements.txt +# but with the version of pywin32 bumped. appdirs==1.4.3 asn1crypto==0.22.0 backports.ssl-match-hostname==3.5.0.1 From bc3fc36670643e17729396124e44d162c97abb32 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Dec 2021 10:14:32 +0000 Subject: [PATCH 0020/2467] Try removing apt install git from Dockerfiles --- .../_flask_server/dockerfiles/target_manager/Dockerfile | 3 --- src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile | 3 --- src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile | 3 --- 3 files changed, 9 deletions(-) diff --git a/src/mock_vws/_flask_server/dockerfiles/target_manager/Dockerfile b/src/mock_vws/_flask_server/dockerfiles/target_manager/Dockerfile index e23650f76..deed84894 100644 --- a/src/mock_vws/_flask_server/dockerfiles/target_manager/Dockerfile +++ b/src/mock_vws/_flask_server/dockerfiles/target_manager/Dockerfile @@ -1,7 +1,4 @@ FROM python:3.9.1-slim-buster -RUN apt update --yes -# git is needed for setuptools-scm. -RUN apt install --yes git COPY . /app WORKDIR /app RUN pip install . diff --git a/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile b/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile index 1db6dfa70..e09b75f37 100644 --- a/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile +++ b/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile @@ -1,7 +1,4 @@ FROM python:3.9.1-slim-buster -RUN apt update --yes -# git is needed for setuptools-scm. -RUN apt install --yes git COPY . /app WORKDIR /app RUN pip install . diff --git a/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile b/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile index eaa05991b..e71bb6039 100644 --- a/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile +++ b/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile @@ -1,7 +1,4 @@ FROM python:3.9.1-slim-buster -RUN apt update --yes -# git is needed for setuptools-scm. -RUN apt install --yes git COPY . /app WORKDIR /app RUN pip install . From 71f11d11dc1c46157526dd4730a786763811da6f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Dec 2021 10:16:59 +0000 Subject: [PATCH 0021/2467] Skip CI [skip ci] From fc627526569d249e9918fd7a7857deb259b7c17f Mon Sep 17 00:00:00 2001 From: adamtheturtle Date: Mon, 27 Dec 2021 10:26:04 +0000 Subject: [PATCH 0022/2467] Bump CHANGELOG --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 456b909e8..85f4c8f1b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,9 @@ Changelog Next ---- +2021.12.27.1 +------------ + 2021.12.27 ------------ From a3b9eb56f885252e30f0f026c8e07535d608e1f1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Dec 2021 10:31:48 +0000 Subject: [PATCH 0023/2467] Add workflow dispatch to all CI jobs [skip ci] --- .github/workflows/ci.yml | 1 + .github/workflows/docker-build.yml | 1 + .github/workflows/lint.yml | 1 + .github/workflows/windows-ci.yml | 1 + 4 files changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6dbf88932..e0152cfe5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ on: # * is a special character in YAML so you have to quote this string # Run at 1:00 every day - cron: '0 1 * * *' + workflow_dispatch: {} jobs: build: diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index be8c840a8..8a4bda365 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -15,6 +15,7 @@ on: # * is a special character in YAML so you have to quote this string # Run at 1:00 every day - cron: '0 1 * * *' + workflow_dispatch: {} jobs: build: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e6a1dba78..9e7ec1fe1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,6 +11,7 @@ on: # * is a special character in YAML so you have to quote this string # Run at 1:00 every day - cron: '0 1 * * *' + workflow_dispatch: {} jobs: build: diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 47a477137..a70a354bf 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -11,6 +11,7 @@ on: # * is a special character in YAML so you have to quote this string # Run at 1:00 every day - cron: '0 1 * * *' + workflow_dispatch: {} jobs: build: From a543b3ea2c94444db5d01cd107257146b814096a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Dec 2021 10:42:56 +0000 Subject: [PATCH 0024/2467] Try QEMU [skip ci] --- .github/workflows/docker-build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 8a4bda365..14bb11325 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -25,6 +25,9 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 From ea3bd88c3d8964ed02236a4c20a374ef727040fb Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Dec 2021 10:48:28 +0000 Subject: [PATCH 0025/2467] Add QEMU to the release build for Docker [skip ci] --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 282b71e73..38f145c73 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -84,6 +84,9 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Build and push target manager Docker image uses: docker/build-push-action@v2.7.0 with: From dc810b9308362a604f5e1400b1f65bf00c4404f0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Dec 2021 10:52:20 +0000 Subject: [PATCH 0026/2467] Bump base Docker image to 3.9.9 [skip ci] --- .../_flask_server/dockerfiles/target_manager/Dockerfile | 2 +- src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile | 2 +- src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mock_vws/_flask_server/dockerfiles/target_manager/Dockerfile b/src/mock_vws/_flask_server/dockerfiles/target_manager/Dockerfile index deed84894..c920115c3 100644 --- a/src/mock_vws/_flask_server/dockerfiles/target_manager/Dockerfile +++ b/src/mock_vws/_flask_server/dockerfiles/target_manager/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.9.1-slim-buster +FROM python:3.9.9-slim-buster COPY . /app WORKDIR /app RUN pip install . diff --git a/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile b/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile index e09b75f37..559352924 100644 --- a/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile +++ b/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.9.1-slim-buster +FROM python:3.9.9-slim-buster COPY . /app WORKDIR /app RUN pip install . diff --git a/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile b/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile index e71bb6039..04edb9415 100644 --- a/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile +++ b/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.9.1-slim-buster +FROM python:3.9.9-slim-buster COPY . /app WORKDIR /app RUN pip install . From 280fab031aaee49bce867bdb936f725cac650f21 Mon Sep 17 00:00:00 2001 From: adamtheturtle Date: Mon, 27 Dec 2021 11:27:36 +0000 Subject: [PATCH 0027/2467] Bump CHANGELOG --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 85f4c8f1b..54cf38088 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,9 @@ Changelog Next ---- +2021.12.27.2 +------------ + 2021.12.27.1 ------------ From 5b19a93963c2762ecf1d29e2796db6e9ffed90e5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Dec 2021 14:07:37 +0000 Subject: [PATCH 0028/2467] Rename MatchProcessing to DeletedTargetMatched --- src/mock_vws/_flask_server/vwq.py | 4 ++-- src/mock_vws/_query_validators/exceptions.py | 11 +++++------ .../_requests_mock_server/mock_web_query_api.py | 10 +++++----- ...onse.html => deleted_target_matched_response.html} | 0 4 files changed, 12 insertions(+), 13 deletions(-) rename src/mock_vws/resources/{match_processing_response.html => deleted_target_matched_response.html} (100%) diff --git a/src/mock_vws/_flask_server/vwq.py b/src/mock_vws/_flask_server/vwq.py index 373e34286..454442f32 100644 --- a/src/mock_vws/_flask_server/vwq.py +++ b/src/mock_vws/_flask_server/vwq.py @@ -19,7 +19,7 @@ ) from mock_vws._query_validators import run_query_validators from mock_vws._query_validators.exceptions import ( - MatchProcessing, + DeletedTargetMatched, ValidatorException, ) from mock_vws.database import VuforiaDatabase @@ -124,7 +124,7 @@ def query() -> Response: ), ) except ActiveMatchingTargetsDeleteProcessing as exc: - raise MatchProcessing from exc + raise DeletedTargetMatched from exc headers = { 'Content-Type': 'application/json', diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py index 4b1bffff8..1a0446abd 100644 --- a/src/mock_vws/_query_validators/exceptions.py +++ b/src/mock_vws/_query_validators/exceptions.py @@ -657,10 +657,9 @@ def __init__(self) -> None: } -class MatchProcessing(ValidatorException): +class DeletedTargetMatched(ValidatorException): """ - Exception raised a target is matched which is processing or recently - deleted. + Exception raised when target which was recently deleted is matched. """ def __init__(self) -> None: @@ -682,9 +681,9 @@ def __init__(self) -> None: # * Do the most unexpected thing. # * Be consistent with every response. resources_dir = Path(__file__).parent.parent / 'resources' - filename = 'match_processing_response.html' - match_processing_resp_file = resources_dir / filename - self.response_text = Path(match_processing_resp_file).read_text( + filename = 'deleted_target_matched_response.html' + deleted_target_matched_resp_file = resources_dir / filename + self.response_text = Path(deleted_target_matched_resp_file).read_text( encoding='utf-8', ) self.headers = { diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py index 6bceab0b1..22c5afb44 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py @@ -21,7 +21,7 @@ ) from mock_vws._query_validators import run_query_validators from mock_vws._query_validators.exceptions import ( - MatchProcessing, + DeletedTargetMatched, ValidatorException, ) from mock_vws.target_manager import TargetManager @@ -138,10 +138,10 @@ def query( ), ) except ActiveMatchingTargetsDeleteProcessing: - match_processing_exception = MatchProcessing() - context.headers = match_processing_exception.headers - context.status_code = match_processing_exception.status_code - return match_processing_exception.response_text + deleted_target_matched_exception = DeletedTargetMatched() + context.headers = deleted_target_matched_exception.headers + context.status_code = deleted_target_matched_exception.status_code + return deleted_target_matched_exception.response_text date = email.utils.formatdate(None, localtime=False, usegmt=True) context.headers = { diff --git a/src/mock_vws/resources/match_processing_response.html b/src/mock_vws/resources/deleted_target_matched_response.html similarity index 100% rename from src/mock_vws/resources/match_processing_response.html rename to src/mock_vws/resources/deleted_target_matched_response.html From 88ab8dd2d8d37ea25988635171d13def0768845f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Dec 2021 14:27:01 +0000 Subject: [PATCH 0029/2467] Note that vws web tools can be used to create database details for tests --- docs/source/contributing.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 6b9b6b049..caade08a1 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -89,8 +89,12 @@ To create an inactive project, delete the license key associated with a database Targets sometimes get stuck at the "Processing" stage meaning that they cannot be deleted. When this happens, create a new target database to use for testing. +To create databases without using the browser, use `vws web tools`_. +See https://github.com/VWS-Python/vws-python-mock/issues/901 for a start on how to use the database details created by that tool. + .. _Vuforia License Manager: https://developer.vuforia.com/targetmanager/licenseManager/licenseListing .. _Vuforia Target Manager: https://developer.vuforia.com/targetmanager +.. _vws web tools: https://github.com/VWS-Python/vws-web-tools Skipping Some Tests ------------------- From 32acc165a9ce8d77dc54caf01a4017b986b56474 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Dec 2021 15:50:51 +0000 Subject: [PATCH 0030/2467] Progress --- tests/mock_vws/fixtures/vuforia_backends.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 375a31b00..6ee54d705 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -26,6 +26,9 @@ LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) +_SKIP_REAL: bool +_SKIP_DOCKER_IN_MEMORY: bool +_SKIP_MOCK: bool def _delete_all_targets(database_keys: VuforiaDatabase) -> None: """ @@ -160,6 +163,11 @@ class VuforiaBackend(Enum): MOCK = 'In Memory Mock Vuforia' DOCKER_IN_MEMORY = 'In Memory version of Docker application' +def pytest_addoption(parser): + parser.addoption( + "--runslow", action="store_true", default=False, help="run slow tests" + ) + @pytest.fixture( params=list(VuforiaBackend), @@ -172,13 +180,14 @@ def verify_mock_vuforia( monkeypatch: MonkeyPatch, ) -> Generator: """ - Test functions which use this fixture are run twice. Once with the real - Vuforia, and once with the mock. + Test functions which use this fixture are run multiple times. Once with the + real Vuforia, and once with each mock. - This is useful for verifying the mock. + This is useful for verifying the mocks. """ backend = request.param should_skip = bool(os.getenv(f'SKIP_{backend.name}') == '1') + request.config.getvalue("--runslow") if should_skip: # pragma: no cover pytest.skip() From fa3f5a0b5b416f57d3a5b276647231c5d8821e34 Mon Sep 17 00:00:00 2001 From: adamtheturtle Date: Mon, 27 Dec 2021 17:14:09 +0000 Subject: [PATCH 0031/2467] Bump CHANGELOG --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 54cf38088..4dfd55514 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,9 @@ Changelog Next ---- +2021.12.27.3 +------------ + 2021.12.27.2 ------------ From 0b4ead22a45e86b5377f7daddfe233a8a426f135 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Dec 2021 17:17:38 +0000 Subject: [PATCH 0032/2467] Progress --- tests/mock_vws/fixtures/vuforia_backends.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 6ee54d705..03c00c1ba 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -164,9 +164,16 @@ class VuforiaBackend(Enum): DOCKER_IN_MEMORY = 'In Memory version of Docker application' def pytest_addoption(parser): - parser.addoption( - "--runslow", action="store_true", default=False, help="run slow tests" - ) + """ + XXX + """ + for backend in VuforiaBackend: + parser.addoption( + "--skip-", + action="store_true", + default=False, + help="run slow tests", + ) @pytest.fixture( From 747b38990230c75a1f5265347ee3e92199944994 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Dec 2021 17:36:21 +0000 Subject: [PATCH 0033/2467] Swap from environment variables to Pytest custom options for skipping tests on different backends [skip ci] --- .github/workflows/windows-ci.yml | 4 +--- docs/source/contributing.rst | 9 ++++++++- tests/mock_vws/fixtures/vuforia_backends.py | 15 ++++++--------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index a70a354bf..abcce2ca5 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -57,10 +57,8 @@ jobs: cp ./vuforia_secrets.env.example ./vuforia_secrets.env - name: "Run tests" - env: - SKIP_REAL: 1 run: | - pytest -s -vvv --exitfirst --cov=src/ --cov=tests --cov-report=xml tests/mock_vws/ + pytest --skip-real -s -vvv --exitfirst --cov=src/ --cov=tests --cov-report=xml tests/mock_vws/ - name: "Show coverage file" run: | diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index caade08a1..80d163a7e 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -99,7 +99,14 @@ See https://github.com/VWS-Python/vws-python-mock/issues/901 for a start on how Skipping Some Tests ------------------- -Set either ``SKIP_MOCK`` or ``SKIP_REAL`` to ``1`` to skip tests against the mock, or tests against the real implementation, for tests which run against both. +Use the following custom ``pytest`` options to skip some tests: + +.. prompt:: bash + + --skip-real Skip tests for Real Vuforia + --skip-mock Skip tests for In Memory Mock Vuforia + --skip-docker_in_memory + Skip tests for In Memory version of Docker application Documentation ------------- diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 03c00c1ba..f49898329 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -26,9 +26,6 @@ LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) -_SKIP_REAL: bool -_SKIP_DOCKER_IN_MEMORY: bool -_SKIP_MOCK: bool def _delete_all_targets(database_keys: VuforiaDatabase) -> None: """ @@ -165,14 +162,15 @@ class VuforiaBackend(Enum): def pytest_addoption(parser): """ - XXX + Add options to the pytest command line for skipping tests with particular + backends. """ for backend in VuforiaBackend: parser.addoption( - "--skip-", - action="store_true", + f'--skip-{backend.name.lower()}', + action='store_true', default=False, - help="run slow tests", + help=f'Skip tests for {backend.value}', ) @@ -193,8 +191,7 @@ def verify_mock_vuforia( This is useful for verifying the mocks. """ backend = request.param - should_skip = bool(os.getenv(f'SKIP_{backend.name}') == '1') - request.config.getvalue("--runslow") + should_skip = request.config.getvalue(f'--skip-{backend.name.lower()}') if should_skip: # pragma: no cover pytest.skip() From bf3aa179d2ba92b3888820159de8bdddd218bace Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Dec 2021 17:46:54 +0000 Subject: [PATCH 0034/2467] Use an environment variable to set the version for setuptools_scm inside Docker --- .../_flask_server/dockerfiles/target_manager/Dockerfile | 4 ++++ src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile | 4 ++++ src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/src/mock_vws/_flask_server/dockerfiles/target_manager/Dockerfile b/src/mock_vws/_flask_server/dockerfiles/target_manager/Dockerfile index c920115c3..0a61f960f 100644 --- a/src/mock_vws/_flask_server/dockerfiles/target_manager/Dockerfile +++ b/src/mock_vws/_flask_server/dockerfiles/target_manager/Dockerfile @@ -1,4 +1,8 @@ FROM python:3.9.9-slim-buster +# We set this pretend version as we do not have Git in our path, and we do +# not care enough about having the version correct inside the Docker container +# to install it. +ENV SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 COPY . /app WORKDIR /app RUN pip install . diff --git a/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile b/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile index 559352924..27b6d370e 100644 --- a/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile +++ b/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile @@ -1,4 +1,8 @@ FROM python:3.9.9-slim-buster +# We set this pretend version as we do not have Git in our path, and we do +# not care enough about having the version correct inside the Docker container +# to install it. +ENV SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 COPY . /app WORKDIR /app RUN pip install . diff --git a/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile b/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile index 04edb9415..e9ed313e5 100644 --- a/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile +++ b/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile @@ -1,5 +1,9 @@ FROM python:3.9.9-slim-buster COPY . /app +# We set this pretend version as we do not have Git in our path, and we do +# not care enough about having the version correct inside the Docker container +# to install it. +ENV SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 WORKDIR /app RUN pip install . EXPOSE 5000 From 757689ea146637779a29b1593d58c91772a2a237 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Dec 2021 18:17:24 +0000 Subject: [PATCH 0035/2467] Fix some lint issues [skip ci] --- tests/mock_vws/fixtures/vuforia_backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index f49898329..54f49788f 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -3,7 +3,6 @@ """ import logging -import os from enum import Enum from typing import Generator @@ -160,6 +159,7 @@ class VuforiaBackend(Enum): MOCK = 'In Memory Mock Vuforia' DOCKER_IN_MEMORY = 'In Memory version of Docker application' + def pytest_addoption(parser): """ Add options to the pytest command line for skipping tests with particular From d11d310aeb790910b2fc8b6bb7111db2e56c2424 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Dec 2021 05:14:15 +0000 Subject: [PATCH 0036/2467] Bump requests-mock-flask from 2021.12.13 to 2021.12.28 in /requirements Bumps [requests-mock-flask](https://github.com/adamtheturtle/requests-mock-flask) from 2021.12.13 to 2021.12.28. - [Release notes](https://github.com/adamtheturtle/requests-mock-flask/releases) - [Changelog](https://github.com/adamtheturtle/requests-mock-flask/blob/master/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/requests-mock-flask/compare/2021.12.13...2021.12.28) --- updated-dependencies: - dependency-name: requests-mock-flask dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 17798f89a..c36667473 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -24,7 +24,7 @@ pyroma==3.2 # Packaging best practices checker pytest-cov==3.0.0 # Measure code coverage pytest-envfiles==0.1.0 # Use files for environment variables for tests pytest==6.2.5 # Test runners -requests-mock-flask==2021.12.13 +requests-mock-flask==2021.12.28 sphinx-autodoc-typehints==1.12.0 sphinx_paramlinks==0.5.2 sphinxcontrib-httpdomain==1.8.0 From 7e6b62108b5d4a4dfa6cff25c2ab948d00714d1c Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 28 Dec 2021 16:50:39 +0000 Subject: [PATCH 0037/2467] Fix mypy issues --- tests/mock_vws/fixtures/vuforia_backends.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 54f49788f..f457034e0 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -10,6 +10,7 @@ import requests import requests_mock from _pytest.fixtures import SubRequest +from _pytest.config.argparsing import Parser from pytest import MonkeyPatch from requests_mock_flask import add_flask_app_to_mock from vws import VWS @@ -160,7 +161,7 @@ class VuforiaBackend(Enum): DOCKER_IN_MEMORY = 'In Memory version of Docker application' -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: """ Add options to the pytest command line for skipping tests with particular backends. From 4b1e68c2075d6d9bb6fddbb3a245655595df4f75 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 28 Dec 2021 16:50:53 +0000 Subject: [PATCH 0038/2467] Fix sort issues --- tests/mock_vws/fixtures/vuforia_backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index f457034e0..50723e0e5 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -9,8 +9,8 @@ import pytest import requests import requests_mock -from _pytest.fixtures import SubRequest from _pytest.config.argparsing import Parser +from _pytest.fixtures import SubRequest from pytest import MonkeyPatch from requests_mock_flask import add_flask_app_to_mock from vws import VWS From eda1ce8e0943aa77753975d7dcbdc49f532dab13 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 29 Dec 2021 01:40:53 +0000 Subject: [PATCH 0039/2467] Test which passes on real but not mock --- tests/mock_vws/test_get_duplicates.py | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/mock_vws/test_get_duplicates.py b/tests/mock_vws/test_get_duplicates.py index a068a3f6b..8dd413d82 100644 --- a/tests/mock_vws/test_get_duplicates.py +++ b/tests/mock_vws/test_get_duplicates.py @@ -2,10 +2,12 @@ Tests for the mock of the get duplicates endpoint. """ +import copy import io import uuid import pytest +from PIL import Image from vws import VWS from vws.exceptions.vws_exceptions import ProjectInactive from vws.reports import TargetStatuses @@ -65,6 +67,50 @@ def test_duplicates( assert duplicates == [similar_target_id] + def test_duplicates_not_same( + self, + high_quality_image: io.BytesIO, + image_file_success_state_low_rating: io.BytesIO, + vws_client: VWS, + ) -> None: + """ + Target IDs of similar targets are returned. + + In the mock, "similar" means that the images are exactly the same. + """ + image_data = high_quality_image + similar_image_data = copy.copy(image_data) + similar_image_buffer = io.BytesIO() + pil_similar_image = Image.open(similar_image_data) + # Re-save means similar but not identical. + pil_similar_image.save(similar_image_buffer, format='JPEG') + assert similar_image_buffer.getvalue() != image_data.getvalue() + + original_target_id = vws_client.add_target( + name=uuid.uuid4().hex, + width=1, + image=image_data, + active_flag=True, + application_metadata=None, + ) + + similar_target_id = vws_client.add_target( + name=uuid.uuid4().hex, + width=1, + image=similar_image_buffer, + active_flag=True, + application_metadata=None, + ) + + vws_client.wait_for_target_processed(target_id=original_target_id) + vws_client.wait_for_target_processed(target_id=similar_target_id) + + duplicates = vws_client.get_duplicate_targets( + target_id=original_target_id, + ) + + assert duplicates == [similar_target_id] + def test_status( self, image_file_failed_state: io.BytesIO, From dbcd4e956b5e21348e56cacc37c49b5c6b0579f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Dec 2021 05:14:05 +0000 Subject: [PATCH 0040/2467] Bump requests-mock-flask in /requirements Bumps [requests-mock-flask](https://github.com/adamtheturtle/requests-mock-flask) from 2021.12.28 to 2021.12.28.1. - [Release notes](https://github.com/adamtheturtle/requests-mock-flask/releases) - [Changelog](https://github.com/adamtheturtle/requests-mock-flask/blob/master/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/requests-mock-flask/compare/2021.12.28...2021.12.28.1) --- updated-dependencies: - dependency-name: requests-mock-flask dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index c36667473..629d508b1 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -24,7 +24,7 @@ pyroma==3.2 # Packaging best practices checker pytest-cov==3.0.0 # Measure code coverage pytest-envfiles==0.1.0 # Use files for environment variables for tests pytest==6.2.5 # Test runners -requests-mock-flask==2021.12.28 +requests-mock-flask==2021.12.28.1 sphinx-autodoc-typehints==1.12.0 sphinx_paramlinks==0.5.2 sphinxcontrib-httpdomain==1.8.0 From bc1a20cbce516dcd69d4e7f98d8788ea4afa7a49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Dec 2021 05:14:07 +0000 Subject: [PATCH 0041/2467] Bump types-requests from 2.26.2 to 2.26.3 in /requirements Bumps [types-requests](https://github.com/python/typeshed) from 2.26.2 to 2.26.3. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index c36667473..081d12ddf 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -32,7 +32,7 @@ sphinxcontrib-spelling==7.3.0 types-Flask==1.1.6 types-freezegun==1.1.3 types-PyYAML==6.0.1 -types-requests==2.26.2 +types-requests==2.26.3 types-setuptools==57.4.4 vulture==2.3 vws-python==2021.3.28.2 From fb64ceb217afa53ec3d0e372d12e2011e0844e9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Dec 2021 12:30:09 +0000 Subject: [PATCH 0042/2467] Bump types-setuptools from 57.4.4 to 57.4.5 in /requirements Bumps [types-setuptools](https://github.com/python/typeshed) from 57.4.4 to 57.4.5. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-setuptools dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 081d12ddf..4bf2d8510 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -33,6 +33,6 @@ types-Flask==1.1.6 types-freezegun==1.1.3 types-PyYAML==6.0.1 types-requests==2.26.3 -types-setuptools==57.4.4 +types-setuptools==57.4.5 vulture==2.3 vws-python==2021.3.28.2 From 4e2625d67f521f2d76fb340710d522222e0e39b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Dec 2021 05:12:59 +0000 Subject: [PATCH 0043/2467] Bump sphinxcontrib-spelling from 7.3.0 to 7.3.2 in /requirements Bumps [sphinxcontrib-spelling](https://github.com/sphinx-contrib/spelling) from 7.3.0 to 7.3.2. - [Release notes](https://github.com/sphinx-contrib/spelling/releases) - [Commits](https://github.com/sphinx-contrib/spelling/compare/7.3.0...7.3.2) --- updated-dependencies: - dependency-name: sphinxcontrib-spelling dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 081d12ddf..7ed405e54 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -28,7 +28,7 @@ requests-mock-flask==2021.12.28 sphinx-autodoc-typehints==1.12.0 sphinx_paramlinks==0.5.2 sphinxcontrib-httpdomain==1.8.0 -sphinxcontrib-spelling==7.3.0 +sphinxcontrib-spelling==7.3.2 types-Flask==1.1.6 types-freezegun==1.1.3 types-PyYAML==6.0.1 From c2573a4779cc2973f9850dcf999eacbe832814c3 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 2 Jan 2022 12:23:36 +0000 Subject: [PATCH 0044/2467] Add show-locals to CI GitHub workflow in order to show more information about failing tests. --- .github/workflows/ci.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0152cfe5..9c5901d15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -137,7 +137,15 @@ jobs: - name: "Run tests" run: | - pytest -s -vvv --exitfirst --cov=src/ --cov=tests --cov-report=xml tests/mock_vws/${{ matrix.ci_pattern }} + pytest \ + -s \ + -vvv \ + --showlocals \ + --exitfirst \ + --cov=src/ \ + --cov=tests \ + --cov-report=xml \ + tests/mock_vws/${{ matrix.ci_pattern }} - name: "Show coverage file" run: | From 02933167f9cb715ef6bfdabd5dfc2688b0fe8166 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 2 Jan 2022 12:37:44 +0000 Subject: [PATCH 0045/2467] Create a variable for response text so that it is shown in CI --- tests/mock_vws/test_authorization_header.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/mock_vws/test_authorization_header.py b/tests/mock_vws/test_authorization_header.py index 27976ae87..f1c7586f1 100644 --- a/tests/mock_vws/test_authorization_header.py +++ b/tests/mock_vws/test_authorization_header.py @@ -210,7 +210,10 @@ def test_missing_signature( content_path_2 = Path(__file__).parent / content_filename_2 content_text = content_path.read_text() content_2_text = content_path_2.read_text() - assert response.text in (content_text, content_2_text) + # We make a new variable for response text so that it is printed + # with ``pytest --showlocals``. + response_text = response.text + assert response_text in (content_text, content_2_text) return assert_vws_failure( From 5e26ca6db99fefbc8cd06dee01d78d7a827ddf8e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 2 Jan 2022 12:55:06 +0000 Subject: [PATCH 0046/2467] Add another out of bounds error to check for --- .../jetty_error_array_out_of_bounds_3.html | 55 +++++++++++++++++++ tests/mock_vws/test_authorization_header.py | 9 ++- 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/mock_vws/jetty_error_array_out_of_bounds_3.html diff --git a/tests/mock_vws/jetty_error_array_out_of_bounds_3.html b/tests/mock_vws/jetty_error_array_out_of_bounds_3.html new file mode 100644 index 000000000..ac280f446 --- /dev/null +++ b/tests/mock_vws/jetty_error_array_out_of_bounds_3.html @@ -0,0 +1,55 @@ + + + +Error 500 java.lang.ArrayIndexOutOfBoundsException: 1 + +

HTTP ERROR 500 java.lang.ArrayIndexOutOfBoundsException: 1

+ + + + + + +
URI:/v1/query
STATUS:500
MESSAGE:java.lang.ArrayIndexOutOfBoundsException: 1
SERVLET:Resteasy
CAUSED BY:java.lang.ArrayIndexOutOfBoundsException: 1
+

Caused by:

java.lang.ArrayIndexOutOfBoundsException: 1
+	at com.kooaba.queryservice.auth.KWSAuthFilter.doFilter(KWSAuthFilter.java:81)
+	at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:201)
+	at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601)
+	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:548)
+	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
+	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:602)
+	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
+	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235)
+	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1624)
+	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233)
+	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1434)
+	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188)
+	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:501)
+	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1594)
+	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186)
+	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1349)
+	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
+	at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:191)
+	at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:146)
+	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
+	at org.eclipse.jetty.server.Server.handle(Server.java:516)
+	at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:388)
+	at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:633)
+	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:380)
+	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:277)
+	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
+	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
+	at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104)
+	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:338)
+	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:315)
+	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:173)
+	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:131)
+	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:386)
+	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883)
+	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034)
+	at java.lang.Thread.run(Thread.java:748)
+
+
Powered by Jetty:// 9.4.43.v20210629
+ + + diff --git a/tests/mock_vws/test_authorization_header.py b/tests/mock_vws/test_authorization_header.py index f1c7586f1..467a372af 100644 --- a/tests/mock_vws/test_authorization_header.py +++ b/tests/mock_vws/test_authorization_header.py @@ -206,14 +206,21 @@ def test_missing_signature( ) content_filename = 'jetty_error_array_out_of_bounds.html' content_filename_2 = 'jetty_error_array_out_of_bounds_2.html' + content_filename_3 = 'jetty_error_array_out_of_bounds_3.html' content_path = Path(__file__).parent / content_filename content_path_2 = Path(__file__).parent / content_filename_2 + content_path_3 = Path(__file__).parent / content_filename_3 content_text = content_path.read_text() content_2_text = content_path_2.read_text() + content_3_text = content_path_3.read_text() # We make a new variable for response text so that it is printed # with ``pytest --showlocals``. response_text = response.text - assert response_text in (content_text, content_2_text) + assert response_text in ( + content_text, + content_2_text, + content_3_text, + ) return assert_vws_failure( From 931cfde4b429628d17f0773db34ca0905765487c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jan 2022 05:15:41 +0000 Subject: [PATCH 0047/2467] Bump keyring from 23.4.0 to 23.5.0 in /requirements Bumps [keyring](https://github.com/jaraco/keyring) from 23.4.0 to 23.5.0. - [Release notes](https://github.com/jaraco/keyring/releases) - [Changelog](https://github.com/jaraco/keyring/blob/main/CHANGES.rst) - [Commits](https://github.com/jaraco/keyring/compare/v23.4.0...v23.5.0) --- updated-dependencies: - dependency-name: keyring dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 549c247db..689bad06f 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -14,7 +14,7 @@ flake8==4.0.1 # Lint freezegun==1.1.0 # Freeze time in tests furo==2021.11.23 isort==5.10.1 # Lint imports -keyring==23.4.0 +keyring==23.5.0 mypy==0.930 # Type checking pip_check_reqs==2.3.2 pydocstyle==6.1.1 # Lint docstrings From b3b7f73a58ce01e008029185ce67b25c690c88f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jan 2022 05:15:47 +0000 Subject: [PATCH 0048/2467] Bump sphinx-autodoc-typehints from 1.12.0 to 1.13.0 in /requirements Bumps [sphinx-autodoc-typehints](https://github.com/tox-dev/sphinx-autodoc-typehints) from 1.12.0 to 1.13.0. - [Release notes](https://github.com/tox-dev/sphinx-autodoc-typehints/releases) - [Changelog](https://github.com/tox-dev/sphinx-autodoc-typehints/blob/main/CHANGELOG.md) - [Commits](https://github.com/tox-dev/sphinx-autodoc-typehints/compare/1.12.0...1.13.0) --- updated-dependencies: - dependency-name: sphinx-autodoc-typehints dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 549c247db..30c0a203f 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -25,7 +25,7 @@ pytest-cov==3.0.0 # Measure code coverage pytest-envfiles==0.1.0 # Use files for environment variables for tests pytest==6.2.5 # Test runners requests-mock-flask==2021.12.28.1 -sphinx-autodoc-typehints==1.12.0 +sphinx-autodoc-typehints==1.13.0 sphinx_paramlinks==0.5.2 sphinxcontrib-httpdomain==1.8.0 sphinxcontrib-spelling==7.3.2 From 06330ad2ae325f1d2bfe0f7a03d2d185fe0fdf5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jan 2022 05:15:50 +0000 Subject: [PATCH 0049/2467] Bump types-freezegun from 1.1.3 to 1.1.4 in /requirements Bumps [types-freezegun](https://github.com/python/typeshed) from 1.1.3 to 1.1.4. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-freezegun dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 549c247db..244a622c0 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -30,7 +30,7 @@ sphinx_paramlinks==0.5.2 sphinxcontrib-httpdomain==1.8.0 sphinxcontrib-spelling==7.3.2 types-Flask==1.1.6 -types-freezegun==1.1.3 +types-freezegun==1.1.4 types-PyYAML==6.0.1 types-requests==2.26.3 types-setuptools==57.4.5 From 9304254d1a4dddbea8671375c79f8ba40d81f6f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jan 2022 19:28:28 +0000 Subject: [PATCH 0050/2467] Bump furo from 2021.11.23 to 2022.1.2 in /requirements Bumps [furo](https://github.com/pradyunsg/furo) from 2021.11.23 to 2022.1.2. - [Release notes](https://github.com/pradyunsg/furo/releases) - [Changelog](https://github.com/pradyunsg/furo/blob/main/docs/changelog.md) - [Commits](https://github.com/pradyunsg/furo/compare/2021.11.23...2022.01.02) --- updated-dependencies: - dependency-name: furo dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 689bad06f..94d78f6bb 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -12,7 +12,7 @@ flake8-commas==2.1.0 # Require silicon valley commas flake8-quotes==3.3.1 # Require single quotes flake8==4.0.1 # Lint freezegun==1.1.0 # Freeze time in tests -furo==2021.11.23 +furo==2022.1.2 isort==5.10.1 # Lint imports keyring==23.5.0 mypy==0.930 # Type checking From e6ad926e68533f7fbb8d5ef2780fdbfc6cbfdea1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 3 Jan 2022 23:30:05 +0000 Subject: [PATCH 0051/2467] Bump VWS-Test-Fixtures requirement to account for Pillow >= 9 --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index c016fc138..f94bc945e 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -1,7 +1,7 @@ PyYAML==6.0 Sphinx-Substitution-Extensions==2020.9.30.0 Sphinx==4.3.2 -VWS-Test-Fixtures==2021.11.5.1 +VWS-Test-Fixtures==2022.1.3 autoflake==1.4 black==21.12b0 check-manifest==0.47 From 86b5f0be884c0306e7cab6d9415fc7b44082777f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Jan 2022 05:14:58 +0000 Subject: [PATCH 0052/2467] Bump sphinx-autodoc-typehints from 1.13.0 to 1.14.0 in /requirements Bumps [sphinx-autodoc-typehints](https://github.com/tox-dev/sphinx-autodoc-typehints) from 1.13.0 to 1.14.0. - [Release notes](https://github.com/tox-dev/sphinx-autodoc-typehints/releases) - [Changelog](https://github.com/tox-dev/sphinx-autodoc-typehints/blob/main/CHANGELOG.md) - [Commits](https://github.com/tox-dev/sphinx-autodoc-typehints/compare/1.13.0...1.14.0) --- updated-dependencies: - dependency-name: sphinx-autodoc-typehints dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index c016fc138..a23fa23af 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -25,7 +25,7 @@ pytest-cov==3.0.0 # Measure code coverage pytest-envfiles==0.1.0 # Use files for environment variables for tests pytest==6.2.5 # Test runners requests-mock-flask==2021.12.28.1 -sphinx-autodoc-typehints==1.13.0 +sphinx-autodoc-typehints==1.14.0 sphinx_paramlinks==0.5.2 sphinxcontrib-httpdomain==1.8.0 sphinxcontrib-spelling==7.3.2 From a4e410699ec4225789ce8ff281d3e4a062bce497 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 05:15:36 +0000 Subject: [PATCH 0053/2467] Bump types-pyyaml from 6.0.1 to 6.0.3 in /requirements Bumps [types-pyyaml](https://github.com/python/typeshed) from 6.0.1 to 6.0.3. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-pyyaml dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index a23fa23af..02ac9aa1d 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -31,7 +31,7 @@ sphinxcontrib-httpdomain==1.8.0 sphinxcontrib-spelling==7.3.2 types-Flask==1.1.6 types-freezegun==1.1.4 -types-PyYAML==6.0.1 +types-PyYAML==6.0.3 types-requests==2.26.3 types-setuptools==57.4.5 vulture==2.3 From 309781d9dc81be8b12df76dbfb5b58ccf33a85ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 05:15:39 +0000 Subject: [PATCH 0054/2467] Bump mypy from 0.930 to 0.931 in /requirements Bumps [mypy](https://github.com/python/mypy) from 0.930 to 0.931. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.930...v0.931) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index a23fa23af..85ed34189 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -15,7 +15,7 @@ freezegun==1.1.0 # Freeze time in tests furo==2022.1.2 isort==5.10.1 # Lint imports keyring==23.5.0 -mypy==0.930 # Type checking +mypy==0.931 # Type checking pip_check_reqs==2.3.2 pydocstyle==6.1.1 # Lint docstrings pyenchant==3.2.2 # Bindings for a spellchecking sytem From c7b87f4d258abc3520949f5d1e6933ed1a3961b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jan 2022 05:16:30 +0000 Subject: [PATCH 0055/2467] Bump sphinx-autodoc-typehints from 1.14.0 to 1.15.2 in /requirements Bumps [sphinx-autodoc-typehints](https://github.com/tox-dev/sphinx-autodoc-typehints) from 1.14.0 to 1.15.2. - [Release notes](https://github.com/tox-dev/sphinx-autodoc-typehints/releases) - [Changelog](https://github.com/tox-dev/sphinx-autodoc-typehints/blob/main/CHANGELOG.md) - [Commits](https://github.com/tox-dev/sphinx-autodoc-typehints/compare/1.14.0...1.15.2) --- updated-dependencies: - dependency-name: sphinx-autodoc-typehints dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index a23fa23af..689ca9641 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -25,7 +25,7 @@ pytest-cov==3.0.0 # Measure code coverage pytest-envfiles==0.1.0 # Use files for environment variables for tests pytest==6.2.5 # Test runners requests-mock-flask==2021.12.28.1 -sphinx-autodoc-typehints==1.14.0 +sphinx-autodoc-typehints==1.15.2 sphinx_paramlinks==0.5.2 sphinxcontrib-httpdomain==1.8.0 sphinxcontrib-spelling==7.3.2 From c2e82c90b26ef4e0bffcb9993957514e2e4cc13d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jan 2022 05:14:19 +0000 Subject: [PATCH 0056/2467] Bump setuptools-scm from 6.3.2 to 6.4.1 in /requirements Bumps [setuptools-scm](https://github.com/pypa/setuptools_scm) from 6.3.2 to 6.4.1. - [Release notes](https://github.com/pypa/setuptools_scm/releases) - [Changelog](https://github.com/pypa/setuptools_scm/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pypa/setuptools_scm/compare/v6.3.2...v6.4.1) --- updated-dependencies: - dependency-name: setuptools-scm dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/setup-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/setup-requirements.txt b/requirements/setup-requirements.txt index 97ab044e1..037dc0da5 100644 --- a/requirements/setup-requirements.txt +++ b/requirements/setup-requirements.txt @@ -1,2 +1,2 @@ -setuptools_scm==6.3.2 +setuptools_scm==6.4.1 setuptools-scm-git-archive==1.1 From 12af1a2e5ab4051cf4baf94c70ac0a4d8f556603 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jan 2022 05:14:25 +0000 Subject: [PATCH 0057/2467] Bump sphinx-autodoc-typehints from 1.15.2 to 1.15.3 in /requirements Bumps [sphinx-autodoc-typehints](https://github.com/tox-dev/sphinx-autodoc-typehints) from 1.15.2 to 1.15.3. - [Release notes](https://github.com/tox-dev/sphinx-autodoc-typehints/releases) - [Changelog](https://github.com/tox-dev/sphinx-autodoc-typehints/blob/main/CHANGELOG.md) - [Commits](https://github.com/tox-dev/sphinx-autodoc-typehints/compare/1.15.2...1.15.3) --- updated-dependencies: - dependency-name: sphinx-autodoc-typehints dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 689ca9641..d0e229587 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -25,7 +25,7 @@ pytest-cov==3.0.0 # Measure code coverage pytest-envfiles==0.1.0 # Use files for environment variables for tests pytest==6.2.5 # Test runners requests-mock-flask==2021.12.28.1 -sphinx-autodoc-typehints==1.15.2 +sphinx-autodoc-typehints==1.15.3 sphinx_paramlinks==0.5.2 sphinxcontrib-httpdomain==1.8.0 sphinxcontrib-spelling==7.3.2 From d36d7703ff9386c9456bc9046aa5d2d60f88727e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 20 Jan 2022 01:54:12 +0000 Subject: [PATCH 0058/2467] Fix add_target oops test --- src/mock_vws/resources/oops_error_occurred_response.html | 5 +++-- tests/mock_vws/test_add_target.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/mock_vws/resources/oops_error_occurred_response.html b/src/mock_vws/resources/oops_error_occurred_response.html index e72b8fc60..d844502ae 100644 --- a/src/mock_vws/resources/oops_error_occurred_response.html +++ b/src/mock_vws/resources/oops_error_occurred_response.html @@ -2,7 +2,8 @@ Error - - - -

Oops, an error occurred

- -

- This exception has been logged with id 7mdbgjp16. -

- - - From de4fe54ea8a9b277d0ecb2b5e5c21784fefa74fe Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 10 Mar 2025 09:37:38 +0000 Subject: [PATCH 1810/2467] Fix mypy --- tests/mock_vws/test_add_target.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/mock_vws/test_add_target.py b/tests/mock_vws/test_add_target.py index 6e6d57e57..cd3ce28d8 100644 --- a/tests/mock_vws/test_add_target.py +++ b/tests/mock_vws/test_add_target.py @@ -350,6 +350,8 @@ def test_name_invalid( "active_flag": True, } + exc: pytest.ExceptionInfo[FailError | ServerError] + if status_code == HTTPStatus.INTERNAL_SERVER_ERROR: with pytest.raises(expected_exception=ServerError) as exc: _add_target_to_vws(vws_client=vws_client, data=data) From ede12f97a73da04d0ad29ddbadf6464884043622 Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Mon, 10 Mar 2025 09:54:03 +0000 Subject: [PATCH 1811/2467] Bump CHANGELOG --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 05f8731ce..aa9d124f8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,9 @@ Changelog Next ---- +2025.03.10 +---------- + 2025.02.21 ---------- From c5d716be96f9fb4edcf44f51cde21eac4e46eb36 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 10 Mar 2025 10:44:17 +0000 Subject: [PATCH 1812/2467] Remove resources directory which is no longer needed --- .pre-commit-config.yaml | 1 - MANIFEST.in | 1 - .../deleted_target_matched_response.html | 105 ------------------ 3 files changed, 107 deletions(-) delete mode 100644 src/mock_vws/resources/deleted_target_matched_response.html diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b905f6a2c..589661b1d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -62,7 +62,6 @@ repos: - id: file-contents-sorter files: spelling_private_dict\.txt$ - id: trailing-whitespace - exclude: ^src/mock_vws/resources/ - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: diff --git a/MANIFEST.in b/MANIFEST.in index 5d89b374a..e69de29bb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +0,0 @@ -recursive-include src/mock_vws/resources * diff --git a/src/mock_vws/resources/deleted_target_matched_response.html b/src/mock_vws/resources/deleted_target_matched_response.html deleted file mode 100644 index 71a6a5cae..000000000 --- a/src/mock_vws/resources/deleted_target_matched_response.html +++ /dev/null @@ -1,105 +0,0 @@ - - - -Error 500 org.jboss.resteasy.spi.UnhandledException: com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input - at [Source: (byte[])""; line: 1, column: 0] - -

HTTP ERROR 500 org.jboss.resteasy.spi.UnhandledException: com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input - at [Source: (byte[])""; line: 1, column: 0]

- - - - - - - -
URI:/v1/query
STATUS:500
MESSAGE:org.jboss.resteasy.spi.UnhandledException: com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input - at [Source: (byte[])""; line: 1, column: 0]
SERVLET:Resteasy
CAUSED BY:org.jboss.resteasy.spi.UnhandledException: com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input - at [Source: (byte[])""; line: 1, column: 0]
CAUSED BY:com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input - at [Source: (byte[])""; line: 1, column: 0]
-

Caused by:

org.jboss.resteasy.spi.UnhandledException: com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
- at [Source: (byte[])""; line: 1, column: 0]
-	at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:106)
-	at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:372)
-	at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:216)
-	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:515)
-	at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:259)
-	at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:160)
-	at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
-	at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:163)
-	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:245)
-	at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:249)
-	at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:60)
-	at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:55)
-	at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
-	at org.eclipse.jetty.servlet.ServletHolder$NotAsyncServlet.service(ServletHolder.java:1411)
-	at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:763)
-	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1651)
-	at com.kooaba.queryservice.services.BrokenMultipartBoundaryWorkaround.doFilter(BrokenMultipartBoundaryWorkaround.java:90)
-	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1638)
-	at com.kooaba.queryservice.auth.KWSAuthFilter.doFilter(KWSAuthFilter.java:171)
-	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1630)
-	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:567)
-	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
-	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:602)
-	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
-	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235)
-	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1610)
-	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233)
-	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1377)
-	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188)
-	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:507)
-	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1580)
-	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186)
-	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1292)
-	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
-	at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:191)
-	at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:146)
-	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
-	at org.eclipse.jetty.server.Server.handle(Server.java:501)
-	at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:383)
-	at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:556)
-	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:375)
-	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:273)
-	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
-	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
-	at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104)
-	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:336)
-	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:313)
-	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:171)
-	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:129)
-	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:375)
-	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:806)
-	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:938)
-	at java.lang.Thread.run(Thread.java:748)
-Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
- at [Source: (byte[])""; line: 1, column: 0]
-	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
-	at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:4666)
-	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4511)
-	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3544)
-	at com.kooaba.queryservice.domain.WebResult.setTargetData(WebResult.java:44)
-	at com.kooaba.queryservice.domain.WebQueryResultProcessor.formatResult(WebQueryResultProcessor.java:82)
-	at com.kooaba.queryservice.services.QueryResourceVuforia.queryCommon(QueryResourceVuforia.java:231)
-	at com.kooaba.queryservice.services.QueryResourceVuforia.limitedConcurrencyQuery(QueryResourceVuforia.java:78)
-	at com.kooaba.queryservice.services.QueryResourceCloudRecoWebAPI.query(QueryResourceCloudRecoWebAPI.java:55)
-	at sun.reflect.GeneratedMethodAccessor89.invoke(Unknown Source)
-	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
-	at java.lang.reflect.Method.invoke(Method.java:498)
-	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:167)
-	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
-	at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:638)
-	at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:504)
-	at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:454)
-	at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
-	at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:456)
-	at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:417)
-	at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:391)
-	at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:68)
-	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:488)
-	... 49 more
-
-
Powered by Jetty:// 9.4.43.v20210629
- - - From 46ed5086091816ecce3a2f4f51b323f6354a6887 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 10 Mar 2025 13:08:50 +0000 Subject: [PATCH 1813/2467] Expect BadImageError on corrupted image --- tests/mock_vws/test_add_target.py | 21 ++++++++++++++------- tests/mock_vws/test_get_target.py | 6 ------ tests/mock_vws/test_update_target.py | 15 +++++++++++---- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/tests/mock_vws/test_add_target.py b/tests/mock_vws/test_add_target.py index cd3ce28d8..2e2cb882c 100644 --- a/tests/mock_vws/test_add_target.py +++ b/tests/mock_vws/test_add_target.py @@ -478,14 +478,21 @@ def test_corrupted( vws_client: VWS, ) -> None: """ - No error is returned when the given image is corrupted. + An error is returned when the given image is corrupted. """ - vws_client.add_target( - name="example_name", - width=1, - image=corrupted_image_file, - application_metadata=None, - active_flag=True, + with pytest.raises(expected_exception=BadImageError) as exc: + vws_client.add_target( + name="example_name", + width=1, + image=corrupted_image_file, + application_metadata=None, + active_flag=True, + ) + + assert_vws_failure( + response=exc.value.response, + status_code=HTTPStatus.UNPROCESSABLE_ENTITY, + result_code=ResultCodes.BAD_IMAGE, ) @staticmethod diff --git a/tests/mock_vws/test_get_target.py b/tests/mock_vws/test_get_target.py index 99fd6a1d4..435982bc5 100644 --- a/tests/mock_vws/test_get_target.py +++ b/tests/mock_vws/test_get_target.py @@ -146,7 +146,6 @@ def test_target_quality( vws_client: VWS, high_quality_image: io.BytesIO, image_file_success_state_low_rating: io.BytesIO, - corrupted_image_file: io.BytesIO, ) -> None: """ The target tracking rating is as expected. @@ -159,14 +158,9 @@ def test_target_quality( vws_client=vws_client, image_file=image_file_success_state_low_rating, ) - corrupted_image_file_tracking_rating = _get_target_tracking_rating( - vws_client=vws_client, - image_file=corrupted_image_file, - ) assert ( high_quality_image_tracking_rating > low_quality_image_tracking_rating - >= corrupted_image_file_tracking_rating ) diff --git a/tests/mock_vws/test_update_target.py b/tests/mock_vws/test_update_target.py index 4cf4911c7..776cb9ee2 100644 --- a/tests/mock_vws/test_update_target.py +++ b/tests/mock_vws/test_update_target.py @@ -670,12 +670,19 @@ def test_corrupted( target_id: str, ) -> None: """ - No error is returned when the given image is corrupted. + An error is returned when the given image is corrupted. """ vws_client.wait_for_target_processed(target_id=target_id) - vws_client.update_target( - target_id=target_id, - image=corrupted_image_file, + with pytest.raises(expected_exception=BadImageError) as exc: + vws_client.update_target( + target_id=target_id, + image=corrupted_image_file, + ) + + assert_vws_failure( + response=exc.value.response, + status_code=HTTPStatus.UNPROCESSABLE_ENTITY, + result_code=ResultCodes.BAD_IMAGE, ) @staticmethod From 47134ff3e642b7fde061b8bb85cf1149cd93944e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 10 Mar 2025 18:27:59 +0000 Subject: [PATCH 1814/2467] Add validator for image integrity --- src/mock_vws/_services_validators/__init__.py | 2 ++ .../_services_validators/image_validators.py | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/mock_vws/_services_validators/__init__.py b/src/mock_vws/_services_validators/__init__.py index ac4c0a331..d8bbf0c78 100644 --- a/src/mock_vws/_services_validators/__init__.py +++ b/src/mock_vws/_services_validators/__init__.py @@ -29,6 +29,7 @@ validate_image_data_type, validate_image_encoding, validate_image_format, + validate_image_integrity, validate_image_is_image, validate_image_size, ) @@ -122,6 +123,7 @@ def run_services_validators( validate_image_format(request_body=request_body) validate_image_color_space(request_body=request_body) validate_image_size(request_body=request_body) + validate_image_integrity(request_body=request_body) validate_name_type(request_body=request_body) validate_name_length(request_body=request_body) diff --git a/src/mock_vws/_services_validators/image_validators.py b/src/mock_vws/_services_validators/image_validators.py index 96b786f28..aeddea76b 100644 --- a/src/mock_vws/_services_validators/image_validators.py +++ b/src/mock_vws/_services_validators/image_validators.py @@ -21,6 +21,33 @@ _LOGGER = logging.getLogger(name=__name__) +@beartype +def validate_image_integrity(*, request_body: bytes) -> None: + """Validate the integrity of the image given to a VWS endpoint. + + Args: + request_body: The body of the request. + + Raises: + BadImageError: The image is given and is not a valid image file. + """ + if not request_body: + return + + request_text = request_body.decode() + image = json.loads(s=request_text).get("image") + decoded = decode_base64(encoded_data=image) + + image_file = io.BytesIO(initial_bytes=decoded) + pil_image = Image.open(fp=image_file) + + try: + pil_image.verify() + except SyntaxError as exc: + _LOGGER.warning(msg="The image is not a valid image file.") + raise BadImageError from exc + + @beartype def validate_image_format(*, request_body: bytes) -> None: """Validate the format of the image given to a VWS endpoint. From 7db3d828134fcf0f8af139ea5ccfb881afb92791 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 10 Mar 2025 18:36:19 +0000 Subject: [PATCH 1815/2467] Handle image not in data --- src/mock_vws/_services_validators/image_validators.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mock_vws/_services_validators/image_validators.py b/src/mock_vws/_services_validators/image_validators.py index aeddea76b..2dd703391 100644 --- a/src/mock_vws/_services_validators/image_validators.py +++ b/src/mock_vws/_services_validators/image_validators.py @@ -36,6 +36,9 @@ def validate_image_integrity(*, request_body: bytes) -> None: request_text = request_body.decode() image = json.loads(s=request_text).get("image") + if image is None: + return + decoded = decode_base64(encoded_data=image) image_file = io.BytesIO(initial_bytes=decoded) From 46e4317983c10fed00b60b95b6a254e109b845bb Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 10 Mar 2025 18:50:12 +0000 Subject: [PATCH 1816/2467] Update more tests to handle new response for corrupted images --- tests/mock_vws/test_flask_app_usage.py | 30 ++++++++++++-------------- tests/mock_vws/test_target_raters.py | 7 +++--- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index 551dc9907..1ce6a6d58 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -455,7 +455,7 @@ class TestTargetRaters: @staticmethod def test_default( - corrupted_image_file: io.BytesIO, + image_file_success_state_low_rating: io.BytesIO, high_quality_image: io.BytesIO, ) -> None: """ @@ -470,10 +470,10 @@ def test_default( server_secret_key=database.server_secret_key, ) - corrupted_image_target_id = vws_client.add_target( + low_rating_image_target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, - image=corrupted_image_file, + image=image_file_success_state_low_rating, application_metadata=None, active_flag=True, ) @@ -487,27 +487,26 @@ def test_default( ) for target_id in ( - corrupted_image_target_id, + low_rating_image_target_id, high_quality_image_target_id, ): vws_client.wait_for_target_processed(target_id=target_id) - corrupted_image_rating = vws_client.get_target_record( - target_id=corrupted_image_target_id, + low_rated_image_rating = vws_client.get_target_record( + target_id=low_rating_image_target_id, ).target_record.tracking_rating high_quality_image_rating = vws_client.get_target_record( target_id=high_quality_image_target_id, ).target_record.tracking_rating - # In the real Vuforia, this image may rate as -2. - assert corrupted_image_rating <= 0 + assert low_rated_image_rating <= 0 assert high_quality_image_rating > 1 @staticmethod def test_brisque( monkeypatch: pytest.MonkeyPatch, - corrupted_image_file: io.BytesIO, + image_file_success_state_low_rating: io.BytesIO, high_quality_image: io.BytesIO, ) -> None: """ @@ -524,10 +523,10 @@ def test_brisque( server_secret_key=database.server_secret_key, ) - corrupted_image_target_id = vws_client.add_target( + low_rating_image_target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, - image=corrupted_image_file, + image=image_file_success_state_low_rating, application_metadata=None, active_flag=True, ) @@ -541,21 +540,20 @@ def test_brisque( ) for target_id in ( - corrupted_image_target_id, + low_rating_image_target_id, high_quality_image_target_id, ): vws_client.wait_for_target_processed(target_id=target_id) - corrupted_image_rating = vws_client.get_target_record( - target_id=corrupted_image_target_id, + low_rated_image_rating = vws_client.get_target_record( + target_id=low_rating_image_target_id, ).target_record.tracking_rating high_quality_image_rating = vws_client.get_target_record( target_id=high_quality_image_target_id, ).target_record.tracking_rating - # In the real Vuforia, this image may rate as -2. - assert corrupted_image_rating <= 0 + assert low_rated_image_rating <= 0 assert high_quality_image_rating > 1 @staticmethod diff --git a/tests/mock_vws/test_target_raters.py b/tests/mock_vws/test_target_raters.py index dda0859df..997833f41 100644 --- a/tests/mock_vws/test_target_raters.py +++ b/tests/mock_vws/test_target_raters.py @@ -50,14 +50,15 @@ class TestBrisqueTargetTrackingRater: """ @staticmethod - def test_low_quality_image(corrupted_image_file: io.BytesIO) -> None: + def test_low_quality_image( + image_file_success_state_low_rating: io.BytesIO, + ) -> None: """ Test that a low quality image returns a low rating. """ rater = BrisqueTargetTrackingRater() - image_content = corrupted_image_file.getvalue() + image_content = image_file_success_state_low_rating.getvalue() rating = rater(image_content=image_content) - # In the real Vuforia, this image may rate as -2. assert rating == 0 @staticmethod From b6dce5a47d3dcb733dce46f3d93a1feb9d7b4d2d Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Mon, 10 Mar 2025 19:09:14 +0000 Subject: [PATCH 1817/2467] Bump CHANGELOG --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index aa9d124f8..a5e8f60f5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,9 @@ Changelog Next ---- +2025.03.10.1 +------------ + 2025.03.10 ---------- From 6b9385c99e559c231a533fbf5e852a23cae8924b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Mar 2025 05:26:39 +0000 Subject: [PATCH 1818/2467] Bump vws-python from 2024.9.21 to 2025.3.10.1 Bumps [vws-python](https://github.com/VWS-Python/vws-python) from 2024.9.21 to 2025.3.10.1. - [Release notes](https://github.com/VWS-Python/vws-python/releases) - [Changelog](https://github.com/VWS-Python/vws-python/blob/main/CHANGELOG.rst) - [Commits](https://github.com/VWS-Python/vws-python/compare/2024.09.21...2025.03.10.1) --- updated-dependencies: - dependency-name: vws-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 998fa1b89..dc82139bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,7 +103,7 @@ optional-dependencies.dev = [ "types-requests==2.32.0.20250306", "urllib3==2.3.0", "vulture==2.14", - "vws-python==2024.9.21", + "vws-python==2025.3.10.1", "vws-test-fixtures==2023.3.5", "vws-web-tools==2024.10.6.1", "yamlfix==1.17.0", From 9956ef4c36689855a003b46d1d4d41a7e8bded47 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 11 Mar 2025 10:49:02 +0000 Subject: [PATCH 1819/2467] Account for moved `Response` in `vws` --- tests/mock_vws/test_add_target.py | 2 +- tests/mock_vws/test_query.py | 2 +- tests/mock_vws/test_update_target.py | 2 +- tests/mock_vws/utils/__init__.py | 2 +- tests/mock_vws/utils/assertions.py | 2 +- tests/mock_vws/utils/too_many_requests.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/mock_vws/test_add_target.py b/tests/mock_vws/test_add_target.py index 2e2cb882c..3c61b9332 100644 --- a/tests/mock_vws/test_add_target.py +++ b/tests/mock_vws/test_add_target.py @@ -24,7 +24,7 @@ ProjectInactiveError, TargetNameExistError, ) -from vws.types import Response +from vws.response import Response from mock_vws._constants import ResultCodes from tests.mock_vws.utils import make_image_file diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index 82bb18c88..7da395b9b 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -34,7 +34,7 @@ ) from vws.exceptions.custom_exceptions import RequestEntityTooLargeError from vws.reports import TargetStatuses -from vws.types import Response +from vws.response import Response from vws_auth_tools import authorization_header, rfc_1123_date from mock_vws.database import VuforiaDatabase diff --git a/tests/mock_vws/test_update_target.py b/tests/mock_vws/test_update_target.py index 776cb9ee2..3b6a0db1e 100644 --- a/tests/mock_vws/test_update_target.py +++ b/tests/mock_vws/test_update_target.py @@ -23,7 +23,7 @@ TargetStatusNotSuccessError, ) from vws.reports import TargetStatuses -from vws.types import Response +from vws.response import Response from mock_vws._constants import ResultCodes from tests.mock_vws.utils import make_image_file diff --git a/tests/mock_vws/utils/__init__.py b/tests/mock_vws/utils/__init__.py index b3b92db61..241c13ec4 100644 --- a/tests/mock_vws/utils/__init__.py +++ b/tests/mock_vws/utils/__init__.py @@ -12,7 +12,7 @@ import requests from PIL import Image from requests.structures import CaseInsensitiveDict -from vws.types import Response +from vws.response import Response from mock_vws._constants import ResultCodes diff --git a/tests/mock_vws/utils/assertions.py b/tests/mock_vws/utils/assertions.py index 936e207bb..8ea4ea12b 100644 --- a/tests/mock_vws/utils/assertions.py +++ b/tests/mock_vws/utils/assertions.py @@ -11,7 +11,7 @@ from zoneinfo import ZoneInfo from beartype import beartype -from vws.types import Response +from vws.response import Response from mock_vws._constants import ResultCodes diff --git a/tests/mock_vws/utils/too_many_requests.py b/tests/mock_vws/utils/too_many_requests.py index 2cb2b6863..aae3742af 100644 --- a/tests/mock_vws/utils/too_many_requests.py +++ b/tests/mock_vws/utils/too_many_requests.py @@ -7,7 +7,7 @@ from beartype import beartype from vws.exceptions.custom_exceptions import ServerError from vws.exceptions.vws_exceptions import TooManyRequestsError -from vws.types import Response +from vws.response import Response @beartype From 333b1865d45ad40e67e2f4916e1030eccafd3768 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Mar 2025 05:11:26 +0000 Subject: [PATCH 1820/2467] Bump shfmt-py from 3.7.0.1 to 3.11.0.2 Bumps [shfmt-py](https://github.com/maxwinterstein/shfmt-py) from 3.7.0.1 to 3.11.0.2. - [Release notes](https://github.com/maxwinterstein/shfmt-py/releases) - [Commits](https://github.com/maxwinterstein/shfmt-py/compare/v3.7.0.1...v3.11.0.2) --- updated-dependencies: - dependency-name: shfmt-py dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 998fa1b89..7c1af0918 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,7 @@ optional-dependencies.dev = [ # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. "shellcheck-py==0.10.0.1", - "shfmt-py==3.7.0.1", + "shfmt-py==3.11.0.2", "sphinx==8.2.3", "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.0", From e91dde7fc358a33e21e52f9eb04896fd50d74f67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Mar 2025 05:22:44 +0000 Subject: [PATCH 1821/2467] Bump ruff from 0.9.10 to 0.10.0 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.10 to 0.10.0. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.9.10...0.10.0) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7c1af0918..fa0dd2283 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ "python-dotenv==1.0.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.9.10", + "ruff==0.10.0", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 6b949b0206a7128e0325633315d05c2c999bfd66 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 14 Mar 2025 16:55:41 +0000 Subject: [PATCH 1822/2467] Remove commented out ruff settings --- pyproject.toml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bec3af26f..48445b45a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -164,11 +164,6 @@ lint.ignore = [ # Ignore "too-many-*" errors as they seem to get in the way more than # helping. "PLR0913", - # Allow 'assert' in tests as it is the standard for pytest. - # Also, allow 'assert' in other code as it is the standard for Python type hint - # narrowing - see - # https://mypy.readthedocs.io/en/stable/type_narrowing.html#type-narrowing-expressions. - # "S101", ] lint.per-file-ignores."ci/test_custom_linters.py" = [ From 0541c0be87a520214cbc993ade7ccb3946f018de Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 14 Mar 2025 18:06:48 +0000 Subject: [PATCH 1823/2467] Have ruff infer Python version from pyproject.toml --- pyproject.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 48445b45a..86e38ff76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -145,8 +145,6 @@ fallback_version = "0.0.0" version_scheme = "post-release" [tool.ruff] -target-version = "py311" - line-length = 79 lint.select = [ "ALL", From 1c109c79caad71ec4e75ff153f66d9d90cd8f202 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 05:25:15 +0000 Subject: [PATCH 1824/2467] Bump ruff from 0.10.0 to 0.11.0 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.10.0 to 0.11.0. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.10.0...0.11.0) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 86e38ff76..7e04309d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ "python-dotenv==1.0.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.10.0", + "ruff==0.11.0", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 994bd20531533dd3802fb65875c5a6bc7c40272f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Mar 2025 05:52:27 +0000 Subject: [PATCH 1825/2467] Bump doccmd from 2025.3.6 to 2025.3.18 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.3.6 to 2025.3.18. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.03.06...2025.03.18) --- updated-dependencies: - dependency-name: doccmd dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7e04309d7..5823890bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ optional-dependencies.dev = [ "deptry==0.23.0", "dirty-equals==0.9.0", "doc8==1.1.1", - "doccmd==2025.3.6", + "doccmd==2025.3.18", "docformatter==1.7.5", "docker==7.1.0", "enum-tools[sphinx]==0.12.0", From 1819e81fa2955555d70cd87c89c8306e64092ab9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Mar 2025 05:52:39 +0000 Subject: [PATCH 1826/2467] Bump pre-commit from 4.1.0 to 4.2.0 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.1.0 to 4.2.0. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v4.1.0...v4.2.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7e04309d7..825e264df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.15.0", "mypy-strict-kwargs==2024.12.25", - "pre-commit==4.1.0", + "pre-commit==4.2.0", "pydocstyle==6.3", "pyenchant==3.3.0rc1", "pylint==3.3.4", From 02c261cf6b60ba1ad6655306ed6677db52b3b46d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Mar 2025 05:21:21 +0000 Subject: [PATCH 1827/2467] Bump pyright from 1.1.396 to 1.1.397 Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.396 to 1.1.397. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.396...v1.1.397) --- updated-dependencies: - dependency-name: pyright dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dc07e2033..74b593ed0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pylint==3.3.4", "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.5.1", - "pyright==1.1.396", + "pyright==1.1.397", "pyroma==4.2", "pytest==8.3.5", "pytest-cov==6.0.0", From 582ede6a71b10ae98a699b9eb0e03e2c4b34055c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 05:14:05 +0000 Subject: [PATCH 1828/2467] Bump pylint from 3.3.4 to 3.3.6 Bumps [pylint](https://github.com/pylint-dev/pylint) from 3.3.4 to 3.3.6. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v3.3.4...v3.3.6) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 74b593ed0..dc781244c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ optional-dependencies.dev = [ "pre-commit==4.2.0", "pydocstyle==6.3", "pyenchant==3.3.0rc1", - "pylint==3.3.4", + "pylint==3.3.6", "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.5.1", "pyright==1.1.397", From 942668e9881e2dbcb10460ec0d23fa6785ecb8d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 05:14:44 +0000 Subject: [PATCH 1829/2467] Bump ruff from 0.11.0 to 0.11.1 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.0 to 0.11.1. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.0...0.11.1) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 74b593ed0..ba8cf6d1b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ "python-dotenv==1.0.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.11.0", + "ruff==0.11.1", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From d1f38bbc20581cc8274f62ca2cc5fd159d77ff55 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 21 Mar 2025 07:07:39 +0000 Subject: [PATCH 1830/2467] Add completion job so we can have dependabot auto-merge and required builds --- .github/workflows/ci.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7909599e..05de00b49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -197,3 +197,15 @@ jobs: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} + + completion: + needs: build + runs-on: ubuntu-latest + if: always() # Run even if one matrix job fails + steps: + - name: Check matrix job status + run: |- + if ! ${{ needs.build.result == 'success' }}; then + echo "One or more matrix jobs failed" + exit 1 + fi From 7c2dfedbc9364467916f5919a89ee1ad7ce9b73f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 21 Mar 2025 08:17:40 +0000 Subject: [PATCH 1831/2467] Try multiple completion builds --- .github/workflows/docker-build.yml | 12 ++++++++++++ .github/workflows/lint.yml | 12 ++++++++++++ .github/workflows/skip-tests.yml | 12 ++++++++++++ .github/workflows/windows-ci.yml | 12 ++++++++++++ 4 files changed, 48 insertions(+) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index b6d932b0a..206c74862 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -47,3 +47,15 @@ jobs: target: ${{ matrix.image.name }} tags: |- adamtheturtle/vuforia-${{ matrix.image.name }}-mock:latest + + completion: + needs: build + runs-on: ubuntu-latest + if: always() # Run even if one matrix job fails + steps: + - name: Check matrix job status + run: |- + if ! ${{ needs.build.result == 'success' }}; then + echo "One or more matrix jobs failed" + exit 1 + fi diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index bf38c9410..158884b56 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -42,3 +42,15 @@ jobs: - uses: pre-commit-ci/lite-action@v1.1.0 if: always() + + completion: + needs: build + runs-on: ubuntu-latest + if: always() # Run even if one matrix job fails + steps: + - name: Check matrix job status + run: |- + if ! ${{ needs.build.result == 'success' }}; then + echo "One or more matrix jobs failed" + exit 1 + fi diff --git a/.github/workflows/skip-tests.yml b/.github/workflows/skip-tests.yml index dcd3e3956..6ae70873c 100644 --- a/.github/workflows/skip-tests.yml +++ b/.github/workflows/skip-tests.yml @@ -87,3 +87,15 @@ jobs: # which tells us to use the token to avoid errors. token: ${{ secrets.CODECOV_TOKEN }} if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} + + completion: + needs: build + runs-on: ubuntu-latest + if: always() # Run even if one matrix job fails + steps: + - name: Check matrix job status + run: |- + if ! ${{ needs.build.result == 'success' }}; then + echo "One or more matrix jobs failed" + exit 1 + fi diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 6dae091e1..3cb3e85f0 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -76,3 +76,15 @@ jobs: # which tells us to use the token to avoid errors. token: ${{ secrets.CODECOV_TOKEN }} if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} + + completion: + needs: build + runs-on: ubuntu-latest + if: always() # Run even if one matrix job fails + steps: + - name: Check matrix job status + run: |- + if ! ${{ needs.build.result == 'success' }}; then + echo "One or more matrix jobs failed" + exit 1 + fi From d85edf62aa78b85b0925798c1d07ac830e60cec2 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 21 Mar 2025 08:34:48 +0000 Subject: [PATCH 1832/2467] Uniquely name completion jobs so we can require them --- .github/workflows/ci.yml | 2 +- .github/workflows/docker-build.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/skip-tests.yml | 2 +- .github/workflows/windows-ci.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05de00b49..9d05fd2c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -198,7 +198,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} - completion: + completion-ci: needs: build runs-on: ubuntu-latest if: always() # Run even if one matrix job fails diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 206c74862..fec42f479 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -48,7 +48,7 @@ jobs: tags: |- adamtheturtle/vuforia-${{ matrix.image.name }}-mock:latest - completion: + completion-docker: needs: build runs-on: ubuntu-latest if: always() # Run even if one matrix job fails diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 158884b56..b4f424fcf 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -43,7 +43,7 @@ jobs: - uses: pre-commit-ci/lite-action@v1.1.0 if: always() - completion: + completion-lint: needs: build runs-on: ubuntu-latest if: always() # Run even if one matrix job fails diff --git a/.github/workflows/skip-tests.yml b/.github/workflows/skip-tests.yml index 6ae70873c..19b89a9e0 100644 --- a/.github/workflows/skip-tests.yml +++ b/.github/workflows/skip-tests.yml @@ -88,7 +88,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} - completion: + completion-skip-tests: needs: build runs-on: ubuntu-latest if: always() # Run even if one matrix job fails diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 3cb3e85f0..ae1e3376b 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -77,7 +77,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} - completion: + completion-windows-ci: needs: build runs-on: ubuntu-latest if: always() # Run even if one matrix job fails From 205ac99d5e237a18664f7a8ebbff2f47601efdd5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 21 Mar 2025 10:43:46 +0000 Subject: [PATCH 1833/2467] Auto-merge dependabot PRs --- .github/workflows/dependabot-merge.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/dependabot-merge.yml diff --git a/.github/workflows/dependabot-merge.yml b/.github/workflows/dependabot-merge.yml new file mode 100644 index 000000000..5238c9f68 --- /dev/null +++ b/.github/workflows/dependabot-merge.yml @@ -0,0 +1,24 @@ +--- + +name: Dependabot auto-merge +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: github.actor == 'dependabot[bot]' + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Enable auto-merge for Dependabot PRs + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} From 87d5ec3767e71e371fa6e17b7199018dac472d91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 18:23:44 +0000 Subject: [PATCH 1834/2467] Bump ruff from 0.11.1 to 0.11.2 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.1 to 0.11.2. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.1...0.11.2) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2210f8319..bd560e762 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ "python-dotenv==1.0.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.11.1", + "ruff==0.11.2", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From db0c5d3e07b39e6cb2fed739e6ffc8ddf08a7355 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Mar 2025 05:16:06 +0000 Subject: [PATCH 1835/2467] Bump types-pyyaml from 6.0.12.20241230 to 6.0.12.20250326 Bumps [types-pyyaml](https://github.com/python/typeshed) from 6.0.12.20241230 to 6.0.12.20250326. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-pyyaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bd560e762..540a7d9e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ optional-dependencies.dev = [ "sybil==9.1.0", "tenacity==9.0.0", "types-docker==7.1.0.20241229", - "types-pyyaml==6.0.12.20241230", + "types-pyyaml==6.0.12.20250326", "types-requests==2.32.0.20250306", "urllib3==2.3.0", "vulture==2.14", From 6424de635e5cc009b385d5a081c609a92960bf65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Mar 2025 05:16:32 +0000 Subject: [PATCH 1836/2467] Bump python-dotenv from 1.0.1 to 1.1.0 Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 1.0.1 to 1.1.0. - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md) - [Commits](https://github.com/theskumar/python-dotenv/compare/v1.0.1...v1.1.0) --- updated-dependencies: - dependency-name: python-dotenv dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bd560e762..83b222dcb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "pytest-cov==6.0.0", "pytest-retry==1.7.0", "pytest-xdist==3.6.1", - "python-dotenv==1.0.1", + "python-dotenv==1.1.0", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", "ruff==0.11.2", From 8ff935cfd43b5ac71ac4aeb9c6075f5870920f64 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 06:01:12 +0000 Subject: [PATCH 1837/2467] Bump pyright from 1.1.397 to 1.1.398 Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.397 to 1.1.398. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.397...v1.1.398) --- updated-dependencies: - dependency-name: pyright dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d34b61f7b..b7ecd6d30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pylint==3.3.6", "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.5.1", - "pyright==1.1.397", + "pyright==1.1.398", "pyroma==4.2", "pytest==8.3.5", "pytest-cov==6.0.0", From 6642788a876ccdabe0b9429c10f9f15171494643 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Mar 2025 05:34:57 +0000 Subject: [PATCH 1838/2467] Bump types-requests from 2.32.0.20250306 to 2.32.0.20250328 Bumps [types-requests](https://github.com/python/typeshed) from 2.32.0.20250306 to 2.32.0.20250328. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b7ecd6d30..1501da513 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,7 @@ optional-dependencies.dev = [ "tenacity==9.0.0", "types-docker==7.1.0.20241229", "types-pyyaml==6.0.12.20250326", - "types-requests==2.32.0.20250306", + "types-requests==2.32.0.20250328", "urllib3==2.3.0", "vulture==2.14", "vws-python==2025.3.10.1", From d6699e9e525241bcd22fe528311aa745e050ad6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Mar 2025 05:35:09 +0000 Subject: [PATCH 1839/2467] Bump doccmd from 2025.3.18 to 2025.3.27 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.3.18 to 2025.3.27. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.03.18...2025.03.27) --- updated-dependencies: - dependency-name: doccmd dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b7ecd6d30..bdec254c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ optional-dependencies.dev = [ "deptry==0.23.0", "dirty-equals==0.9.0", "doc8==1.1.1", - "doccmd==2025.3.18", + "doccmd==2025.3.27", "docformatter==1.7.5", "docker==7.1.0", "enum-tools[sphinx]==0.12.0", From 77d380607e2a596060570f8172a0cf52be44f9ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 05:43:05 +0000 Subject: [PATCH 1840/2467] Bump mypy-strict-kwargs from 2024.12.25 to 2025.3.28 Bumps [mypy-strict-kwargs](https://github.com/adamtheturtle/mypy-strict-kwargs) from 2024.12.25 to 2025.3.28. - [Release notes](https://github.com/adamtheturtle/mypy-strict-kwargs/releases) - [Changelog](https://github.com/adamtheturtle/mypy-strict-kwargs/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/mypy-strict-kwargs/compare/2024.12.25...2025.03.28) --- updated-dependencies: - dependency-name: mypy-strict-kwargs dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ad254ad22..dbe823e15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional-dependencies.dev = [ "furo==2024.8.6", "interrogate==1.7.0", "mypy[faster-cache]==1.15.0", - "mypy-strict-kwargs==2024.12.25", + "mypy-strict-kwargs==2025.3.28", "pre-commit==4.2.0", "pydocstyle==6.3", "pyenchant==3.3.0rc1", From c7814dc9796fbbb153e553977937703a131b2744 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Apr 2025 06:02:50 +0000 Subject: [PATCH 1841/2467] Bump types-pyyaml from 6.0.12.20250326 to 6.0.12.20250402 Bumps [types-pyyaml](https://github.com/typeshed-internal/stub_uploader) from 6.0.12.20250326 to 6.0.12.20250402. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-pyyaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dbe823e15..70b20c1c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ optional-dependencies.dev = [ "sybil==9.1.0", "tenacity==9.0.0", "types-docker==7.1.0.20241229", - "types-pyyaml==6.0.12.20250326", + "types-pyyaml==6.0.12.20250402", "types-requests==2.32.0.20250328", "urllib3==2.3.0", "vulture==2.14", From 7cd2d2d23a53cf74463fce2436d22c96c3f4256b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Apr 2025 06:03:14 +0000 Subject: [PATCH 1842/2467] Bump pytest-cov from 6.0.0 to 6.1.0 Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 6.0.0 to 6.1.0. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v6.0.0...v6.1.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dbe823e15..ac436cdbb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,7 +75,7 @@ optional-dependencies.dev = [ "pyright==1.1.398", "pyroma==4.2", "pytest==8.3.5", - "pytest-cov==6.0.0", + "pytest-cov==6.1.0", "pytest-retry==1.7.0", "pytest-xdist==3.6.1", "python-dotenv==1.1.0", From bc04168eb513fb78f3a8bf1f400262e2ac2fd249 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Apr 2025 05:51:28 +0000 Subject: [PATCH 1843/2467] Bump tenacity from 9.0.0 to 9.1.2 Bumps [tenacity](https://github.com/jd/tenacity) from 9.0.0 to 9.1.2. - [Release notes](https://github.com/jd/tenacity/releases) - [Commits](https://github.com/jd/tenacity/compare/9.0.0...9.1.2) --- updated-dependencies: - dependency-name: tenacity dependency-version: 9.1.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0e2fe3ae2..76202f895 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ optional-dependencies.dev = [ "sphinxcontrib-httpdomain==1.8.1", "sphinxcontrib-spelling==8.0.1", "sybil==9.1.0", - "tenacity==9.0.0", + "tenacity==9.1.2", "types-docker==7.1.0.20241229", "types-pyyaml==6.0.12.20250402", "types-requests==2.32.0.20250328", From a727d6806510f8c14d949c86f0e1c3a067d38e41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 05:23:23 +0000 Subject: [PATCH 1844/2467] Bump doccmd from 2025.3.27 to 2025.4.3 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.3.27 to 2025.4.3. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.03.27...2025.04.03) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.4.3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 76202f895..344b533e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ optional-dependencies.dev = [ "deptry==0.23.0", "dirty-equals==0.9.0", "doc8==1.1.1", - "doccmd==2025.3.27", + "doccmd==2025.4.3", "docformatter==1.7.5", "docker==7.1.0", "enum-tools[sphinx]==0.12.0", From c886fe10a960745f414ffa3046540c372d09b459 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 05:23:34 +0000 Subject: [PATCH 1845/2467] Bump mypy-strict-kwargs from 2025.3.28 to 2025.4.3 Bumps [mypy-strict-kwargs](https://github.com/adamtheturtle/mypy-strict-kwargs) from 2025.3.28 to 2025.4.3. - [Release notes](https://github.com/adamtheturtle/mypy-strict-kwargs/releases) - [Changelog](https://github.com/adamtheturtle/mypy-strict-kwargs/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/mypy-strict-kwargs/compare/2025.03.28...2025.04.03) --- updated-dependencies: - dependency-name: mypy-strict-kwargs dependency-version: 2025.4.3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 76202f895..3a4af7253 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional-dependencies.dev = [ "furo==2024.8.6", "interrogate==1.7.0", "mypy[faster-cache]==1.15.0", - "mypy-strict-kwargs==2025.3.28", + "mypy-strict-kwargs==2025.4.3", "pre-commit==4.2.0", "pydocstyle==6.3", "pyenchant==3.3.0rc1", From 629fd2590626780d406a500b7d73c0e7dba3d20b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 05:23:45 +0000 Subject: [PATCH 1846/2467] Bump sphinx-substitution-extensions from 2025.3.3 to 2025.4.3 Bumps [sphinx-substitution-extensions](https://github.com/adamtheturtle/sphinx-substitution-extensions) from 2025.3.3 to 2025.4.3. - [Release notes](https://github.com/adamtheturtle/sphinx-substitution-extensions/releases) - [Changelog](https://github.com/adamtheturtle/sphinx-substitution-extensions/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/sphinx-substitution-extensions/compare/2025.03.03...2025.04.03) --- updated-dependencies: - dependency-name: sphinx-substitution-extensions dependency-version: 2025.4.3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 76202f895..e389b30ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,7 +92,7 @@ optional-dependencies.dev = [ "sphinx-lint==1.0.0", "sphinx-paramlinks==0.6", "sphinx-pyproject==0.3.0", - "sphinx-substitution-extensions==2025.3.3", + "sphinx-substitution-extensions==2025.4.3", "sphinx-toolbox==3.9.0", "sphinxcontrib-httpdomain==1.8.1", "sphinxcontrib-spelling==8.0.1", From 280dfc9d66b72719963dbf18ca580e089216c0c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 05:23:57 +0000 Subject: [PATCH 1847/2467] Bump ruff from 0.11.2 to 0.11.3 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.2 to 0.11.3. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.2...0.11.3) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 76202f895..f4344faed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.0", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.11.2", + "ruff==0.11.3", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From f30495a83e781b4c6e557c30f7863f80fcda72b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 05:50:08 +0000 Subject: [PATCH 1848/2467] Bump doccmd from 2025.4.3 to 2025.4.4 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.4.3 to 2025.4.4. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.04.03...2025.04.04) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.4.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5bfa2a0e8..4a44a991f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ optional-dependencies.dev = [ "deptry==0.23.0", "dirty-equals==0.9.0", "doc8==1.1.1", - "doccmd==2025.4.3", + "doccmd==2025.4.4", "docformatter==1.7.5", "docker==7.1.0", "enum-tools[sphinx]==0.12.0", From 52ac831aff8b9b237ad1d6b5fbf65f7b01d543b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 05:52:13 +0000 Subject: [PATCH 1849/2467] Bump ruff from 0.11.3 to 0.11.4 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.3 to 0.11.4. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.3...0.11.4) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5bfa2a0e8..926a8e33c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.0", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.11.3", + "ruff==0.11.4", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From a2838f45d70dda6a7dfb926a769c600c643b0e26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 05:53:13 +0000 Subject: [PATCH 1850/2467] Bump pytest-cov from 6.1.0 to 6.1.1 Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 6.1.0 to 6.1.1. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v6.1.0...v6.1.1) --- updated-dependencies: - dependency-name: pytest-cov dependency-version: 6.1.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5bfa2a0e8..25e8617ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,7 +75,7 @@ optional-dependencies.dev = [ "pyright==1.1.398", "pyroma==4.2", "pytest==8.3.5", - "pytest-cov==6.1.0", + "pytest-cov==6.1.1", "pytest-retry==1.7.0", "pytest-xdist==3.6.1", "python-dotenv==1.1.0", From 4f558a1d7c60a0820d0bc9fbffc8c00f89ce439d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 05:32:38 +0000 Subject: [PATCH 1851/2467] Bump doccmd from 2025.4.4 to 2025.4.7 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.4.4 to 2025.4.7. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.04.04...2025.04.07) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.4.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2661df31e..376381386 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ optional-dependencies.dev = [ "deptry==0.23.0", "dirty-equals==0.9.0", "doc8==1.1.1", - "doccmd==2025.4.4", + "doccmd==2025.4.7", "docformatter==1.7.5", "docker==7.1.0", "enum-tools[sphinx]==0.12.0", From 7ebf4a235a1a0b93bb614b8d1bc1591c0fbf7edc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Apr 2025 05:43:34 +0000 Subject: [PATCH 1852/2467] Bump doccmd from 2025.4.7 to 2025.4.8 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.4.7 to 2025.4.8. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.04.07...2025.04.08) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.4.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 376381386..7acbd568e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ optional-dependencies.dev = [ "deptry==0.23.0", "dirty-equals==0.9.0", "doc8==1.1.1", - "doccmd==2025.4.7", + "doccmd==2025.4.8", "docformatter==1.7.5", "docker==7.1.0", "enum-tools[sphinx]==0.12.0", From a3a664cc6ca09339fbd767ef462679a86917a900 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Apr 2025 05:38:01 +0000 Subject: [PATCH 1853/2467] Bump pyright from 1.1.398 to 1.1.399 Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.398 to 1.1.399. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.398...v1.1.399) --- updated-dependencies: - dependency-name: pyright dependency-version: 1.1.399 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9f945b383..a19fe79e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pylint==3.3.6", "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.5.1", - "pyright==1.1.398", + "pyright==1.1.399", "pyroma==4.2", "pytest==8.3.5", "pytest-cov==6.1.1", From e9fcaee02495cedeb7fa8bedbbf57763bcd05cef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Apr 2025 05:31:32 +0000 Subject: [PATCH 1854/2467] Bump ruff from 0.11.4 to 0.11.5 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.4 to 0.11.5. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.4...0.11.5) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9f945b383..34c4feee9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.0", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.11.4", + "ruff==0.11.5", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 7b62a6e39f3928bef19dcf6ec79ed83a5a50f05e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Apr 2025 05:31:56 +0000 Subject: [PATCH 1855/2467] Bump urllib3 from 2.3.0 to 2.4.0 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.3.0 to 2.4.0. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.3.0...2.4.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9f945b383..0eb31c090 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,7 +101,7 @@ optional-dependencies.dev = [ "types-docker==7.1.0.20241229", "types-pyyaml==6.0.12.20250402", "types-requests==2.32.0.20250328", - "urllib3==2.3.0", + "urllib3==2.4.0", "vulture==2.14", "vws-python==2025.3.10.1", "vws-test-fixtures==2023.3.5", From cfefa76e4b9347fcd14803458a09976b451f6174 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 12 Apr 2025 19:24:03 +0100 Subject: [PATCH 1856/2467] Remove now-unnecessary wheel build dependency --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 42ac5f20b..87d87f272 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,6 @@ build-backend = "setuptools.build_meta" requires = [ "setuptools", "setuptools-scm>=8.1.0", - "wheel", ] [project] From e7a29c84d5678ed7364f051d4aebb132af896ab5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 12 Apr 2025 19:27:48 +0100 Subject: [PATCH 1857/2467] Specify a defaultfactory type --- src/mock_vws/database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py index a06422f8e..1d3b62659 100644 --- a/src/mock_vws/database.py +++ b/src/mock_vws/database.py @@ -67,7 +67,7 @@ class VuforiaDatabase: # ``frozen=True`` while still being able to keep the interface we want. # In particular, we might want to inspect the ``database`` object's targets # as they change via API requests. - targets: set[Target] = field(default_factory=set, hash=False) + targets: set[Target] = field(default_factory=set[Target], hash=False) state: States = States.WORKING request_quota: int = 100000 From 52c9e284b34680624a0bbf7907e11aa39ef7aeb2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Apr 2025 05:26:49 +0000 Subject: [PATCH 1858/2467] Bump types-docker from 7.1.0.20241229 to 7.1.0.20250416 Bumps [types-docker](https://github.com/typeshed-internal/stub_uploader) from 7.1.0.20241229 to 7.1.0.20250416. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20250416 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7d402e34f..3f177a43c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.1", "sybil==9.1.0", "tenacity==9.1.2", - "types-docker==7.1.0.20241229", + "types-docker==7.1.0.20250416", "types-pyyaml==6.0.12.20250402", "types-requests==2.32.0.20250328", "urllib3==2.4.0", From d2418067fb9bd85d55fcb2324091c3f1946a28d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Apr 2025 06:01:09 +0000 Subject: [PATCH 1859/2467] Bump ruff from 0.11.5 to 0.11.6 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.5 to 0.11.6. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.5...0.11.6) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3f177a43c..ceea27ec0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.0", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.11.5", + "ruff==0.11.6", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 781544e96295c0933e4e392f7104d8b67100ed28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Apr 2025 06:01:20 +0000 Subject: [PATCH 1860/2467] Bump enum-tools[sphinx] from 0.12.0 to 0.13.0 Bumps [enum-tools[sphinx]](https://github.com/domdfcoding/enum_tools) from 0.12.0 to 0.13.0. - [Release notes](https://github.com/domdfcoding/enum_tools/releases) - [Commits](https://github.com/domdfcoding/enum_tools/compare/v0.12.0...v0.13.0) --- updated-dependencies: - dependency-name: enum-tools[sphinx] dependency-version: 0.13.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3f177a43c..f54a8068e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "doccmd==2025.4.8", "docformatter==1.7.5", "docker==7.1.0", - "enum-tools[sphinx]==0.12.0", + "enum-tools[sphinx]==0.13.0", "freezegun==1.5.1", "furo==2024.8.6", "interrogate==1.7.0", From a4be99bacc229474a2bc1f975f67cf1c89fc1d3b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 24 Apr 2025 10:43:32 +0100 Subject: [PATCH 1861/2467] Fix test_invalid_value --- src/mock_vws/_query_validators/exceptions.py | 2 +- tests/mock_vws/test_query.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py index 87b87390a..986376ac0 100644 --- a/src/mock_vws/_query_validators/exceptions.py +++ b/src/mock_vws/_query_validators/exceptions.py @@ -514,7 +514,7 @@ def __init__(self, given_value: str) -> None: super().__init__() self.status_code = HTTPStatus.BAD_REQUEST unexpected_target_data_message = ( - f"Invalid value '{given_value}' in form data part " + f"Invalid value '{given_value.lower()}' in form data part " "'include_target_data'. " "Expecting one of the (unquoted) string values 'all', 'none' or " "'top'." diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index 7da395b9b..a34d2abda 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -1155,8 +1155,8 @@ def test_invalid_value( response = _query(vuforia_database=vuforia_database, body=body) expected_text = ( - f"Invalid value '{include_target_data}' in form data " - "part 'include_target_data'. " + f"Invalid value '{str(object=include_target_data).lower()}' in " + "form data part 'include_target_data'. " "Expecting one of the (unquoted) string values 'all', 'none' or " "'top'." ) From 9b5136e75c3c5243edc02fa2bee6c032c6d5c96e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 24 Apr 2025 10:58:45 +0100 Subject: [PATCH 1862/2467] Try to fix Jetty content type error --- src/mock_vws/_query_validators/exceptions.py | 7 +++---- tests/mock_vws/test_query.py | 9 ++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py index 986376ac0..e27f2ccdb 100644 --- a/src/mock_vws/_query_validators/exceptions.py +++ b/src/mock_vws/_query_validators/exceptions.py @@ -748,17 +748,16 @@ def __init__(self) -> None: text="""\ - + Error 400 Bad Request -

HTTP ERROR 400 Bad Request

+ -
URI:/v1/query
STATUS:400
MESSAGE:Bad Request
SERVLET:Resteasy
-
Powered by Jetty:// 9.4.43.v20210629
+
Powered by Jetty:// 12.0.16
diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index a34d2abda..e59521518 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -55,21 +55,20 @@ text="""\ - + Error 400 Bad Request -

HTTP ERROR 400 Bad Request

+ -
URI:/v1/query
STATUS:400
MESSAGE:Bad Request
SERVLET:Resteasy
-
Powered by Jetty:// 9.4.43.v20210629
+
Powered by Jetty:// 12.0.16
- """, # noqa: E501 + """, ) _NGINX_REQUEST_ENTITY_TOO_LARGE_ERROR = textwrap.dedent( From 6430788c69515c0a16d4be596430c4fc21b0781b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 05:12:04 +0000 Subject: [PATCH 1863/2467] Bump astral-sh/setup-uv from 5 to 6 Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 5 to 6. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v5...v6) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/skip-tests.yml | 2 +- .github/workflows/windows-ci.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d05fd2c6..f234b78a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,7 +125,7 @@ jobs: fetch-depth: 2 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b4f424fcf..dc92665fb 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aa74448c5..4e41af122 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/skip-tests.yml b/.github/workflows/skip-tests.yml index 19b89a9e0..ee4058f4e 100644 --- a/.github/workflows/skip-tests.yml +++ b/.github/workflows/skip-tests.yml @@ -32,7 +32,7 @@ jobs: fetch-depth: 2 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index ae1e3376b..9437ad82c 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -30,7 +30,7 @@ jobs: fetch-depth: 2 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' From a2922e044c3b175fdc8b323cc26556aac1b524bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 05:12:06 +0000 Subject: [PATCH 1864/2467] Bump docker/build-push-action from 6.15.0 to 6.16.0 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.15.0 to 6.16.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v6.15.0...v6.16.0) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: 6.16.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-build.yml | 2 +- .github/workflows/release.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index fec42f479..bce13c2f6 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -39,7 +39,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Build Docker image - uses: docker/build-push-action@v6.15.0 + uses: docker/build-push-action@v6.16.0 with: platforms: linux/amd64,linux/arm64 file: src/mock_vws/_flask_server/Dockerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aa74448c5..ac76e7119 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -112,7 +112,7 @@ jobs: uses: docker/setup-qemu-action@v3 - name: Build and push target manager Docker image - uses: docker/build-push-action@v6.15.0 + uses: docker/build-push-action@v6.16.0 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 @@ -123,7 +123,7 @@ jobs: adamtheturtle/vuforia-target-manager-mock:${{ steps.calver.outputs.release }} - name: Build and push VWS Docker image - uses: docker/build-push-action@v6.15.0 + uses: docker/build-push-action@v6.16.0 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 @@ -134,7 +134,7 @@ jobs: adamtheturtle/vuforia-vws-mock:${{ steps.calver.outputs.release }} - name: Build and push VWQ Docker image - uses: docker/build-push-action@v6.15.0 + uses: docker/build-push-action@v6.16.0 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 From 5f15f12db074e5be1cf73236cf58a94ce6875434 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 05:16:56 +0000 Subject: [PATCH 1865/2467] Bump pyright from 1.1.399 to 1.1.400 Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.399 to 1.1.400. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.399...v1.1.400) --- updated-dependencies: - dependency-name: pyright dependency-version: 1.1.400 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8d722df01..126b5c974 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,7 @@ optional-dependencies.dev = [ "pylint==3.3.6", "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.5.1", - "pyright==1.1.399", + "pyright==1.1.400", "pyroma==4.2", "pytest==8.3.5", "pytest-cov==6.1.1", From b0d0370b52c248f254e051f4d2751c6af8ad4200 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 05:17:09 +0000 Subject: [PATCH 1866/2467] Bump ruff from 0.11.6 to 0.11.7 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.6 to 0.11.7. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.6...0.11.7) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8d722df01..f8989c32f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.0", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.11.6", + "ruff==0.11.7", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From cc16b9ac0e811a3ba0875063b04cdbbe91828e02 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 25 Apr 2025 06:33:01 +0100 Subject: [PATCH 1867/2467] Fix one more test --- src/mock_vws/_query_validators/exceptions.py | 3 ++- tests/mock_vws/test_query.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py index e27f2ccdb..bd244f52b 100644 --- a/src/mock_vws/_query_validators/exceptions.py +++ b/src/mock_vws/_query_validators/exceptions.py @@ -752,8 +752,9 @@ def __init__(self) -> None: Error 400 Bad Request +

HTTP ERROR 400 Bad Request

- +
URI:/v1/query
URI:http://cloudreco.vuforia.com/v1/query
STATUS:400
MESSAGE:Bad Request
diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index e59521518..a19b8bbdb 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -59,8 +59,9 @@ Error 400 Bad Request +

HTTP ERROR 400 Bad Request

- +
URI:/v1/query
URI:http://cloudreco.vuforia.com/v1/query
STATUS:400
MESSAGE:Bad Request
From d88d6c965695dffec69679ab66bada3514bd3e8e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 25 Apr 2025 06:55:29 +0100 Subject: [PATCH 1868/2467] Fix a query no boundary error --- src/mock_vws/_query_validators/exceptions.py | 7 +++---- tests/mock_vws/test_query.py | 13 ++++++------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py index bd244f52b..5f4ba2326 100644 --- a/src/mock_vws/_query_validators/exceptions.py +++ b/src/mock_vws/_query_validators/exceptions.py @@ -612,10 +612,9 @@ def __init__(self) -> None: raised. """ super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST + self.status_code = HTTPStatus.INTERNAL_SERVER_ERROR self.response_text = ( - "java.io.IOException: RESTEASY007550: " - "Unable to get boundary for multipart" + "RESTEASY007550: Unable to get boundary for multipart" ) date = email.utils.formatdate( @@ -624,7 +623,7 @@ def __init__(self) -> None: usegmt=True, ) self.headers = { - "Content-Type": "text/html;charset=utf-8", + "Content-Type": "application/json", "Connection": "keep-alive", "Server": "nginx", "Date": date, diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index a19b8bbdb..50e4ff3a1 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -177,13 +177,10 @@ class TestContentType: ), ( "*/*", - HTTPStatus.BAD_REQUEST, - "text/html;charset=utf-8", + HTTPStatus.INTERNAL_SERVER_ERROR, + "application/json", None, - ( - "java.io.IOException: RESTEASY007550: Unable to get " - "boundary for multipart" - ), + "RESTEASY007550: Unable to get boundary for multipart", ), ( "text/*", @@ -255,7 +252,9 @@ def test_incorrect_no_boundary( request_body=requests_response.request.body, tell_position=requests_response.raw.tell(), ) - handle_server_errors(response=vws_response) + + if resp_status_code != HTTPStatus.INTERNAL_SERVER_ERROR: + handle_server_errors(response=vws_response) assert requests_response.text == resp_text assert_vwq_failure( From ac55a6427976621b42d970c59f4769461fe7dfcb Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 25 Apr 2025 06:57:06 +0100 Subject: [PATCH 1869/2467] Another test --- tests/mock_vws/test_query.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index 50e4ff3a1..c89b6810c 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -348,7 +348,7 @@ def test_no_boundary( content_type: str, ) -> None: """ - If no boundary is given, a ``BAD_REQUEST`` is returned. + If no boundary is given, an ``INTERNAL_SERVER_ERROR`` is returned. """ image_content = high_quality_image.getvalue() date = rfc_1123_date() @@ -392,17 +392,12 @@ def test_no_boundary( request_body=requests_response.request.body, tell_position=requests_response.raw.tell(), ) - handle_server_errors(response=vws_response) - - expected_text = ( - "java.io.IOException: RESTEASY007550: " - "Unable to get boundary for multipart" - ) + expected_text = "RESTEASY007550: Unable to get boundary for multipart" assert requests_response.text == expected_text assert_vwq_failure( response=vws_response, - status_code=HTTPStatus.BAD_REQUEST, - content_type="text/html;charset=utf-8", + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + content_type="application/json", cache_control=None, www_authenticate=None, connection="keep-alive", From b9691b7bb9924a7e72a8c75f6c5455d695776163 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 May 2025 05:58:57 +0000 Subject: [PATCH 1870/2467] Bump ruff from 0.11.7 to 0.11.8 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.7 to 0.11.8. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.7...0.11.8) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 116b7fae3..058913855 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.0", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.11.7", + "ruff==0.11.8", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From bf1c45335ca51c0cb0c0ca287bf4e702a3f0d65f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 06:24:56 +0000 Subject: [PATCH 1871/2467] Bump pylint from 3.3.6 to 3.3.7 Bumps [pylint](https://github.com/pylint-dev/pylint) from 3.3.6 to 3.3.7. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v3.3.6...v3.3.7) --- updated-dependencies: - dependency-name: pylint dependency-version: 3.3.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 058913855..30501cddc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ optional-dependencies.dev = [ "pre-commit==4.2.0", "pydocstyle==6.3", "pyenchant==3.3.0rc1", - "pylint==3.3.6", + "pylint==3.3.7", "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.5.1", "pyright==1.1.400", From 56336d087b87581c5f03f0e8473415342b7e5291 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 06:27:39 +0000 Subject: [PATCH 1872/2467] Bump types-docker from 7.1.0.20250416 to 7.1.0.20250503 Bumps [types-docker](https://github.com/typeshed-internal/stub_uploader) from 7.1.0.20250416 to 7.1.0.20250503. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20250503 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 058913855..4f739066b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.1", "sybil==9.1.0", "tenacity==9.1.2", - "types-docker==7.1.0.20250416", + "types-docker==7.1.0.20250503", "types-pyyaml==6.0.12.20250402", "types-requests==2.32.0.20250328", "urllib3==2.4.0", From f5dfa7b5692515fcdeae3835e95747b287bd0e64 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 05:25:19 +0000 Subject: [PATCH 1873/2467] Bump sphinx-toolbox from 3.9.0 to 3.10.0 Bumps [sphinx-toolbox](https://github.com/sphinx-toolbox/sphinx-toolbox) from 3.9.0 to 3.10.0. - [Release notes](https://github.com/sphinx-toolbox/sphinx-toolbox/releases) - [Changelog](https://github.com/sphinx-toolbox/sphinx-toolbox/blob/master/doc-source/changelog.rst) - [Commits](https://github.com/sphinx-toolbox/sphinx-toolbox/compare/v3.9.0...v3.10.0) --- updated-dependencies: - dependency-name: sphinx-toolbox dependency-version: 3.10.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3d37025e9..8b54f74d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,7 +92,7 @@ optional-dependencies.dev = [ "sphinx-paramlinks==0.6", "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2025.4.3", - "sphinx-toolbox==3.9.0", + "sphinx-toolbox==3.10.0", "sphinxcontrib-httpdomain==1.8.1", "sphinxcontrib-spelling==8.0.1", "sybil==9.1.0", From 4afc604ae3e37d647c73868fd615554c5351e7f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 May 2025 05:53:42 +0000 Subject: [PATCH 1874/2467] Bump docformatter from 1.7.5 to 1.7.6 Bumps [docformatter](https://github.com/PyCQA/docformatter) from 1.7.5 to 1.7.6. - [Release notes](https://github.com/PyCQA/docformatter/releases) - [Changelog](https://github.com/PyCQA/docformatter/blob/master/CHANGELOG.md) - [Commits](https://github.com/PyCQA/docformatter/compare/v1.7.5...v1.7.6) --- updated-dependencies: - dependency-name: docformatter dependency-version: 1.7.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8b54f74d1..202a325d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ optional-dependencies.dev = [ "dirty-equals==0.9.0", "doc8==1.1.1", "doccmd==2025.4.8", - "docformatter==1.7.5", + "docformatter==1.7.6", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", "freezegun==1.5.1", From 56e27c07603e09b5c7e00062896fe79d428a1a36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 05:26:39 +0000 Subject: [PATCH 1875/2467] Bump ruff from 0.11.8 to 0.11.9 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.8 to 0.11.9. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.8...0.11.9) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 202a325d8..83f6aaaf7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.0", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.11.8", + "ruff==0.11.9", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From c4f64cdd0e7835cfe1bddf34184ee32881a0b321 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 05:27:05 +0000 Subject: [PATCH 1876/2467] Bump docformatter from 1.7.6 to 1.7.7 Bumps [docformatter](https://github.com/PyCQA/docformatter) from 1.7.6 to 1.7.7. - [Release notes](https://github.com/PyCQA/docformatter/releases) - [Changelog](https://github.com/PyCQA/docformatter/blob/master/CHANGELOG.md) - [Commits](https://github.com/PyCQA/docformatter/compare/v1.7.6...v1.7.7) --- updated-dependencies: - dependency-name: docformatter dependency-version: 1.7.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 202a325d8..7f06d9911 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ optional-dependencies.dev = [ "dirty-equals==0.9.0", "doc8==1.1.1", "doccmd==2025.4.8", - "docformatter==1.7.6", + "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", "freezegun==1.5.1", From f844037ef496fd551ad737f99fad00e1b054c6f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 05:28:01 +0000 Subject: [PATCH 1877/2467] Bump check-wheel-contents from 0.6.1 to 0.6.2 Bumps [check-wheel-contents](https://github.com/jwodder/check-wheel-contents) from 0.6.1 to 0.6.2. - [Release notes](https://github.com/jwodder/check-wheel-contents/releases) - [Changelog](https://github.com/jwodder/check-wheel-contents/blob/master/CHANGELOG.md) - [Commits](https://github.com/jwodder/check-wheel-contents/compare/v0.6.1...v0.6.2) --- updated-dependencies: - dependency-name: check-wheel-contents dependency-version: 0.6.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 202a325d8..a09ec4c4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ dependencies = [ optional-dependencies.dev = [ "actionlint-py==1.7.7.23", "check-manifest==0.50", - "check-wheel-contents==0.6.1", + "check-wheel-contents==0.6.2", "deptry==0.23.0", "dirty-equals==0.9.0", "doc8==1.1.1", @@ -107,7 +107,7 @@ optional-dependencies.dev = [ "vws-web-tools==2024.10.6.1", "yamlfix==1.17.0", ] -optional-dependencies.release = [ "check-wheel-contents==0.6.1" ] +optional-dependencies.release = [ "check-wheel-contents==0.6.2" ] urls.Documentation = "https://vws-python.github.io/vws-python-mock/" urls.Source = "https://github.com/VWS-Python/vws-python-mock" From b86c7855b7d4bbed9a2227f8b40eba2c2a15795a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 May 2025 05:37:25 +0000 Subject: [PATCH 1878/2467] Bump sphinx-toolbox from 3.10.0 to 4.0.0 Bumps [sphinx-toolbox](https://github.com/sphinx-toolbox/sphinx-toolbox) from 3.10.0 to 4.0.0. - [Release notes](https://github.com/sphinx-toolbox/sphinx-toolbox/releases) - [Changelog](https://github.com/sphinx-toolbox/sphinx-toolbox/blob/master/doc-source/changelog.rst) - [Commits](https://github.com/sphinx-toolbox/sphinx-toolbox/compare/v3.10.0...v4.0.0) --- updated-dependencies: - dependency-name: sphinx-toolbox dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d57b59c74..e0abd6ff9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,7 +92,7 @@ optional-dependencies.dev = [ "sphinx-paramlinks==0.6", "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2025.4.3", - "sphinx-toolbox==3.10.0", + "sphinx-toolbox==4.0.0", "sphinxcontrib-httpdomain==1.8.1", "sphinxcontrib-spelling==8.0.1", "sybil==9.1.0", From b032950f1d619aad7111e95eb481fbe5f1640b73 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 05:55:38 +0000 Subject: [PATCH 1879/2467] Bump types-requests from 2.32.0.20250328 to 2.32.0.20250515 Bumps [types-requests](https://github.com/typeshed-internal/stub_uploader) from 2.32.0.20250328 to 2.32.0.20250515. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.32.0.20250515 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e0abd6ff9..f8d149a86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ optional-dependencies.dev = [ "tenacity==9.1.2", "types-docker==7.1.0.20250503", "types-pyyaml==6.0.12.20250402", - "types-requests==2.32.0.20250328", + "types-requests==2.32.0.20250515", "urllib3==2.4.0", "vulture==2.14", "vws-python==2025.3.10.1", From 08341ed1a0b4741481ba5281d3e6f6687fc92f5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 05:29:43 +0000 Subject: [PATCH 1880/2467] Bump docker/build-push-action from 6.16.0 to 6.17.0 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.16.0 to 6.17.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v6.16.0...v6.17.0) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: 6.17.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-build.yml | 2 +- .github/workflows/release.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index bce13c2f6..e5158d7f7 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -39,7 +39,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Build Docker image - uses: docker/build-push-action@v6.16.0 + uses: docker/build-push-action@v6.17.0 with: platforms: linux/amd64,linux/arm64 file: src/mock_vws/_flask_server/Dockerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d7904de00..0d649fb4f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -112,7 +112,7 @@ jobs: uses: docker/setup-qemu-action@v3 - name: Build and push target manager Docker image - uses: docker/build-push-action@v6.16.0 + uses: docker/build-push-action@v6.17.0 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 @@ -123,7 +123,7 @@ jobs: adamtheturtle/vuforia-target-manager-mock:${{ steps.calver.outputs.release }} - name: Build and push VWS Docker image - uses: docker/build-push-action@v6.16.0 + uses: docker/build-push-action@v6.17.0 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 @@ -134,7 +134,7 @@ jobs: adamtheturtle/vuforia-vws-mock:${{ steps.calver.outputs.release }} - name: Build and push VWQ Docker image - uses: docker/build-push-action@v6.16.0 + uses: docker/build-push-action@v6.17.0 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 From 6a95e47fa4a4084ef37465dacc4ccc71c56c9de2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 05:51:16 +0000 Subject: [PATCH 1881/2467] Bump types-pyyaml from 6.0.12.20250402 to 6.0.12.20250516 Bumps [types-pyyaml](https://github.com/typeshed-internal/stub_uploader) from 6.0.12.20250402 to 6.0.12.20250516. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-pyyaml dependency-version: 6.0.12.20250516 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f8d149a86..7819df8cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sybil==9.1.0", "tenacity==9.1.2", "types-docker==7.1.0.20250503", - "types-pyyaml==6.0.12.20250402", + "types-pyyaml==6.0.12.20250516", "types-requests==2.32.0.20250515", "urllib3==2.4.0", "vulture==2.14", From 55d71b7266638ee0e0f64a96af92c663847941e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 05:51:27 +0000 Subject: [PATCH 1882/2467] Bump ruff from 0.11.9 to 0.11.10 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.9 to 0.11.10. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.9...0.11.10) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f8d149a86..916584c48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.0", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.11.9", + "ruff==0.11.10", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 144934f40ba993c0a4d98fb7dddcd3397e0b2de1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 May 2025 05:35:57 +0000 Subject: [PATCH 1883/2467] Bump pyproject-fmt from 2.5.1 to 2.6.0 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.5.1 to 2.6.0. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.5.1...pyproject-fmt/2.6.0) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.6.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9a564d8dc..4295857b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pyenchant==3.3.0rc1", "pylint==3.3.7", "pylint-per-file-ignores==1.4.0", - "pyproject-fmt==2.5.1", + "pyproject-fmt==2.6.0", "pyright==1.1.400", "pyroma==4.2", "pytest==8.3.5", From e32190807ba6648b0ce2f69f036a0bd1dfd638c8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 20 May 2025 09:58:29 +0100 Subject: [PATCH 1884/2467] Account for Jetty bump --- src/mock_vws/_query_validators/exceptions.py | 2 +- tests/mock_vws/test_query.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py index 5f4ba2326..3d225297c 100644 --- a/src/mock_vws/_query_validators/exceptions.py +++ b/src/mock_vws/_query_validators/exceptions.py @@ -757,7 +757,7 @@ def __init__(self) -> None: STATUS:400 MESSAGE:Bad Request -
Powered by Jetty:// 12.0.16
+
Powered by Jetty:// 12.0.20
diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index c89b6810c..53736d033 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -65,7 +65,7 @@ STATUS:400 MESSAGE:Bad Request -
Powered by Jetty:// 12.0.16
+
Powered by Jetty:// 12.0.20
From 80ee793419ebc05da21374cf0d4ce19e9be0b1d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 05:30:06 +0000 Subject: [PATCH 1885/2467] Bump pyright from 1.1.400 to 1.1.401 Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.400 to 1.1.401. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.400...v1.1.401) --- updated-dependencies: - dependency-name: pyright dependency-version: 1.1.401 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4295857b6..89d2d6cad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,7 @@ optional-dependencies.dev = [ "pylint==3.3.7", "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.6.0", - "pyright==1.1.400", + "pyright==1.1.401", "pyroma==4.2", "pytest==8.3.5", "pytest-cov==6.1.1", From e92a9ac951174180888959d200498fa9de0bf07b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 05:22:13 +0000 Subject: [PATCH 1886/2467] Bump types-docker from 7.1.0.20250503 to 7.1.0.20250523 Bumps [types-docker](https://github.com/typeshed-internal/stub_uploader) from 7.1.0.20250503 to 7.1.0.20250523. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20250523 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 89d2d6cad..96da7d977 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.1", "sybil==9.1.0", "tenacity==9.1.2", - "types-docker==7.1.0.20250503", + "types-docker==7.1.0.20250523", "types-pyyaml==6.0.12.20250516", "types-requests==2.32.0.20250515", "urllib3==2.4.0", From 39d58f68e8f58bad2743a8845958289906b4f922 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 05:22:22 +0000 Subject: [PATCH 1887/2467] Bump ruff from 0.11.10 to 0.11.11 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.10 to 0.11.11. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.10...0.11.11) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.11 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 89d2d6cad..0bdf87044 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.0", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.11.10", + "ruff==0.11.11", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 578a8b09b900c9e02bca1fbe6cb92c7345e8763b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 05:33:12 +0000 Subject: [PATCH 1888/2467] Bump freezegun from 1.5.1 to 1.5.2 Bumps [freezegun](https://github.com/spulec/freezegun) from 1.5.1 to 1.5.2. - [Release notes](https://github.com/spulec/freezegun/releases) - [Changelog](https://github.com/spulec/freezegun/blob/master/CHANGELOG) - [Commits](https://github.com/spulec/freezegun/compare/1.5.1...1.5.2) --- updated-dependencies: - dependency-name: freezegun dependency-version: 1.5.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bfb9eb1b7..4b2e83fea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ optional-dependencies.dev = [ "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", - "freezegun==1.5.1", + "freezegun==1.5.2", "furo==2024.8.6", "interrogate==1.7.0", "mypy[faster-cache]==1.15.0", From fd6ae0c65f394d3eb8e7048f57e8995b742ea5e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 05:15:33 +0000 Subject: [PATCH 1889/2467] Bump pytest-xdist from 3.6.1 to 3.7.0 Bumps [pytest-xdist](https://github.com/pytest-dev/pytest-xdist) from 3.6.1 to 3.7.0. - [Release notes](https://github.com/pytest-dev/pytest-xdist/releases) - [Changelog](https://github.com/pytest-dev/pytest-xdist/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-xdist/compare/v3.6.1...v3.7.0) --- updated-dependencies: - dependency-name: pytest-xdist dependency-version: 3.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4b2e83fea..1b0eef556 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ optional-dependencies.dev = [ "pytest==8.3.5", "pytest-cov==6.1.1", "pytest-retry==1.7.0", - "pytest-xdist==3.6.1", + "pytest-xdist==3.7.0", "python-dotenv==1.1.0", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", From c5a6479222b8bf81fffb4a8d3c1887801d450060 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 05:25:24 +0000 Subject: [PATCH 1890/2467] Bump docker/build-push-action from 6.17.0 to 6.18.0 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.17.0 to 6.18.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v6.17.0...v6.18.0) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: 6.18.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-build.yml | 2 +- .github/workflows/release.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index e5158d7f7..b3d31f88b 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -39,7 +39,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Build Docker image - uses: docker/build-push-action@v6.17.0 + uses: docker/build-push-action@v6.18.0 with: platforms: linux/amd64,linux/arm64 file: src/mock_vws/_flask_server/Dockerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0d649fb4f..4d34aaaf9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -112,7 +112,7 @@ jobs: uses: docker/setup-qemu-action@v3 - name: Build and push target manager Docker image - uses: docker/build-push-action@v6.17.0 + uses: docker/build-push-action@v6.18.0 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 @@ -123,7 +123,7 @@ jobs: adamtheturtle/vuforia-target-manager-mock:${{ steps.calver.outputs.release }} - name: Build and push VWS Docker image - uses: docker/build-push-action@v6.17.0 + uses: docker/build-push-action@v6.18.0 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 @@ -134,7 +134,7 @@ jobs: adamtheturtle/vuforia-vws-mock:${{ steps.calver.outputs.release }} - name: Build and push VWQ Docker image - uses: docker/build-push-action@v6.17.0 + uses: docker/build-push-action@v6.18.0 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 From d261c427ad09a72c725b2c7183286563f43f7f89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 05:15:18 +0000 Subject: [PATCH 1891/2467] Bump mypy[faster-cache] from 1.15.0 to 1.16.0 Bumps [mypy[faster-cache]](https://github.com/python/mypy) from 1.15.0 to 1.16.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.15.0...v1.16.0) --- updated-dependencies: - dependency-name: mypy[faster-cache] dependency-version: 1.16.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1b0eef556..3bdd2fd47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ optional-dependencies.dev = [ "freezegun==1.5.2", "furo==2024.8.6", "interrogate==1.7.0", - "mypy[faster-cache]==1.15.0", + "mypy[faster-cache]==1.16.0", "mypy-strict-kwargs==2025.4.3", "pre-commit==4.2.0", "pydocstyle==6.3", From ddb0b5ad49c92ee5bf773ca1461ba6cd8ec28e32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 05:15:34 +0000 Subject: [PATCH 1892/2467] Bump ruff from 0.11.11 to 0.11.12 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.11 to 0.11.12. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.11...0.11.12) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.12 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1b0eef556..241faa811 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.0", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.11.11", + "ruff==0.11.12", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 3d8961bf7fe64bd19af4e657c830fb9aa8d22f3f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 06:21:58 +0000 Subject: [PATCH 1893/2467] Bump types-requests from 2.32.0.20250515 to 2.32.0.20250602 Bumps [types-requests](https://github.com/typeshed-internal/stub_uploader) from 2.32.0.20250515 to 2.32.0.20250602. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.32.0.20250602 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f37133e44..12acd3cbb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ optional-dependencies.dev = [ "tenacity==9.1.2", "types-docker==7.1.0.20250523", "types-pyyaml==6.0.12.20250516", - "types-requests==2.32.0.20250515", + "types-requests==2.32.0.20250602", "urllib3==2.4.0", "vulture==2.14", "vws-python==2025.3.10.1", From d202ca58a33e63a8bed71e087f9adc934e77dddb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Jun 2025 05:41:33 +0000 Subject: [PATCH 1894/2467] Bump ruff from 0.11.12 to 0.11.13 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.12 to 0.11.13. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.12...0.11.13) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 12acd3cbb..04a4d869d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.0", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.11.12", + "ruff==0.11.13", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 11fb2bd4f94d4a23c0c3731047d5323725210d73 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 05:57:49 +0000 Subject: [PATCH 1895/2467] Bump sphinx-substitution-extensions from 2025.4.3 to 2025.6.6 Bumps [sphinx-substitution-extensions](https://github.com/adamtheturtle/sphinx-substitution-extensions) from 2025.4.3 to 2025.6.6. - [Release notes](https://github.com/adamtheturtle/sphinx-substitution-extensions/releases) - [Changelog](https://github.com/adamtheturtle/sphinx-substitution-extensions/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/sphinx-substitution-extensions/compare/2025.04.03...2025.06.06) --- updated-dependencies: - dependency-name: sphinx-substitution-extensions dependency-version: 2025.6.6 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 04a4d869d..ac333f6c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,7 @@ optional-dependencies.dev = [ "sphinx-lint==1.0.0", "sphinx-paramlinks==0.6", "sphinx-pyproject==0.3.0", - "sphinx-substitution-extensions==2025.4.3", + "sphinx-substitution-extensions==2025.6.6", "sphinx-toolbox==4.0.0", "sphinxcontrib-httpdomain==1.8.1", "sphinxcontrib-spelling==8.0.1", From 744b429c5236f461a0019d7c83bd33ace99df9e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 05:09:29 +0000 Subject: [PATCH 1896/2467] Bump stefanzweifel/git-auto-commit-action from 5 to 6 Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 5 to 6. - [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases) - [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/v5...v6) --- updated-dependencies: - dependency-name: stefanzweifel/git-auto-commit-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0d649fb4f..f8e03a53f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,7 +60,7 @@ jobs: include: CHANGELOG.rst regex: false - - uses: stefanzweifel/git-auto-commit-action@v5 + - uses: stefanzweifel/git-auto-commit-action@v6 id: commit with: commit_message: Bump CHANGELOG From 7f50da1a45197968a7447f4ca6b092ea28033cd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 05:42:56 +0000 Subject: [PATCH 1897/2467] Bump types-requests from 2.32.0.20250602 to 2.32.4.20250611 Bumps [types-requests](https://github.com/typeshed-internal/stub_uploader) from 2.32.0.20250602 to 2.32.4.20250611. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.32.4.20250611 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ac333f6c3..634d83c44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ optional-dependencies.dev = [ "tenacity==9.1.2", "types-docker==7.1.0.20250523", "types-pyyaml==6.0.12.20250516", - "types-requests==2.32.0.20250602", + "types-requests==2.32.4.20250611", "urllib3==2.4.0", "vulture==2.14", "vws-python==2025.3.10.1", From 938e9f02025eaabaa7ec413b222bebd6cc141978 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 05:45:12 +0000 Subject: [PATCH 1898/2467] Bump pyright from 1.1.401 to 1.1.402 Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.401 to 1.1.402. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.401...v1.1.402) --- updated-dependencies: - dependency-name: pyright dependency-version: 1.1.402 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 634d83c44..363bfaf63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,7 @@ optional-dependencies.dev = [ "pylint==3.3.7", "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.6.0", - "pyright==1.1.401", + "pyright==1.1.402", "pyroma==4.2", "pytest==8.3.5", "pytest-cov==6.1.1", From d095db7e810dcb43e613ddada5e337fb2037aca0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 05:58:38 +0000 Subject: [PATCH 1899/2467] Bump pytest-cov from 6.1.1 to 6.2.0 Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 6.1.1 to 6.2.0. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v6.1.1...v6.2.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 363bfaf63..7dbf627d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ optional-dependencies.dev = [ "pyright==1.1.402", "pyroma==4.2", "pytest==8.3.5", - "pytest-cov==6.1.1", + "pytest-cov==6.2.0", "pytest-retry==1.7.0", "pytest-xdist==3.7.0", "python-dotenv==1.1.0", From 17a149276c5146e3d9e5253eac75a6df02015ff4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 05:54:28 +0000 Subject: [PATCH 1900/2467] Bump pytest-cov from 6.2.0 to 6.2.1 Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 6.2.0 to 6.2.1. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v6.2.0...v6.2.1) --- updated-dependencies: - dependency-name: pytest-cov dependency-version: 6.2.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7dbf627d6..d8b7dad5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ optional-dependencies.dev = [ "pyright==1.1.402", "pyroma==4.2", "pytest==8.3.5", - "pytest-cov==6.2.0", + "pytest-cov==6.2.1", "pytest-retry==1.7.0", "pytest-xdist==3.7.0", "python-dotenv==1.1.0", From 2d70aead289677e32638c00040502880fb593a80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 06:06:08 +0000 Subject: [PATCH 1901/2467] Bump pytest from 8.3.5 to 8.4.0 Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.5 to 8.4.0. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.3.5...8.4.0) --- updated-dependencies: - dependency-name: pytest dependency-version: 8.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d8b7dad5e..46a7cdcec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pyproject-fmt==2.6.0", "pyright==1.1.402", "pyroma==4.2", - "pytest==8.3.5", + "pytest==8.4.0", "pytest-cov==6.2.1", "pytest-retry==1.7.0", "pytest-xdist==3.7.0", From 6b121e5d48304d24ef16f6b706f034a0f204b6a7 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 14 Jun 2025 08:27:12 +0100 Subject: [PATCH 1902/2467] Do not beartype pytest fixtures See https://github.com/beartype/beartype/issues/532 --- tests/conftest.py | 9 --------- tests/mock_vws/fixtures/credentials.py | 3 --- tests/mock_vws/fixtures/prepared_requests.py | 10 ---------- tests/mock_vws/fixtures/vuforia_backends.py | 2 -- tests/mock_vws/test_docker.py | 1 - tests/mock_vws/test_flask_app_usage.py | 2 -- 6 files changed, 27 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 4c05f7cc1..ce7b979b7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,6 @@ import uuid import pytest -from beartype import beartype from vws import VWS, CloudRecoService from mock_vws.database import VuforiaDatabase @@ -21,7 +20,6 @@ ] -@beartype @pytest.fixture(name="vws_client") def fixture_vws_client(vuforia_database: VuforiaDatabase) -> VWS: """ @@ -33,7 +31,6 @@ def fixture_vws_client(vuforia_database: VuforiaDatabase) -> VWS: ) -@beartype @pytest.fixture def cloud_reco_client(vuforia_database: VuforiaDatabase) -> CloudRecoService: """ @@ -45,7 +42,6 @@ def cloud_reco_client(vuforia_database: VuforiaDatabase) -> CloudRecoService: ) -@beartype @pytest.fixture(name="inactive_vws_client") def fixture_inactive_vws_client(inactive_database: VuforiaDatabase) -> VWS: """ @@ -57,7 +53,6 @@ def fixture_inactive_vws_client(inactive_database: VuforiaDatabase) -> VWS: ) -@beartype @pytest.fixture def inactive_cloud_reco_client( inactive_database: VuforiaDatabase, @@ -71,7 +66,6 @@ def inactive_cloud_reco_client( ) -@beartype @pytest.fixture def target_id( image_file_success_state_low_rating: io.BytesIO, @@ -90,7 +84,6 @@ def target_id( ) -@beartype @pytest.fixture( params=[ "add_target", @@ -112,7 +105,6 @@ def endpoint(request: pytest.FixtureRequest) -> Endpoint: return endpoint_fixture -@beartype @pytest.fixture( params=[ pytest.param( @@ -147,7 +139,6 @@ def not_base64_encoded_processable(request: pytest.FixtureRequest) -> str: return not_base64_encoded_string -@beartype @pytest.fixture( params=[ pytest.param( diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index a7e163da7..5fbc1ed9d 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -5,7 +5,6 @@ from pathlib import Path import pytest -from beartype import beartype from pydantic_settings import BaseSettings, SettingsConfigDict from mock_vws.database import VuforiaDatabase @@ -42,7 +41,6 @@ class _InactiveVuforiaDatabaseSettings(_VuforiaDatabaseSettings): ) -@beartype @pytest.fixture def vuforia_database() -> VuforiaDatabase: """ @@ -59,7 +57,6 @@ def vuforia_database() -> VuforiaDatabase: ) -@beartype @pytest.fixture def inactive_database() -> VuforiaDatabase: """ diff --git a/tests/mock_vws/fixtures/prepared_requests.py b/tests/mock_vws/fixtures/prepared_requests.py index b9c8e4f50..ac813c943 100644 --- a/tests/mock_vws/fixtures/prepared_requests.py +++ b/tests/mock_vws/fixtures/prepared_requests.py @@ -9,7 +9,6 @@ from typing import Any import pytest -from beartype import beartype from urllib3.filepost import encode_multipart_formdata from vws import VWS from vws_auth_tools import authorization_header, rfc_1123_date @@ -36,7 +35,6 @@ def _wait_for_target_processed(vws_client: VWS, target_id: str) -> None: vws_client.wait_for_target_processed(target_id=target_id) -@beartype @pytest.fixture def add_target( vuforia_database: VuforiaDatabase, @@ -93,7 +91,6 @@ def add_target( ) -@beartype @pytest.fixture def delete_target( vuforia_database: VuforiaDatabase, @@ -140,7 +137,6 @@ def delete_target( ) -@beartype @pytest.fixture def database_summary(vuforia_database: VuforiaDatabase) -> Endpoint: """ @@ -183,7 +179,6 @@ def database_summary(vuforia_database: VuforiaDatabase) -> Endpoint: ) -@beartype @pytest.fixture def get_duplicates( vuforia_database: VuforiaDatabase, @@ -232,7 +227,6 @@ def get_duplicates( ) -@beartype @pytest.fixture def get_target( vuforia_database: VuforiaDatabase, @@ -280,7 +274,6 @@ def get_target( ) -@beartype @pytest.fixture def target_list(vuforia_database: VuforiaDatabase) -> Endpoint: """ @@ -323,7 +316,6 @@ def target_list(vuforia_database: VuforiaDatabase) -> Endpoint: ) -@beartype @pytest.fixture def target_summary( vuforia_database: VuforiaDatabase, @@ -371,7 +363,6 @@ def target_summary( ) -@beartype @pytest.fixture def update_target( vuforia_database: VuforiaDatabase, @@ -422,7 +413,6 @@ def update_target( ) -@beartype @pytest.fixture def query( vuforia_database: VuforiaDatabase, diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 2c67081f7..ddfdbf16c 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -235,7 +235,6 @@ def pytest_collection_modifyitems( item.add_marker(marker=skip_docker_build_tests_marker) -@beartype @pytest.fixture( name="verify_mock_vuforia", params=list(VuforiaBackend), @@ -275,7 +274,6 @@ def fixture_verify_mock_vuforia( ) -@beartype @pytest.fixture( params=[item for item in VuforiaBackend if item != VuforiaBackend.REAL], ids=[ diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py index 68f3bdbfa..56c5fc6ce 100644 --- a/tests/mock_vws/test_docker.py +++ b/tests/mock_vws/test_docker.py @@ -51,7 +51,6 @@ def wait_for_health_check(container: Container) -> None: raise ValueError(error_message) -@beartype @pytest.fixture(name="custom_bridge_network") def fixture_custom_bridge_network() -> Iterator[Network]: """Yield a custom bridge network which containers can connect to. diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index 1ce6a6d58..d79b998fe 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -11,7 +11,6 @@ import pytest import requests import responses -from beartype import beartype from PIL import Image from requests_mock_flask import add_flask_app_to_mock from vws import VWS, CloudRecoService @@ -27,7 +26,6 @@ _EXAMPLE_URL_FOR_TARGET_MANAGER = "http://" + uuid.uuid4().hex + ".com" -@beartype @pytest.fixture(autouse=True) def _(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]: """ From 8acf0ecba4adef5192026e8680c633a32ec5d51a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 05:16:08 +0000 Subject: [PATCH 1903/2467] Bump mypy[faster-cache] from 1.16.0 to 1.16.1 Bumps [mypy[faster-cache]](https://github.com/python/mypy) from 1.16.0 to 1.16.1. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.16.0...v1.16.1) --- updated-dependencies: - dependency-name: mypy[faster-cache] dependency-version: 1.16.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 46a7cdcec..07404b820 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ optional-dependencies.dev = [ "freezegun==1.5.2", "furo==2024.8.6", "interrogate==1.7.0", - "mypy[faster-cache]==1.16.0", + "mypy[faster-cache]==1.16.1", "mypy-strict-kwargs==2025.4.3", "pre-commit==4.2.0", "pydocstyle==6.3", From 240132b2519a15fac7a3736a4bdac9ada1775d17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 05:49:12 +0000 Subject: [PATCH 1904/2467] Bump pytest from 8.4.0 to 8.4.1 Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.4.0 to 8.4.1. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.4.0...8.4.1) --- updated-dependencies: - dependency-name: pytest dependency-version: 8.4.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 07404b820..3226f478a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pyproject-fmt==2.6.0", "pyright==1.1.402", "pyroma==4.2", - "pytest==8.4.0", + "pytest==8.4.1", "pytest-cov==6.2.1", "pytest-retry==1.7.0", "pytest-xdist==3.7.0", From b2fc057087bf98d2163a0b8dde0679ff92416c5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 05:49:24 +0000 Subject: [PATCH 1905/2467] Bump ruff from 0.11.13 to 0.12.0 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.13 to 0.12.0. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.13...0.12.0) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 07404b820..0ae54b63b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.0", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.11.13", + "ruff==0.12.0", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 6df0223f69d930be6e664f696f7ea517b709935c Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Jun 2025 12:16:28 +0100 Subject: [PATCH 1906/2467] Fix new ruff issues --- tests/mock_vws/test_add_target.py | 1 + tests/mock_vws/test_query.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/mock_vws/test_add_target.py b/tests/mock_vws/test_add_target.py index 3c61b9332..7134b994b 100644 --- a/tests/mock_vws/test_add_target.py +++ b/tests/mock_vws/test_add_target.py @@ -674,6 +674,7 @@ class TestActiveFlag: argvalues=[True, False, None], ) def test_valid( + *, active_flag: bool | None, image_file_failed_state: io.BytesIO, vws_client: VWS, diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index 53736d033..f2a34c16b 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -1133,6 +1133,7 @@ def test_all( argvalues=["a", True, 0], ) def test_invalid_value( + *, high_quality_image: io.BytesIO, vuforia_database: VuforiaDatabase, include_target_data: str | bool | int, From 3ae095f48f267f6b463732b4c1004c87f66178b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 05:41:46 +0000 Subject: [PATCH 1907/2467] Bump urllib3 from 2.4.0 to 2.5.0 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.4.0 to 2.5.0. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.4.0...2.5.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e1c50f1a0..c0049d658 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,7 @@ optional-dependencies.dev = [ "types-docker==7.1.0.20250523", "types-pyyaml==6.0.12.20250516", "types-requests==2.32.4.20250611", - "urllib3==2.4.0", + "urllib3==2.5.0", "vulture==2.14", "vws-python==2025.3.10.1", "vws-test-fixtures==2023.3.5", From 764230b5b6e599ace223bda67846a31d1aa755f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 05:31:33 +0000 Subject: [PATCH 1908/2467] Bump python-dotenv from 1.1.0 to 1.1.1 Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 1.1.0 to 1.1.1. - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md) - [Commits](https://github.com/theskumar/python-dotenv/compare/v1.1.0...v1.1.1) --- updated-dependencies: - dependency-name: python-dotenv dependency-version: 1.1.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c0049d658..54c194158 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ optional-dependencies.dev = [ "pytest-cov==6.2.1", "pytest-retry==1.7.0", "pytest-xdist==3.7.0", - "python-dotenv==1.1.0", + "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", "ruff==0.12.0", From d0c8b142f8cdb32e6ad416bcffdc19cc37bd4337 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 05:10:02 +0000 Subject: [PATCH 1909/2467] Bump ruff from 0.12.0 to 0.12.1 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.12.0 to 0.12.1. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.12.0...0.12.1) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.12.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 54c194158..4dda58be1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.12.0", + "ruff==0.12.1", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 8910d9290e3f6e2446042372a0f51b9b96e65e4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 05:43:57 +0000 Subject: [PATCH 1910/2467] Bump pyroma from 4.2 to 4.3.1 Bumps [pyroma](https://github.com/regebro/pyroma) from 4.2 to 4.3.1. - [Changelog](https://github.com/regebro/pyroma/blob/master/CHANGES.txt) - [Commits](https://github.com/regebro/pyroma/compare/4.2...4.3.1) --- updated-dependencies: - dependency-name: pyroma dependency-version: 4.3.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4dda58be1..4aa38911d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.6.0", "pyright==1.1.402", - "pyroma==4.2", + "pyroma==4.3.1", "pytest==8.4.1", "pytest-cov==6.2.1", "pytest-retry==1.7.0", From dd359b406abcba9e0bb74377e56e0edce3e28d59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 05:44:14 +0000 Subject: [PATCH 1911/2467] Bump pytest-xdist from 3.7.0 to 3.8.0 Bumps [pytest-xdist](https://github.com/pytest-dev/pytest-xdist) from 3.7.0 to 3.8.0. - [Release notes](https://github.com/pytest-dev/pytest-xdist/releases) - [Changelog](https://github.com/pytest-dev/pytest-xdist/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-xdist/compare/v3.7.0...v3.8.0) --- updated-dependencies: - dependency-name: pytest-xdist dependency-version: 3.8.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4dda58be1..ea96e7802 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ optional-dependencies.dev = [ "pytest==8.4.1", "pytest-cov==6.2.1", "pytest-retry==1.7.0", - "pytest-xdist==3.7.0", + "pytest-xdist==3.8.0", "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", From 11d1c29d0610d5ea5dd68a0f284c57e4054649c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 05:39:30 +0000 Subject: [PATCH 1912/2467] Bump pyroma from 4.3.1 to 4.3.2 Bumps [pyroma](https://github.com/regebro/pyroma) from 4.3.1 to 4.3.2. - [Changelog](https://github.com/regebro/pyroma/blob/master/CHANGES.txt) - [Commits](https://github.com/regebro/pyroma/compare/4.3.1...4.3.2) --- updated-dependencies: - dependency-name: pyroma dependency-version: 4.3.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 77d885f97..f7da4e861 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.6.0", "pyright==1.1.402", - "pyroma==4.3.1", + "pyroma==4.3.2", "pytest==8.4.1", "pytest-cov==6.2.1", "pytest-retry==1.7.0", From 282b4ca07d2e8b20335d0dc37456fd903595ac76 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 05:39:08 +0000 Subject: [PATCH 1913/2467] Bump pyroma from 4.3.2 to 4.3.3 Bumps [pyroma](https://github.com/regebro/pyroma) from 4.3.2 to 4.3.3. - [Changelog](https://github.com/regebro/pyroma/blob/master/CHANGES.txt) - [Commits](https://github.com/regebro/pyroma/compare/4.3.2...4.3.3) --- updated-dependencies: - dependency-name: pyroma dependency-version: 4.3.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f7da4e861..b0290f494 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.6.0", "pyright==1.1.402", - "pyroma==4.3.2", + "pyroma==4.3.3", "pytest==8.4.1", "pytest-cov==6.2.1", "pytest-retry==1.7.0", From e24414a5878b34a6066148d40f9f4c5a93c28e6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 05:39:41 +0000 Subject: [PATCH 1914/2467] Bump ruff from 0.12.1 to 0.12.2 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.12.1 to 0.12.2. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.12.1...0.12.2) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.12.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f7da4e861..ad5fad786 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.12.1", + "ruff==0.12.2", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 00d23da4c8842a9a4c25931f5b78135bb2919162 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 07:13:51 +0000 Subject: [PATCH 1915/2467] Bump types-docker from 7.1.0.20250523 to 7.1.0.20250705 Bumps [types-docker](https://github.com/typeshed-internal/stub_uploader) from 7.1.0.20250523 to 7.1.0.20250705. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20250705 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2ddf16176..f912d490a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.1", "sybil==9.1.0", "tenacity==9.1.2", - "types-docker==7.1.0.20250523", + "types-docker==7.1.0.20250705", "types-pyyaml==6.0.12.20250516", "types-requests==2.32.4.20250611", "urllib3==2.5.0", From cd3c0edd02b6de1e5423f4b6129635dbc7852d21 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 05:48:09 +0000 Subject: [PATCH 1916/2467] Bump shfmt-py from 3.11.0.2 to 3.12.0.2 Bumps [shfmt-py](https://github.com/maxwinterstein/shfmt-py) from 3.11.0.2 to 3.12.0.2. - [Release notes](https://github.com/maxwinterstein/shfmt-py/releases) - [Commits](https://github.com/maxwinterstein/shfmt-py/commits) --- updated-dependencies: - dependency-name: shfmt-py dependency-version: 3.12.0.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f912d490a..fb7145848 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,7 +85,7 @@ optional-dependencies.dev = [ # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. "shellcheck-py==0.10.0.1", - "shfmt-py==3.11.0.2", + "shfmt-py==3.12.0.2", "sphinx==8.2.3", "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.0", From da7cf23c4bf3c0c9e51ca1fb670172c7a1ff5db4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 05:40:19 +0000 Subject: [PATCH 1917/2467] Bump pyright from 1.1.402 to 1.1.403 Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.402 to 1.1.403. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.402...v1.1.403) --- updated-dependencies: - dependency-name: pyright dependency-version: 1.1.403 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fb7145848..e4c0916b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,7 @@ optional-dependencies.dev = [ "pylint==3.3.7", "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.6.0", - "pyright==1.1.402", + "pyright==1.1.403", "pyroma==4.3.3", "pytest==8.4.1", "pytest-cov==6.2.1", From 24f499bb9e6e0f37b9363ed09fed928cdac10347 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 06:39:39 +0000 Subject: [PATCH 1918/2467] Bump ruff from 0.12.2 to 0.12.3 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.12.2 to 0.12.3. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.12.2...0.12.3) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.12.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e4c0916b2..7bb056181 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.12.2", + "ruff==0.12.3", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From b7299628eb360bb9ea013c730f4b70cb9c5f518a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 06:47:08 +0000 Subject: [PATCH 1919/2467] Bump freezegun from 1.5.2 to 1.5.3 Bumps [freezegun](https://github.com/spulec/freezegun) from 1.5.2 to 1.5.3. - [Release notes](https://github.com/spulec/freezegun/releases) - [Changelog](https://github.com/spulec/freezegun/blob/master/CHANGELOG) - [Commits](https://github.com/spulec/freezegun/compare/1.5.2...1.5.3) --- updated-dependencies: - dependency-name: freezegun dependency-version: 1.5.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e4c0916b2..9870c34b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ optional-dependencies.dev = [ "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", - "freezegun==1.5.2", + "freezegun==1.5.3", "furo==2024.8.6", "interrogate==1.7.0", "mypy[faster-cache]==1.16.1", From 4c7af0e0cd39bc133567fd51cf5a23b87ffaa6c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 07:07:26 +0000 Subject: [PATCH 1920/2467] Bump mypy[faster-cache] from 1.16.1 to 1.17.0 Bumps [mypy[faster-cache]](https://github.com/python/mypy) from 1.16.1 to 1.17.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.16.1...v1.17.0) --- updated-dependencies: - dependency-name: mypy[faster-cache] dependency-version: 1.17.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 17f29b8a1..f805d46c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ optional-dependencies.dev = [ "freezegun==1.5.3", "furo==2024.8.6", "interrogate==1.7.0", - "mypy[faster-cache]==1.16.1", + "mypy[faster-cache]==1.17.0", "mypy-strict-kwargs==2025.4.3", "pre-commit==4.2.0", "pydocstyle==6.3", From 020eab51b26c980bb95604f1749432eb93c06e97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 05:44:18 +0000 Subject: [PATCH 1921/2467] Bump pyroma from 4.3.3 to 5.0 --- updated-dependencies: - dependency-name: pyroma dependency-version: '5.0' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f805d46c6..df8f33faf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.6.0", "pyright==1.1.403", - "pyroma==4.3.3", + "pyroma==5.0", "pytest==8.4.1", "pytest-cov==6.2.1", "pytest-retry==1.7.0", From fbf71f25b482e5488d8f5bb16c089fd092db819f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 06:00:17 +0000 Subject: [PATCH 1922/2467] Bump ruff from 0.12.3 to 0.12.4 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.12.3 to 0.12.4. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.12.3...0.12.4) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.12.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index df8f33faf..a2a0e2d8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.12.3", + "ruff==0.12.4", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 3854a2f056b8c5cd7615b264e4ed9d063432bc0a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 18 Jul 2025 16:57:32 +0100 Subject: [PATCH 1923/2467] Avoid ty error on method name --- src/mock_vws/_requests_mock_server/mock_web_query_api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py index f22cd9c69..26c2442c4 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py @@ -7,6 +7,7 @@ import email.utils from collections.abc import Callable, Iterable, Mapping from http import HTTPMethod, HTTPStatus +from types import MethodType from beartype import beartype from requests.models import PreparedRequest @@ -52,8 +53,9 @@ def decorator( The given `method` with multiple changes, including added validators. """ + route_name = method.__name__ if isinstance(method, MethodType) else "" new_route = Route( - route_name=method.__name__, + route_name=route_name, path_pattern=path_pattern, http_methods=frozenset(http_methods), ) From 70368d762650288cfed6b8fef5bd94e64de03e48 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 18 Jul 2025 17:01:12 +0100 Subject: [PATCH 1924/2467] No ty errors on src/ --- src/mock_vws/_requests_mock_server/mock_web_services_api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index 9a6b30a8d..96e996f70 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -12,6 +12,7 @@ import uuid from collections.abc import Callable, Iterable, Mapping from http import HTTPMethod, HTTPStatus +from types import MethodType from typing import Any from zoneinfo import ZoneInfo @@ -67,8 +68,9 @@ def decorator( The given `method` with multiple changes, including added validators. """ + route_name = method.__name__ if isinstance(method, MethodType) else "" new_route = Route( - route_name=method.__name__, + route_name=route_name, path_pattern=path_pattern, http_methods=frozenset(http_methods), ) From f5075a7c6cd0a2c274dd770df498fbb78d21b868 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 06:38:10 +0000 Subject: [PATCH 1925/2467] Bump furo from 2024.8.6 to 2025.7.19 --- updated-dependencies: - dependency-name: furo dependency-version: 2025.7.19 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a2a0e2d8a..533c88b75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "docker==7.1.0", "enum-tools[sphinx]==0.13.0", "freezegun==1.5.3", - "furo==2024.8.6", + "furo==2025.7.19", "interrogate==1.7.0", "mypy[faster-cache]==1.17.0", "mypy-strict-kwargs==2025.4.3", From c14413118c38a9b901219be6b6ee4fa794c63931 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Jul 2025 05:17:48 +0000 Subject: [PATCH 1926/2467] Bump ruff from 0.12.4 to 0.12.5 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.12.4 to 0.12.5. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.12.4...0.12.5) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.12.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 533c88b75..625276b7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.12.4", + "ruff==0.12.5", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 0abe8e781f44eb7d748ffd0b26e7d670a072552c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 05:16:12 +0000 Subject: [PATCH 1927/2467] Bump ruff from 0.12.5 to 0.12.7 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.12.5 to 0.12.7. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.12.5...0.12.7) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.12.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 625276b7a..69e1275f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.12.5", + "ruff==0.12.7", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 2625e4b18761f93926f06906e833f62ff6bab08d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 05:55:40 +0000 Subject: [PATCH 1928/2467] Bump freezegun from 1.5.3 to 1.5.4 Bumps [freezegun](https://github.com/spulec/freezegun) from 1.5.3 to 1.5.4. - [Release notes](https://github.com/spulec/freezegun/releases) - [Changelog](https://github.com/spulec/freezegun/blob/master/CHANGELOG) - [Commits](https://github.com/spulec/freezegun/compare/1.5.3...1.5.4) --- updated-dependencies: - dependency-name: freezegun dependency-version: 1.5.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 69e1275f6..e49199080 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ optional-dependencies.dev = [ "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", - "freezegun==1.5.3", + "freezegun==1.5.4", "furo==2025.7.19", "interrogate==1.7.0", "mypy[faster-cache]==1.17.0", From a3cf2237ad5d3c9fa64f45d82695e392132fd94e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Aug 2025 05:39:47 +0000 Subject: [PATCH 1929/2467] Bump mypy[faster-cache] from 1.17.0 to 1.17.1 Bumps [mypy[faster-cache]](https://github.com/python/mypy) from 1.17.0 to 1.17.1. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.17.0...v1.17.1) --- updated-dependencies: - dependency-name: mypy[faster-cache] dependency-version: 1.17.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e49199080..8af966e4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ optional-dependencies.dev = [ "freezegun==1.5.4", "furo==2025.7.19", "interrogate==1.7.0", - "mypy[faster-cache]==1.17.0", + "mypy[faster-cache]==1.17.1", "mypy-strict-kwargs==2025.4.3", "pre-commit==4.2.0", "pydocstyle==6.3", From 1e90dbc3b59dff3dbda6638fb6f9d077ffe4c4dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 06:50:50 +0000 Subject: [PATCH 1930/2467] Bump check-wheel-contents from 0.6.2 to 0.6.3 Bumps [check-wheel-contents](https://github.com/jwodder/check-wheel-contents) from 0.6.2 to 0.6.3. - [Release notes](https://github.com/jwodder/check-wheel-contents/releases) - [Changelog](https://github.com/jwodder/check-wheel-contents/blob/master/CHANGELOG.md) - [Commits](https://github.com/jwodder/check-wheel-contents/compare/v0.6.2...v0.6.3) --- updated-dependencies: - dependency-name: check-wheel-contents dependency-version: 0.6.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8af966e4b..03d3265c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ dependencies = [ optional-dependencies.dev = [ "actionlint-py==1.7.7.23", "check-manifest==0.50", - "check-wheel-contents==0.6.2", + "check-wheel-contents==0.6.3", "deptry==0.23.0", "dirty-equals==0.9.0", "doc8==1.1.1", @@ -107,7 +107,7 @@ optional-dependencies.dev = [ "vws-web-tools==2024.10.6.1", "yamlfix==1.17.0", ] -optional-dependencies.release = [ "check-wheel-contents==0.6.2" ] +optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python-mock/" urls.Source = "https://github.com/VWS-Python/vws-python-mock" From 46e6be4b233940a65a8021ae901267a84ee0cff5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 07:03:50 +0000 Subject: [PATCH 1931/2467] Bump deptry from 0.23.0 to 0.23.1 Bumps [deptry](https://github.com/fpgmaas/deptry) from 0.23.0 to 0.23.1. - [Release notes](https://github.com/fpgmaas/deptry/releases) - [Changelog](https://github.com/fpgmaas/deptry/blob/main/CHANGELOG.md) - [Commits](https://github.com/fpgmaas/deptry/compare/0.23.0...0.23.1) --- updated-dependencies: - dependency-name: deptry dependency-version: 0.23.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 03d3265c6..4140eea37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ optional-dependencies.dev = [ "actionlint-py==1.7.7.23", "check-manifest==0.50", "check-wheel-contents==0.6.3", - "deptry==0.23.0", + "deptry==0.23.1", "dirty-equals==0.9.0", "doc8==1.1.1", "doccmd==2025.4.8", From 2cc02960d49d4cf3f8aefa23972d662892dbd20d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 05:13:08 +0000 Subject: [PATCH 1932/2467] Bump ruff from 0.12.7 to 0.12.8 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.12.7 to 0.12.8. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.12.7...0.12.8) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.12.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4140eea37..a838c217e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.12.7", + "ruff==0.12.8", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From d9a3a2ff78dd41dbc9562f22258897a1bc761595 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 07:40:52 +0000 Subject: [PATCH 1933/2467] Bump types-requests from 2.32.4.20250611 to 2.32.4.20250809 Bumps [types-requests](https://github.com/typeshed-internal/stub_uploader) from 2.32.4.20250611 to 2.32.4.20250809. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.32.4.20250809 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a838c217e..b74644664 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ optional-dependencies.dev = [ "tenacity==9.1.2", "types-docker==7.1.0.20250705", "types-pyyaml==6.0.12.20250516", - "types-requests==2.32.4.20250611", + "types-requests==2.32.4.20250809", "urllib3==2.5.0", "vulture==2.14", "vws-python==2025.3.10.1", From ef8edf900930b71ba8c34d359917333084c630f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 07:56:25 +0000 Subject: [PATCH 1934/2467] Bump freezegun from 1.5.4 to 1.5.5 Bumps [freezegun](https://github.com/spulec/freezegun) from 1.5.4 to 1.5.5. - [Release notes](https://github.com/spulec/freezegun/releases) - [Changelog](https://github.com/spulec/freezegun/blob/master/CHANGELOG) - [Commits](https://github.com/spulec/freezegun/compare/1.5.4...1.5.5) --- updated-dependencies: - dependency-name: freezegun dependency-version: 1.5.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a838c217e..5984d0bac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ optional-dependencies.dev = [ "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", - "freezegun==1.5.4", + "freezegun==1.5.5", "furo==2025.7.19", "interrogate==1.7.0", "mypy[faster-cache]==1.17.1", From aec522a0d2f97650fa98ec2f92c87ef48050956c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 08:03:41 +0000 Subject: [PATCH 1935/2467] Bump shellcheck-py from 0.10.0.1 to 0.11.0.1 Bumps [shellcheck-py](https://github.com/ryanrhee/shellcheck-py) from 0.10.0.1 to 0.11.0.1. - [Commits](https://github.com/ryanrhee/shellcheck-py/compare/v0.10.0.1...v0.11.0.1) --- updated-dependencies: - dependency-name: shellcheck-py dependency-version: 0.11.0.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a838c217e..905e90615 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,7 @@ optional-dependencies.dev = [ # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. - "shellcheck-py==0.10.0.1", + "shellcheck-py==0.11.0.1", "shfmt-py==3.12.0.2", "sphinx==8.2.3", "sphinx-copybutton==0.5.2", From 509183b8c7812889645157751e45aa66dfcb073f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 08:18:55 +0000 Subject: [PATCH 1936/2467] Bump types-docker from 7.1.0.20250705 to 7.1.0.20250809 Bumps [types-docker](https://github.com/typeshed-internal/stub_uploader) from 7.1.0.20250705 to 7.1.0.20250809. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20250809 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a838c217e..c1f524051 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.1", "sybil==9.1.0", "tenacity==9.1.2", - "types-docker==7.1.0.20250705", + "types-docker==7.1.0.20250809", "types-pyyaml==6.0.12.20250516", "types-requests==2.32.4.20250611", "urllib3==2.5.0", From 8c99d990bef633f46ccfd59045ce7796ef9ea349 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 08:32:47 +0000 Subject: [PATCH 1937/2467] Bump types-pyyaml from 6.0.12.20250516 to 6.0.12.20250809 Bumps [types-pyyaml](https://github.com/typeshed-internal/stub_uploader) from 6.0.12.20250516 to 6.0.12.20250809. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-pyyaml dependency-version: 6.0.12.20250809 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 238b1c965..3407c61a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sybil==9.1.0", "tenacity==9.1.2", "types-docker==7.1.0.20250809", - "types-pyyaml==6.0.12.20250516", + "types-pyyaml==6.0.12.20250809", "types-requests==2.32.4.20250809", "urllib3==2.5.0", "vulture==2.14", From e629c3defb22de5a89a6e27814132f745d0c76e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 08:52:17 +0000 Subject: [PATCH 1938/2467] Bump sybil from 9.1.0 to 9.2.0 Bumps [sybil](https://github.com/simplistix/sybil) from 9.1.0 to 9.2.0. - [Changelog](https://github.com/simplistix/sybil/blob/master/CHANGELOG.rst) - [Commits](https://github.com/simplistix/sybil/compare/9.1.0...9.2.0) --- updated-dependencies: - dependency-name: sybil dependency-version: 9.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3407c61a4..29bafd54d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ optional-dependencies.dev = [ "sphinx-toolbox==4.0.0", "sphinxcontrib-httpdomain==1.8.1", "sphinxcontrib-spelling==8.0.1", - "sybil==9.1.0", + "sybil==9.2.0", "tenacity==9.1.2", "types-docker==7.1.0.20250809", "types-pyyaml==6.0.12.20250809", From e906acd91aeb8bde1e6de1c5676ba259fda670a8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 22:17:49 +0000 Subject: [PATCH 1939/2467] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 589661b1d..627aa7066 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,7 +46,7 @@ repos: hooks: - id: check-useless-excludes - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-added-large-files - id: check-case-conflict From 45596617188b3810561c5660535f212762f3b750 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:44:19 +0000 Subject: [PATCH 1940/2467] Bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- .github/workflows/docker-build.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/skip-tests.yml | 2 +- .github/workflows/windows-ci.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f234b78a7..676ccd6e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,7 +119,7 @@ jobs: - docs/source/basic-example.rst steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: # See https://github.com/codecov/codecov-action/issues/190. fetch-depth: 2 diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index b3d31f88b..c9bc1593b 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -30,7 +30,7 @@ jobs: - name: vwq steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index dc92665fb..4bb62749d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,7 +24,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install uv uses: astral-sh/setup-uv@v6 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 83471ebbc..422dff0df 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: # Fetch all history including tags. # Needed to find the latest tag. diff --git a/.github/workflows/skip-tests.yml b/.github/workflows/skip-tests.yml index ee4058f4e..2a3107c5d 100644 --- a/.github/workflows/skip-tests.yml +++ b/.github/workflows/skip-tests.yml @@ -26,7 +26,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: # See https://github.com/codecov/codecov-action/issues/190. fetch-depth: 2 diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 9437ad82c..3dd3ff0a2 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -24,7 +24,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: # See https://github.com/codecov/codecov-action/issues/190. fetch-depth: 2 From f2622d0b4e5ac8556d0929d5967b8b98c9a39834 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Aug 2025 05:58:01 +0000 Subject: [PATCH 1941/2467] Bump ruff from 0.12.8 to 0.12.9 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.12.8 to 0.12.9. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.12.8...0.12.9) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.12.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3407c61a4..8e7568516 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.12.8", + "ruff==0.12.9", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From f76ab0f803676b77559981d542a5405336743b1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 06:03:50 +0000 Subject: [PATCH 1942/2467] Bump pyright from 1.1.403 to 1.1.404 Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.403 to 1.1.404. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.403...v1.1.404) --- updated-dependencies: - dependency-name: pyright dependency-version: 1.1.404 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8e7568516..e7098fca3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,7 @@ optional-dependencies.dev = [ "pylint==3.3.7", "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.6.0", - "pyright==1.1.403", + "pyright==1.1.404", "pyroma==5.0", "pytest==8.4.1", "pytest-cov==6.2.1", From 413a42ad959211131650d9ba33dfe8aa74782821 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 06:21:38 +0000 Subject: [PATCH 1943/2467] Bump pylint from 3.3.7 to 3.3.8 Bumps [pylint](https://github.com/pylint-dev/pylint) from 3.3.7 to 3.3.8. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v3.3.7...v3.3.8) --- updated-dependencies: - dependency-name: pylint dependency-version: 3.3.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e7098fca3..31670596d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ optional-dependencies.dev = [ "pre-commit==4.2.0", "pydocstyle==6.3", "pyenchant==3.3.0rc1", - "pylint==3.3.7", + "pylint==3.3.8", "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.6.0", "pyright==1.1.404", From 4c94196469faace49863cb5791919b603c405aca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 06:34:41 +0000 Subject: [PATCH 1944/2467] Bump pre-commit from 4.2.0 to 4.3.0 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.2.0 to 4.3.0. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v4.2.0...v4.3.0) --- updated-dependencies: - dependency-name: pre-commit dependency-version: 4.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 31670596d..c938fb184 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.17.1", "mypy-strict-kwargs==2025.4.3", - "pre-commit==4.2.0", + "pre-commit==4.3.0", "pydocstyle==6.3", "pyenchant==3.3.0rc1", "pylint==3.3.8", From 08f1f88c97f25bfebfe50e2927c7a92ba30043cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 05:20:09 +0000 Subject: [PATCH 1945/2467] Bump types-pyyaml from 6.0.12.20250809 to 6.0.12.20250822 Bumps [types-pyyaml](https://github.com/typeshed-internal/stub_uploader) from 6.0.12.20250809 to 6.0.12.20250822. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-pyyaml dependency-version: 6.0.12.20250822 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a727c90e4..90babea8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sybil==9.2.0", "tenacity==9.1.2", "types-docker==7.1.0.20250809", - "types-pyyaml==6.0.12.20250809", + "types-pyyaml==6.0.12.20250822", "types-requests==2.32.4.20250809", "urllib3==2.5.0", "vulture==2.14", From 106028e9aba6f6169b9aac6fd9b2330d6c32e232 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 05:20:23 +0000 Subject: [PATCH 1946/2467] Bump ruff from 0.12.9 to 0.12.10 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.12.9 to 0.12.10. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.12.9...0.12.10) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.12.10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a727c90e4..e20012e3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.12.9", + "ruff==0.12.10", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 472150479df959e1d159d1c600245dddb4f15d97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 05:34:32 +0000 Subject: [PATCH 1947/2467] Bump types-docker from 7.1.0.20250809 to 7.1.0.20250822 Bumps [types-docker](https://github.com/typeshed-internal/stub_uploader) from 7.1.0.20250809 to 7.1.0.20250822. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20250822 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 83070d068..bc46bac73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.1", "sybil==9.2.0", "tenacity==9.1.2", - "types-docker==7.1.0.20250809", + "types-docker==7.1.0.20250822", "types-pyyaml==6.0.12.20250822", "types-requests==2.32.4.20250809", "urllib3==2.5.0", From 034b4ac65433ff6814ffb4381ae8aa6c35b48fce Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 24 Aug 2025 17:13:44 +0100 Subject: [PATCH 1948/2467] Use new way of specifying license --- LICENSE | 19 ------------------- pyproject.toml | 3 +-- 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index ef26969f8..000000000 --- a/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -The MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/pyproject.toml b/pyproject.toml index a2a0e2d8a..f9777608c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ keywords = [ "vuforia", "vws", ] -license = { file = "LICENSE" } +license = "MIT" authors = [ { name = "Adam Dangoor", email = "adamdangoor@gmail.com" }, ] @@ -25,7 +25,6 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Pytest", - "License :: OSI Approved :: MIT License", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python :: 3 :: Only", From 40c90feea30c23fc2155d8c244c98920d33af81a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 24 Aug 2025 17:15:29 +0100 Subject: [PATCH 1949/2467] Undo unnecessary change --- src/mock_vws/_requests_mock_server/mock_web_query_api.py | 4 +--- src/mock_vws/_requests_mock_server/mock_web_services_api.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py index 26c2442c4..f22cd9c69 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py @@ -7,7 +7,6 @@ import email.utils from collections.abc import Callable, Iterable, Mapping from http import HTTPMethod, HTTPStatus -from types import MethodType from beartype import beartype from requests.models import PreparedRequest @@ -53,9 +52,8 @@ def decorator( The given `method` with multiple changes, including added validators. """ - route_name = method.__name__ if isinstance(method, MethodType) else "" new_route = Route( - route_name=route_name, + route_name=method.__name__, path_pattern=path_pattern, http_methods=frozenset(http_methods), ) diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index 96e996f70..9a6b30a8d 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -12,7 +12,6 @@ import uuid from collections.abc import Callable, Iterable, Mapping from http import HTTPMethod, HTTPStatus -from types import MethodType from typing import Any from zoneinfo import ZoneInfo @@ -68,9 +67,8 @@ def decorator( The given `method` with multiple changes, including added validators. """ - route_name = method.__name__ if isinstance(method, MethodType) else "" new_route = Route( - route_name=route_name, + route_name=method.__name__, path_pattern=path_pattern, http_methods=frozenset(http_methods), ) From 0dc7c3d760b93c43717c2b1f4bb3672b1d9614fc Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 24 Aug 2025 17:48:08 +0100 Subject: [PATCH 1950/2467] Run pre-commit hooks on only one stage each --- .pre-commit-config.yaml | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 627aa7066..2490fcbb4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,36 +45,55 @@ repos: - repo: meta hooks: - id: check-useless-excludes + stages: [pre-commit] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: check-added-large-files + stages: [pre-commit] - id: check-case-conflict + stages: [pre-commit] - id: check-executables-have-shebangs + stages: [pre-commit] - id: check-merge-conflict + stages: [pre-commit] - id: check-shebang-scripts-are-executable + stages: [pre-commit] - id: check-symlinks + stages: [pre-commit] - id: check-json + stages: [pre-commit] - id: check-toml + stages: [pre-commit] - id: check-vcs-permalinks + stages: [pre-commit] - id: check-yaml + stages: [pre-commit] - id: end-of-file-fixer + stages: [pre-commit] - id: file-contents-sorter files: spelling_private_dict\.txt$ + stages: [pre-commit] - id: trailing-whitespace + stages: [pre-commit] - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: - id: rst-directive-colons + stages: [pre-commit] - id: rst-inline-touching-normal + stages: [pre-commit] - id: text-unicode-replacement-char + stages: [pre-commit] - id: rst-backticks + stages: [pre-commit] - repo: https://github.com/AleksaC/hadolint-py rev: v2.12.1b3 hooks: - id: hadolint + stages: [pre-commit] - repo: local hooks: - id: custom-linters @@ -93,6 +112,7 @@ repos: pass_filenames: false types_or: [yaml] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: docformatter name: docformatter @@ -100,6 +120,7 @@ repos: language: python types_or: [python] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: shellcheck name: shellcheck @@ -107,6 +128,7 @@ repos: language: python types_or: [shell] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: shellcheck-docs name: shellcheck-docs @@ -116,6 +138,7 @@ repos: language: python types_or: [markdown, rst] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: shfmt name: shfmt @@ -123,6 +146,7 @@ repos: language: python types_or: [shell] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: shfmt-docs name: shfmt-docs @@ -131,6 +155,7 @@ repos: language: python types_or: [markdown, rst] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: mypy name: mypy @@ -188,6 +213,7 @@ repos: types_or: [python] pass_filenames: false additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: vulture-docs name: vulture docs @@ -196,6 +222,7 @@ repos: types_or: [python] pass_filenames: false additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: pyroma name: pyroma @@ -204,6 +231,7 @@ repos: pass_filenames: false types_or: [toml] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: deptry name: deptry @@ -211,6 +239,7 @@ repos: language: python pass_filenames: false additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: pylint name: pylint @@ -233,6 +262,7 @@ repos: language: python types_or: [python] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: ruff-check-fix-docs name: Ruff check fix docs @@ -240,6 +270,7 @@ repos: language: python types_or: [markdown, rst] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: ruff-format-fix name: Ruff format @@ -247,6 +278,7 @@ repos: language: python types_or: [python] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: ruff-format-fix-docs name: Ruff format docs @@ -255,6 +287,7 @@ repos: language: python types_or: [markdown, rst] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: doc8 name: doc8 @@ -262,6 +295,7 @@ repos: language: python types_or: [rst] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: interrogate name: interrogate @@ -269,6 +303,7 @@ repos: language: python types_or: [python] exclude_types: [executable] + stages: [pre-commit] - id: interrogate-docs name: interrogate docs @@ -276,6 +311,7 @@ repos: language: python types_or: [markdown, rst] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: pyproject-fmt-fix name: pyproject-fmt @@ -284,6 +320,7 @@ repos: types_or: [toml] files: pyproject.toml + stages: [pre-commit] - id: linkcheck name: linkcheck entry: make -C docs/ linkcheck SPHINXOPTS=-W @@ -316,6 +353,7 @@ repos: language: python types_or: [yaml] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: sphinx-lint name: sphinx-lint @@ -323,3 +361,4 @@ repos: language: python types_or: [rst] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] From 33ed62d221623e7b934b4268ccab0bf7d2008e75 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 27 Aug 2025 23:22:20 +0100 Subject: [PATCH 1951/2467] Bump pylint per-file-ignores --- pyproject.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 63af509da..8c27ebd1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pyenchant==3.3.0rc1", "pylint==3.3.8", - "pylint-per-file-ignores==1.4.0", + "pylint-per-file-ignores==2.0.3", "pyproject-fmt==2.6.0", "pyright==1.1.404", "pyroma==5.0", @@ -224,7 +224,8 @@ load-plugins = [ # - We want to use generated module names, which may not be valid, but are never seen. # - We want to use global variables in documentation, which may not be uppercase per-file-ignores = [ - "docs/:invalid-name", + "docs/source/conf.py:invalid-name", + "docs/source/doccmd_*.py:invalid-name", "doccmd_README_rst.*.py:invalid-name", ] From 57a3abd6852bb12afc5a0b3f0c936e08d92f5e20 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 27 Aug 2025 23:29:42 +0100 Subject: [PATCH 1952/2467] Bump pylint per-file-ignores --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8c27ebd1e..dc5341537 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -226,7 +226,7 @@ load-plugins = [ per-file-ignores = [ "docs/source/conf.py:invalid-name", "docs/source/doccmd_*.py:invalid-name", - "doccmd_README_rst.*.py:invalid-name", + "doccmd_README_rst_*.py:invalid-name", ] [tool.pylint.'MESSAGES CONTROL'] From 0172f48bccf1e87be4b63703c49c8186d0bd7bad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 05:06:45 +0000 Subject: [PATCH 1953/2467] Bump ruff from 0.12.10 to 0.12.11 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.12.10 to 0.12.11. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.12.10...0.12.11) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.12.11 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dc5341537..aac6ce086 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.12.10", + "ruff==0.12.11", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 1310107980fd0fd8f98ece5653e1447a47e91b71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 10:51:09 +0000 Subject: [PATCH 1954/2467] Bump pyright from 1.1.404 to 1.1.405 Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.404 to 1.1.405. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.404...v1.1.405) --- updated-dependencies: - dependency-name: pyright dependency-version: 1.1.405 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index aac6ce086..11b9236b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint==3.3.8", "pylint-per-file-ignores==2.0.3", "pyproject-fmt==2.6.0", - "pyright==1.1.404", + "pyright==1.1.405", "pyroma==5.0", "pytest==8.4.1", "pytest-cov==6.2.1", From 5e0b5a001bf369ea7447f69ec9e21e96ef320127 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 05:05:42 +0000 Subject: [PATCH 1955/2467] Bump pytest from 8.4.1 to 8.4.2 Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.4.1 to 8.4.2. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.4.1...8.4.2) --- updated-dependencies: - dependency-name: pytest dependency-version: 8.4.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 11b9236b5..82201a489 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pyproject-fmt==2.6.0", "pyright==1.1.405", "pyroma==5.0", - "pytest==8.4.1", + "pytest==8.4.2", "pytest-cov==6.2.1", "pytest-retry==1.7.0", "pytest-xdist==3.8.0", From dc75ea9699bcb8b4aa3ef1f55e930700620d2a5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 05:05:55 +0000 Subject: [PATCH 1956/2467] Bump ruff from 0.12.11 to 0.12.12 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.12.11 to 0.12.12. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.12.11...0.12.12) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.12.12 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 11b9236b5..fdb3f8250 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.12.11", + "ruff==0.12.12", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 1ce7db76bbf235f1b74fdcb78e422d4c8022fbdc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:07:36 +0000 Subject: [PATCH 1957/2467] Bump yamlfix from 1.17.0 to 1.18.0 Bumps [yamlfix](https://github.com/lyz-code/yamlfix) from 1.17.0 to 1.18.0. - [Changelog](https://github.com/lyz-code/yamlfix/blob/main/CHANGELOG.md) - [Commits](https://github.com/lyz-code/yamlfix/compare/1.17.0...1.18.0) --- updated-dependencies: - dependency-name: yamlfix dependency-version: 1.18.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 44b2188ca..6aa46dd8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,7 +104,7 @@ optional-dependencies.dev = [ "vws-python==2025.3.10.1", "vws-test-fixtures==2023.3.5", "vws-web-tools==2024.10.6.1", - "yamlfix==1.17.0", + "yamlfix==1.18.0", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python-mock/" From 30f449e43b6c5a880aa1ce77cb1410faea86d3a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:07:43 +0000 Subject: [PATCH 1958/2467] Bump types-docker from 7.1.0.20250822 to 7.1.0.20250907 Bumps [types-docker](https://github.com/typeshed-internal/stub_uploader) from 7.1.0.20250822 to 7.1.0.20250907. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20250907 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 44b2188ca..6ae2065a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.1", "sybil==9.2.0", "tenacity==9.1.2", - "types-docker==7.1.0.20250822", + "types-docker==7.1.0.20250907", "types-pyyaml==6.0.12.20250822", "types-requests==2.32.4.20250809", "urllib3==2.5.0", From e93823a332fb5b0ed231cd695ed979809a605cce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:07:52 +0000 Subject: [PATCH 1959/2467] Bump pytest-cov from 6.2.1 to 6.3.0 Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 6.2.1 to 6.3.0. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v6.2.1...v6.3.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-version: 6.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 44b2188ca..64e7999c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pyright==1.1.405", "pyroma==5.0", "pytest==8.4.2", - "pytest-cov==6.2.1", + "pytest-cov==6.3.0", "pytest-retry==1.7.0", "pytest-xdist==3.8.0", "python-dotenv==1.1.1", From 7ce9d8f37b80256120fa2ad90a5ab4067dc5aa6a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 21:44:47 +0000 Subject: [PATCH 1960/2467] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/AleksaC/hadolint-py: v2.12.1b3 → v2.13.1](https://github.com/AleksaC/hadolint-py/compare/v2.12.1b3...v2.13.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2490fcbb4..b41c129b5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -89,7 +89,7 @@ repos: stages: [pre-commit] - repo: https://github.com/AleksaC/hadolint-py - rev: v2.12.1b3 + rev: v2.13.1 hooks: - id: hadolint From 3125dbd7fb168caf9cfb8a5cd1fc74372c9a1ec3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 05:06:20 +0000 Subject: [PATCH 1961/2467] Bump pytest-cov from 6.3.0 to 7.0.0 Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 6.3.0 to 7.0.0. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v6.3.0...v7.0.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5461e914e..956e46678 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pyright==1.1.405", "pyroma==5.0", "pytest==8.4.2", - "pytest-cov==6.3.0", + "pytest-cov==7.0.0", "pytest-retry==1.7.0", "pytest-xdist==3.8.0", "python-dotenv==1.1.1", From 1bc7f8230c87d0d61734e3a4c7ab8f64fe20ed6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 05:06:51 +0000 Subject: [PATCH 1962/2467] Bump ruff from 0.12.12 to 0.13.0 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.12.12 to 0.13.0. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.12.12...0.13.0) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.13.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 956e46678..fa36847db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.12.12", + "ruff==0.13.0", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 083db0f0860548f91a5ee82758ad69d84d3e8c82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Sep 2025 04:08:41 +0000 Subject: [PATCH 1963/2467] Bump mypy[faster-cache] from 1.17.1 to 1.18.1 Bumps [mypy[faster-cache]](https://github.com/python/mypy) from 1.17.1 to 1.18.1. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.17.1...v1.18.1) --- updated-dependencies: - dependency-name: mypy[faster-cache] dependency-version: 1.18.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fa36847db..acef269a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ optional-dependencies.dev = [ "freezegun==1.5.5", "furo==2025.7.19", "interrogate==1.7.0", - "mypy[faster-cache]==1.17.1", + "mypy[faster-cache]==1.18.1", "mypy-strict-kwargs==2025.4.3", "pre-commit==4.3.0", "pydocstyle==6.3", From 90fcecf833d3881c2c9fe92498bfef57d4aaf508 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 14 Sep 2025 22:35:09 +0100 Subject: [PATCH 1964/2467] Remove direct pyenchant dependency now that 3.3.0 supports the latest macOS --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index acef269a1..22afdfb62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,6 @@ optional-dependencies.dev = [ "mypy-strict-kwargs==2025.4.3", "pre-commit==4.3.0", "pydocstyle==6.3", - "pyenchant==3.3.0rc1", "pylint==3.3.8", "pylint-per-file-ignores==2.0.3", "pyproject-fmt==2.6.0", From 44ef2f3dc1b1647d2d4eba20fef46de3afff3c59 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 15 Sep 2025 03:30:10 +0100 Subject: [PATCH 1965/2467] Make pylint spelling dependency explicit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 22afdfb62..01135013b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ optional-dependencies.dev = [ "mypy-strict-kwargs==2025.4.3", "pre-commit==4.3.0", "pydocstyle==6.3", - "pylint==3.3.8", + "pylint[spelling]==3.3.8", "pylint-per-file-ignores==2.0.3", "pyproject-fmt==2.6.0", "pyright==1.1.405", From 62c4d4cdd4806115792c07c8238a2e43f8669b6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 05:24:21 +0000 Subject: [PATCH 1966/2467] Bump types-pyyaml from 6.0.12.20250822 to 6.0.12.20250915 Bumps [types-pyyaml](https://github.com/typeshed-internal/stub_uploader) from 6.0.12.20250822 to 6.0.12.20250915. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-pyyaml dependency-version: 6.0.12.20250915 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 01135013b..059d788f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,7 @@ optional-dependencies.dev = [ "sybil==9.2.0", "tenacity==9.1.2", "types-docker==7.1.0.20250907", - "types-pyyaml==6.0.12.20250822", + "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250809", "urllib3==2.5.0", "vulture==2.14", From c77f8c4009750fb8a28bae361ad0f3f147d44252 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 05:56:00 +0000 Subject: [PATCH 1967/2467] Bump types-requests from 2.32.4.20250809 to 2.32.4.20250913 Bumps [types-requests](https://github.com/typeshed-internal/stub_uploader) from 2.32.4.20250809 to 2.32.4.20250913. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.32.4.20250913 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 059d788f2..7f9770733 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ optional-dependencies.dev = [ "tenacity==9.1.2", "types-docker==7.1.0.20250907", "types-pyyaml==6.0.12.20250915", - "types-requests==2.32.4.20250809", + "types-requests==2.32.4.20250913", "urllib3==2.5.0", "vulture==2.14", "vws-python==2025.3.10.1", From bf5e427e6adcc62a859f53b281bb7dfa54bb7393 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 05:06:04 +0000 Subject: [PATCH 1968/2467] Bump types-docker from 7.1.0.20250907 to 7.1.0.20250916 Bumps [types-docker](https://github.com/typeshed-internal/stub_uploader) from 7.1.0.20250907 to 7.1.0.20250916. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20250916 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7f9770733..0958f32a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.1", "sybil==9.2.0", "tenacity==9.1.2", - "types-docker==7.1.0.20250907", + "types-docker==7.1.0.20250916", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", "urllib3==2.5.0", From 3635e1ee44afd106d5d89f6ae829731b9d884153 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 06:12:52 +0000 Subject: [PATCH 1969/2467] Bump doccmd from 2025.4.8 to 2025.9.19 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.4.8 to 2025.9.19. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.04.08...2025.09.19) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.9.19 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0958f32a9..7b0cb7449 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "deptry==0.23.1", "dirty-equals==0.9.0", "doc8==1.1.1", - "doccmd==2025.4.8", + "doccmd==2025.9.19", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From dded6a359d8b79c2b8be3f7ea011b32e0babb8b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 06:26:06 +0000 Subject: [PATCH 1970/2467] Bump ruff from 0.13.0 to 0.13.1 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.13.0 to 0.13.1. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.13.0...0.13.1) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.13.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7b0cb7449..c3924dbdc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.13.0", + "ruff==0.13.1", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From e25c105f706f8dde7418dc2467e960141a4bc3d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 06:26:17 +0000 Subject: [PATCH 1971/2467] Bump mypy[faster-cache] from 1.18.1 to 1.18.2 Bumps [mypy[faster-cache]](https://github.com/python/mypy) from 1.18.1 to 1.18.2. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.18.1...v1.18.2) --- updated-dependencies: - dependency-name: mypy[faster-cache] dependency-version: 1.18.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7b0cb7449..51dd7ce94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ optional-dependencies.dev = [ "freezegun==1.5.5", "furo==2025.7.19", "interrogate==1.7.0", - "mypy[faster-cache]==1.18.1", + "mypy[faster-cache]==1.18.2", "mypy-strict-kwargs==2025.4.3", "pre-commit==4.3.0", "pydocstyle==6.3", From 5f46601911599d297187cf3be4ae2f90556f3b35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 05:17:29 +0000 Subject: [PATCH 1972/2467] Bump dirty-equals from 0.9.0 to 0.10.0 Bumps [dirty-equals](https://github.com/samuelcolvin/dirty-equals) from 0.9.0 to 0.10.0. - [Release notes](https://github.com/samuelcolvin/dirty-equals/releases) - [Commits](https://github.com/samuelcolvin/dirty-equals/compare/v0.9.0...v0.10.0) --- updated-dependencies: - dependency-name: dirty-equals dependency-version: 0.10.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ff0293575..cac02ccb9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ optional-dependencies.dev = [ "check-manifest==0.50", "check-wheel-contents==0.6.3", "deptry==0.23.1", - "dirty-equals==0.9.0", + "dirty-equals==0.10.0", "doc8==1.1.1", "doccmd==2025.9.19", "docformatter==1.7.7", From 1972da8ef845a5b73bb81d5091e1273335ab1cc0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 22:12:34 +0000 Subject: [PATCH 1973/2467] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/AleksaC/hadolint-py: v2.13.1 → v2.14.0](https://github.com/AleksaC/hadolint-py/compare/v2.13.1...v2.14.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b41c129b5..804ad23ab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -89,7 +89,7 @@ repos: stages: [pre-commit] - repo: https://github.com/AleksaC/hadolint-py - rev: v2.13.1 + rev: v2.14.0 hooks: - id: hadolint From 6f75cbaf7d3114526c789728c36bde6b1ef93cfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 05:06:51 +0000 Subject: [PATCH 1974/2467] Bump ruff from 0.13.1 to 0.13.2 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.13.1 to 0.13.2. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.13.1...0.13.2) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.13.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cac02ccb9..91ac712cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.2", "requests-mock-flask==2025.1.13", - "ruff==0.13.1", + "ruff==0.13.2", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 449e5ee4c655570dcfa3929b10c3e7ebfa8a3d42 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 05:06:58 +0000 Subject: [PATCH 1975/2467] Bump furo from 2025.7.19 to 2025.9.25 Bumps [furo](https://github.com/pradyunsg/furo) from 2025.7.19 to 2025.9.25. - [Release notes](https://github.com/pradyunsg/furo/releases) - [Changelog](https://github.com/pradyunsg/furo/blob/main/docs/changelog.md) - [Commits](https://github.com/pradyunsg/furo/compare/2025.07.19...2025.09.25) --- updated-dependencies: - dependency-name: furo dependency-version: 2025.9.25 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cac02ccb9..fdfff5f40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ optional-dependencies.dev = [ "docker==7.1.0", "enum-tools[sphinx]==0.13.0", "freezegun==1.5.5", - "furo==2025.7.19", + "furo==2025.9.25", "interrogate==1.7.0", "mypy[faster-cache]==1.18.2", "mypy-strict-kwargs==2025.4.3", From 951ee8206c430da8f7c3a01cc414ccbba384708e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 05:22:39 +0000 Subject: [PATCH 1976/2467] Bump pyyaml from 6.0.2 to 6.0.3 Bumps [pyyaml](https://github.com/yaml/pyyaml) from 6.0.2 to 6.0.3. - [Release notes](https://github.com/yaml/pyyaml/releases) - [Changelog](https://github.com/yaml/pyyaml/blob/6.0.3/CHANGES) - [Commits](https://github.com/yaml/pyyaml/compare/6.0.2...6.0.3) --- updated-dependencies: - dependency-name: pyyaml dependency-version: 6.0.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 91ac712cc..188aa6a5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ optional-dependencies.dev = [ "pytest-retry==1.7.0", "pytest-xdist==3.8.0", "python-dotenv==1.1.1", - "pyyaml==6.0.2", + "pyyaml==6.0.3", "requests-mock-flask==2025.1.13", "ruff==0.13.2", # We add shellcheck-py not only for shell scripts and shell code blocks, From f2a48725e248b2289bbaac64522d1014af2a6906 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 05:06:52 +0000 Subject: [PATCH 1977/2467] Bump pyright from 1.1.405 to 1.1.406 Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.405 to 1.1.406. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.405...v1.1.406) --- updated-dependencies: - dependency-name: pyright dependency-version: 1.1.406 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 188aa6a5b..920c1035d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ optional-dependencies.dev = [ "pylint[spelling]==3.3.8", "pylint-per-file-ignores==2.0.3", "pyproject-fmt==2.6.0", - "pyright==1.1.405", + "pyright==1.1.406", "pyroma==5.0", "pytest==8.4.2", "pytest-cov==7.0.0", From 55110b04d18d649b7bf8de15ec1c1fc0a4b4e174 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 05:41:05 +0000 Subject: [PATCH 1978/2467] Bump pyproject-fmt from 2.6.0 to 2.7.0 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.6.0 to 2.7.0. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.6.0...pyproject-fmt/2.7.0) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 920c1035d..348e4711a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==3.3.8", "pylint-per-file-ignores==2.0.3", - "pyproject-fmt==2.6.0", + "pyproject-fmt==2.7.0", "pyright==1.1.406", "pyroma==5.0", "pytest==8.4.2", From 6954a95aea3e79bdf0b8801b86b6b4278dc6e76e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 05:05:45 +0000 Subject: [PATCH 1979/2467] Bump ruff from 0.13.2 to 0.13.3 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.13.2 to 0.13.3. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.13.2...0.13.3) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.13.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 348e4711a..b432a065c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.3", "requests-mock-flask==2025.1.13", - "ruff==0.13.2", + "ruff==0.13.3", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 49c861cc3d2a88bdb6cd663209412579ed5459f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 05:13:43 +0000 Subject: [PATCH 1980/2467] Bump pylint[spelling] from 3.3.8 to 3.3.9 Bumps [pylint[spelling]](https://github.com/pylint-dev/pylint) from 3.3.8 to 3.3.9. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v3.3.8...v3.3.9) --- updated-dependencies: - dependency-name: pylint[spelling] dependency-version: 3.3.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 348e4711a..c2da135d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ optional-dependencies.dev = [ "mypy-strict-kwargs==2025.4.3", "pre-commit==4.3.0", "pydocstyle==6.3", - "pylint[spelling]==3.3.8", + "pylint[spelling]==3.3.9", "pylint-per-file-ignores==2.0.3", "pyproject-fmt==2.7.0", "pyright==1.1.406", From 29aa84e5559e771e3b70ab7faae665e1c385ddc8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 09:14:17 +0100 Subject: [PATCH 1981/2467] Progress towards no-codecov --- .github/workflows/ci.yml | 64 ++++++++++++++++++--------- .github/workflows/skip-tests.yml | 75 +++++++++++++++++++------------- .github/workflows/windows-ci.yml | 75 +++++++++++++++++++------------- README.rst | 4 +- codecov.yaml | 7 --- pyproject.toml | 1 - 6 files changed, 133 insertions(+), 93 deletions(-) delete mode 100644 codecov.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 676ccd6e4..60288e070 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,9 +120,6 @@ jobs: steps: - uses: actions/checkout@v5 - with: - # See https://github.com/codecov/codecov-action/issues/190. - fetch-depth: 2 - name: Install uv uses: astral-sh/setup-uv@v6 @@ -179,24 +176,13 @@ jobs: # https://github.com/VWS-Python/vws-python-mock/issues/708 cat ./coverage.xml - # We run this job on every PR, on every merge to main, and nightly. - # This causes us to hit an issue with Codecov. - # - # We see "Too many uploads to this commit.". - # See https://community.codecov.io/t/too-many-uploads-to-this-commit/2574. - # - # To work around this, we do not upload coverage data on scheduled runs. - # We print the event name here to help with debugging. - - name: Show event name - run: | - echo ${{ github.event_name }} - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 + - name: Upload coverage data + uses: actions/upload-artifact@v4 with: - fail_ci_if_error: true - token: ${{ secrets.CODECOV_TOKEN }} - if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} + name: coverage-data-${{ matrix.python-version }}-${{ matrix.ci_pattern }} + path: .coverage.* + include-hidden-files: true + if-no-files-found: ignore completion-ci: needs: build @@ -209,3 +195,41 @@ jobs: echo "One or more matrix jobs failed" exit 1 fi + + coverage: + name: Combine & check coverage + if: always() + needs: build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 + with: + # Use latest Python, so it understands all syntax. + python-version: '3.13' + + - uses: actions/download-artifact@v4 + with: + pattern: coverage-data-* + merge-multiple: true + + - name: Combine coverage & fail if it's <100% + run: | + uv tool install 'coverage[toml]' + + coverage combine + coverage html --skip-covered --skip-empty + + # Report and write to summary. + coverage report --format=markdown >> "$GITHUB_STEP_SUMMARY" + + # Report again and fail if under 100%. + coverage report --fail-under=100 + + - name: Upload HTML report if check failed + uses: actions/upload-artifact@v4 + with: + name: html-report + path: htmlcov + if: ${{ failure() }} diff --git a/.github/workflows/skip-tests.yml b/.github/workflows/skip-tests.yml index 2a3107c5d..8beeaa7f8 100644 --- a/.github/workflows/skip-tests.yml +++ b/.github/workflows/skip-tests.yml @@ -27,9 +27,6 @@ jobs: steps: - uses: actions/checkout@v5 - with: - # See https://github.com/codecov/codecov-action/issues/190. - fetch-depth: 2 - name: Install uv uses: astral-sh/setup-uv@v6 @@ -58,35 +55,13 @@ jobs: env: UV_PYTHON: ${{ matrix.python-version }} - - name: Show coverage file - run: | - # Sometimes we have been sure that we have 100% coverage, but codecov - # says otherwise. - # - # We show the coverage file here to help with debugging. - # https://github.com/VWS-Python/vws-python-mock/issues/708 - cat ./coverage.xml - - # We run this job on every PR, on every merge to main, and nightly. - # This causes us to hit an issue with Codecov. - # - # We see "Too many uploads to this commit.". - # See https://community.codecov.io/t/too-many-uploads-to-this-commit/2574. - # - # To work around this, we do not upload coverage data on scheduled runs. - # We print the event name here to help with debugging. - - name: Show event name - run: | - echo ${{ github.event_name }} - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 + - name: Upload coverage data + uses: actions/upload-artifact@v4 with: - fail_ci_if_error: true - # See https://community.codecov.com/t/upload-issues-unable-to-locate-build-via-github-actions-api/3954 - # which tells us to use the token to avoid errors. - token: ${{ secrets.CODECOV_TOKEN }} - if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} + name: coverage-data-skip-tests-${{ matrix.python-version }} + path: .coverage.* + include-hidden-files: true + if-no-files-found: ignore completion-skip-tests: needs: build @@ -99,3 +74,41 @@ jobs: echo "One or more matrix jobs failed" exit 1 fi + + coverage-skip-tests: + name: Combine & check coverage (skip-tests) + if: always() + needs: build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 + with: + # Use latest Python, so it understands all syntax. + python-version: '3.13' + + - uses: actions/download-artifact@v4 + with: + pattern: coverage-data-skip-tests-* + merge-multiple: true + + - name: Combine coverage & fail if it's <100% + run: | + uv tool install 'coverage[toml]' + + coverage combine + coverage html --skip-covered --skip-empty + + # Report and write to summary. + coverage report --format=markdown >> "$GITHUB_STEP_SUMMARY" + + # Report again and fail if under 100%. + coverage report --fail-under=100 + + - name: Upload HTML report if check failed + uses: actions/upload-artifact@v4 + with: + name: html-report-skip-tests + path: htmlcov + if: ${{ failure() }} diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 3dd3ff0a2..fc1f580b5 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -25,9 +25,6 @@ jobs: steps: - uses: actions/checkout@v5 - with: - # See https://github.com/codecov/codecov-action/issues/190. - fetch-depth: 2 - name: Install uv uses: astral-sh/setup-uv@v6 @@ -47,35 +44,13 @@ jobs: env: UV_PYTHON: ${{ matrix.python-version }} - - name: Show coverage file - run: | - # Sometimes we have been sure that we have 100% coverage, but codecov - # says otherwise. - # - # We show the coverage file here to help with debugging. - # https://github.com/VWS-Python/vws-python-mock/issues/708 - cat ./coverage.xml - - # We run this job on every PR, on every merge to main, and nightly. - # This causes us to hit an issue with Codecov. - # - # We see "Too many uploads to this commit.". - # See https://community.codecov.io/t/too-many-uploads-to-this-commit/2574. - # - # To work around this, we do not upload coverage data on scheduled runs. - # We print the event name here to help with debugging. - - name: Show event name - run: | - echo ${{ github.event_name }} - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 + - name: Upload coverage data + uses: actions/upload-artifact@v4 with: - fail_ci_if_error: true - # See https://community.codecov.com/t/upload-issues-unable-to-locate-build-via-github-actions-api/3954 - # which tells us to use the token to avoid errors. - token: ${{ secrets.CODECOV_TOKEN }} - if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} + name: coverage-data-windows-${{ matrix.python-version }} + path: .coverage.* + include-hidden-files: true + if-no-files-found: ignore completion-windows-ci: needs: build @@ -88,3 +63,41 @@ jobs: echo "One or more matrix jobs failed" exit 1 fi + + coverage-windows: + name: Combine & check coverage (Windows) + if: always() + needs: build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 + with: + # Use latest Python, so it understands all syntax. + python-version: '3.13' + + - uses: actions/download-artifact@v4 + with: + pattern: coverage-data-windows-* + merge-multiple: true + + - name: Combine coverage & fail if it's <100% + run: | + uv tool install 'coverage[toml]' + + coverage combine + coverage html --skip-covered --skip-empty + + # Report and write to summary. + coverage report --format=markdown >> "$GITHUB_STEP_SUMMARY" + + # Report again and fail if under 100%. + coverage report --fail-under=100 + + - name: Upload HTML report if check failed + uses: actions/upload-artifact@v4 + with: + name: html-report-windows + path: htmlcov + if: ${{ failure() }} diff --git a/README.rst b/README.rst index c2a566463..98004f17c 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -|Build Status| |codecov| |PyPI| +|Build Status| |PyPI| VWS Mock ======== @@ -56,8 +56,6 @@ This includes details on how to use the mock, options, and details of the differ .. |Build Status| image:: https://github.com/VWS-Python/vws-python-mock/actions/workflows/ci.yml/badge.svg?branch=main :target: https://github.com/VWS-Python/vws-python-mock/actions -.. |codecov| image:: https://codecov.io/gh/VWS-Python/vws-python-mock/branch/main/graph/badge.svg - :target: https://codecov.io/gh/VWS-Python/vws-python-mock .. |PyPI| image:: https://badge.fury.io/py/VWS-Python-Mock.svg :target: https://badge.fury.io/py/VWS-Python-Mock .. |minimum-python-version| replace:: 3.13 diff --git a/codecov.yaml b/codecov.yaml deleted file mode 100644 index 5c35baac9..000000000 --- a/codecov.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -coverage: - status: - patch: - default: - # Require 100% test coverage. - target: 100% diff --git a/pyproject.toml b/pyproject.toml index c2da135d8..627672bac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -313,7 +313,6 @@ ignore = [ "Makefile", "ci", "ci/**", - "codecov.yaml", "docs", "docs/**", ".git_archival.txt", From e3085791c860d49103cea3335c71b6ff37ba2872 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 09:17:50 +0100 Subject: [PATCH 1982/2467] Try to remove codecov --- .github/workflows/ci.yml | 50 ++------------------------------ .github/workflows/coverage.yml | 45 ++++++++++++++++++++++++++++ .github/workflows/skip-tests.yml | 38 ------------------------ .github/workflows/windows-ci.yml | 38 ------------------------ 4 files changed, 47 insertions(+), 124 deletions(-) create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60288e070..1c36d20c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -167,19 +167,11 @@ jobs: env: UV_PYTHON: ${{ matrix.python-version }} - - name: Show coverage file - run: | - # Sometimes we have been sure that we have 100% coverage, but codecov - # says otherwise. - # - # We show the coverage file here to help with debugging. - # https://github.com/VWS-Python/vws-python-mock/issues/708 - cat ./coverage.xml - - name: Upload coverage data uses: actions/upload-artifact@v4 with: - name: coverage-data-${{ matrix.python-version }}-${{ matrix.ci_pattern }} + name: | + coverage-data-ci-${{ matrix.python-version }}-${{ matrix.ci_pattern}} path: .coverage.* include-hidden-files: true if-no-files-found: ignore @@ -195,41 +187,3 @@ jobs: echo "One or more matrix jobs failed" exit 1 fi - - coverage: - name: Combine & check coverage - if: always() - needs: build - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 - with: - # Use latest Python, so it understands all syntax. - python-version: '3.13' - - - uses: actions/download-artifact@v4 - with: - pattern: coverage-data-* - merge-multiple: true - - - name: Combine coverage & fail if it's <100% - run: | - uv tool install 'coverage[toml]' - - coverage combine - coverage html --skip-covered --skip-empty - - # Report and write to summary. - coverage report --format=markdown >> "$GITHUB_STEP_SUMMARY" - - # Report again and fail if under 100%. - coverage report --fail-under=100 - - - name: Upload HTML report if check failed - uses: actions/upload-artifact@v4 - with: - name: html-report - path: htmlcov - if: ${{ failure() }} diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 000000000..aad465758 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,45 @@ +--- +name: Coverage + +on: + workflow_run: + workflows: [CI, Skip tests, Windows CI] + types: [completed] + +jobs: + coverage: + name: Combine & check coverage + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 + with: + # Use latest Python, so it understands all syntax. + python-version: '3.13' + + - uses: actions/download-artifact@v4 + with: + pattern: coverage-data-* + merge-multiple: true + + - name: Combine coverage & fail if it's <100% + run: | + uv tool install 'coverage[toml]' + + coverage combine + coverage html --skip-covered --skip-empty + + # Report and write to summary. + coverage report --format=markdown >> "$GITHUB_STEP_SUMMARY" + + # Report again and fail if under 100%. + coverage report --fail-under=100 + + - name: Upload HTML report if check failed + uses: actions/upload-artifact@v4 + with: + name: html-report + path: htmlcov + if: ${{ failure() }} diff --git a/.github/workflows/skip-tests.yml b/.github/workflows/skip-tests.yml index 8beeaa7f8..fb9fea203 100644 --- a/.github/workflows/skip-tests.yml +++ b/.github/workflows/skip-tests.yml @@ -74,41 +74,3 @@ jobs: echo "One or more matrix jobs failed" exit 1 fi - - coverage-skip-tests: - name: Combine & check coverage (skip-tests) - if: always() - needs: build - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 - with: - # Use latest Python, so it understands all syntax. - python-version: '3.13' - - - uses: actions/download-artifact@v4 - with: - pattern: coverage-data-skip-tests-* - merge-multiple: true - - - name: Combine coverage & fail if it's <100% - run: | - uv tool install 'coverage[toml]' - - coverage combine - coverage html --skip-covered --skip-empty - - # Report and write to summary. - coverage report --format=markdown >> "$GITHUB_STEP_SUMMARY" - - # Report again and fail if under 100%. - coverage report --fail-under=100 - - - name: Upload HTML report if check failed - uses: actions/upload-artifact@v4 - with: - name: html-report-skip-tests - path: htmlcov - if: ${{ failure() }} diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index fc1f580b5..16bcaff76 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -63,41 +63,3 @@ jobs: echo "One or more matrix jobs failed" exit 1 fi - - coverage-windows: - name: Combine & check coverage (Windows) - if: always() - needs: build - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 - with: - # Use latest Python, so it understands all syntax. - python-version: '3.13' - - - uses: actions/download-artifact@v4 - with: - pattern: coverage-data-windows-* - merge-multiple: true - - - name: Combine coverage & fail if it's <100% - run: | - uv tool install 'coverage[toml]' - - coverage combine - coverage html --skip-covered --skip-empty - - # Report and write to summary. - coverage report --format=markdown >> "$GITHUB_STEP_SUMMARY" - - # Report again and fail if under 100%. - coverage report --fail-under=100 - - - name: Upload HTML report if check failed - uses: actions/upload-artifact@v4 - with: - name: html-report-windows - path: htmlcov - if: ${{ failure() }} From b24da0fb1c99ce4314264870fb338e18da6f9805 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 09:34:57 +0100 Subject: [PATCH 1983/2467] Try using setup-uv --- .github/workflows/coverage.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index aad465758..a8f80c7dd 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -14,10 +14,11 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: - # Use latest Python, so it understands all syntax. - python-version: '3.13' + enable-cache: true + cache-dependency-glob: '**/pyproject.toml' - uses: actions/download-artifact@v4 with: From f4d0840f4db63598c50c1ccd2be417dc4110f529 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 09:56:24 +0100 Subject: [PATCH 1984/2467] Try to fix coverage workflow to run --- .github/workflows/coverage.yml | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index a8f80c7dd..1c9d22089 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -2,18 +2,36 @@ name: Coverage on: - workflow_run: - workflows: [CI, Skip tests, Windows CI] - types: [completed] + push: + branches: [main] + pull_request: + branches: [main] + schedule: + # * is a special character in YAML so you have to quote this string + # Run at 1:00 every day + - cron: 0 1 * * * + workflow_dispatch: {} jobs: coverage: name: Combine & check coverage - if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 + + - name: Wait for other workflows + uses: lewagon/wait-on-check-action@v1.3.4 + with: + ref: ${{ github.ref }} + check-name: | + CI + Skip tests + Windows CI + repo-token: ${{ secrets.GITHUB_TOKEN }} + wait-interval: 10 + allowed-conclusions: success + - name: Install uv uses: astral-sh/setup-uv@v6 with: From 304f484b3e15b5f2e2f9fe525c4c5e363833b483 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 10:09:39 +0100 Subject: [PATCH 1985/2467] Try combining test jobs --- .github/workflows/coverage.yml | 64 ---------- .github/workflows/skip-tests.yml | 76 ----------- .github/workflows/{ci.yml => test.yml} | 168 ++++++++++++++++++++++--- .github/workflows/windows-ci.yml | 65 ---------- 4 files changed, 150 insertions(+), 223 deletions(-) delete mode 100644 .github/workflows/coverage.yml delete mode 100644 .github/workflows/skip-tests.yml rename .github/workflows/{ci.yml => test.yml} (66%) delete mode 100644 .github/workflows/windows-ci.yml diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml deleted file mode 100644 index 1c9d22089..000000000 --- a/.github/workflows/coverage.yml +++ /dev/null @@ -1,64 +0,0 @@ ---- -name: Coverage - -on: - push: - branches: [main] - pull_request: - branches: [main] - schedule: - # * is a special character in YAML so you have to quote this string - # Run at 1:00 every day - - cron: 0 1 * * * - workflow_dispatch: {} - -jobs: - coverage: - name: Combine & check coverage - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v5 - - - name: Wait for other workflows - uses: lewagon/wait-on-check-action@v1.3.4 - with: - ref: ${{ github.ref }} - check-name: | - CI - Skip tests - Windows CI - repo-token: ${{ secrets.GITHUB_TOKEN }} - wait-interval: 10 - allowed-conclusions: success - - - name: Install uv - uses: astral-sh/setup-uv@v6 - with: - enable-cache: true - cache-dependency-glob: '**/pyproject.toml' - - - uses: actions/download-artifact@v4 - with: - pattern: coverage-data-* - merge-multiple: true - - - name: Combine coverage & fail if it's <100% - run: | - uv tool install 'coverage[toml]' - - coverage combine - coverage html --skip-covered --skip-empty - - # Report and write to summary. - coverage report --format=markdown >> "$GITHUB_STEP_SUMMARY" - - # Report again and fail if under 100%. - coverage report --fail-under=100 - - - name: Upload HTML report if check failed - uses: actions/upload-artifact@v4 - with: - name: html-report - path: htmlcov - if: ${{ failure() }} diff --git a/.github/workflows/skip-tests.yml b/.github/workflows/skip-tests.yml deleted file mode 100644 index fb9fea203..000000000 --- a/.github/workflows/skip-tests.yml +++ /dev/null @@ -1,76 +0,0 @@ ---- - -# We check that using all --skip options does not error. - -name: Skip tests - -on: - push: - branches: [main] - pull_request: - branches: [main] - schedule: - # * is a special character in YAML so you have to quote this string - # Run at 1:00 every day - - cron: 0 1 * * * - workflow_dispatch: {} - -jobs: - build: - - strategy: - matrix: - python-version: ['3.13'] - platform: [ubuntu-latest] - - runs-on: ${{ matrix.platform }} - - steps: - - uses: actions/checkout@v5 - - - name: Install uv - uses: astral-sh/setup-uv@v6 - with: - enable-cache: true - cache-dependency-glob: '**/pyproject.toml' - - - name: Set secrets file - run: | - cp ./vuforia_secrets.env.example ./vuforia_secrets.env - - - name: Run tests - run: | - uv run --extra=dev pytest \ - --skip-docker_build_tests \ - --skip-docker_in_memory \ - --skip-mock \ - --skip-real \ - --capture=no \ - -vvv \ - --exitfirst \ - --cov=src/ \ - --cov=tests/ \ - --cov-report=xml \ - . - env: - UV_PYTHON: ${{ matrix.python-version }} - - - name: Upload coverage data - uses: actions/upload-artifact@v4 - with: - name: coverage-data-skip-tests-${{ matrix.python-version }} - path: .coverage.* - include-hidden-files: true - if-no-files-found: ignore - - completion-skip-tests: - needs: build - runs-on: ubuntu-latest - if: always() # Run even if one matrix job fails - steps: - - name: Check matrix job status - run: |- - if ! ${{ needs.build.result == 'success' }}; then - echo "One or more matrix jobs failed" - exit 1 - fi diff --git a/.github/workflows/ci.yml b/.github/workflows/test.yml similarity index 66% rename from .github/workflows/ci.yml rename to .github/workflows/test.yml index 1c36d20c2..c74fd506a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,5 @@ --- - -name: CI +name: Test on: push: @@ -13,16 +12,13 @@ on: - cron: 0 1 * * * workflow_dispatch: {} -# We share Vuforia credentials and therefore Vuforia databases across -# workflows. -# We therefore want to run only one workflow at a time. -concurrency: vuforia_credentials - jobs: - build: - + # CI tests with matrix + ci-tests: runs-on: ubuntu-latest - + # We share Vuforia credentials and therefore Vuforia databases across + # workflows. We therefore want to run only one workflow at a time. + concurrency: vuforia_credentials strategy: fail-fast: false matrix: @@ -170,20 +166,156 @@ jobs: - name: Upload coverage data uses: actions/upload-artifact@v4 with: - name: | - coverage-data-ci-${{ matrix.python-version }}-${{ matrix.ci_pattern}} + name: coverage-data-ci-${{ matrix.python-version }}-${{ matrix.ci_pattern + }} + path: .coverage.* + include-hidden-files: true + if-no-files-found: ignore + + # Skip tests + skip-tests: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.13'] + platform: [ubuntu-latest] + + steps: + - uses: actions/checkout@v5 + + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + cache-dependency-glob: '**/pyproject.toml' + + - name: Set secrets file + run: | + cp ./vuforia_secrets.env.example ./vuforia_secrets.env + + - name: Run tests + run: | + uv run --extra=dev pytest \ + --skip-docker_build_tests \ + --skip-docker_in_memory \ + --skip-mock \ + --skip-real \ + --capture=no \ + -vvv \ + --exitfirst \ + --cov=src/ \ + --cov=tests/ \ + --cov-report=xml \ + . + env: + UV_PYTHON: ${{ matrix.python-version }} + + - name: Upload coverage data + uses: actions/upload-artifact@v4 + with: + name: coverage-data-skip-tests-${{ matrix.python-version }} + path: .coverage.* + include-hidden-files: true + if-no-files-found: ignore + + # Windows tests + windows-tests: + runs-on: windows-latest + strategy: + matrix: + python-version: ['3.13'] + + steps: + - uses: actions/checkout@v5 + + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + cache-dependency-glob: '**/pyproject.toml' + + - name: Set secrets file + run: | + cp ./vuforia_secrets.env.example ./vuforia_secrets.env + + - name: Run tests + run: | + # We use pytest-xdist to make this run much faster. + # The downside is that we cannot use -s / --capture=no. + uv run --extra=dev pytest --skip-real -vvv --exitfirst -n auto --cov=src/ --cov=tests/ --cov-report=xml . + env: + UV_PYTHON: ${{ matrix.python-version }} + + - name: Upload coverage data + uses: actions/upload-artifact@v4 + with: + name: coverage-data-windows-${{ matrix.python-version }} path: .coverage.* include-hidden-files: true if-no-files-found: ignore - completion-ci: - needs: build + # Coverage combination and enforcement + coverage: + name: Combine & check coverage + needs: [ci-tests, skip-tests, windows-tests] + if: always() runs-on: ubuntu-latest - if: always() # Run even if one matrix job fails + steps: - - name: Check matrix job status + - uses: actions/checkout@v5 + + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + cache-dependency-glob: '**/pyproject.toml' + + - uses: actions/download-artifact@v4 + with: + pattern: coverage-data-* + merge-multiple: true + + - name: Combine coverage & fail if it's <100% + run: | + uv tool install 'coverage[toml]' + + coverage combine + coverage html --skip-covered --skip-empty + + # Report and write to summary. + coverage report --format=markdown >> "$GITHUB_STEP_SUMMARY" + + # Report again and fail if under 100%. + coverage report --fail-under=100 + + - name: Upload HTML report if check failed + uses: actions/upload-artifact@v4 + with: + name: html-report + path: htmlcov + if: ${{ failure() }} + + # Final completion check + completion: + needs: [ci-tests, skip-tests, windows-tests, coverage] + runs-on: ubuntu-latest + if: always() + steps: + - name: Check all jobs status run: |- - if ! ${{ needs.build.result == 'success' }}; then - echo "One or more matrix jobs failed" + if ! ${{ needs.ci-tests.result == 'success' }}; then + echo "CI tests failed" + exit 1 + fi + if ! ${{ needs.skip-tests.result == 'success' }}; then + echo "Skip tests failed" + exit 1 + fi + if ! ${{ needs.windows-tests.result == 'success' }}; then + echo "Windows tests failed" + exit 1 + fi + if ! ${{ needs.coverage.result == 'success' }}; then + echo "Coverage check failed" exit 1 fi diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml deleted file mode 100644 index 16bcaff76..000000000 --- a/.github/workflows/windows-ci.yml +++ /dev/null @@ -1,65 +0,0 @@ ---- - -name: Windows CI - -on: - push: - branches: [main] - pull_request: - branches: [main] - schedule: - # * is a special character in YAML so you have to quote this string - # Run at 1:00 every day - - cron: 0 1 * * * - workflow_dispatch: {} - -jobs: - build: - - strategy: - matrix: - python-version: ['3.13'] - platform: [windows-latest] - - runs-on: ${{ matrix.platform }} - - steps: - - uses: actions/checkout@v5 - - - name: Install uv - uses: astral-sh/setup-uv@v6 - with: - enable-cache: true - cache-dependency-glob: '**/pyproject.toml' - - - name: Set secrets file - run: | - cp ./vuforia_secrets.env.example ./vuforia_secrets.env - - - name: Run tests - run: | - # We use pytest-xdist to make this run much faster. - # The downside is that we cannot use -s / --capture=no. - uv run --extra=dev pytest --skip-real -vvv --exitfirst -n auto --cov=src/ --cov=tests/ --cov-report=xml . - env: - UV_PYTHON: ${{ matrix.python-version }} - - - name: Upload coverage data - uses: actions/upload-artifact@v4 - with: - name: coverage-data-windows-${{ matrix.python-version }} - path: .coverage.* - include-hidden-files: true - if-no-files-found: ignore - - completion-windows-ci: - needs: build - runs-on: ubuntu-latest - if: always() # Run even if one matrix job fails - steps: - - name: Check matrix job status - run: |- - if ! ${{ needs.build.result == 'success' }}; then - echo "One or more matrix jobs failed" - exit 1 - fi From de06ee0514173be0620d3201bc3a88aa93db1b4f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 10:15:44 +0100 Subject: [PATCH 1986/2467] Update custom linter --- ci/test_custom_linters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/test_custom_linters.py b/ci/test_custom_linters.py index 8de79cbb8..f740f39a0 100644 --- a/ci/test_custom_linters.py +++ b/ci/test_custom_linters.py @@ -18,9 +18,9 @@ def _ci_patterns(*, repository_root: Path) -> set[str]: """ Return the CI patterns given in the CI configuration file. """ - ci_file = repository_root / ".github" / "workflows" / "ci.yml" + ci_file = repository_root / ".github" / "workflows" / "test.yml" github_workflow_config = yaml.safe_load(stream=ci_file.read_text()) - matrix = github_workflow_config["jobs"]["build"]["strategy"]["matrix"] + matrix = github_workflow_config["jobs"]["ci-tests"]["strategy"]["matrix"] ci_pattern_list = matrix["ci_pattern"] ci_patterns = set(ci_pattern_list) assert len(ci_pattern_list) == len(ci_patterns) From 1834228d643171c8b2b21e325ccd0c3ae6e97d1d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 10:36:00 +0100 Subject: [PATCH 1987/2467] Allow vuforia jobs to run in parallel --- .github/workflows/test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c74fd506a..d9982e8e1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,13 +12,14 @@ on: - cron: 0 1 * * * workflow_dispatch: {} +# We share Vuforia credentials and therefore Vuforia databases across +# workflows. We therefore want to run only one workflow at a time. +concurrency: vuforia_credentials + jobs: # CI tests with matrix ci-tests: runs-on: ubuntu-latest - # We share Vuforia credentials and therefore Vuforia databases across - # workflows. We therefore want to run only one workflow at a time. - concurrency: vuforia_credentials strategy: fail-fast: false matrix: From 6ec29b01d86ca2a69ed42dc98b9a2794114ddba1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 11:03:07 +0100 Subject: [PATCH 1988/2467] Error earlier if no fiels are found --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d9982e8e1..71933870d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -171,7 +171,7 @@ jobs: }} path: .coverage.* include-hidden-files: true - if-no-files-found: ignore + if-no-files-found: error # Skip tests skip-tests: @@ -217,7 +217,7 @@ jobs: name: coverage-data-skip-tests-${{ matrix.python-version }} path: .coverage.* include-hidden-files: true - if-no-files-found: ignore + if-no-files-found: error # Windows tests windows-tests: @@ -253,7 +253,7 @@ jobs: name: coverage-data-windows-${{ matrix.python-version }} path: .coverage.* include-hidden-files: true - if-no-files-found: ignore + if-no-files-found: error # Coverage combination and enforcement coverage: From 646510d5c1bb865f2268020da93ed2b346c8a2f5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 11:03:46 +0100 Subject: [PATCH 1989/2467] Add an ID to require 100% coverage step --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 71933870d..7a0409a88 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -276,7 +276,8 @@ jobs: pattern: coverage-data-* merge-multiple: true - - name: Combine coverage & fail if it's <100% + - name: Require 100% Coverage + id: coverage run: | uv tool install 'coverage[toml]' From 3201034ecdf30abfa5ee40423b51b211aa7f1a52 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 11:05:31 +0100 Subject: [PATCH 1990/2467] Try --cov-append --- .github/workflows/test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7a0409a88..0663b7cb9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -157,6 +157,7 @@ jobs: -vvv \ --showlocals \ --exitfirst \ + --cov-append \ --cov=src/ \ --cov=tests/ \ --cov-report=xml \ @@ -204,6 +205,7 @@ jobs: --capture=no \ -vvv \ --exitfirst \ + --cov-append \ --cov=src/ \ --cov=tests/ \ --cov-report=xml \ @@ -243,7 +245,7 @@ jobs: run: | # We use pytest-xdist to make this run much faster. # The downside is that we cannot use -s / --capture=no. - uv run --extra=dev pytest --skip-real -vvv --exitfirst -n auto --cov=src/ --cov=tests/ --cov-report=xml . + uv run --extra=dev pytest --skip-real -vvv --exitfirst -n auto --cov-append --cov=src/ --cov=tests/ --cov-report=xml . env: UV_PYTHON: ${{ matrix.python-version }} From b8b4e412388a788e2c8e89bd7cdddf5cddfbf619 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 11:14:12 +0100 Subject: [PATCH 1991/2467] Do not write unnecessary XML --- .github/workflows/test.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0663b7cb9..0e2146f35 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -160,7 +160,6 @@ jobs: --cov-append \ --cov=src/ \ --cov=tests/ \ - --cov-report=xml \ ${{ matrix.ci_pattern }} env: UV_PYTHON: ${{ matrix.python-version }} @@ -208,7 +207,6 @@ jobs: --cov-append \ --cov=src/ \ --cov=tests/ \ - --cov-report=xml \ . env: UV_PYTHON: ${{ matrix.python-version }} @@ -245,7 +243,7 @@ jobs: run: | # We use pytest-xdist to make this run much faster. # The downside is that we cannot use -s / --capture=no. - uv run --extra=dev pytest --skip-real -vvv --exitfirst -n auto --cov-append --cov=src/ --cov=tests/ --cov-report=xml . + uv run --extra=dev pytest --skip-real -vvv --exitfirst -n auto --cov-append --cov=src/ --cov=tests/ . env: UV_PYTHON: ${{ matrix.python-version }} From 8fa90b53f451141abc89c8da095bfe395e6c6da7 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 11:24:43 +0100 Subject: [PATCH 1992/2467] Try without pytest-cov --- .github/workflows/test.yml | 62 +++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0e2146f35..6da17f205 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -152,23 +152,25 @@ jobs: - name: Run tests run: | - uv run --extra=dev pytest \ - -s \ - -vvv \ - --showlocals \ - --exitfirst \ - --cov-append \ - --cov=src/ \ - --cov=tests/ \ - ${{ matrix.ci_pattern }} + uv run --extra=dev \ + coverage run \ + --parallel-mode \ + --source=src/ \ + --source=tests/ \ + -m pytest \ + -s \ + -vvv \ + --showlocals \ + --exitfirst \ + ${{ matrix.ci_pattern }} env: UV_PYTHON: ${{ matrix.python-version }} - name: Upload coverage data uses: actions/upload-artifact@v4 with: - name: coverage-data-ci-${{ matrix.python-version }}-${{ matrix.ci_pattern - }} + name: | + coverage-data-ci-${{ matrix.python-version }}-${{ matrix.ci_pattern}} path: .coverage.* include-hidden-files: true if-no-files-found: error @@ -196,18 +198,20 @@ jobs: - name: Run tests run: | - uv run --extra=dev pytest \ - --skip-docker_build_tests \ - --skip-docker_in_memory \ - --skip-mock \ - --skip-real \ - --capture=no \ - -vvv \ - --exitfirst \ - --cov-append \ - --cov=src/ \ - --cov=tests/ \ - . + uv run --extra=dev \ + coverage run \ + --parallel-mode \ + --source=src/ \ + --source=tests/ \ + -m pytest \ + --skip-docker_build_tests \ + --skip-docker_in_memory \ + --skip-mock \ + --skip-real \ + --capture=no \ + -vvv \ + --exitfirst \ + . env: UV_PYTHON: ${{ matrix.python-version }} @@ -243,7 +247,17 @@ jobs: run: | # We use pytest-xdist to make this run much faster. # The downside is that we cannot use -s / --capture=no. - uv run --extra=dev pytest --skip-real -vvv --exitfirst -n auto --cov-append --cov=src/ --cov=tests/ . + uv run --extra=dev \ + coverage run \ + --parallel-mode \ + --source=src/ \ + --source=tests/ \ + -m pytest \ + --skip-real \ + -vvv \ + --exitfirst \ + -n auto \ + . env: UV_PYTHON: ${{ matrix.python-version }} From 58d25ceb2ea5032205e408fcc02b3e3b00305592 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 11:27:10 +0100 Subject: [PATCH 1993/2467] Switch to bash shell on Windows --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6da17f205..82d112cee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -244,6 +244,7 @@ jobs: cp ./vuforia_secrets.env.example ./vuforia_secrets.env - name: Run tests + shell: bash run: | # We use pytest-xdist to make this run much faster. # The downside is that we cannot use -s / --capture=no. From 5670e38e030a97e6ffa916dc3705478311f45dca Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 11:30:37 +0100 Subject: [PATCH 1994/2467] Sanitize pattern names --- .github/workflows/test.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 82d112cee..5e966b674 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -166,11 +166,16 @@ jobs: env: UV_PYTHON: ${{ matrix.python-version }} + - name: Sanitize pattern for artifact name + id: sanitize + run: | + SANITIZED_PATTERN=$(echo "${{ matrix.ci_pattern }}" | sed 's/::/-/g' | sed 's/:/-/g' | sed 's|/|-|g') + echo "name=coverage-data-ci-${{ matrix.python-version }}-${SANITIZED_PATTERN}" >> "$GITHUB_OUTPUT" + - name: Upload coverage data uses: actions/upload-artifact@v4 with: - name: | - coverage-data-ci-${{ matrix.python-version }}-${{ matrix.ci_pattern}} + name: ${{ steps.sanitize.outputs.name }} path: .coverage.* include-hidden-files: true if-no-files-found: error From 9e568f60ce45a871960d24dc55f0e01e2a4e94f6 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 11:44:09 +0100 Subject: [PATCH 1995/2467] Try not doing Windows coverage --- .github/workflows/test.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5e966b674..526172e0e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -267,13 +267,13 @@ jobs: env: UV_PYTHON: ${{ matrix.python-version }} - - name: Upload coverage data - uses: actions/upload-artifact@v4 - with: - name: coverage-data-windows-${{ matrix.python-version }} - path: .coverage.* - include-hidden-files: true - if-no-files-found: error + # - name: Upload coverage data + # uses: actions/upload-artifact@v4 + # with: + # name: coverage-data-windows-${{ matrix.python-version }} + # path: .coverage.* + # include-hidden-files: true + # if-no-files-found: error # Coverage combination and enforcement coverage: From 957340153f58befa11a8ef69eb0831144c138ecb Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 12:04:25 +0100 Subject: [PATCH 1996/2467] Tidy up a bit --- .github/workflows/test.yml | 15 ++++++--------- pyproject.toml | 2 +- tests/mock_vws/test_docker.py | 12 +++++++----- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 526172e0e..1042c73d2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -253,6 +253,12 @@ jobs: run: | # We use pytest-xdist to make this run much faster. # The downside is that we cannot use -s / --capture=no. + # + # We use coverage to collect coverage data but we currently + # do not upload / use it because combining Windows and Linux + # coverage is challenging. + # + # We therefore have a few ``# pragma: no cover`` statements. uv run --extra=dev \ coverage run \ --parallel-mode \ @@ -267,15 +273,6 @@ jobs: env: UV_PYTHON: ${{ matrix.python-version }} - # - name: Upload coverage data - # uses: actions/upload-artifact@v4 - # with: - # name: coverage-data-windows-${{ matrix.python-version }} - # path: .coverage.* - # include-hidden-files: true - # if-no-files-found: error - - # Coverage combination and enforcement coverage: name: Combine & check coverage needs: [ci-tests, skip-tests, windows-tests] diff --git a/pyproject.toml b/pyproject.toml index 627672bac..051535a80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,7 @@ optional-dependencies.dev = [ "actionlint-py==1.7.7.23", "check-manifest==0.50", "check-wheel-contents==0.6.3", + "coverage==7.10.7", "deptry==0.23.1", "dirty-equals==0.10.0", "doc8==1.1.1", @@ -72,7 +73,6 @@ optional-dependencies.dev = [ "pyright==1.1.406", "pyroma==5.0", "pytest==8.4.2", - "pytest-cov==7.0.0", "pytest-retry==1.7.0", "pytest-xdist==3.8.0", "python-dotenv==1.1.1", diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py index 56c5fc6ce..d3d18c16a 100644 --- a/tests/mock_vws/test_docker.py +++ b/tests/mock_vws/test_docker.py @@ -65,7 +65,9 @@ def fixture_custom_bridge_network() -> Iterator[Network]: name = "test-vws-bridge-" + uuid.uuid4().hex try: network = client.networks.create(name=name, driver="bridge") - except NotFound: + # We skip coverage here because combining Windows and Linux coverage + # is challenging. + except NotFound: # pragma: no cover # On Windows the "bridge" network driver is not available and we use # the "nat" driver instead. network = client.networks.create(name=name, driver="nat") @@ -116,15 +118,15 @@ def test_build_and_run( target="target-manager", rm=True, ) - except BuildError as exc: + # We skip coverage here because combining Windows and Linux coverage + # is challenging. + except BuildError as exc: # pragma: no cover full_log = "\n".join( [item["stream"] for item in exc.build_log if "stream" in item], ) # If this assertion fails, it may be useful to look at the other # properties of ``exc``. - if ( - "no matching manifest for windows/amd64" not in exc.msg - ): # pragma: no cover + if "no matching manifest for windows/amd64" not in exc.msg: raise AssertionError(full_log) from exc pytest.skip( reason="We do not currently support using Windows containers." From 0a08bf823c7d903958bca8c2d4151d968386d561 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 13:48:16 +0100 Subject: [PATCH 1997/2467] Update CI badge link in README --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 98004f17c..cd8434d30 100644 --- a/README.rst +++ b/README.rst @@ -54,7 +54,7 @@ See the `full documentation `__. This includes details on how to use the mock, options, and details of the differences between the mock and the real Vuforia Web Services. -.. |Build Status| image:: https://github.com/VWS-Python/vws-python-mock/actions/workflows/ci.yml/badge.svg?branch=main +.. |Build Status| image:: https://github.com/VWS-Python/vws-python-mock/actions/workflows/test.yml/badge.svg?branch=main :target: https://github.com/VWS-Python/vws-python-mock/actions .. |PyPI| image:: https://badge.fury.io/py/VWS-Python-Mock.svg :target: https://badge.fury.io/py/VWS-Python-Mock From c8e3ef5e503948c7830a745582910fcbe032a6a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 05:06:30 +0000 Subject: [PATCH 1998/2467] Bump astral-sh/setup-uv from 6 to 7 Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 6 to 7. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v6...v7) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4bb62749d..1c429db99 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v5 - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 422dff0df..c9038cc5c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1042c73d2..dee8d5e1d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -119,7 +119,7 @@ jobs: - uses: actions/checkout@v5 - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' @@ -192,7 +192,7 @@ jobs: - uses: actions/checkout@v5 - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' @@ -239,7 +239,7 @@ jobs: - uses: actions/checkout@v5 - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' @@ -283,7 +283,7 @@ jobs: - uses: actions/checkout@v5 - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' From bcfd59ced44282492f4e89f7433148ca9e37b9e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 05:06:33 +0000 Subject: [PATCH 1999/2467] Bump actions/download-artifact from 4 to 5 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1042c73d2..be69096ab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -288,7 +288,7 @@ jobs: enable-cache: true cache-dependency-glob: '**/pyproject.toml' - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: pattern: coverage-data-* merge-multiple: true From 9b1a2f38cd8a97a5f8a563c3fb92f7474f456d8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 05:07:35 +0000 Subject: [PATCH 2000/2467] Bump ruff from 0.13.3 to 0.14.0 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.13.3 to 0.14.0. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.13.3...0.14.0) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.14.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d232b6245..a9db4441c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.3", "requests-mock-flask==2025.1.13", - "ruff==0.13.3", + "ruff==0.14.0", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 7847cfa75f98e9ce26a96b4b456283b001356333 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 05:06:05 +0000 Subject: [PATCH 2001/2467] Bump pyproject-fmt from 2.7.0 to 2.10.0 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.7.0 to 2.10.0. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.7.0...pyproject-fmt/2.10.0) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.10.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a9db4441c..25e3b52c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==3.3.9", "pylint-per-file-ignores==2.0.3", - "pyproject-fmt==2.7.0", + "pyproject-fmt==2.10.0", "pyright==1.1.406", "pyroma==5.0", "pytest==8.4.2", From 8ed16922592b72be5fd694bb85ebcb24be6cc76a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 05:06:32 +0000 Subject: [PATCH 2002/2467] Bump types-docker from 7.1.0.20250916 to 7.1.0.20251009 Bumps [types-docker](https://github.com/typeshed-internal/stub_uploader) from 7.1.0.20250916 to 7.1.0.20251009. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20251009 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a9db4441c..18627d4ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.1", "sybil==9.2.0", "tenacity==9.1.2", - "types-docker==7.1.0.20250916", + "types-docker==7.1.0.20251009", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", "urllib3==2.5.0", From c4a959ab84201f89ae4d19058b0ef173d9d842f1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 10 Oct 2025 08:46:33 +0100 Subject: [PATCH 2003/2467] Fix and simplify coverage config coverage only takes the last --source --- .github/workflows/test.yml | 56 +++++++++++++++----------------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 831c05d79..1cb5974e4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -153,16 +153,12 @@ jobs: - name: Run tests run: | uv run --extra=dev \ - coverage run \ - --parallel-mode \ - --source=src/ \ - --source=tests/ \ - -m pytest \ - -s \ - -vvv \ - --showlocals \ - --exitfirst \ - ${{ matrix.ci_pattern }} + coverage run -m pytest \ + -s \ + -vvv \ + --showlocals \ + --exitfirst \ + ${{ matrix.ci_pattern }} env: UV_PYTHON: ${{ matrix.python-version }} @@ -204,19 +200,15 @@ jobs: - name: Run tests run: | uv run --extra=dev \ - coverage run \ - --parallel-mode \ - --source=src/ \ - --source=tests/ \ - -m pytest \ - --skip-docker_build_tests \ - --skip-docker_in_memory \ - --skip-mock \ - --skip-real \ - --capture=no \ - -vvv \ - --exitfirst \ - . + coverage run -m pytest \ + --skip-docker_build_tests \ + --skip-docker_in_memory \ + --skip-mock \ + --skip-real \ + --capture=no \ + -vvv \ + --exitfirst \ + . env: UV_PYTHON: ${{ matrix.python-version }} @@ -260,16 +252,12 @@ jobs: # # We therefore have a few ``# pragma: no cover`` statements. uv run --extra=dev \ - coverage run \ - --parallel-mode \ - --source=src/ \ - --source=tests/ \ - -m pytest \ - --skip-real \ - -vvv \ - --exitfirst \ - -n auto \ - . + coverage run -m pytest \ + --skip-real \ + -vvv \ + --exitfirst \ + -n auto \ + . env: UV_PYTHON: ${{ matrix.python-version }} @@ -305,7 +293,7 @@ jobs: coverage report --format=markdown >> "$GITHUB_STEP_SUMMARY" # Report again and fail if under 100%. - coverage report --fail-under=100 + coverage report - name: Upload HTML report if check failed uses: actions/upload-artifact@v4 From 75fa85fefd05dba01119e0df0422b1532730e24a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 10 Oct 2025 08:56:45 +0100 Subject: [PATCH 2004/2467] Use parallel settings --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 128e02c88..013d9bbc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -365,6 +365,8 @@ branch = true omit = [ "src/mock_vws/_flask_server/healthcheck.py", ] +parallel = true +source = [ "src/", "tests/" ] [tool.coverage.report] @@ -372,6 +374,7 @@ exclude_also = [ "if TYPE_CHECKING:", "class .*\\bProtocol\\):", ] +fail_under = 100 [tool.mypy] From 66b5aa4e02b2c218f11510a269af13f585495d0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 05:07:31 +0000 Subject: [PATCH 2005/2467] Bump stefanzweifel/git-auto-commit-action from 6 to 7 Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 6 to 7. - [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases) - [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/v6...v7) --- updated-dependencies: - dependency-name: stefanzweifel/git-auto-commit-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c9038cc5c..501f46ba5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,7 +60,7 @@ jobs: include: CHANGELOG.rst regex: false - - uses: stefanzweifel/git-auto-commit-action@v6 + - uses: stefanzweifel/git-auto-commit-action@v7 id: commit with: commit_message: Bump CHANGELOG From 177dfa2b6085d1dde1c14c85b8745589283135cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 05:09:59 +0000 Subject: [PATCH 2006/2467] Bump actionlint-py from 1.7.7.23 to 1.7.8.24 Bumps [actionlint-py](https://github.com/Mateusz-Grzelinski/actionlint-py) from 1.7.7.23 to 1.7.8.24. - [Commits](https://github.com/Mateusz-Grzelinski/actionlint-py/compare/v1.7.7.23...v1.7.8.24) --- updated-dependencies: - dependency-name: actionlint-py dependency-version: 1.7.8.24 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 013d9bbc2..d1a43252b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dependencies = [ "werkzeug>=3.1.2", ] optional-dependencies.dev = [ - "actionlint-py==1.7.7.23", + "actionlint-py==1.7.8.24", "check-manifest==0.50", "check-wheel-contents==0.6.3", "coverage==7.10.7", From f6af6b23997c659d3ab388c561741515e3a64da2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 05:10:20 +0000 Subject: [PATCH 2007/2467] Bump pylint-per-file-ignores from 2.0.3 to 3.0.0 Bumps [pylint-per-file-ignores](https://github.com/SAP/pylint-per-file-ignores) from 2.0.3 to 3.0.0. - [Release notes](https://github.com/SAP/pylint-per-file-ignores/releases) - [Changelog](https://github.com/SAP/pylint-per-file-ignores/blob/main/CHANGELOG.md) - [Commits](https://github.com/SAP/pylint-per-file-ignores/compare/v2.0.3...v3.0.0) --- updated-dependencies: - dependency-name: pylint-per-file-ignores dependency-version: 3.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 013d9bbc2..c0fe35697 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ optional-dependencies.dev = [ "pre-commit==4.3.0", "pydocstyle==6.3", "pylint[spelling]==3.3.9", - "pylint-per-file-ignores==2.0.3", + "pylint-per-file-ignores==3.0.0", "pyproject-fmt==2.10.0", "pyright==1.1.406", "pyroma==5.0", From fbbdb0bd90aea01bb57e17a44941dc8738273848 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 05:11:32 +0000 Subject: [PATCH 2008/2467] Bump yamlfix from 1.18.0 to 1.19.0 Bumps [yamlfix](https://github.com/lyz-code/yamlfix) from 1.18.0 to 1.19.0. - [Changelog](https://github.com/lyz-code/yamlfix/blob/main/CHANGELOG.md) - [Commits](https://github.com/lyz-code/yamlfix/commits) --- updated-dependencies: - dependency-name: yamlfix dependency-version: 1.19.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 013d9bbc2..bf210e880 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,7 +103,7 @@ optional-dependencies.dev = [ "vws-python==2025.3.10.1", "vws-test-fixtures==2023.3.5", "vws-web-tools==2024.10.6.1", - "yamlfix==1.18.0", + "yamlfix==1.19.0", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python-mock/" From 7ac8d0e0740adea888276785b0aa573bf4f09370 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 05:05:55 +0000 Subject: [PATCH 2009/2467] Bump pylint-per-file-ignores from 3.0.0 to 3.1.0 Bumps [pylint-per-file-ignores](https://github.com/SAP/pylint-per-file-ignores) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/SAP/pylint-per-file-ignores/releases) - [Changelog](https://github.com/SAP/pylint-per-file-ignores/blob/main/CHANGELOG.md) - [Commits](https://github.com/SAP/pylint-per-file-ignores/compare/v3.0.0...v3.1.0) --- updated-dependencies: - dependency-name: pylint-per-file-ignores dependency-version: 3.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3016185ae..f07304e53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ optional-dependencies.dev = [ "pre-commit==4.3.0", "pydocstyle==6.3", "pylint[spelling]==3.3.9", - "pylint-per-file-ignores==3.0.0", + "pylint-per-file-ignores==3.1.0", "pyproject-fmt==2.10.0", "pyright==1.1.406", "pyroma==5.0", From 800c580b922bfb9dae8708c436184694c6b689d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 05:20:24 +0000 Subject: [PATCH 2010/2467] Bump pylint[spelling] from 3.3.9 to 4.0.0 Bumps [pylint[spelling]](https://github.com/pylint-dev/pylint) from 3.3.9 to 4.0.0. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v3.3.9...v4.0.0) --- updated-dependencies: - dependency-name: pylint[spelling] dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f07304e53..e3a2681e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ optional-dependencies.dev = [ "mypy-strict-kwargs==2025.4.3", "pre-commit==4.3.0", "pydocstyle==6.3", - "pylint[spelling]==3.3.9", + "pylint[spelling]==4.0.0", "pylint-per-file-ignores==3.1.0", "pyproject-fmt==2.10.0", "pyright==1.1.406", From 7ce35e0406f4d182162d88a854f7463e9d63f384 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 05:06:08 +0000 Subject: [PATCH 2011/2467] Bump check-manifest from 0.50 to 0.51 Bumps [check-manifest](https://github.com/mgedmin/check-manifest) from 0.50 to 0.51. - [Changelog](https://github.com/mgedmin/check-manifest/blob/master/CHANGES.rst) - [Commits](https://github.com/mgedmin/check-manifest/compare/0.50...0.51) --- updated-dependencies: - dependency-name: check-manifest dependency-version: '0.51' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e3a2681e1..cce3cec58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ dependencies = [ ] optional-dependencies.dev = [ "actionlint-py==1.7.8.24", - "check-manifest==0.50", + "check-manifest==0.51", "check-wheel-contents==0.6.3", "coverage==7.10.7", "deptry==0.23.1", From 29c74d1fcf46df08e570cb0a54822644c72a5362 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 05:06:14 +0000 Subject: [PATCH 2012/2467] Bump pylint[spelling] from 4.0.0 to 4.0.1 Bumps [pylint[spelling]](https://github.com/pylint-dev/pylint) from 4.0.0 to 4.0.1. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v4.0.0...v4.0.1) --- updated-dependencies: - dependency-name: pylint[spelling] dependency-version: 4.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e3a2681e1..61393b55c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ optional-dependencies.dev = [ "mypy-strict-kwargs==2025.4.3", "pre-commit==4.3.0", "pydocstyle==6.3", - "pylint[spelling]==4.0.0", + "pylint[spelling]==4.0.1", "pylint-per-file-ignores==3.1.0", "pyproject-fmt==2.10.0", "pyright==1.1.406", From 65b955c1eeb9964bd6ad050c3846beae6ea25bdb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 05:06:19 +0000 Subject: [PATCH 2013/2467] Bump pyproject-fmt from 2.10.0 to 2.11.0 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.10.0 to 2.11.0. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.10.0...pyproject-fmt/2.11.0) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e3a2681e1..b4aa861a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==4.0.0", "pylint-per-file-ignores==3.1.0", - "pyproject-fmt==2.10.0", + "pyproject-fmt==2.11.0", "pyright==1.1.406", "pyroma==5.0", "pytest==8.4.2", From ed39625b17812ad8059ea6695bebbbea49c89ba7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 05:22:25 +0000 Subject: [PATCH 2014/2467] Bump coverage from 7.10.7 to 7.11.0 Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.10.7 to 7.11.0. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/7.10.7...7.11.0) --- updated-dependencies: - dependency-name: coverage dependency-version: 7.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cce3cec58..ccd4aad06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ optional-dependencies.dev = [ "actionlint-py==1.7.8.24", "check-manifest==0.51", "check-wheel-contents==0.6.3", - "coverage==7.10.7", + "coverage==7.11.0", "deptry==0.23.1", "dirty-equals==0.10.0", "doc8==1.1.1", From c248059452796884c6456bba0deb8597c70bd381 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 05:06:42 +0000 Subject: [PATCH 2015/2467] Bump ruff from 0.14.0 to 0.14.1 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.0 to 0.14.1. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.14.0...0.14.1) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.14.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 366f3cdcb..0d0020128 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.3", "requests-mock-flask==2025.1.13", - "ruff==0.14.0", + "ruff==0.14.1", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From acc7c3870cc1e4880472ba331da8560f9f496ec5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 05:15:48 +0000 Subject: [PATCH 2016/2467] Bump doccmd from 2025.9.19 to 2025.10.18 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.9.19 to 2025.10.18. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.09.19...2025.10.18) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.10.18 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0d0020128..d9c5d1750 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.23.1", "dirty-equals==0.10.0", "doc8==1.1.1", - "doccmd==2025.9.19", + "doccmd==2025.10.18", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From 7928cdfb1ab7e390a3ee7290401d8e4e3158a642 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 05:06:37 +0000 Subject: [PATCH 2017/2467] Bump pylint[spelling] from 4.0.1 to 4.0.2 Bumps [pylint[spelling]](https://github.com/pylint-dev/pylint) from 4.0.1 to 4.0.2. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v4.0.1...v4.0.2) --- updated-dependencies: - dependency-name: pylint[spelling] dependency-version: 4.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d9c5d1750..c8de3540d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ optional-dependencies.dev = [ "mypy-strict-kwargs==2025.4.3", "pre-commit==4.3.0", "pydocstyle==6.3", - "pylint[spelling]==4.0.1", + "pylint[spelling]==4.0.2", "pylint-per-file-ignores==3.1.0", "pyproject-fmt==2.11.0", "pyright==1.1.406", From 2f774ee9c725c9196f1764e956a9a5dc3794bf79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 05:06:21 +0000 Subject: [PATCH 2018/2467] Bump ruff from 0.14.1 to 0.14.2 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.1 to 0.14.2. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.14.1...0.14.2) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.14.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c8de3540d..69aa34ee9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "python-dotenv==1.1.1", "pyyaml==6.0.3", "requests-mock-flask==2025.1.13", - "ruff==0.14.1", + "ruff==0.14.2", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From cff70281ed1fd49f3bb25c4f68489e94b11f0edf Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Oct 2025 02:08:12 +0000 Subject: [PATCH 2019/2467] Remove docs/Makefile and update pre-commit to use sphinx-build directly --- .pre-commit-config.yaml | 15 +++++++++------ docs/Makefile | 19 ------------------- 2 files changed, 9 insertions(+), 25 deletions(-) delete mode 100644 docs/Makefile diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 804ad23ab..164c11489 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -133,7 +133,8 @@ repos: - id: shellcheck-docs name: shellcheck-docs # We exclude SC2215 as it is a false positive for an unknown reason on Windows. - entry: uv run --extra=dev doccmd --language=shell --language=console --command="shellcheck + entry: + uv run --extra=dev doccmd --language=shell --language=console --command="shellcheck --shell=bash --exclude=SC2215" language: python types_or: [markdown, rst] @@ -150,7 +151,8 @@ repos: - id: shfmt-docs name: shfmt-docs - entry: uv run --extra=dev doccmd --language=shell --language=console --skip-marker=shfmt + entry: + uv run --extra=dev doccmd --language=shell --language=console --skip-marker=shfmt --no-pad-file --command="shfmt --write --space-redirects --indent=4" language: python types_or: [markdown, rst] @@ -282,7 +284,8 @@ repos: - id: ruff-format-fix-docs name: Ruff format docs - entry: uv run --extra=dev doccmd --language=python --no-pad-file --command="ruff + entry: + uv run --extra=dev doccmd --language=python --no-pad-file --command="ruff format" language: python types_or: [markdown, rst] @@ -323,7 +326,7 @@ repos: stages: [pre-commit] - id: linkcheck name: linkcheck - entry: make -C docs/ linkcheck SPHINXOPTS=-W + entry: uv run --extra=dev sphinx-build -M linkcheck docs/source docs/build -W language: python types_or: [rst] stages: [manual] @@ -332,7 +335,7 @@ repos: - id: spelling name: spelling - entry: make -C docs/ spelling SPHINXOPTS=-W + entry: uv run --extra=dev sphinx-build -M spelling docs/source docs/build -W language: python types_or: [rst] stages: [manual] @@ -341,7 +344,7 @@ repos: - id: docs name: Build Documentation - entry: make docs + entry: uv run --extra=dev sphinx-build -M html docs/source docs/build language: python stages: [manual] pass_filenames: false diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 7aba47eda..000000000 --- a/docs/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @uv run --extra=dev $(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @uv run --extra=dev $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) From 695aef955825dd3be17850753644eacd9cf98b5a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 02:10:32 +0000 Subject: [PATCH 2020/2467] [pre-commit.ci lite] apply automatic fixes --- .pre-commit-config.yaml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 164c11489..de9a52f52 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -133,8 +133,7 @@ repos: - id: shellcheck-docs name: shellcheck-docs # We exclude SC2215 as it is a false positive for an unknown reason on Windows. - entry: - uv run --extra=dev doccmd --language=shell --language=console --command="shellcheck + entry: uv run --extra=dev doccmd --language=shell --language=console --command="shellcheck --shell=bash --exclude=SC2215" language: python types_or: [markdown, rst] @@ -151,8 +150,7 @@ repos: - id: shfmt-docs name: shfmt-docs - entry: - uv run --extra=dev doccmd --language=shell --language=console --skip-marker=shfmt + entry: uv run --extra=dev doccmd --language=shell --language=console --skip-marker=shfmt --no-pad-file --command="shfmt --write --space-redirects --indent=4" language: python types_or: [markdown, rst] @@ -284,8 +282,7 @@ repos: - id: ruff-format-fix-docs name: Ruff format docs - entry: - uv run --extra=dev doccmd --language=python --no-pad-file --command="ruff + entry: uv run --extra=dev doccmd --language=python --no-pad-file --command="ruff format" language: python types_or: [markdown, rst] @@ -326,7 +323,8 @@ repos: stages: [pre-commit] - id: linkcheck name: linkcheck - entry: uv run --extra=dev sphinx-build -M linkcheck docs/source docs/build -W + entry: uv run --extra=dev sphinx-build -M linkcheck docs/source docs/build + -W language: python types_or: [rst] stages: [manual] @@ -335,7 +333,8 @@ repos: - id: spelling name: spelling - entry: uv run --extra=dev sphinx-build -M spelling docs/source docs/build -W + entry: uv run --extra=dev sphinx-build -M spelling docs/source docs/build + -W language: python types_or: [rst] stages: [manual] From 3685694b94abab34a8b72708487ff3d953c61b0e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Oct 2025 02:12:10 +0000 Subject: [PATCH 2021/2467] Update base Makefile to use sphinx-build directly --- Makefile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Makefile b/Makefile index e7c51da19..e6ee537af 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,4 @@ SHELL := /bin/bash -euxo pipefail - -# Treat Sphinx warnings as errors -SPHINXOPTS := -W - .PHONY: update-secrets update-secrets: # After updating secrets, commit the new secrets.tar.gpg file. @@ -11,7 +7,7 @@ update-secrets: .PHONY: docs docs: - make -C docs clean html SPHINXOPTS=$(SPHINXOPTS) + uv run --extra=dev sphinx-build -M html docs/source docs/build -W .PHONY: open-docs open-docs: From 6154730dd1d5668e4869e35d0f2c8cd389a4061d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Oct 2025 02:22:53 +0000 Subject: [PATCH 2022/2467] Add -W flag to docs hook for consistent error handling --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index de9a52f52..046d35636 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -343,7 +343,7 @@ repos: - id: docs name: Build Documentation - entry: uv run --extra=dev sphinx-build -M html docs/source docs/build + entry: uv run --extra=dev sphinx-build -M html docs/source docs/build -W language: python stages: [manual] pass_filenames: false From 480ce898b0d2216a6a94fcb98e945cc1832931aa Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Oct 2025 02:34:38 +0000 Subject: [PATCH 2023/2467] Add MIT LICENSE file --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..c9f18d1a3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Adam Dangoor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 1983798cae5388c68c3b16125eaa6b628e627c10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 05:21:06 +0000 Subject: [PATCH 2024/2467] Bump python-dotenv from 1.1.1 to 1.2.1 Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 1.1.1 to 1.2.1. - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md) - [Commits](https://github.com/theskumar/python-dotenv/compare/v1.1.1...v1.2.1) --- updated-dependencies: - dependency-name: python-dotenv dependency-version: 1.2.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 69aa34ee9..9d7e5f4ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,7 +75,7 @@ optional-dependencies.dev = [ "pytest==8.4.2", "pytest-retry==1.7.0", "pytest-xdist==3.8.0", - "python-dotenv==1.1.1", + "python-dotenv==1.2.1", "pyyaml==6.0.3", "requests-mock-flask==2025.1.13", "ruff==0.14.2", From a186b56e573f9e1b716a2394bec9d61f82e68738 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 05:23:01 +0000 Subject: [PATCH 2025/2467] Bump actions/download-artifact from 5 to 6 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1cb5974e4..683799d86 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -276,7 +276,7 @@ jobs: enable-cache: true cache-dependency-glob: '**/pyproject.toml' - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: pattern: coverage-data-* merge-multiple: true From edcbe5c89632de8422eb829507f1485033f01017 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 05:23:37 +0000 Subject: [PATCH 2026/2467] Bump pyright from 1.1.406 to 1.1.407 Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.406 to 1.1.407. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.406...v1.1.407) --- updated-dependencies: - dependency-name: pyright dependency-version: 1.1.407 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 69aa34ee9..07f0fefec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.2", "pylint-per-file-ignores==3.1.0", "pyproject-fmt==2.11.0", - "pyright==1.1.406", + "pyright==1.1.407", "pyroma==5.0", "pytest==8.4.2", "pytest-retry==1.7.0", From 6eb8da0c1483a708607f5bd9e3d1d54875b9dd22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 05:23:49 +0000 Subject: [PATCH 2027/2467] Bump doccmd from 2025.10.18 to 2025.10.25 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.10.18 to 2025.10.25. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.10.18...2025.10.25) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.10.25 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 69aa34ee9..c2ae397e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.23.1", "dirty-equals==0.10.0", "doc8==1.1.1", - "doccmd==2025.10.18", + "doccmd==2025.10.25", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From 108258149554fd77043c63dc41410024455d1770 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 05:24:03 +0000 Subject: [PATCH 2028/2467] Bump sphinx-substitution-extensions from 2025.6.6 to 2025.10.24 Bumps [sphinx-substitution-extensions](https://github.com/adamtheturtle/sphinx-substitution-extensions) from 2025.6.6 to 2025.10.24. - [Release notes](https://github.com/adamtheturtle/sphinx-substitution-extensions/releases) - [Changelog](https://github.com/adamtheturtle/sphinx-substitution-extensions/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/sphinx-substitution-extensions/compare/2025.06.06...2025.10.24) --- updated-dependencies: - dependency-name: sphinx-substitution-extensions dependency-version: 2025.10.24 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 69aa34ee9..ed5e22919 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,7 +89,7 @@ optional-dependencies.dev = [ "sphinx-lint==1.0.0", "sphinx-paramlinks==0.6", "sphinx-pyproject==0.3.0", - "sphinx-substitution-extensions==2025.6.6", + "sphinx-substitution-extensions==2025.10.24", "sphinx-toolbox==4.0.0", "sphinxcontrib-httpdomain==1.8.1", "sphinxcontrib-spelling==8.0.1", From 0e366b5bb43a9d99e7dd92e8e644a71d7c1da517 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 05:24:03 +0000 Subject: [PATCH 2029/2467] Bump actions/upload-artifact from 4 to 5 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1cb5974e4..8de39cfac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -169,7 +169,7 @@ jobs: echo "name=coverage-data-ci-${{ matrix.python-version }}-${SANITIZED_PATTERN}" >> "$GITHUB_OUTPUT" - name: Upload coverage data - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ steps.sanitize.outputs.name }} path: .coverage.* @@ -213,7 +213,7 @@ jobs: UV_PYTHON: ${{ matrix.python-version }} - name: Upload coverage data - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: coverage-data-skip-tests-${{ matrix.python-version }} path: .coverage.* @@ -296,7 +296,7 @@ jobs: coverage report - name: Upload HTML report if check failed - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: html-report path: htmlcov From 55d91d93c5afe316b9a54bee3827bf1d50df8fdb Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Oct 2025 12:22:45 +0000 Subject: [PATCH 2030/2467] Add .prettierrc configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Prettier configuration file with YAML single quote override from click-compose. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .prettierrc | 10 ++++++++++ pyproject.toml | 1 + 2 files changed, 11 insertions(+) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..3ab9aa054 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "overrides": [ + { + "files": ["*.yaml", "*.yml"], + "options": { + "singleQuote": true + } + } + ] +} diff --git a/pyproject.toml b/pyproject.toml index 69aa34ee9..8f140b0f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -302,6 +302,7 @@ make-summary-multi-line = true ignore = [ ".checkmake-config.ini", + ".prettierrc", ".yamlfmt", "*.enc", "admin/**", From 30ba1374b8e2a1472d00dab9d82b4777e8fba429 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Oct 2025 22:19:05 +0000 Subject: [PATCH 2031/2467] Remove Makefile and replace make commands with direct equivalents --- Makefile | 14 -------------- admin/create_secrets_files.py | 3 +++ docs/source/ci-setup.rst | 3 ++- docs/source/contributing.rst | 4 ++-- 4 files changed, 7 insertions(+), 17 deletions(-) delete mode 100644 Makefile diff --git a/Makefile b/Makefile deleted file mode 100644 index e6ee537af..000000000 --- a/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -SHELL := /bin/bash -euxo pipefail -.PHONY: update-secrets -update-secrets: - # After updating secrets, commit the new secrets.tar.gpg file. - tar cvf secrets.tar ci_secrets/ - gpg --yes --batch --passphrase=${PASSPHRASE_FOR_VUFORIA_SECRETS} --symmetric --cipher-algo AES256 secrets.tar - -.PHONY: docs -docs: - uv run --extra=dev sphinx-build -M html docs/source docs/build -W - -.PHONY: open-docs -open-docs: - python -c 'import os, webbrowser; webbrowser.open("file://" + os.path.abspath("docs/build/html/index.html"))' diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 702091c87..3b1e4244f 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -10,6 +10,9 @@ $ export EXISTING_SECRETS_FILE=/existing/file/with/inactive/db/creds # You may have to run this a few times, but it is idempotent. $ python admin/create_secrets_files.py + # After creating the secrets, update the encrypted archive: + $ tar cvf secrets.tar ci_secrets/ + $ gpg --yes --batch --passphrase=${PASSPHRASE_FOR_VUFORIA_SECRETS} --symmetric --cipher-algo AES256 secrets.tar """ import datetime diff --git a/docs/source/ci-setup.rst b/docs/source/ci-setup.rst index 0c46b4d0b..3e8c4bfd9 100644 --- a/docs/source/ci-setup.rst +++ b/docs/source/ci-setup.rst @@ -35,7 +35,8 @@ Add the encrypted secrets files to the repository: .. code-block:: console - $ PASSPHRASE_FOR_VUFORIA_SECRETS="" make update-secrets + $ tar cvf secrets.tar ci_secrets/ + $ gpg --yes --batch --passphrase=${PASSPHRASE_FOR_VUFORIA_SECRETS} --symmetric --cipher-algo AES256 secrets.tar $ git add secrets.tar.gpg $ git commit -m "Update secret archive" $ git push diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index daa07ba76..e507386e4 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -120,8 +120,8 @@ Run the following commands to build and view documentation locally: .. code-block:: console - $ make docs - $ make open-docs + $ uv run --extra=dev sphinx-build -M html docs/source docs/build -W + $ python -c 'import os, webbrowser; webbrowser.open("file://" + os.path.abspath("docs/build/html/index.html"))' Continuous Integration ---------------------- From ae9f6b1fe93b998387d667e17fd751b771fb2b0b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Oct 2025 22:26:53 +0000 Subject: [PATCH 2032/2467] Quote an environment variable --- admin/create_secrets_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 3b1e4244f..d226395d9 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -12,7 +12,7 @@ $ python admin/create_secrets_files.py # After creating the secrets, update the encrypted archive: $ tar cvf secrets.tar ci_secrets/ - $ gpg --yes --batch --passphrase=${PASSPHRASE_FOR_VUFORIA_SECRETS} --symmetric --cipher-algo AES256 secrets.tar + $ gpg --yes --batch --passphrase="${PASSPHRASE_FOR_VUFORIA_SECRETS}" --symmetric --cipher-algo AES256 secrets.tar """ import datetime From 188a51d9c2bd970bf65e92d84024936358bb685b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Oct 2025 22:27:23 +0000 Subject: [PATCH 2033/2467] Fix ruff --- admin/create_secrets_files.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index d226395d9..19c971199 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -12,7 +12,13 @@ $ python admin/create_secrets_files.py # After creating the secrets, update the encrypted archive: $ tar cvf secrets.tar ci_secrets/ - $ gpg --yes --batch --passphrase="${PASSPHRASE_FOR_VUFORIA_SECRETS}" --symmetric --cipher-algo AES256 secrets.tar + $ gpg \ + --yes \ + --batch \ + --passphrase="${PASSPHRASE_FOR_VUFORIA_SECRETS}" \ + --symmetric \ + --cipher-algo AES256 \ + secrets.tar """ import datetime From 0850aa718e416cd9d1a993302606ae3ce34af42a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Oct 2025 22:29:07 +0000 Subject: [PATCH 2034/2467] Improve docstring --- admin/create_secrets_files.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 19c971199..0f0a45146 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -4,14 +4,12 @@ $ export VWS_EMAIL_ADDRESS=... $ export VWS_PASSWORD=... - # For ``make update-secrets`` to work, this has to be ``./ci_secrets``, or - # you have to copy the secrets there later. $ export NEW_SECRETS_DIR=... $ export EXISTING_SECRETS_FILE=/existing/file/with/inactive/db/creds # You may have to run this a few times, but it is idempotent. $ python admin/create_secrets_files.py # After creating the secrets, update the encrypted archive: - $ tar cvf secrets.tar ci_secrets/ + $ tar cvf secrets.tar "${NEW_SECRETS_DIR}" $ gpg \ --yes \ --batch \ From 847a5aa61361d8229e52d1dee201fb573d928bac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 05:06:06 +0000 Subject: [PATCH 2035/2467] Bump doccmd from 2025.10.25 to 2025.10.27 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.10.25 to 2025.10.27. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.10.25...2025.10.27) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.10.27 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5f5300df2..7079536be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.23.1", "dirty-equals==0.10.0", "doc8==1.1.1", - "doccmd==2025.10.25", + "doccmd==2025.10.27", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From 283379de6b357804204c883a2195beed4f4ed229 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 28 Oct 2025 10:39:15 +0000 Subject: [PATCH 2036/2467] Fix quoting in GPG command for secrets archive --- docs/source/ci-setup.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/ci-setup.rst b/docs/source/ci-setup.rst index 3e8c4bfd9..dbe735882 100644 --- a/docs/source/ci-setup.rst +++ b/docs/source/ci-setup.rst @@ -36,7 +36,7 @@ Add the encrypted secrets files to the repository: .. code-block:: console $ tar cvf secrets.tar ci_secrets/ - $ gpg --yes --batch --passphrase=${PASSPHRASE_FOR_VUFORIA_SECRETS} --symmetric --cipher-algo AES256 secrets.tar + $ gpg --yes --batch --passphrase="${PASSPHRASE_FOR_VUFORIA_SECRETS}" --symmetric --cipher-algo AES256 secrets.tar $ git add secrets.tar.gpg $ git commit -m "Update secret archive" $ git push From 8e66aed3bc48eb6d3372615809eae3a7f43e3e13 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 28 Oct 2025 11:11:47 +0000 Subject: [PATCH 2037/2467] Bump uv to 0.9.5 (#2739) --- .pre-commit-config.yaml | 54 ++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 046d35636..512e80393 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -103,7 +103,7 @@ repos: language: python types_or: [yaml, python] pass_filenames: false - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] - id: actionlint name: actionlint @@ -111,7 +111,7 @@ repos: language: python pass_filenames: false types_or: [yaml] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: docformatter @@ -119,7 +119,7 @@ repos: entry: uv run --extra=dev -m docformatter --in-place language: python types_or: [python] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: shellcheck @@ -127,7 +127,7 @@ repos: entry: uv run --extra=dev shellcheck --shell=bash language: python types_or: [shell] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: shellcheck-docs @@ -137,7 +137,7 @@ repos: --shell=bash --exclude=SC2215" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: shfmt @@ -145,7 +145,7 @@ repos: entry: shfmt --write --space-redirects --indent=4 language: python types_or: [shell] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: shfmt-docs @@ -154,7 +154,7 @@ repos: --no-pad-file --command="shfmt --write --space-redirects --indent=4" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: mypy @@ -164,7 +164,7 @@ repos: language: python types_or: [python, toml] pass_filenames: false - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] - id: mypy-docs name: mypy-docs @@ -179,7 +179,7 @@ repos: entry: uv run --extra=dev -m check_manifest language: python pass_filenames: false - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] - id: pyright name: pyright @@ -188,7 +188,7 @@ repos: language: python types_or: [python, toml] pass_filenames: false - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] - id: pyright-docs name: pyright-docs @@ -204,7 +204,7 @@ repos: language: python pass_filenames: false types_or: [python] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] - id: vulture name: vulture @@ -212,7 +212,7 @@ repos: language: python types_or: [python] pass_filenames: false - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: vulture-docs @@ -221,7 +221,7 @@ repos: language: python types_or: [python] pass_filenames: false - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: pyroma @@ -230,7 +230,7 @@ repos: language: python pass_filenames: false types_or: [toml] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: deptry @@ -238,7 +238,7 @@ repos: entry: uv run --extra=dev -m deptry src/ language: python pass_filenames: false - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: pylint @@ -247,7 +247,7 @@ repos: language: python stages: [manual] pass_filenames: false - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] - id: pylint-docs name: pylint-docs @@ -261,7 +261,7 @@ repos: entry: uv run --extra=dev -m ruff check --fix language: python types_or: [python] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: ruff-check-fix-docs @@ -269,7 +269,7 @@ repos: entry: uv run --extra=dev doccmd --language=python --command="ruff check --fix" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: ruff-format-fix @@ -277,7 +277,7 @@ repos: entry: uv run --extra=dev -m ruff format language: python types_or: [python] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: ruff-format-fix-docs @@ -286,7 +286,7 @@ repos: format" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: doc8 @@ -294,7 +294,7 @@ repos: entry: uv run --extra=dev -m doc8 language: python types_or: [rst] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: interrogate @@ -310,7 +310,7 @@ repos: entry: uv run --extra=dev doccmd --language=python --command="interrogate" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: pyproject-fmt-fix @@ -329,7 +329,7 @@ repos: types_or: [rst] stages: [manual] pass_filenames: false - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] - id: spelling name: spelling @@ -339,7 +339,7 @@ repos: types_or: [rst] stages: [manual] pass_filenames: false - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] - id: docs name: Build Documentation @@ -347,14 +347,14 @@ repos: language: python stages: [manual] pass_filenames: false - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] - id: yamlfix name: pyproject-fmt entry: uv run --extra=dev yamlfix language: python types_or: [yaml] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: sphinx-lint @@ -362,5 +362,5 @@ repos: entry: uv run --extra=dev sphinx-lint --enable=all --disable=line-too-long language: python types_or: [rst] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] From 60c30dfacbfeb0eaa0244461549f33e110b365fd Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 28 Oct 2025 11:45:24 +0000 Subject: [PATCH 2038/2467] Move instructions to contributing docs --- admin/create_secrets_files.py | 18 +----------------- docs/source/contributing.rst | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 0f0a45146..4a6dd7449 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -1,22 +1,6 @@ """Create licenses and target databases for the tests to run against. -Usage: - - $ export VWS_EMAIL_ADDRESS=... - $ export VWS_PASSWORD=... - $ export NEW_SECRETS_DIR=... - $ export EXISTING_SECRETS_FILE=/existing/file/with/inactive/db/creds - # You may have to run this a few times, but it is idempotent. - $ python admin/create_secrets_files.py - # After creating the secrets, update the encrypted archive: - $ tar cvf secrets.tar "${NEW_SECRETS_DIR}" - $ gpg \ - --yes \ - --batch \ - --passphrase="${PASSPHRASE_FOR_VUFORIA_SECRETS}" \ - --symmetric \ - --cipher-algo AES256 \ - secrets.tar +See the instructions in the contributing guide in the documentation. """ import datetime diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index e507386e4..6d7d40a7a 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -91,8 +91,25 @@ To create an inactive project, delete the license key associated with a database Targets sometimes get stuck at the "Processing" stage meaning that they cannot be deleted. When this happens, create a new target database to use for testing. -To create databases without using the browser, use :file:`admin/create_secrets_files.py`. -See instructions in that file. +To create databases without using the browser, use :file:`admin/create_secrets_files.py`: + +.. code-block:: bash + + $ export VWS_EMAIL_ADDRESS=... + $ export VWS_PASSWORD=... + $ export NEW_SECRETS_DIR=... + $ export EXISTING_SECRETS_FILE=/existing/file/with/inactive/db/creds + # You may have to run this a few times, but it is idempotent. + $ python admin/create_secrets_files.py + # After creating the secrets, update the encrypted archive: + $ tar cvf secrets.tar "${NEW_SECRETS_DIR}" + $ gpg \ + --yes \ + --batch \ + --passphrase="${PASSPHRASE_FOR_VUFORIA_SECRETS}" \ + --symmetric \ + --cipher-algo AES256 \ + secrets.tar .. _Vuforia License Manager: https://developer.vuforia.com/vui/develop/licenses .. _Vuforia Target Manager: https://developer.vuforia.com/vui/develop/databases From e4e482ec21641dfc2eb4f1436fa15ff3d1f9e097 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 28 Oct 2025 12:19:13 +0000 Subject: [PATCH 2039/2467] Add view and edit buttons to Sphinx docs --- docs/source/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 3c1e6a83b..d5da1232e 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -74,6 +74,7 @@ html_show_sourcelink = False html_theme_options = { "sidebar_hide_name": False, + "top_of_page_buttons": ["view", "edit"], } # Retry link checking to avoid transient network errors. From 867a6034d823f2ee74792b75a8ee9f0f5e4bbc5f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 28 Oct 2025 13:42:06 +0000 Subject: [PATCH 2040/2467] Configure Furo top-of-page buttons with repository details --- docs/source/conf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index d5da1232e..c24499f79 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -74,7 +74,9 @@ html_show_sourcelink = False html_theme_options = { "sidebar_hide_name": False, - "top_of_page_buttons": ["view", "edit"], + "source_repository": "https://github.com/VWS-Python/vws-python-mock/", + "source_branch": "main", + "source_directory": "docs/source/", } # Retry link checking to avoid transient network errors. From e274a1b07f921874e596c556da4aa41911418ae3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Oct 2025 05:05:16 +0000 Subject: [PATCH 2041/2467] Bump sphinx-lint from 1.0.0 to 1.0.1 Bumps [sphinx-lint](https://github.com/sphinx-contrib/sphinx-lint) from 1.0.0 to 1.0.1. - [Release notes](https://github.com/sphinx-contrib/sphinx-lint/releases) - [Commits](https://github.com/sphinx-contrib/sphinx-lint/compare/v1.0.0...v1.0.1) --- updated-dependencies: - dependency-name: sphinx-lint dependency-version: 1.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7079536be..d486cc9dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,7 @@ optional-dependencies.dev = [ "shfmt-py==3.12.0.2", "sphinx==8.2.3", "sphinx-copybutton==0.5.2", - "sphinx-lint==1.0.0", + "sphinx-lint==1.0.1", "sphinx-paramlinks==0.6", "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2025.10.24", From 380423dbae6e84e56f0b43ba4f3480bd310ba4c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 05:06:12 +0000 Subject: [PATCH 2042/2467] Bump ruff from 0.14.2 to 0.14.3 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.2 to 0.14.3. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.14.2...0.14.3) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.14.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d486cc9dc..f3551a613 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "python-dotenv==1.2.1", "pyyaml==6.0.3", "requests-mock-flask==2025.1.13", - "ruff==0.14.2", + "ruff==0.14.3", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 8eb5c5bdc0a2ad36e17a170c81f4a78ffbe853d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 05:05:50 +0000 Subject: [PATCH 2043/2467] Bump pyproject-fmt from 2.11.0 to 2.11.1 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.11.0 to 2.11.1. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/commits) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.11.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f3551a613..2f6e5ed20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==4.0.2", "pylint-per-file-ignores==3.1.0", - "pyproject-fmt==2.11.0", + "pyproject-fmt==2.11.1", "pyright==1.1.407", "pyroma==5.0", "pytest==8.4.2", From 6aaa0ffa18815c829718fc9c1181101df7eb2393 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 05:06:32 +0000 Subject: [PATCH 2044/2467] Bump ruff from 0.14.3 to 0.14.4 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.3 to 0.14.4. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.14.3...0.14.4) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.14.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2f6e5ed20..d17e40b11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "python-dotenv==1.2.1", "pyyaml==6.0.3", "requests-mock-flask==2025.1.13", - "ruff==0.14.3", + "ruff==0.14.4", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 842a8276201a2913103609fc1dc49cb7530acc32 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 8 Nov 2025 16:40:38 +0000 Subject: [PATCH 2045/2467] Add --no-write-to-file flag for read-only doccmd commands (#2746) --- .pre-commit-config.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 512e80393..32aec9be7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -133,8 +133,8 @@ repos: - id: shellcheck-docs name: shellcheck-docs # We exclude SC2215 as it is a false positive for an unknown reason on Windows. - entry: uv run --extra=dev doccmd --language=shell --language=console --command="shellcheck - --shell=bash --exclude=SC2215" + entry: uv run --extra=dev doccmd --no-write-to-file --language=shell --language=console + --command="shellcheck --shell=bash --exclude=SC2215" language: python types_or: [markdown, rst] additional_dependencies: [uv==0.9.5] @@ -169,7 +169,7 @@ repos: - id: mypy-docs name: mypy-docs stages: [pre-push] - entry: uv run --extra=dev doccmd --language=python --command="mypy" + entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="mypy" language: python types_or: [markdown, rst] @@ -193,7 +193,7 @@ repos: - id: pyright-docs name: pyright-docs stages: [pre-push] - entry: uv run --extra=dev doccmd --language=python --command="pyright" + entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="pyright" language: python types_or: [markdown, rst] @@ -217,7 +217,7 @@ repos: - id: vulture-docs name: vulture docs - entry: uv run --extra=dev doccmd --language=python --command="vulture" + entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="vulture" language: python types_or: [python] pass_filenames: false @@ -251,7 +251,7 @@ repos: - id: pylint-docs name: pylint-docs - entry: uv run --extra=dev doccmd --language=python --command="pylint" + entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="pylint" language: python stages: [manual] types_or: [markdown, rst] @@ -307,7 +307,7 @@ repos: - id: interrogate-docs name: interrogate docs - entry: uv run --extra=dev doccmd --language=python --command="interrogate" + entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="interrogate" language: python types_or: [markdown, rst] additional_dependencies: [uv==0.9.5] From b696d9c81f547340635bba165c11d979c4545a65 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 8 Nov 2025 20:13:52 +0000 Subject: [PATCH 2046/2467] Add --example-workers 0 to read-only doccmd hooks --- .pre-commit-config.yaml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 32aec9be7..b1f42363a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -133,8 +133,8 @@ repos: - id: shellcheck-docs name: shellcheck-docs # We exclude SC2215 as it is a false positive for an unknown reason on Windows. - entry: uv run --extra=dev doccmd --no-write-to-file --language=shell --language=console - --command="shellcheck --shell=bash --exclude=SC2215" + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=shell + --language=console --command="shellcheck --shell=bash --exclude=SC2215" language: python types_or: [markdown, rst] additional_dependencies: [uv==0.9.5] @@ -169,7 +169,8 @@ repos: - id: mypy-docs name: mypy-docs stages: [pre-push] - entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="mypy" + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python + --command="mypy" language: python types_or: [markdown, rst] @@ -193,7 +194,8 @@ repos: - id: pyright-docs name: pyright-docs stages: [pre-push] - entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="pyright" + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python + --command="pyright" language: python types_or: [markdown, rst] @@ -217,7 +219,8 @@ repos: - id: vulture-docs name: vulture docs - entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="vulture" + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python + --command="vulture" language: python types_or: [python] pass_filenames: false @@ -251,7 +254,8 @@ repos: - id: pylint-docs name: pylint-docs - entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="pylint" + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python + --command="pylint" language: python stages: [manual] types_or: [markdown, rst] @@ -307,7 +311,8 @@ repos: - id: interrogate-docs name: interrogate docs - entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="interrogate" + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python + --command="interrogate" language: python types_or: [markdown, rst] additional_dependencies: [uv==0.9.5] From 61d0921b708d9b16394d37a113ef9fff61d8491d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 05:11:01 +0000 Subject: [PATCH 2047/2467] Bump pre-commit from 4.3.0 to 4.4.0 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.3.0 to 4.4.0. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v4.3.0...v4.4.0) --- updated-dependencies: - dependency-name: pre-commit dependency-version: 4.4.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d17e40b11..79ddd79c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.18.2", "mypy-strict-kwargs==2025.4.3", - "pre-commit==4.3.0", + "pre-commit==4.4.0", "pydocstyle==6.3", "pylint[spelling]==4.0.2", "pylint-per-file-ignores==3.1.0", From 24dc761eb7fa31a29d799f3f522d1dbe63daee84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 05:11:12 +0000 Subject: [PATCH 2048/2467] Bump coverage from 7.11.0 to 7.11.3 Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.11.0 to 7.11.3. - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](https://github.com/coveragepy/coveragepy/compare/7.11.0...7.11.3) --- updated-dependencies: - dependency-name: coverage dependency-version: 7.11.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d17e40b11..92c217938 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ optional-dependencies.dev = [ "actionlint-py==1.7.8.24", "check-manifest==0.51", "check-wheel-contents==0.6.3", - "coverage==7.11.0", + "coverage==7.11.3", "deptry==0.23.1", "dirty-equals==0.10.0", "doc8==1.1.1", From afcbecb72ae1f49981b175040e1f530c7d0d70e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 05:11:45 +0000 Subject: [PATCH 2049/2467] Bump doccmd from 2025.10.27 to 2025.11.8.1 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.10.27 to 2025.11.8.1. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.10.27...2025.11.08.1) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.11.8.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d17e40b11..b24e89022 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.23.1", "dirty-equals==0.10.0", "doc8==1.1.1", - "doccmd==2025.10.27", + "doccmd==2025.11.8.1", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From 8b73302c686e1305747b8abe815f03e3311bdd50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 05:12:00 +0000 Subject: [PATCH 2050/2467] Bump pytest from 8.4.2 to 9.0.0 Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.4.2 to 9.0.0. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.4.2...9.0.0) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d17e40b11..16df9af4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pyproject-fmt==2.11.1", "pyright==1.1.407", "pyroma==5.0", - "pytest==8.4.2", + "pytest==9.0.0", "pytest-retry==1.7.0", "pytest-xdist==3.8.0", "python-dotenv==1.2.1", From 4b2c9a6801a22fb3e9668fbbb1e258a10f6a4591 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:36:06 +0000 Subject: [PATCH 2051/2467] Bump deptry from 0.23.1 to 0.24.0 Bumps [deptry](https://github.com/fpgmaas/deptry) from 0.23.1 to 0.24.0. - [Release notes](https://github.com/fpgmaas/deptry/releases) - [Changelog](https://github.com/fpgmaas/deptry/blob/main/CHANGELOG.md) - [Commits](https://github.com/fpgmaas/deptry/compare/0.23.1...0.24.0) --- updated-dependencies: - dependency-name: deptry dependency-version: 0.24.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index eed624d7c..4b0909935 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "check-wheel-contents==0.6.3", "coverage==7.11.3", - "deptry==0.23.1", + "deptry==0.24.0", "dirty-equals==0.10.0", "doc8==1.1.1", "doccmd==2025.11.8.1", From 07ab2d105d8b1520584c575e836a2928d961bf1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 05:06:21 +0000 Subject: [PATCH 2052/2467] Bump pytest from 9.0.0 to 9.0.1 Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.0 to 9.0.1. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/9.0.0...9.0.1) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4b0909935..ef5df2e31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pyproject-fmt==2.11.1", "pyright==1.1.407", "pyroma==5.0", - "pytest==9.0.0", + "pytest==9.0.1", "pytest-retry==1.7.0", "pytest-xdist==3.8.0", "python-dotenv==1.2.1", From d762e1c43a544df1e4c7ef7cba4f8578b4631c50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 05:08:22 +0000 Subject: [PATCH 2053/2467] Bump pylint[spelling] from 4.0.2 to 4.0.3 Bumps [pylint[spelling]](https://github.com/pylint-dev/pylint) from 4.0.2 to 4.0.3. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v4.0.2...v4.0.3) --- updated-dependencies: - dependency-name: pylint[spelling] dependency-version: 4.0.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ef5df2e31..92e04fd04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ optional-dependencies.dev = [ "mypy-strict-kwargs==2025.4.3", "pre-commit==4.4.0", "pydocstyle==6.3", - "pylint[spelling]==4.0.2", + "pylint[spelling]==4.0.3", "pylint-per-file-ignores==3.1.0", "pyproject-fmt==2.11.1", "pyright==1.1.407", From 3ea55879c28f278295d0bcf909493c426ded847f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 05:08:38 +0000 Subject: [PATCH 2054/2467] Bump ruff from 0.14.4 to 0.14.5 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.4 to 0.14.5. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.14.4...0.14.5) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.14.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ef5df2e31..06694a73d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "python-dotenv==1.2.1", "pyyaml==6.0.3", "requests-mock-flask==2025.1.13", - "ruff==0.14.4", + "ruff==0.14.5", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 1d54f6e084b3dfc15bcfda54852701e11343c044 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 05:07:09 +0000 Subject: [PATCH 2055/2467] Bump dirty-equals from 0.10.0 to 0.11 Bumps [dirty-equals](https://github.com/samuelcolvin/dirty-equals) from 0.10.0 to 0.11. - [Release notes](https://github.com/samuelcolvin/dirty-equals/releases) - [Commits](https://github.com/samuelcolvin/dirty-equals/compare/v0.10.0...v0.11.0) --- updated-dependencies: - dependency-name: dirty-equals dependency-version: '0.11' dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 65cd1d0ad..4e5ca3328 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ optional-dependencies.dev = [ "check-wheel-contents==0.6.3", "coverage==7.11.3", "deptry==0.24.0", - "dirty-equals==0.10.0", + "dirty-equals==0.11", "doc8==1.1.1", "doccmd==2025.11.8.1", "docformatter==1.7.7", From d35048e9d15dcffba5d46aa95cf256731bfa1822 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 05:07:14 +0000 Subject: [PATCH 2056/2467] Bump sphinx-substitution-extensions from 2025.10.24 to 2025.11.17 Bumps [sphinx-substitution-extensions](https://github.com/adamtheturtle/sphinx-substitution-extensions) from 2025.10.24 to 2025.11.17. - [Release notes](https://github.com/adamtheturtle/sphinx-substitution-extensions/releases) - [Changelog](https://github.com/adamtheturtle/sphinx-substitution-extensions/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/sphinx-substitution-extensions/compare/2025.10.24...2025.11.17) --- updated-dependencies: - dependency-name: sphinx-substitution-extensions dependency-version: 2025.11.17 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 65cd1d0ad..030b6f610 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,7 +89,7 @@ optional-dependencies.dev = [ "sphinx-lint==1.0.1", "sphinx-paramlinks==0.6", "sphinx-pyproject==0.3.0", - "sphinx-substitution-extensions==2025.10.24", + "sphinx-substitution-extensions==2025.11.17", "sphinx-toolbox==4.0.0", "sphinxcontrib-httpdomain==1.8.1", "sphinxcontrib-spelling==8.0.1", From 36a292875abf50104474515ad934912a558d5983 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 05:06:51 +0000 Subject: [PATCH 2057/2467] Bump coverage from 7.11.3 to 7.12.0 Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.11.3 to 7.12.0. - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](https://github.com/coveragepy/coveragepy/compare/7.11.3...7.12.0) --- updated-dependencies: - dependency-name: coverage dependency-version: 7.12.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 369f1d2d1..163c179bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ optional-dependencies.dev = [ "actionlint-py==1.7.8.24", "check-manifest==0.51", "check-wheel-contents==0.6.3", - "coverage==7.11.3", + "coverage==7.12.0", "deptry==0.24.0", "dirty-equals==0.11", "doc8==1.1.1", From 6ba88269bf528055401e7b7a6238ebcc5473d473 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 05:06:23 +0000 Subject: [PATCH 2058/2467] Bump doccmd from 2025.11.8.1 to 2025.11.20 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.11.8.1 to 2025.11.20. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.11.08.1...2025.11.20) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.11.20 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 163c179bf..09a1dcd98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==1.1.1", - "doccmd==2025.11.8.1", + "doccmd==2025.11.20", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From 4aa515b08392169d970607e41fe3162d9fac89df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 05:06:39 +0000 Subject: [PATCH 2059/2467] Bump sphinx-lint from 1.0.1 to 1.0.2 Bumps [sphinx-lint](https://github.com/sphinx-contrib/sphinx-lint) from 1.0.1 to 1.0.2. - [Release notes](https://github.com/sphinx-contrib/sphinx-lint/releases) - [Commits](https://github.com/sphinx-contrib/sphinx-lint/compare/v1.0.1...v1.0.2) --- updated-dependencies: - dependency-name: sphinx-lint dependency-version: 1.0.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 163c179bf..3db638b16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,7 @@ optional-dependencies.dev = [ "shfmt-py==3.12.0.2", "sphinx==8.2.3", "sphinx-copybutton==0.5.2", - "sphinx-lint==1.0.1", + "sphinx-lint==1.0.2", "sphinx-paramlinks==0.6", "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2025.11.17", From cf471dc6532c253793d29b764010bde29f5c6bfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 05:06:23 +0000 Subject: [PATCH 2060/2467] Bump actions/checkout from 5 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-build.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index c9bc1593b..63e9b6d26 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -30,7 +30,7 @@ jobs: - name: vwq steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1c429db99..5127a1bbc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,7 +24,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install uv uses: astral-sh/setup-uv@v7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 501f46ba5..582548baa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: contents: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: # Fetch all history including tags. # Needed to find the latest tag. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8605aa834..e1f354fc2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -116,7 +116,7 @@ jobs: - docs/source/basic-example.rst steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install uv uses: astral-sh/setup-uv@v7 @@ -185,7 +185,7 @@ jobs: platform: [ubuntu-latest] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install uv uses: astral-sh/setup-uv@v7 @@ -228,7 +228,7 @@ jobs: python-version: ['3.13'] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install uv uses: astral-sh/setup-uv@v7 @@ -268,7 +268,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install uv uses: astral-sh/setup-uv@v7 From 81db7b89605fdea0aae6cae5418a5b16f973c789 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 05:07:56 +0000 Subject: [PATCH 2061/2467] Bump actionlint-py from 1.7.8.24 to 1.7.9.24 Bumps [actionlint-py](https://github.com/Mateusz-Grzelinski/actionlint-py) from 1.7.8.24 to 1.7.9.24. - [Commits](https://github.com/Mateusz-Grzelinski/actionlint-py/compare/v1.7.8.24...v1.7.9.24) --- updated-dependencies: - dependency-name: actionlint-py dependency-version: 1.7.9.24 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 772ec17e0..a672cb33c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dependencies = [ "werkzeug>=3.1.2", ] optional-dependencies.dev = [ - "actionlint-py==1.7.8.24", + "actionlint-py==1.7.9.24", "check-manifest==0.51", "check-wheel-contents==0.6.3", "coverage==7.12.0", From 0e8585c7419c77c27342c642e187b95114612ebb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 05:08:17 +0000 Subject: [PATCH 2062/2467] Bump pre-commit from 4.4.0 to 4.5.0 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.4.0 to 4.5.0. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v4.4.0...v4.5.0) --- updated-dependencies: - dependency-name: pre-commit dependency-version: 4.5.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 772ec17e0..ac23426e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.18.2", "mypy-strict-kwargs==2025.4.3", - "pre-commit==4.4.0", + "pre-commit==4.5.0", "pydocstyle==6.3", "pylint[spelling]==4.0.3", "pylint-per-file-ignores==3.1.0", From 2285bc5f77400f43fcb4eba864ac25fb9a6aae77 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 05:07:11 +0000 Subject: [PATCH 2063/2467] Bump types-docker from 7.1.0.20251009 to 7.1.0.20251125 Bumps [types-docker](https://github.com/typeshed-internal/stub_uploader) from 7.1.0.20251009 to 7.1.0.20251125. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20251125 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ccc0b5d00..564e40e1b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.1", "sybil==9.2.0", "tenacity==9.1.2", - "types-docker==7.1.0.20251009", + "types-docker==7.1.0.20251125", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", "urllib3==2.5.0", From 24f5ba9c588988115f19af669b7ef758c8ff18be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 05:05:10 +0000 Subject: [PATCH 2064/2467] Bump pylint-per-file-ignores from 3.1.0 to 3.2.0 Bumps [pylint-per-file-ignores](https://github.com/SAP/pylint-per-file-ignores) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/SAP/pylint-per-file-ignores/releases) - [Changelog](https://github.com/SAP/pylint-per-file-ignores/blob/main/CHANGELOG.md) - [Commits](https://github.com/SAP/pylint-per-file-ignores/compare/v3.1.0...v3.2.0) --- updated-dependencies: - dependency-name: pylint-per-file-ignores dependency-version: 3.2.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 564e40e1b..9d5a44dd4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ optional-dependencies.dev = [ "pre-commit==4.5.0", "pydocstyle==6.3", "pylint[spelling]==4.0.3", - "pylint-per-file-ignores==3.1.0", + "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", "pyright==1.1.407", "pyroma==5.0", From fc78b67e6bddeb5bae77ad5eba63a0c71eef5859 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 05:06:17 +0000 Subject: [PATCH 2065/2467] Bump types-docker from 7.1.0.20251125 to 7.1.0.20251127 Bumps [types-docker](https://github.com/typeshed-internal/stub_uploader) from 7.1.0.20251125 to 7.1.0.20251127. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20251127 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9d5a44dd4..0bb13f0b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.1", "sybil==9.2.0", "tenacity==9.1.2", - "types-docker==7.1.0.20251125", + "types-docker==7.1.0.20251127", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", "urllib3==2.5.0", From e4bdb2c7745adec8084ca194e044b1f1e8f09477 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 05:42:04 +0000 Subject: [PATCH 2066/2467] Bump sphinxcontrib-spelling from 8.0.1 to 8.0.2 Bumps [sphinxcontrib-spelling](https://github.com/sphinx-contrib/spelling) from 8.0.1 to 8.0.2. - [Release notes](https://github.com/sphinx-contrib/spelling/releases) - [Commits](https://github.com/sphinx-contrib/spelling/compare/8.0.1...8.0.2) --- updated-dependencies: - dependency-name: sphinxcontrib-spelling dependency-version: 8.0.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0bb13f0b9..250ea347e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,7 +92,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.11.17", "sphinx-toolbox==4.0.0", "sphinxcontrib-httpdomain==1.8.1", - "sphinxcontrib-spelling==8.0.1", + "sphinxcontrib-spelling==8.0.2", "sybil==9.2.0", "tenacity==9.1.2", "types-docker==7.1.0.20251127", From 43c3a95dd5952e19a2f9e961a710bcbf4451a8c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 05:42:34 +0000 Subject: [PATCH 2067/2467] Bump pylint[spelling] from 4.0.3 to 4.0.4 Bumps [pylint[spelling]](https://github.com/pylint-dev/pylint) from 4.0.3 to 4.0.4. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v4.0.3...v4.0.4) --- updated-dependencies: - dependency-name: pylint[spelling] dependency-version: 4.0.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0bb13f0b9..b590130ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ optional-dependencies.dev = [ "mypy-strict-kwargs==2025.4.3", "pre-commit==4.5.0", "pydocstyle==6.3", - "pylint[spelling]==4.0.3", + "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", "pyright==1.1.407", From 29b14ef6e57f3a46f05ce51df496e1a8e1cc0225 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 05:42:48 +0000 Subject: [PATCH 2068/2467] Bump ruff from 0.14.5 to 0.14.7 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.5 to 0.14.7. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.14.5...0.14.7) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.14.7 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0bb13f0b9..46a9cb796 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "python-dotenv==1.2.1", "pyyaml==6.0.3", "requests-mock-flask==2025.1.13", - "ruff==0.14.5", + "ruff==0.14.7", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 1e13bc0f1311061da94bc4b04e57184cf4e8b942 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 05:42:52 +0000 Subject: [PATCH 2069/2467] Bump mypy[faster-cache] from 1.18.2 to 1.19.0 Bumps [mypy[faster-cache]](https://github.com/python/mypy) from 1.18.2 to 1.19.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.18.2...v1.19.0) --- updated-dependencies: - dependency-name: mypy[faster-cache] dependency-version: 1.19.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0bb13f0b9..97e2944e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ optional-dependencies.dev = [ "freezegun==1.5.5", "furo==2025.9.25", "interrogate==1.7.0", - "mypy[faster-cache]==1.18.2", + "mypy[faster-cache]==1.19.0", "mypy-strict-kwargs==2025.4.3", "pre-commit==4.5.0", "pydocstyle==6.3", From a74b25c597db868bcb2f5c0138abf131bac4698b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 06:04:24 +0000 Subject: [PATCH 2070/2467] Bump types-docker from 7.1.0.20251127 to 7.1.0.20251129 Bumps [types-docker](https://github.com/typeshed-internal/stub_uploader) from 7.1.0.20251127 to 7.1.0.20251129. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20251129 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 250ea347e..195859cd1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.2.0", "tenacity==9.1.2", - "types-docker==7.1.0.20251127", + "types-docker==7.1.0.20251129", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", "urllib3==2.5.0", From 06a8b9731c495b34bfd58597a51e49b404905120 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 05:06:39 +0000 Subject: [PATCH 2071/2467] Bump types-docker from 7.1.0.20251129 to 7.1.0.20251202 Bumps [types-docker](https://github.com/typeshed-internal/stub_uploader) from 7.1.0.20251129 to 7.1.0.20251202. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20251202 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 195859cd1..30e8d931e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.2.0", "tenacity==9.1.2", - "types-docker==7.1.0.20251129", + "types-docker==7.1.0.20251202", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", "urllib3==2.5.0", From 6fd487db03087f36a8777a07af9cd9d8f75b3573 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 05:05:47 +0000 Subject: [PATCH 2072/2467] Bump sybil from 9.2.0 to 9.3.0 Bumps [sybil](https://github.com/simplistix/sybil) from 9.2.0 to 9.3.0. - [Changelog](https://github.com/simplistix/sybil/blob/master/CHANGELOG.rst) - [Commits](https://github.com/simplistix/sybil/compare/9.2.0...9.3.0) --- updated-dependencies: - dependency-name: sybil dependency-version: 9.3.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 30e8d931e..882f1e3e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,7 +93,7 @@ optional-dependencies.dev = [ "sphinx-toolbox==4.0.0", "sphinxcontrib-httpdomain==1.8.1", "sphinxcontrib-spelling==8.0.2", - "sybil==9.2.0", + "sybil==9.3.0", "tenacity==9.1.2", "types-docker==7.1.0.20251202", "types-pyyaml==6.0.12.20250915", From 582d92e3aa3bc29cdc820986f23155a52697613d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 05:06:09 +0000 Subject: [PATCH 2073/2467] Bump doccmd from 2025.11.20 to 2025.12.3 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.11.20 to 2025.12.3. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.11.20...2025.12.03) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.12.3 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 882f1e3e7..cd08abcae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==1.1.1", - "doccmd==2025.11.20", + "doccmd==2025.12.3", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From 7cdca4d95d106caf9555857f8a87fc41b0f7fa02 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 4 Dec 2025 13:52:29 +0000 Subject: [PATCH 2074/2467] Get the code ready for ty --- .../mock_web_query_api.py | 22 +++++++++++++++--- .../mock_web_services_api.py | 23 +++++++++++++++---- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py index f22cd9c69..d9fbe9b8c 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py @@ -7,6 +7,7 @@ import email.utils from collections.abc import Callable, Iterable, Mapping from http import HTTPMethod, HTTPStatus +from typing import ParamSpec, Protocol from beartype import beartype from requests.models import PreparedRequest @@ -25,13 +26,28 @@ _ROUTES: set[Route] = set() _ResponseType = tuple[int, Mapping[str, str], str] +_P = ParamSpec("_P") + + +class _RouteMethod(Protocol[_P]): + """ + Callable used for routing which also exposes ``__name__``. + """ + + __name__: str + + def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _ResponseType: + """ + Return a mock response. + """ + ... # pylint: disable=unnecessary-ellipsis @beartype def route( path_pattern: str, http_methods: Iterable[str], -) -> Callable[[Callable[..., _ResponseType]], Callable[..., _ResponseType]]: +) -> Callable[[_RouteMethod[_P]], _RouteMethod[_P]]: """Register a decorated method so that it can be recognized as a route. Args: @@ -44,8 +60,8 @@ def route( """ def decorator( - method: Callable[..., _ResponseType], - ) -> Callable[..., _ResponseType]: + method: _RouteMethod[_P], + ) -> _RouteMethod[_P]: """Register a decorated method so that it can be recognized as a route. Returns: diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index 9a6b30a8d..d5cfdfc1b 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -12,7 +12,7 @@ import uuid from collections.abc import Callable, Iterable, Mapping from http import HTTPMethod, HTTPStatus -from typing import Any +from typing import Any, ParamSpec, Protocol from zoneinfo import ZoneInfo from beartype import BeartypeConf, beartype @@ -39,13 +39,28 @@ _ROUTES: set[Route] = set() _ResponseType = tuple[int, Mapping[str, str], str] +_P = ParamSpec("_P") + + +class _RouteMethod(Protocol[_P]): + """ + Callable used for routing which also exposes ``__name__``. + """ + + __name__: str + + def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _ResponseType: + """ + Return a mock response. + """ + ... # pylint: disable=unnecessary-ellipsis @beartype def route( path_pattern: str, http_methods: Iterable[HTTPMethod], -) -> Callable[[Callable[..., _ResponseType]], Callable[..., _ResponseType]]: +) -> Callable[[_RouteMethod[_P]], _RouteMethod[_P]]: """Register a decorated method so that it can be recognized as a route. Args: @@ -59,8 +74,8 @@ def route( @beartype def decorator( - method: Callable[..., _ResponseType], - ) -> Callable[..., _ResponseType]: + method: _RouteMethod[_P], + ) -> _RouteMethod[_P]: """Register a decorated method so that it can be recognized as a route. Returns: From a9da08cd21e2ed97ab67357480658eb5dd46c69e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 4 Dec 2025 14:43:12 +0000 Subject: [PATCH 2075/2467] Make new protocols runtime_checkable --- src/mock_vws/_requests_mock_server/mock_web_query_api.py | 3 ++- src/mock_vws/_requests_mock_server/mock_web_services_api.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py index d9fbe9b8c..545ecfe03 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py @@ -7,7 +7,7 @@ import email.utils from collections.abc import Callable, Iterable, Mapping from http import HTTPMethod, HTTPStatus -from typing import ParamSpec, Protocol +from typing import ParamSpec, Protocol, runtime_checkable from beartype import beartype from requests.models import PreparedRequest @@ -29,6 +29,7 @@ _P = ParamSpec("_P") +@runtime_checkable class _RouteMethod(Protocol[_P]): """ Callable used for routing which also exposes ``__name__``. diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index d5cfdfc1b..13570ed9b 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -12,7 +12,7 @@ import uuid from collections.abc import Callable, Iterable, Mapping from http import HTTPMethod, HTTPStatus -from typing import Any, ParamSpec, Protocol +from typing import Any, ParamSpec, Protocol, runtime_checkable from zoneinfo import ZoneInfo from beartype import BeartypeConf, beartype @@ -42,6 +42,7 @@ _P = ParamSpec("_P") +@runtime_checkable class _RouteMethod(Protocol[_P]): """ Callable used for routing which also exposes ``__name__``. From 6fe3e6bcd091c753a6124997ca4e65081963dd19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 05:06:25 +0000 Subject: [PATCH 2076/2467] Bump ruff from 0.14.7 to 0.14.8 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.7 to 0.14.8. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.14.7...0.14.8) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.14.8 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c3769e557..510cc31d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "python-dotenv==1.2.1", "pyyaml==6.0.3", "requests-mock-flask==2025.1.13", - "ruff==0.14.7", + "ruff==0.14.8", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From cc079f6b29c131a0b865d7eee34a20a533dacf6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 05:06:02 +0000 Subject: [PATCH 2077/2467] Bump urllib3 from 2.5.0 to 2.6.0 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.5.0 to 2.6.0. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.5.0...2.6.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 83ed0421e..555088691 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "types-docker==7.1.0.20251202", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", - "urllib3==2.5.0", + "urllib3==2.6.0", "vulture==2.14", "vws-python==2025.3.10.1", "vws-test-fixtures==2023.3.5", From 4c64d0ce0c804b08b48bd1a4536b551c79d7a947 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 05:06:42 +0000 Subject: [PATCH 2078/2467] Bump sphinx-toolbox from 4.0.0 to 4.1.0 Bumps [sphinx-toolbox](https://github.com/sphinx-toolbox/sphinx-toolbox) from 4.0.0 to 4.1.0. - [Release notes](https://github.com/sphinx-toolbox/sphinx-toolbox/releases) - [Changelog](https://github.com/sphinx-toolbox/sphinx-toolbox/blob/master/doc-source/changelog.rst) - [Commits](https://github.com/sphinx-toolbox/sphinx-toolbox/compare/v4.0.0...v4.1.0) --- updated-dependencies: - dependency-name: sphinx-toolbox dependency-version: 4.1.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 83ed0421e..00a98c9b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,7 +90,7 @@ optional-dependencies.dev = [ "sphinx-paramlinks==0.6", "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2025.11.17", - "sphinx-toolbox==4.0.0", + "sphinx-toolbox==4.1.0", "sphinxcontrib-httpdomain==1.8.1", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", From cb549ec788228e76d62dd3e51921dfe220b45b79 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 8 Dec 2025 07:40:42 +0000 Subject: [PATCH 2079/2467] Add validation for gha-find-replace actions (#2787) Fail the workflow if no files are modified during file updates. This prevents silent failures in the release process. --- .github/workflows/release.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 582548baa..9cd203fa1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,6 +52,7 @@ jobs: echo "underline=${underline}" >> "$GITHUB_OUTPUT" - name: Update changelog + id: update_changelog uses: jacobtomlinson/gha-find-replace@v3 with: find: "Next\n----" @@ -60,6 +61,12 @@ jobs: include: CHANGELOG.rst regex: false + - name: Check Update changelog was modified + run: | + if [ "${{ steps.update_changelog.outputs.modifiedFiles }}" = "0" ]; then + echo "Error: No files were modified when updating changelog" + exit 1 + fi - uses: stefanzweifel/git-auto-commit-action@v7 id: commit with: From 6872e5fb246f820454cf4fd596f16e38a62357fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 05:06:00 +0000 Subject: [PATCH 2080/2467] Bump coverage from 7.12.0 to 7.13.0 Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.12.0 to 7.13.0. - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](https://github.com/coveragepy/coveragepy/compare/7.12.0...7.13.0) --- updated-dependencies: - dependency-name: coverage dependency-version: 7.13.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 35d209ed7..da84c71aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ optional-dependencies.dev = [ "actionlint-py==1.7.9.24", "check-manifest==0.51", "check-wheel-contents==0.6.3", - "coverage==7.12.0", + "coverage==7.13.0", "deptry==0.24.0", "dirty-equals==0.11", "doc8==1.1.1", From 93abdc4e09a5053b2826c3a0a10a3fb5d3ac61d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 05:06:12 +0000 Subject: [PATCH 2081/2467] Bump doccmd from 2025.12.3 to 2025.12.8.5 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.12.3 to 2025.12.8.5. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.12.03...2025.12.08.5) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.12.8.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 35d209ed7..ba643b399 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==1.1.1", - "doccmd==2025.12.3", + "doccmd==2025.12.8.5", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From 3a4f373d578021b8013ed43174924487142587f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 05:06:22 +0000 Subject: [PATCH 2082/2467] Bump pyroma from 5.0 to 5.0.1 Bumps [pyroma](https://github.com/regebro/pyroma) from 5.0 to 5.0.1. - [Changelog](https://github.com/regebro/pyroma/blob/master/CHANGES.txt) - [Commits](https://github.com/regebro/pyroma/compare/5.0...5.0.1) --- updated-dependencies: - dependency-name: pyroma dependency-version: 5.0.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index da84c71aa..8ce3581df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,7 @@ optional-dependencies.dev = [ "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", "pyright==1.1.407", - "pyroma==5.0", + "pyroma==5.0.1", "pytest==9.0.1", "pytest-retry==1.7.0", "pytest-xdist==3.8.0", From a6b508fa09c37295edb7e24ff5384efdcfc728d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 05:24:46 +0000 Subject: [PATCH 2083/2467] Bump pytest from 9.0.1 to 9.0.2 Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.1 to 9.0.2. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/9.0.1...9.0.2) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8ce3581df..03bc24c67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pyproject-fmt==2.11.1", "pyright==1.1.407", "pyroma==5.0.1", - "pytest==9.0.1", + "pytest==9.0.2", "pytest-retry==1.7.0", "pytest-xdist==3.8.0", "python-dotenv==1.2.1", From 4c7ae0124629999fadf76b66cd71e4b37c988198 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 05:06:18 +0000 Subject: [PATCH 2084/2467] Bump doccmd from 2025.12.8.5 to 2025.12.10 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.12.8.5 to 2025.12.10. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.12.08.5...2025.12.10) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.12.10 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 027f6bcdc..9e8766249 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==1.1.1", - "doccmd==2025.12.8.5", + "doccmd==2025.12.10", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From 4469576ad2d3f6a42424c2bc55c7c2fa3747a9a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 05:06:16 +0000 Subject: [PATCH 2085/2467] Bump urllib3 from 2.6.0 to 2.6.2 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.0 to 2.6.2. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.6.0...2.6.2) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9e8766249..a1c8c459a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "types-docker==7.1.0.20251202", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", - "urllib3==2.6.0", + "urllib3==2.6.2", "vulture==2.14", "vws-python==2025.3.10.1", "vws-test-fixtures==2023.3.5", From d2e2eeefcf29d94198e17b89eb09caee929fdb51 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 05:06:33 +0000 Subject: [PATCH 2086/2467] Bump ruff from 0.14.8 to 0.14.9 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.8 to 0.14.9. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.14.8...0.14.9) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.14.9 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9e8766249..4a07fa9c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "python-dotenv==1.2.1", "pyyaml==6.0.3", "requests-mock-flask==2025.1.13", - "ruff==0.14.8", + "ruff==0.14.9", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From a1a0a6b45f5aa3efb0f930de6e72ce6b0f735c6e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 13 Dec 2025 07:47:16 +0000 Subject: [PATCH 2087/2467] Bump beartype to 0.22.9 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 28385ceaf..57274f2fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dynamic = [ "version", ] dependencies = [ - "beartype>=0.19.0", + "beartype>=0.22.9", "flask>=3.0.3", "numpy>=1.26.4", "pillow>=11.0.0", From c26243281c59db8f76caa42e42f3f9d1901a795b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 13 Dec 2025 08:49:03 +0000 Subject: [PATCH 2088/2467] Add ty to pre-commit --- .pre-commit-config.yaml | 20 ++++++++++++++++++++ pyproject.toml | 1 + 2 files changed, 21 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b1f42363a..610fffad8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,8 @@ ci: - pyright - pyright-docs - pyright-verifytypes + - ty + - ty-docs - pyroma - ruff-check-fix - ruff-check-fix-docs @@ -208,6 +210,24 @@ repos: types_or: [python] additional_dependencies: [uv==0.9.5] + - id: ty + name: ty + stages: [pre-push] + entry: uv run --extra=dev ty check + language: python + types_or: [python, toml] + pass_filenames: false + additional_dependencies: [uv==0.9.5] + + - id: ty-docs + name: ty-docs + stages: [pre-push] + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python + --command="ty check" + language: python + types_or: [markdown, rst] + additional_dependencies: [uv==0.9.5] + - id: vulture name: vulture entry: uv run --extra=dev -m vulture . diff --git a/pyproject.toml b/pyproject.toml index 57274f2fe..443898598 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,6 +95,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.2", + "ty==0.0.1a34", "types-docker==7.1.0.20251202", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", From 0207eb7894dfb61eac221fc7b3549949334c2a77 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 05:04:44 +0000 Subject: [PATCH 2089/2467] Bump actions/download-artifact from 6 to 7 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e1f354fc2..55fc7e540 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -276,7 +276,7 @@ jobs: enable-cache: true cache-dependency-glob: '**/pyproject.toml' - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: pattern: coverage-data-* merge-multiple: true From ae88bf26b70fcaa911bbc1ea9a1506e26b034aa8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 05:04:48 +0000 Subject: [PATCH 2090/2467] Bump actions/upload-artifact from 5 to 6 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e1f354fc2..5f26d31d9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -169,7 +169,7 @@ jobs: echo "name=coverage-data-ci-${{ matrix.python-version }}-${SANITIZED_PATTERN}" >> "$GITHUB_OUTPUT" - name: Upload coverage data - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: ${{ steps.sanitize.outputs.name }} path: .coverage.* @@ -213,7 +213,7 @@ jobs: UV_PYTHON: ${{ matrix.python-version }} - name: Upload coverage data - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: coverage-data-skip-tests-${{ matrix.python-version }} path: .coverage.* @@ -296,7 +296,7 @@ jobs: coverage report - name: Upload HTML report if check failed - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: html-report path: htmlcov From dfb0cd7ee70d2ae6a18d19496440017552b69d5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 05:05:08 +0000 Subject: [PATCH 2091/2467] Bump doccmd from 2025.12.10 to 2025.12.13 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.12.10 to 2025.12.13. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.12.10...2025.12.13) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.12.13 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 443898598..fb650bf3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==1.1.1", - "doccmd==2025.12.10", + "doccmd==2025.12.13", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From d152f1ba9b440db4c5db03d9ba852377c85e025e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 05:05:32 +0000 Subject: [PATCH 2092/2467] Bump mypy[faster-cache] from 1.19.0 to 1.19.1 Bumps [mypy[faster-cache]](https://github.com/python/mypy) from 1.19.0 to 1.19.1. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.19.0...v1.19.1) --- updated-dependencies: - dependency-name: mypy[faster-cache] dependency-version: 1.19.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 443898598..bd80414b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ optional-dependencies.dev = [ "freezegun==1.5.5", "furo==2025.9.25", "interrogate==1.7.0", - "mypy[faster-cache]==1.19.0", + "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2025.4.3", "pre-commit==4.5.0", "pydocstyle==6.3", From ede793c877e7d3c04eaf03e04f203e234585f76e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 05:05:46 +0000 Subject: [PATCH 2093/2467] Bump sphinx-substitution-extensions from 2025.11.17 to 2025.12.15 Bumps [sphinx-substitution-extensions](https://github.com/adamtheturtle/sphinx-substitution-extensions) from 2025.11.17 to 2025.12.15. - [Release notes](https://github.com/adamtheturtle/sphinx-substitution-extensions/releases) - [Changelog](https://github.com/adamtheturtle/sphinx-substitution-extensions/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/sphinx-substitution-extensions/compare/2025.11.17...2025.12.15) --- updated-dependencies: - dependency-name: sphinx-substitution-extensions dependency-version: 2025.12.15 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3b279b987..b48673d5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,7 +89,7 @@ optional-dependencies.dev = [ "sphinx-lint==1.0.2", "sphinx-paramlinks==0.6", "sphinx-pyproject==0.3.0", - "sphinx-substitution-extensions==2025.11.17", + "sphinx-substitution-extensions==2025.12.15", "sphinx-toolbox==4.1.0", "sphinxcontrib-httpdomain==1.8.1", "sphinxcontrib-spelling==8.0.2", From dc7aed44c8b4dc526d8d90a55dac58e4e2f980b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 05:05:23 +0000 Subject: [PATCH 2094/2467] Bump ty from 0.0.1a34 to 0.0.2 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.1a34 to 0.0.2. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.1-alpha.34...0.0.2) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b48673d5f..559e2acb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.2", - "ty==0.0.1a34", + "ty==0.0.2", "types-docker==7.1.0.20251202", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", From 52b85c21e29f6889c8f2601aecfa68e7570483ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 05:05:53 +0000 Subject: [PATCH 2095/2467] Bump pre-commit from 4.5.0 to 4.5.1 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.5.0 to 4.5.1. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v4.5.0...v4.5.1) --- updated-dependencies: - dependency-name: pre-commit dependency-version: 4.5.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b48673d5f..9e924ba56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2025.4.3", - "pre-commit==4.5.0", + "pre-commit==4.5.1", "pydocstyle==6.3", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", From 9476354ea8fcfa5b65ade98196ea912a8e0e3bb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 05:06:01 +0000 Subject: [PATCH 2096/2467] Bump ruff from 0.14.9 to 0.14.10 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.9 to 0.14.10. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.14.9...0.14.10) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.14.10 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c238a0668..0f50fcdc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "python-dotenv==1.2.1", "pyyaml==6.0.3", "requests-mock-flask==2025.1.13", - "ruff==0.14.9", + "ruff==0.14.10", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 34e22dd612b078c6c2902d30c4b8c43be335b580 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 05:06:11 +0000 Subject: [PATCH 2097/2467] Bump ty from 0.0.2 to 0.0.4 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.2 to 0.0.4. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.2...0.0.4) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c238a0668..5141514e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.2", - "ty==0.0.2", + "ty==0.0.4", "types-docker==7.1.0.20251202", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", From aac2c37c6a7eff531fd5149f0ecc3407a5cff2e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 05:06:14 +0000 Subject: [PATCH 2098/2467] Bump yamlfix from 1.19.0 to 1.19.1 Bumps [yamlfix](https://github.com/lyz-code/yamlfix) from 1.19.0 to 1.19.1. - [Changelog](https://github.com/lyz-code/yamlfix/blob/main/CHANGELOG.md) - [Commits](https://github.com/lyz-code/yamlfix/compare/1.19.0...1.19.1) --- updated-dependencies: - dependency-name: yamlfix dependency-version: 1.19.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c238a0668..b8737c09e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,7 +104,7 @@ optional-dependencies.dev = [ "vws-python==2025.3.10.1", "vws-test-fixtures==2023.3.5", "vws-web-tools==2024.10.6.1", - "yamlfix==1.19.0", + "yamlfix==1.19.1", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python-mock/" From 8997d341158f9e5c0d3d9fef6ff913b090416745 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 05:05:21 +0000 Subject: [PATCH 2099/2467] Bump furo from 2025.9.25 to 2025.12.19 Bumps [furo](https://github.com/pradyunsg/furo) from 2025.9.25 to 2025.12.19. - [Release notes](https://github.com/pradyunsg/furo/releases) - [Changelog](https://github.com/pradyunsg/furo/blob/main/docs/changelog.md) - [Commits](https://github.com/pradyunsg/furo/compare/2025.09.25...2025.12.19) --- updated-dependencies: - dependency-name: furo dependency-version: 2025.12.19 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ca795d7ac..0614b24c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "docker==7.1.0", "enum-tools[sphinx]==0.13.0", "freezegun==1.5.5", - "furo==2025.9.25", + "furo==2025.12.19", "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2025.4.3", From 7796a98c955dd483bbe64bdbd42e01e9c8b7f9ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 05:05:52 +0000 Subject: [PATCH 2100/2467] Bump ty from 0.0.4 to 0.0.5 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.4 to 0.0.5. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.4...0.0.5) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ca795d7ac..5d166bec5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.2", - "ty==0.0.4", + "ty==0.0.5", "types-docker==7.1.0.20251202", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", From 1ce31b03a839e534e34de8368ea3cebf41636d78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Dec 2025 05:06:42 +0000 Subject: [PATCH 2101/2467] Bump ty from 0.0.5 to 0.0.6 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.5 to 0.0.6. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.5...0.0.6) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.6 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1fd79f409..8f48753cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.2", - "ty==0.0.5", + "ty==0.0.6", "types-docker==7.1.0.20251202", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", From ac02619e4d05ff465c2f82a58e06e145a46c9d53 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Dec 2025 05:04:34 +0000 Subject: [PATCH 2102/2467] Bump ty from 0.0.6 to 0.0.7 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.6 to 0.0.7. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.6...0.0.7) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.7 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8f48753cc..e6f49d5b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.2", - "ty==0.0.6", + "ty==0.0.7", "types-docker==7.1.0.20251202", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", From 8bfae4e77734ae5b15148e1f9eb92a46002ac191 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 26 Dec 2025 14:25:22 +0000 Subject: [PATCH 2103/2467] Add pyrefly and pyrefly-docs configuration --- .pre-commit-config.yaml | 20 ++++++++++++++++++++ pyproject.toml | 1 + 2 files changed, 21 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 610fffad8..b6bd8e18f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,6 +38,8 @@ ci: - vulture - vulture-docs - yamlfix + - pyrefly + - pyrefly-docs # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks @@ -389,3 +391,21 @@ repos: types_or: [rst] additional_dependencies: [uv==0.9.5] stages: [pre-commit] + + - id: pyrefly + name: pyrefly + stages: [pre-push] + entry: uv run --extra=dev pyrefly check + language: python + types_or: [python, toml] + pass_filenames: false + additional_dependencies: [uv==0.9.5] + + - id: pyrefly-docs + name: pyrefly-docs + stages: [pre-push] + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python + --command="pyrefly check" + language: python + types_or: [markdown, rst] + additional_dependencies: [uv==0.9.5] diff --git a/pyproject.toml b/pyproject.toml index 1fd79f409..dac6af2ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,6 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", + "pyrefly==0.46.1", "pyright==1.1.407", "pyroma==5.0.1", "pytest==9.0.2", From 097bba6f671231bf286107246c87defcaa7b9ab5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 26 Dec 2025 15:24:27 +0000 Subject: [PATCH 2104/2467] Fix pyrefly --- admin/create_secrets_files.py | 6 +++++- pyproject.toml | 6 ++++++ src/mock_vws/_flask_server/target_manager.py | 9 +++++++-- .../_requests_mock_server/mock_web_services_api.py | 9 +++++++-- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 4a6dd7449..a41a5ad8b 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -8,12 +8,16 @@ import sys import textwrap from pathlib import Path +from typing import TYPE_CHECKING import vws_web_tools from dotenv import load_dotenv from selenium import webdriver from selenium.common.exceptions import TimeoutException +if TYPE_CHECKING: + from selenium.webdriver.remote.webdriver import WebDriver + def main() -> None: """ @@ -37,7 +41,7 @@ def main() -> None: for i in range(num_databases) ] files_to_create = [file for file in required_files if not file.exists()] - driver = None + driver: WebDriver | None = None while files_to_create: if driver is None: diff --git a/pyproject.toml b/pyproject.toml index dac6af2ff..445a09775 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -390,6 +390,12 @@ plugins = [ ] follow_untyped_imports = true +[tool.pyrefly] +search_path = [ + ".", + "src", +] + [tool.pyright] enableTypeIgnoreComments = false diff --git a/src/mock_vws/_flask_server/target_manager.py b/src/mock_vws/_flask_server/target_manager.py index 3e97b86d4..7b112d364 100644 --- a/src/mock_vws/_flask_server/target_manager.py +++ b/src/mock_vws/_flask_server/target_manager.py @@ -248,7 +248,11 @@ def delete_target(database_name: str, target_id: str) -> Response: ) target = database.get_target(target_id=target_id) now = datetime.datetime.now(tz=target.upload_date.tzinfo) - new_target = copy.replace(target, delete_date=now) + # See https://github.com/facebook/pyrefly/issues/1897 + new_target = copy.replace( + target, # pyrefly: ignore[bad-argument-type] + delete_date=now, + ) database.targets.remove(target) database.targets.add(new_target) return Response( @@ -289,8 +293,9 @@ def update_target(database_name: str, target_id: str) -> Response: gmt = ZoneInfo(key="GMT") last_modified_date = datetime.datetime.now(tz=gmt) + # See https://github.com/facebook/pyrefly/issues/1897 new_target = copy.replace( - target, + target, # pyrefly: ignore[bad-argument-type] name=name, width=width, active_flag=active_flag, diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index 13570ed9b..a4c2065dc 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -259,7 +259,11 @@ def delete_target(self, request: PreparedRequest) -> _ResponseType: ) now = datetime.datetime.now(tz=target.upload_date.tzinfo) - new_target = copy.replace(target, delete_date=now) + # See https://github.com/facebook/pyrefly/issues/1897 + new_target = copy.replace( + target, # pyrefly: ignore[bad-argument-type] + delete_date=now, + ) database.targets.remove(target) database.targets.add(new_target) date = email.utils.formatdate( @@ -618,8 +622,9 @@ def update_target(self, request: PreparedRequest) -> _ResponseType: gmt = ZoneInfo(key="GMT") last_modified_date = datetime.datetime.now(tz=gmt) + # See https://github.com/facebook/pyrefly/issues/1897 new_target = copy.replace( - target, + target, # pyrefly: ignore[bad-argument-type] name=name, width=width, active_flag=active_flag, From b7fcaf691550ae95b5817948b5862d8d8f4ca70b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 26 Dec 2025 15:43:27 +0000 Subject: [PATCH 2105/2467] Allow "pyrefly" in comments --- spelling_private_dict.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index 431551716..365309073 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -74,6 +74,7 @@ plugins png pragma processable +pyrefly pyright pytest readme From 53751f43f22c8f91de5124995d3de77a4a403c3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 05:06:08 +0000 Subject: [PATCH 2106/2467] Bump coverage from 7.13.0 to 7.13.1 Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.13.0 to 7.13.1. - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](https://github.com/coveragepy/coveragepy/compare/7.13.0...7.13.1) --- updated-dependencies: - dependency-name: coverage dependency-version: 7.13.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7a4d4a6e1..e14c7285f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ optional-dependencies.dev = [ "actionlint-py==1.7.9.24", "check-manifest==0.51", "check-wheel-contents==0.6.3", - "coverage==7.13.0", + "coverage==7.13.1", "deptry==0.24.0", "dirty-equals==0.11", "doc8==1.1.1", From 318b0ab09538230b143f0eb37ca4742dbab282f0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 29 Dec 2025 13:26:02 +0000 Subject: [PATCH 2107/2467] Add zizmor for GitHub Actions security linting (#2816) --- .pre-commit-config.yaml | 10 ++++++++++ pyproject.toml | 1 + 2 files changed, 11 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b6bd8e18f..610a8ad0d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,6 +38,7 @@ ci: - vulture - vulture-docs - yamlfix + - zizmor - pyrefly - pyrefly-docs @@ -384,6 +385,15 @@ repos: additional_dependencies: [uv==0.9.5] stages: [pre-commit] + - id: zizmor + name: zizmor + entry: uv run --extra=dev zizmor .github + language: python + pass_filenames: false + types_or: [yaml] + additional_dependencies: [uv==0.9.5] + stages: [pre-commit] + - id: sphinx-lint name: sphinx-lint entry: uv run --extra=dev sphinx-lint --enable=all --disable=line-too-long diff --git a/pyproject.toml b/pyproject.toml index e14c7285f..7d16625de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,6 +106,7 @@ optional-dependencies.dev = [ "vws-test-fixtures==2023.3.5", "vws-web-tools==2024.10.6.1", "yamlfix==1.19.1", + "zizmor==1.19.0", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python-mock/" From bc1e9a368d808763cbba1d9119b994b9254937a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 05:06:07 +0000 Subject: [PATCH 2108/2467] Bump ty from 0.0.7 to 0.0.8 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.7 to 0.0.8. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.7...0.0.8) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.8 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7d16625de..095a9afd4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.2", - "ty==0.0.7", + "ty==0.0.8", "types-docker==7.1.0.20251202", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", From c4f48e11ae5126a7839463e23e37e3a20372268e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 05:06:18 +0000 Subject: [PATCH 2109/2467] Bump pyrefly from 0.46.1 to 0.46.2 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.46.1 to 0.46.2. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.46.1...0.46.2) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.46.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7d16625de..52281c499 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", - "pyrefly==0.46.1", + "pyrefly==0.46.2", "pyright==1.1.407", "pyroma==5.0.1", "pytest==9.0.2", From 29b9c19b521a1fb2cd67f5b29e738a166be097f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Dec 2025 05:05:35 +0000 Subject: [PATCH 2110/2467] Bump pyrefly from 0.46.2 to 0.46.3 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.46.2 to 0.46.3. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.46.2...0.46.3) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.46.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 01599eb85..028ad3723 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", - "pyrefly==0.46.2", + "pyrefly==0.46.3", "pyright==1.1.407", "pyroma==5.0.1", "pytest==9.0.2", From 95d7f51815f30c5a9e8d49dba510ee096fefb940 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 05:05:07 +0000 Subject: [PATCH 2111/2467] Bump actionlint-py from 1.7.9.24 to 1.7.10.24 Bumps [actionlint-py](https://github.com/Mateusz-Grzelinski/actionlint-py) from 1.7.9.24 to 1.7.10.24. - [Commits](https://github.com/Mateusz-Grzelinski/actionlint-py/compare/v1.7.9.24...v1.7.10.24) --- updated-dependencies: - dependency-name: actionlint-py dependency-version: 1.7.10.24 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 028ad3723..ed508368b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dependencies = [ "werkzeug>=3.1.2", ] optional-dependencies.dev = [ - "actionlint-py==1.7.9.24", + "actionlint-py==1.7.10.24", "check-manifest==0.51", "check-wheel-contents==0.6.3", "coverage==7.13.1", From a9eec24e825ce9f1c3d58bdfbc5b17544b9af3c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 05:05:31 +0000 Subject: [PATCH 2112/2467] Bump sphinx-toolbox from 4.1.0 to 4.1.1 Bumps [sphinx-toolbox](https://github.com/sphinx-toolbox/sphinx-toolbox) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/sphinx-toolbox/sphinx-toolbox/releases) - [Changelog](https://github.com/sphinx-toolbox/sphinx-toolbox/blob/master/doc-source/changelog.rst) - [Commits](https://github.com/sphinx-toolbox/sphinx-toolbox/compare/v4.1.0...v4.1.1) --- updated-dependencies: - dependency-name: sphinx-toolbox dependency-version: 4.1.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 028ad3723..9e92ed85a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,7 @@ optional-dependencies.dev = [ "sphinx-paramlinks==0.6", "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2025.12.15", - "sphinx-toolbox==4.1.0", + "sphinx-toolbox==4.1.1", "sphinxcontrib-httpdomain==1.8.1", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", From c562f9741e0b1e06aca1af8ab2f1ec9fc8c751a3 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 2 Jan 2026 07:56:32 +0000 Subject: [PATCH 2113/2467] Switch from pre-commit to prek (#2823) --- .github/workflows/lint.yml | 6 +++--- .pre-commit-config.yaml | 2 +- docs/source/contributing.rst | 8 ++++---- pyproject.toml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5127a1bbc..c850e0fad 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -34,9 +34,9 @@ jobs: - name: Lint run: | - uv run --extra=dev pre-commit run --all-files --hook-stage pre-commit --verbose - uv run --extra=dev pre-commit run --all-files --hook-stage pre-push --verbose - uv run --extra=dev pre-commit run --all-files --hook-stage manual --verbose + uv run --extra=dev prek run --all-files --hook-stage pre-commit --verbose + uv run --extra=dev prek run --all-files --hook-stage pre-push --verbose + uv run --extra=dev prek run --all-files --hook-stage manual --verbose env: UV_PYTHON: ${{ matrix.python-version }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 610a8ad0d..d4d9fa9da 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,7 +44,7 @@ ci: # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks -default_install_hook_types: [pre-commit, pre-push, commit-msg] +default_install_hook_types: [pre-commit, pre-push] repos: - repo: meta diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 6d7d40a7a..b93e5f20b 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -31,7 +31,7 @@ Install ``pre-commit`` hooks: .. code-block:: console - $ pre-commit install + $ prek install Linting ------- @@ -40,9 +40,9 @@ Run lint tools either by committing, or with: .. code-block:: console - $ pre-commit run --all-files --hook-stage pre-commit --verbose - $ pre-commit run --all-files --hook-stage pre-push --verbose - $ pre-commit run --all-files --hook-stage manual --verbose + $ prek run --all-files --hook-stage pre-commit --verbose + $ prek run --all-files --hook-stage pre-push --verbose + $ prek run --all-files --hook-stage manual --verbose .. _Homebrew: https://brew.sh diff --git a/pyproject.toml b/pyproject.toml index 2f7f51e68..bf20a3bb9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2025.4.3", - "pre-commit==4.5.1", + "prek==0.2.25", "pydocstyle==6.3", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", From bd4cd4e4b8291592dfe7577cf81d8cb395952203 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 3 Jan 2026 07:37:49 +0000 Subject: [PATCH 2114/2467] Make prettier more compatible with yamlfix --- .prettierrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.prettierrc b/.prettierrc index 3ab9aa054..cbaad29a4 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,7 +3,8 @@ { "files": ["*.yaml", "*.yml"], "options": { - "singleQuote": true + "singleQuote": true, + "printWidth": 100" } } ] From 7b8efd4196bc2e294d038e837262531f5827b18c Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 3 Jan 2026 07:43:21 +0000 Subject: [PATCH 2115/2467] Add zizmor --- zizmor.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 zizmor.yml diff --git a/zizmor.yml b/zizmor.yml new file mode 100644 index 000000000..f63e179d2 --- /dev/null +++ b/zizmor.yml @@ -0,0 +1,12 @@ +--- +rules: + unpinned-uses: + disable: true + cache-poisoning: + disable: true + bot-conditions: + disable: true + dependabot-cooldown: + disable: true + template-injection: + disable: true From 238737c408e39f68462cb32e84a629b0a0b5a765 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 3 Jan 2026 07:46:46 +0000 Subject: [PATCH 2116/2467] Fix prettier --- .prettierrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.prettierrc b/.prettierrc index cbaad29a4..4a36aae89 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,7 +4,7 @@ "files": ["*.yaml", "*.yml"], "options": { "singleQuote": true, - "printWidth": 100" + "printWidth": 100 } } ] From 694ce56a73697dab28ca4ae97df322352752129b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 3 Jan 2026 07:49:33 +0000 Subject: [PATCH 2117/2467] Fix zizmor --- .github/workflows/docker-build.yml | 5 ++++- .github/workflows/lint.yml | 6 ++++-- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 10 ++++++++++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 63e9b6d26..ad39dd8a2 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -1,5 +1,4 @@ --- - name: Build Docker images # This matches the Docker image building done in the release process. @@ -17,6 +16,8 @@ on: - cron: 0 1 * * * workflow_dispatch: {} +permissions: {} + jobs: build: name: Build Docker images @@ -31,6 +32,8 @@ jobs: steps: - uses: actions/checkout@v6 + with: + persist-credentials: false - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c850e0fad..12b738ab7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,5 +1,4 @@ --- - name: Lint on: @@ -13,9 +12,10 @@ on: - cron: 0 1 * * * workflow_dispatch: {} +permissions: {} + jobs: build: - strategy: matrix: python-version: ['3.13'] @@ -25,6 +25,8 @@ jobs: steps: - uses: actions/checkout@v6 + with: + persist-credentials: false - name: Install uv uses: astral-sh/setup-uv@v7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9cd203fa1..8ae91c012 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,4 @@ --- - name: Release on: workflow_dispatch @@ -29,6 +28,7 @@ jobs: # Also, avoids # https://github.com/stefanzweifel/git-auto-commit-action/issues/99. fetch-depth: 0 + persist-credentials: false - name: Install uv uses: astral-sh/setup-uv@v7 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 992027cdc..386db8031 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,6 +16,8 @@ on: # workflows. We therefore want to run only one workflow at a time. concurrency: vuforia_credentials +permissions: {} + jobs: # CI tests with matrix ci-tests: @@ -117,6 +119,8 @@ jobs: steps: - uses: actions/checkout@v6 + with: + persist-credentials: false - name: Install uv uses: astral-sh/setup-uv@v7 @@ -186,6 +190,8 @@ jobs: steps: - uses: actions/checkout@v6 + with: + persist-credentials: false - name: Install uv uses: astral-sh/setup-uv@v7 @@ -229,6 +235,8 @@ jobs: steps: - uses: actions/checkout@v6 + with: + persist-credentials: false - name: Install uv uses: astral-sh/setup-uv@v7 @@ -269,6 +277,8 @@ jobs: steps: - uses: actions/checkout@v6 + with: + persist-credentials: false - name: Install uv uses: astral-sh/setup-uv@v7 From 48c43454db78ecf7fa50d875593da9aa90eb1443 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 3 Jan 2026 08:14:43 +0000 Subject: [PATCH 2118/2467] Make zizmor collection strict --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d4d9fa9da..568751b20 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -387,7 +387,7 @@ repos: - id: zizmor name: zizmor - entry: uv run --extra=dev zizmor .github + entry: uv run --extra=dev zizmor --strict-collection .github language: python pass_filenames: false types_or: [yaml] From 52bc9aa6254fd4de678efa32e3dc2265d653d2cf Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 3 Jan 2026 09:07:29 +0000 Subject: [PATCH 2119/2467] Fix zizmor warning in release workflow (#2826) --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ae91c012..b38508530 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -56,8 +56,8 @@ jobs: uses: jacobtomlinson/gha-find-replace@v3 with: find: "Next\n----" - replace: "Next\n----\n\n${{ steps.calver.outputs.release }}\n${{ steps.changelog_underline.outputs.underline\ - \ }}" + replace: |- + Next\n----\n\n${{ steps.calver.outputs.release }}\n${{ steps.changelog_underline.outputs.underline }}\n include: CHANGELOG.rst regex: false From b263e6734ed3adc5b91336677d06da57624ed5ca Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 3 Jan 2026 09:46:27 +0000 Subject: [PATCH 2120/2467] Use literal block scalar for changelog replace string (#2828) --- .github/workflows/release.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b38508530..474c6649a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -56,8 +56,12 @@ jobs: uses: jacobtomlinson/gha-find-replace@v3 with: find: "Next\n----" - replace: |- - Next\n----\n\n${{ steps.calver.outputs.release }}\n${{ steps.changelog_underline.outputs.underline }}\n + replace: | + Next + ---- + + ${{ steps.calver.outputs.release }} + ${{ steps.changelog_underline.outputs.underline }} include: CHANGELOG.rst regex: false From 9eadbb531e24556287f3e17b7db2ca5347f1e093 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 05:05:37 +0000 Subject: [PATCH 2121/2467] Bump doccmd from 2025.12.13 to 2026.1.3.2 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.12.13 to 2026.1.3.2. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2025.12.13...2026.01.03.2) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.1.3.2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bf20a3bb9..cb64fed3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==1.1.1", - "doccmd==2025.12.13", + "doccmd==2026.1.3.2", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From a42e2cfc13de029e9918800d5cfd73f8768dff2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 05:07:16 +0000 Subject: [PATCH 2122/2467] Bump pyrefly from 0.46.3 to 0.47.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.46.3 to 0.47.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.46.3...0.47.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.47.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cb64fed3f..31c381c71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", - "pyrefly==0.46.3", + "pyrefly==0.47.0", "pyright==1.1.407", "pyroma==5.0.1", "pytest==9.0.2", From 0ba151ac8942948a0470dd4f4bbb974dd0677375 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 05:07:22 +0000 Subject: [PATCH 2123/2467] Bump zizmor from 1.19.0 to 1.20.0 Bumps [zizmor](https://github.com/zizmorcore/zizmor) from 1.19.0 to 1.20.0. - [Release notes](https://github.com/zizmorcore/zizmor/releases) - [Changelog](https://github.com/zizmorcore/zizmor/blob/main/docs/release-notes.md) - [Commits](https://github.com/zizmorcore/zizmor/compare/v1.19.0...v1.20.0) --- updated-dependencies: - dependency-name: zizmor dependency-version: 1.20.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cb64fed3f..743b99956 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,7 +106,7 @@ optional-dependencies.dev = [ "vws-test-fixtures==2023.3.5", "vws-web-tools==2024.10.6.1", "yamlfix==1.19.1", - "zizmor==1.19.0", + "zizmor==1.20.0", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python-mock/" From f9f542501132c37c182fa235d4501ab94b60a88a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 05:07:43 +0000 Subject: [PATCH 2124/2467] Bump ty from 0.0.8 to 0.0.9 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.8 to 0.0.9. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.8...0.0.9) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.9 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cb64fed3f..87d0381c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.2", - "ty==0.0.8", + "ty==0.0.9", "types-docker==7.1.0.20251202", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20250913", From 0d984f725fa1f7b11f5734785b455145a8b4fbce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 05:08:07 +0000 Subject: [PATCH 2125/2467] Bump types-requests from 2.32.4.20250913 to 2.32.4.20260107 Bumps [types-requests](https://github.com/typeshed-internal/stub_uploader) from 2.32.4.20250913 to 2.32.4.20260107. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.32.4.20260107 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 16212f059..af5610183 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ optional-dependencies.dev = [ "ty==0.0.9", "types-docker==7.1.0.20251202", "types-pyyaml==6.0.12.20250915", - "types-requests==2.32.4.20250913", + "types-requests==2.32.4.20260107", "urllib3==2.6.2", "vulture==2.14", "vws-python==2025.3.10.1", From e8e5531e0071c8bfd584650268f20950eed56b20 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 05:07:55 +0000 Subject: [PATCH 2126/2467] Bump ty from 0.0.9 to 0.0.10 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.9 to 0.0.10. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.9...0.0.10) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.10 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index af5610183..0c760ab71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.2", - "ty==0.0.9", + "ty==0.0.10", "types-docker==7.1.0.20251202", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", From 84c6f81b4912191edac4cf7c08a58593490e1b3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 05:08:02 +0000 Subject: [PATCH 2127/2467] Bump urllib3 from 2.6.2 to 2.6.3 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.2 to 2.6.3. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.6.2...2.6.3) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index af5610183..20e9c9c0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,7 @@ optional-dependencies.dev = [ "types-docker==7.1.0.20251202", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", - "urllib3==2.6.2", + "urllib3==2.6.3", "vulture==2.14", "vws-python==2025.3.10.1", "vws-test-fixtures==2023.3.5", From 759e0f89f1ea293dcb55bcc89f9a86c0491625aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 05:08:15 +0000 Subject: [PATCH 2128/2467] Bump prek from 0.2.25 to 0.2.27 Bumps [prek](https://github.com/j178/prek) from 0.2.25 to 0.2.27. - [Release notes](https://github.com/j178/prek/releases) - [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md) - [Commits](https://github.com/j178/prek/compare/v0.2.25...v0.2.27) --- updated-dependencies: - dependency-name: prek dependency-version: 0.2.27 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index af5610183..6dd9b497f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2025.4.3", - "prek==0.2.25", + "prek==0.2.27", "pydocstyle==6.3", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", From f978e0c694b850e45e7b812509f446c551aaefc8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 05:07:46 +0000 Subject: [PATCH 2129/2467] Bump ruff from 0.14.10 to 0.14.11 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.10 to 0.14.11. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.14.10...0.14.11) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.14.11 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 540ed80d9..8920eb454 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ "python-dotenv==1.2.1", "pyyaml==6.0.3", "requests-mock-flask==2025.1.13", - "ruff==0.14.10", + "ruff==0.14.11", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From dc650cf494b7a1c41d91207a0ad7b01f994b6d5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 05:07:53 +0000 Subject: [PATCH 2130/2467] Bump pyright from 1.1.407 to 1.1.408 Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.407 to 1.1.408. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.407...v1.1.408) --- updated-dependencies: - dependency-name: pyright dependency-version: 1.1.408 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 540ed80d9..3e9491506 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,7 @@ optional-dependencies.dev = [ "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", "pyrefly==0.47.0", - "pyright==1.1.407", + "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", "pytest-retry==1.7.0", From edbd9f46645c213b5d73b2e6279aa6be5e073647 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 05:08:09 +0000 Subject: [PATCH 2131/2467] Bump types-docker from 7.1.0.20251202 to 7.1.0.20260109 Bumps [types-docker](https://github.com/typeshed-internal/stub_uploader) from 7.1.0.20251202 to 7.1.0.20260109. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20260109 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 540ed80d9..9513d9c7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ optional-dependencies.dev = [ "sybil==9.3.0", "tenacity==9.1.2", "ty==0.0.10", - "types-docker==7.1.0.20251202", + "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", "urllib3==2.6.3", From 35a52c3a4e1c476aaf2e4e48585ed2719703f361 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 05:11:32 +0000 Subject: [PATCH 2132/2467] Bump ty from 0.0.10 to 0.0.11 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.10 to 0.0.11. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.10...0.0.11) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.11 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 356ffbdf7..50587d1b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.2", - "ty==0.0.10", + "ty==0.0.11", "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", From fd50e1d09d4e08d9945bed3fcec9bdcf06843daf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 06:39:17 +0000 Subject: [PATCH 2133/2467] Bump mypy-strict-kwargs from 2025.4.3 to 2026.1.12 Bumps [mypy-strict-kwargs](https://github.com/adamtheturtle/mypy-strict-kwargs) from 2025.4.3 to 2026.1.12. - [Release notes](https://github.com/adamtheturtle/mypy-strict-kwargs/releases) - [Changelog](https://github.com/adamtheturtle/mypy-strict-kwargs/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/mypy-strict-kwargs/compare/2025.04.03...2026.01.12) --- updated-dependencies: - dependency-name: mypy-strict-kwargs dependency-version: 2026.1.12 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 50587d1b5..27bcf2b72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "furo==2025.12.19", "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", - "mypy-strict-kwargs==2025.4.3", + "mypy-strict-kwargs==2026.1.12", "prek==0.2.27", "pydocstyle==6.3", "pylint[spelling]==4.0.4", From 7146d6a8f9552a15cf0253948a8782e33afc8b40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 06:39:28 +0000 Subject: [PATCH 2134/2467] Bump pyrefly from 0.47.0 to 0.48.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.47.0 to 0.48.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.47.0...0.48.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.48.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 50587d1b5..e2564406b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", - "pyrefly==0.47.0", + "pyrefly==0.48.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 73d4ac264404dc97570dca5e3a312ef6e81ab1b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 06:39:35 +0000 Subject: [PATCH 2135/2467] Bump requests-mock-flask from 2025.1.13 to 2026.1.12 Bumps [requests-mock-flask](https://github.com/adamtheturtle/requests-mock-flask) from 2025.1.13 to 2026.1.12. - [Release notes](https://github.com/adamtheturtle/requests-mock-flask/releases) - [Changelog](https://github.com/adamtheturtle/requests-mock-flask/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/requests-mock-flask/compare/2025.01.13...2026.01.12) --- updated-dependencies: - dependency-name: requests-mock-flask dependency-version: 2026.1.12 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 50587d1b5..add9b960a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "pytest-xdist==3.8.0", "python-dotenv==1.2.1", "pyyaml==6.0.3", - "requests-mock-flask==2025.1.13", + "requests-mock-flask==2026.1.12", "ruff==0.14.11", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will From 260f1e9fce1825d8b1c93ba5de7525384b1558b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 06:39:46 +0000 Subject: [PATCH 2136/2467] Bump sphinx-substitution-extensions from 2025.12.15 to 2026.1.12 Bumps [sphinx-substitution-extensions](https://github.com/adamtheturtle/sphinx-substitution-extensions) from 2025.12.15 to 2026.1.12. - [Release notes](https://github.com/adamtheturtle/sphinx-substitution-extensions/releases) - [Changelog](https://github.com/adamtheturtle/sphinx-substitution-extensions/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/sphinx-substitution-extensions/compare/2025.12.15...2026.01.12) --- updated-dependencies: - dependency-name: sphinx-substitution-extensions dependency-version: 2026.1.12 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 50587d1b5..460d7496e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,7 +90,7 @@ optional-dependencies.dev = [ "sphinx-lint==1.0.2", "sphinx-paramlinks==0.6", "sphinx-pyproject==0.3.0", - "sphinx-substitution-extensions==2025.12.15", + "sphinx-substitution-extensions==2026.1.12", "sphinx-toolbox==4.1.1", "sphinxcontrib-httpdomain==1.8.1", "sphinxcontrib-spelling==8.0.2", From 2f02f55ec7b6d0f295e2e928208dd6421a87c0a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 06:39:54 +0000 Subject: [PATCH 2137/2467] Bump doccmd from 2026.1.3.2 to 2026.1.12 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.1.3.2 to 2026.1.12. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2026.01.03.2...2026.01.12) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.1.12 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 50587d1b5..0911c0cc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==1.1.1", - "doccmd==2026.1.3.2", + "doccmd==2026.1.12", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From ceccdb23004a4a52e6b3a17c26e03d8b4d55b27e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 05:04:40 +0000 Subject: [PATCH 2138/2467] Bump prek from 0.2.27 to 0.2.28 Bumps [prek](https://github.com/j178/prek) from 0.2.27 to 0.2.28. - [Release notes](https://github.com/j178/prek/releases) - [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md) - [Commits](https://github.com/j178/prek/compare/v0.2.27...v0.2.28) --- updated-dependencies: - dependency-name: prek dependency-version: 0.2.28 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e12493b6a..7ea4eebfe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2026.1.12", - "prek==0.2.27", + "prek==0.2.28", "pydocstyle==6.3", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", From 3eeb000b44bf6b600d9112b40ac2c618eb539ff1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 05:04:17 +0000 Subject: [PATCH 2139/2467] Bump ty from 0.0.11 to 0.0.12 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.11 to 0.0.12. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.11...0.0.12) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.12 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7ea4eebfe..6ad566278 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.2", - "ty==0.0.11", + "ty==0.0.12", "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", From 8d652a38fa1183b65fd0e400ab0188e7515690b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 05:04:43 +0000 Subject: [PATCH 2140/2467] Bump sphinx-toolbox from 4.1.1 to 4.1.2 Bumps [sphinx-toolbox](https://github.com/sphinx-toolbox/sphinx-toolbox) from 4.1.1 to 4.1.2. - [Release notes](https://github.com/sphinx-toolbox/sphinx-toolbox/releases) - [Changelog](https://github.com/sphinx-toolbox/sphinx-toolbox/blob/master/doc-source/changelog.rst) - [Commits](https://github.com/sphinx-toolbox/sphinx-toolbox/compare/v4.1.1...v4.1.2) --- updated-dependencies: - dependency-name: sphinx-toolbox dependency-version: 4.1.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7ea4eebfe..5b1d447fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,7 @@ optional-dependencies.dev = [ "sphinx-paramlinks==0.6", "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2026.1.12", - "sphinx-toolbox==4.1.1", + "sphinx-toolbox==4.1.2", "sphinxcontrib-httpdomain==1.8.1", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", From f4639aea742320d6f3d5bea3b0faf4ff556b9c8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 05:27:30 +0000 Subject: [PATCH 2141/2467] Bump sphinx from 8.2.3 to 9.1.0 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 8.2.3 to 9.1.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES.rst) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v8.2.3...v9.1.0) --- updated-dependencies: - dependency-name: sphinx dependency-version: 9.1.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fd9058eae..9279bf407 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,7 +85,7 @@ optional-dependencies.dev = [ # use it to lint shell commands in GitHub workflow files. "shellcheck-py==0.11.0.1", "shfmt-py==3.12.0.2", - "sphinx==8.2.3", + "sphinx==9.1.0", "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.2", "sphinx-paramlinks==0.6", From c0bd327daa82780f75c337196eebdad7a82b74fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 05:03:59 +0000 Subject: [PATCH 2142/2467] Bump pyrefly from 0.48.0 to 0.48.1 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.48.0 to 0.48.1. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.48.0...0.48.1) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.48.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fd9058eae..fb85b62b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", - "pyrefly==0.48.0", + "pyrefly==0.48.1", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 18d7ba34d501861d4fe51f5aaac49d7f4705d63e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 05:04:10 +0000 Subject: [PATCH 2143/2467] Bump zizmor from 1.20.0 to 1.21.0 Bumps [zizmor](https://github.com/zizmorcore/zizmor) from 1.20.0 to 1.21.0. - [Release notes](https://github.com/zizmorcore/zizmor/releases) - [Changelog](https://github.com/zizmorcore/zizmor/blob/main/docs/release-notes.md) - [Commits](https://github.com/zizmorcore/zizmor/compare/v1.20.0...v1.21.0) --- updated-dependencies: - dependency-name: zizmor dependency-version: 1.21.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fd9058eae..d80eb3d7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,7 +106,7 @@ optional-dependencies.dev = [ "vws-test-fixtures==2023.3.5", "vws-web-tools==2024.10.6.1", "yamlfix==1.19.1", - "zizmor==1.20.0", + "zizmor==1.21.0", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python-mock/" From e935afd87c8f909d64f4ef7d8ecae6555ed7b115 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 05:04:31 +0000 Subject: [PATCH 2144/2467] Bump ruff from 0.14.11 to 0.14.13 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.11 to 0.14.13. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.14.11...0.14.13) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.14.13 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fd9058eae..ed20c76a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ "python-dotenv==1.2.1", "pyyaml==6.0.3", "requests-mock-flask==2026.1.12", - "ruff==0.14.11", + "ruff==0.14.13", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From d4f167ed482edad886431d3c302acd4189b04322 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 18 Jan 2026 06:49:57 +0000 Subject: [PATCH 2145/2467] Bump doc8 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9279bf407..1fd5acdec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "coverage==7.13.1", "deptry==0.24.0", "dirty-equals==0.11", - "doc8==1.1.1", + "doc8==2.0.0", "doccmd==2026.1.12", "docformatter==1.7.7", "docker==7.1.0", From e4df6581c985a05d768b6d12d0ae1016b361e177 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 18 Jan 2026 08:01:00 +0000 Subject: [PATCH 2146/2467] Revert "Bump sphinx from 8.2.3 to 9.1.0" --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1ca10bac9..e0035b79b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "coverage==7.13.1", "deptry==0.24.0", "dirty-equals==0.11", - "doc8==2.0.0", + "doc8==1.1.1", "doccmd==2026.1.12", "docformatter==1.7.7", "docker==7.1.0", @@ -85,7 +85,7 @@ optional-dependencies.dev = [ # use it to lint shell commands in GitHub workflow files. "shellcheck-py==0.11.0.1", "shfmt-py==3.12.0.2", - "sphinx==9.1.0", + "sphinx==8.2.3", "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.2", "sphinx-paramlinks==0.6", From a8c4a456133cf1bffe061f9e5c11211fa50398c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 05:22:14 +0000 Subject: [PATCH 2147/2467] Bump zizmor from 1.21.0 to 1.22.0 Bumps [zizmor](https://github.com/zizmorcore/zizmor) from 1.21.0 to 1.22.0. - [Release notes](https://github.com/zizmorcore/zizmor/releases) - [Changelog](https://github.com/zizmorcore/zizmor/blob/main/docs/release-notes.md) - [Commits](https://github.com/zizmorcore/zizmor/compare/v1.21.0...v1.22.0) --- updated-dependencies: - dependency-name: zizmor dependency-version: 1.22.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e0035b79b..92488ff22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,7 +106,7 @@ optional-dependencies.dev = [ "vws-test-fixtures==2023.3.5", "vws-web-tools==2024.10.6.1", "yamlfix==1.19.1", - "zizmor==1.21.0", + "zizmor==1.22.0", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python-mock/" From 10a514c9255f1f150746426fe469dca5cf547ba1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 05:22:25 +0000 Subject: [PATCH 2148/2467] Bump prek from 0.2.28 to 0.2.30 Bumps [prek](https://github.com/j178/prek) from 0.2.28 to 0.2.30. - [Release notes](https://github.com/j178/prek/releases) - [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md) - [Commits](https://github.com/j178/prek/compare/v0.2.28...v0.2.30) --- updated-dependencies: - dependency-name: prek dependency-version: 0.2.30 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e0035b79b..078560104 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2026.1.12", - "prek==0.2.28", + "prek==0.2.30", "pydocstyle==6.3", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", From a7155472b2b5ff6d31d90d7c0306bbf476d53e20 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 05:22:52 +0000 Subject: [PATCH 2149/2467] Bump doccmd from 2026.1.12 to 2026.1.18 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.1.12 to 2026.1.18. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2026.01.12...2026.01.18) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.1.18 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e0035b79b..c877f0e85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==1.1.1", - "doccmd==2026.1.12", + "doccmd==2026.1.18", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From d7338912b53991b5a63c8db4ac9590607a9e8888 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 05:22:59 +0000 Subject: [PATCH 2150/2467] Bump pyrefly from 0.48.1 to 0.48.2 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.48.1 to 0.48.2. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.48.1...0.48.2) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.48.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e0035b79b..4829da47b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", - "pyrefly==0.48.1", + "pyrefly==0.48.2", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 87a33c8438114fc04871142b3c0c9cb583f927d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 05:04:08 +0000 Subject: [PATCH 2151/2467] Bump pyrefly from 0.48.2 to 0.49.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.48.2 to 0.49.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.48.2...0.49.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.49.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 80b92dbbd..805cc401f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", - "pyrefly==0.48.2", + "pyrefly==0.49.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 5eaea5212ff0d912af850358006a3b72137e8ec1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 05:04:13 +0000 Subject: [PATCH 2152/2467] Bump doccmd from 2026.1.18 to 2026.1.21.2 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.1.18 to 2026.1.21.2. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2026.01.18...2026.01.21.2) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.1.21.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 805cc401f..b66c3be9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==1.1.1", - "doccmd==2026.1.18", + "doccmd==2026.1.21.2", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From 2ac083fb624a748fc188e5f34891c8368ceaf5c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 05:04:24 +0000 Subject: [PATCH 2153/2467] Bump prek from 0.2.30 to 0.3.0 Bumps [prek](https://github.com/j178/prek) from 0.2.30 to 0.3.0. - [Release notes](https://github.com/j178/prek/releases) - [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md) - [Commits](https://github.com/j178/prek/compare/v0.2.30...v0.3.0) --- updated-dependencies: - dependency-name: prek dependency-version: 0.3.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 805cc401f..d186cf5cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2026.1.12", - "prek==0.2.30", + "prek==0.3.0", "pydocstyle==6.3", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", From 6df81e16d58c18b057e67c22d5f048943814fc01 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 05:18:19 +0000 Subject: [PATCH 2154/2467] Bump ty from 0.0.12 to 0.0.13 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.12 to 0.0.13. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.12...0.0.13) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.13 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b66c3be9b..b62532241 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.2", - "ty==0.0.12", + "ty==0.0.13", "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", From 9111ce2c9f0c41476fce717b57f483e891beb3be Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 22 Jan 2026 17:39:51 +0000 Subject: [PATCH 2155/2467] Use bash shell for Lint step to ensure early failure PowerShell does not fail on intermediate command failures by default. By using bash shell, we ensure that any failing command causes the step to fail immediately. --- .github/workflows/lint.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 12b738ab7..36158fc56 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -35,6 +35,9 @@ jobs: cache-dependency-glob: '**/pyproject.toml' - name: Lint + # Use bash to ensure the step fails if any command fails. + # PowerShell does not fail on intermediate command failures by default. + shell: bash run: | uv run --extra=dev prek run --all-files --hook-stage pre-commit --verbose uv run --extra=dev prek run --all-files --hook-stage pre-push --verbose From 05b5c24535054dbfb20b6eecb5cbb3afd964f1d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 05:04:08 +0000 Subject: [PATCH 2156/2467] Bump ruff from 0.14.13 to 0.14.14 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.13 to 0.14.14. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.14.13...0.14.14) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.14.14 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b62532241..fd1340bb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ "python-dotenv==1.2.1", "pyyaml==6.0.3", "requests-mock-flask==2026.1.12", - "ruff==0.14.13", + "ruff==0.14.14", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 0411195371767614f3b7d4daa4ecdc74fb18f19e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 05:04:21 +0000 Subject: [PATCH 2157/2467] Bump doccmd from 2026.1.21.2 to 2026.1.22.1 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.1.21.2 to 2026.1.22.1. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2026.01.21.2...2026.01.22.1) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.1.22.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b62532241..2507bdc85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==1.1.1", - "doccmd==2026.1.21.2", + "doccmd==2026.1.22.1", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From 245cb92d1b68d35384220dfa370e76fbeb0d7039 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 05:04:09 +0000 Subject: [PATCH 2158/2467] Bump doccmd from 2026.1.22.1 to 2026.1.25 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.1.22.1 to 2026.1.25. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2026.01.22.1...2026.01.25) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.1.25 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a07a5c805..5e5d25baa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==1.1.1", - "doccmd==2026.1.22.1", + "doccmd==2026.1.25", "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From 1723ce6991f68e8363d8f4e36bb9fe7e266efe15 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 05:04:17 +0000 Subject: [PATCH 2159/2467] Bump coverage from 7.13.1 to 7.13.2 Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.13.1 to 7.13.2. - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](https://github.com/coveragepy/coveragepy/compare/7.13.1...7.13.2) --- updated-dependencies: - dependency-name: coverage dependency-version: 7.13.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a07a5c805..ed348e312 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ optional-dependencies.dev = [ "actionlint-py==1.7.10.24", "check-manifest==0.51", "check-wheel-contents==0.6.3", - "coverage==7.13.1", + "coverage==7.13.2", "deptry==0.24.0", "dirty-equals==0.11", "doc8==1.1.1", From 063a8071a266ccc205407eb5889fe4a39b00fcde Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 26 Jan 2026 11:20:35 +0000 Subject: [PATCH 2160/2467] Replace docformatter with pydocstringformatter - Replace docformatter==1.7.7 with pydocstringformatter==0.7.3 - Replace [tool.docformatter] with [tool.pydocstringformatter] config - Update ruff ignore comments (D200 -> D205/D212) - Don't use linewrap-full-docstring to avoid breaking URLs (https://github.com/DanielNoord/pydocstringformatter/issues/540) --- .pre-commit-config.yaml | 8 +- admin/__init__.py | 4 +- admin/create_secrets_files.py | 4 +- ci/__init__.py | 4 +- ci/test_custom_linters.py | 15 +- conftest.py | 8 +- docs/source/__init__.py | 4 +- docs/source/conf.py | 4 +- pyproject.toml | 23 +- src/mock_vws/__init__.py | 4 +- src/mock_vws/_base64_decoding.py | 4 +- src/mock_vws/_constants.py | 4 +- src/mock_vws/_database_matchers.py | 10 +- src/mock_vws/_flask_server/__init__.py | 4 +- src/mock_vws/_flask_server/healthcheck.py | 8 +- src/mock_vws/_flask_server/target_manager.py | 32 +-- src/mock_vws/_flask_server/vwq.py | 24 +-- src/mock_vws/_flask_server/vws.py | 24 +-- src/mock_vws/_mock_common.py | 4 +- src/mock_vws/_query_tools.py | 4 +- src/mock_vws/_query_validators/__init__.py | 4 +- .../accept_header_validators.py | 4 +- .../_query_validators/auth_validators.py | 10 +- .../content_length_validators.py | 4 +- .../content_type_validators.py | 4 +- .../_query_validators/date_validators.py | 4 +- src/mock_vws/_query_validators/exceptions.py | 145 +++++++------ .../_query_validators/fields_validators.py | 4 +- .../_query_validators/image_validators.py | 4 +- .../include_target_data_validators.py | 7 +- .../num_results_validators.py | 7 +- .../project_state_validators.py | 4 +- .../_requests_mock_server/__init__.py | 4 +- .../_requests_mock_server/decorators.py | 18 +- .../mock_web_query_api.py | 22 +- .../mock_web_services_api.py | 24 +-- src/mock_vws/_services_validators/__init__.py | 4 +- .../active_flag_validators.py | 4 +- .../_services_validators/auth_validators.py | 10 +- .../content_length_validators.py | 4 +- .../content_type_validators.py | 4 +- .../_services_validators/date_validators.py | 4 +- .../_services_validators/exceptions.py | 91 ++++---- .../_services_validators/image_validators.py | 4 +- .../_services_validators/json_validators.py | 4 +- .../_services_validators/key_validators.py | 4 +- .../metadata_validators.py | 7 +- .../_services_validators/name_validators.py | 10 +- .../project_state_validators.py | 4 +- .../_services_validators/target_validators.py | 4 +- .../_services_validators/width_validators.py | 4 +- src/mock_vws/database.py | 44 +--- src/mock_vws/image_matchers.py | 15 +- src/mock_vws/states.py | 8 +- src/mock_vws/target.py | 35 +--- src/mock_vws/target_manager.py | 16 +- src/mock_vws/target_raters.py | 20 +- tests/__init__.py | 4 +- tests/conftest.py | 29 +-- tests/mock_vws/__init__.py | 4 +- tests/mock_vws/fixtures/__init__.py | 4 +- tests/mock_vws/fixtures/credentials.py | 19 +- tests/mock_vws/fixtures/prepared_requests.py | 33 ++- tests/mock_vws/fixtures/vuforia_backends.py | 33 ++- tests/mock_vws/test_add_target.py | 133 +++++------- tests/mock_vws/test_authorization_header.py | 27 ++- tests/mock_vws/test_content_length.py | 14 +- tests/mock_vws/test_database_summary.py | 70 +++---- tests/mock_vws/test_date_header.py | 29 +-- tests/mock_vws/test_delete_target.py | 22 +- tests/mock_vws/test_docker.py | 11 +- tests/mock_vws/test_flask_app_usage.py | 86 +++----- tests/mock_vws/test_get_duplicates.py | 38 ++-- tests/mock_vws/test_get_target.py | 30 +-- tests/mock_vws/test_invalid_given_id.py | 9 +- tests/mock_vws/test_invalid_json.py | 16 +- tests/mock_vws/test_query.py | 198 ++++++++---------- tests/mock_vws/test_requests_mock_usage.py | 132 ++++-------- tests/mock_vws/test_target_list.py | 24 +-- tests/mock_vws/test_target_raters.py | 26 +-- tests/mock_vws/test_target_summary.py | 31 +-- tests/mock_vws/test_unexpected_json.py | 11 +- tests/mock_vws/test_update_target.py | 121 +++++------ tests/mock_vws/utils/__init__.py | 12 +- tests/mock_vws/utils/assertions.py | 7 +- tests/mock_vws/utils/retries.py | 4 +- tests/mock_vws/utils/too_many_requests.py | 4 +- tests/mock_vws/utils/usage_test_helpers.py | 8 +- 88 files changed, 730 insertions(+), 1224 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 568751b20..083d2a28d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ ci: - ruff-check-fix-docs - ruff-format-fix - ruff-format-fix-docs - - docformatter + - pydocstringformatter - shellcheck - shellcheck-docs - shfmt @@ -119,9 +119,9 @@ repos: additional_dependencies: [uv==0.9.5] stages: [pre-commit] - - id: docformatter - name: docformatter - entry: uv run --extra=dev -m docformatter --in-place + - id: pydocstringformatter + name: pydocstringformatter + entry: uv run --extra=dev pydocstringformatter language: python types_or: [python] additional_dependencies: [uv==0.9.5] diff --git a/admin/__init__.py b/admin/__init__.py index 6a8f8f73b..1a76e35be 100644 --- a/admin/__init__.py +++ b/admin/__init__.py @@ -1,3 +1 @@ -""" -Admin tools. -""" +"""Admin tools.""" diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index a41a5ad8b..4f1b7183c 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -20,9 +20,7 @@ def main() -> None: - """ - Create secrets files. - """ + """Create secrets files.""" email_address = os.environ["VWS_EMAIL_ADDRESS"] password = os.environ["VWS_PASSWORD"] new_secrets_dir = Path(os.environ["NEW_SECRETS_DIR"]).expanduser() diff --git a/ci/__init__.py b/ci/__init__.py index fdd0b5af8..4b867b2bd 100644 --- a/ci/__init__.py +++ b/ci/__init__.py @@ -1,3 +1 @@ -""" -CI helpers. -""" +"""CI helpers.""" diff --git a/ci/test_custom_linters.py b/ci/test_custom_linters.py index f740f39a0..c25260b46 100644 --- a/ci/test_custom_linters.py +++ b/ci/test_custom_linters.py @@ -1,6 +1,4 @@ -""" -Custom lint tests. -""" +"""Custom lint tests.""" from pathlib import Path from typing import TYPE_CHECKING @@ -15,9 +13,7 @@ @beartype def _ci_patterns(*, repository_root: Path) -> set[str]: - """ - Return the CI patterns given in the CI configuration file. - """ + """Return the CI patterns given in the CI configuration file.""" ci_file = repository_root / ".github" / "workflows" / "test.yml" github_workflow_config = yaml.safe_load(stream=ci_file.read_text()) matrix = github_workflow_config["jobs"]["ci-tests"]["strategy"]["matrix"] @@ -33,9 +29,7 @@ def _tests_from_pattern( ci_pattern: str, capsys: pytest.CaptureFixture[str], ) -> set[str]: - """ - From a CI pattern, get all tests ``pytest`` would collect. - """ + """From a CI pattern, get all tests ``pytest`` would collect.""" # Clear the captured output. capsys.readouterr() tests: Iterable[str] = set() @@ -59,7 +53,8 @@ def _tests_from_pattern( def test_ci_patterns_valid(request: pytest.FixtureRequest) -> None: """ - All of the CI patterns in the CI configuration match at least one test in + All of the CI patterns in the CI configuration match at least one + test in the test suite. """ ci_patterns = _ci_patterns(repository_root=request.config.rootpath) diff --git a/conftest.py b/conftest.py index ed36d6500..93484cd3e 100644 --- a/conftest.py +++ b/conftest.py @@ -1,6 +1,4 @@ -""" -Setup for Sybil. -""" +"""Setup for Sybil.""" from doctest import ELLIPSIS @@ -17,9 +15,7 @@ @beartype def pytest_collection_modifyitems(items: list[pytest.Item]) -> None: - """ - Apply the beartype decorator to all collected test functions. - """ + """Apply the beartype decorator to all collected test functions.""" for item in items: if isinstance(item, pytest.Function): item.obj = beartype(obj=item.obj) diff --git a/docs/source/__init__.py b/docs/source/__init__.py index b63eed5fb..535ceb2ec 100644 --- a/docs/source/__init__.py +++ b/docs/source/__init__.py @@ -1,3 +1 @@ -""" -Documentation. -""" +"""Documentation.""" diff --git a/docs/source/conf.py b/docs/source/conf.py index c24499f79..72d9d43da 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -""" -Configuration for Sphinx. -""" +"""Configuration for Sphinx.""" import importlib.metadata from pathlib import Path diff --git a/pyproject.toml b/pyproject.toml index eb860f1c1..3b2feaf22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,6 @@ optional-dependencies.dev = [ "dirty-equals==0.11", "doc8==1.1.1", "doccmd==2026.1.25", - "docformatter==1.7.7", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", "freezegun==1.5.5", @@ -66,6 +65,7 @@ optional-dependencies.dev = [ "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2026.1.12", "prek==0.3.0", + "pydocstringformatter==0.7.3", "pydocstyle==6.3", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", @@ -152,8 +152,8 @@ lint.select = [ lint.ignore = [ # Ruff warns that this conflicts with the formatter. "COM812", - # Allow our chosen docstring line-style - no one-line summary. - "D200", + # Allow our chosen docstring line-style - pydocstringformatter handles formatting + # but doesn't enforce D205 (blank line after summary) or D212 (summary on first line). "D205", "D212", "D415", @@ -169,6 +169,14 @@ lint.per-file-ignores."ci/test_custom_linters.py" = [ "S101", ] +lint.per-file-ignores."doccmd_*.py" = [ + # Allow our chosen docstring line-style - pydocstringformatter handles + # formatting but docstrings in docs may not match this style. + "D200", + # Allow asserts in docs. + "S101", +] + lint.per-file-ignores."tests/**" = [ # Allow asserts in tests. "S101", @@ -298,9 +306,6 @@ spelling-private-dict-file = 'spelling_private_dict.txt' # --spelling-private-dict-file option instead of raising a message. spelling-store-unknown-words = 'no' -[tool.docformatter] -make-summary-multi-line = true - [tool.check-manifest] ignore = [ @@ -403,6 +408,12 @@ enableTypeIgnoreComments = false reportUnnecessaryTypeIgnoreComment = true typeCheckingMode = "strict" +[tool.pydocstringformatter] +write = true +split-summary-body = false +max-line-length = 75 +linewrap-full-docstring = true + [tool.interrogate] fail-under = 100 omit-covered-files = true diff --git a/src/mock_vws/__init__.py b/src/mock_vws/__init__.py index 357f764b7..d6d5e053a 100644 --- a/src/mock_vws/__init__.py +++ b/src/mock_vws/__init__.py @@ -1,6 +1,4 @@ -""" -Tools for using a fake implementation of Vuforia. -""" +"""Tools for using a fake implementation of Vuforia.""" from mock_vws._requests_mock_server.decorators import ( MissingSchemeError, diff --git a/src/mock_vws/_base64_decoding.py b/src/mock_vws/_base64_decoding.py index 0d9ae1159..d6ed379b1 100644 --- a/src/mock_vws/_base64_decoding.py +++ b/src/mock_vws/_base64_decoding.py @@ -1,6 +1,4 @@ -""" -Helpers for handling Base64 like Vuforia does. -""" +"""Helpers for handling Base64 like Vuforia does.""" import base64 import binascii diff --git a/src/mock_vws/_constants.py b/src/mock_vws/_constants.py index 61443bbc7..1f832af1f 100644 --- a/src/mock_vws/_constants.py +++ b/src/mock_vws/_constants.py @@ -1,6 +1,4 @@ -""" -Constants used to make the VWS mock. -""" +"""Constants used to make the VWS mock.""" from enum import Enum, unique diff --git a/src/mock_vws/_database_matchers.py b/src/mock_vws/_database_matchers.py index 211e4c70f..899cf71b2 100644 --- a/src/mock_vws/_database_matchers.py +++ b/src/mock_vws/_database_matchers.py @@ -1,6 +1,4 @@ -""" -Helpers for getting databases which match keys given in requests. -""" +"""Helpers for getting databases which match keys given in requests.""" from collections.abc import Iterable, Mapping @@ -19,7 +17,8 @@ def get_database_matching_client_keys( request_path: str, databases: Iterable[VuforiaDatabase], ) -> VuforiaDatabase: - """Return the first of the given databases which is being accessed by the + """Return the first of the given databases which is being accessed by + the given client request. Args: @@ -67,7 +66,8 @@ def get_database_matching_server_keys( request_path: str, databases: Iterable[VuforiaDatabase], ) -> VuforiaDatabase: - """Return the first of the given databases which is being accessed by the + """Return the first of the given databases which is being accessed by + the given server request. Args: diff --git a/src/mock_vws/_flask_server/__init__.py b/src/mock_vws/_flask_server/__init__.py index 50e18f288..81533727f 100644 --- a/src/mock_vws/_flask_server/__init__.py +++ b/src/mock_vws/_flask_server/__init__.py @@ -1,3 +1 @@ -""" -Flask server for the mock Vuforia web service. -""" +"""Flask server for the mock Vuforia web service.""" diff --git a/src/mock_vws/_flask_server/healthcheck.py b/src/mock_vws/_flask_server/healthcheck.py index 39c37a5e2..dbc5dee4b 100644 --- a/src/mock_vws/_flask_server/healthcheck.py +++ b/src/mock_vws/_flask_server/healthcheck.py @@ -1,6 +1,4 @@ -""" -Health check for the Flask server. -""" +"""Health check for the Flask server.""" import http.client import socket @@ -12,9 +10,7 @@ @beartype def flask_app_healthy(port: int) -> bool: - """ - Check if the Flask app is healthy. - """ + """Check if the Flask app is healthy.""" conn = http.client.HTTPConnection(host="localhost", port=port) try: conn.request(method="GET", url="/some-random-endpoint") diff --git a/src/mock_vws/_flask_server/target_manager.py b/src/mock_vws/_flask_server/target_manager.py index 7b112d364..90f3341eb 100644 --- a/src/mock_vws/_flask_server/target_manager.py +++ b/src/mock_vws/_flask_server/target_manager.py @@ -1,6 +1,4 @@ -""" -Storage layer for the mock Vuforia Flask application. -""" +"""Storage layer for the mock Vuforia Flask application.""" import base64 import copy @@ -32,18 +30,14 @@ @beartype class _TargetRaterChoice(StrEnum): - """ - Target rater choices. - """ + """Target rater choices.""" BRISQUE = auto() PERFECT = auto() RANDOM = auto() def to_target_rater(self) -> TargetTrackingRater: - """ - Get the target rater. - """ + """Get the target rater.""" match self: case self.BRISQUE: return BrisqueTargetTrackingRater() @@ -57,9 +51,7 @@ def to_target_rater(self) -> TargetTrackingRater: @beartype class TargetManagerSettings(BaseSettings): - """ - Settings for the Target Manager Flask app. - """ + """Settings for the Target Manager Flask app.""" target_manager_host: str = "" target_rater: _TargetRaterChoice = _TargetRaterChoice.BRISQUE @@ -91,9 +83,7 @@ def delete_database(database_name: str) -> Response: @TARGET_MANAGER_FLASK_APP.route(rule="/databases", methods=[HTTPMethod.GET]) @beartype def get_databases() -> Response: - """ - Return a list of all databases. - """ + """Return a list of all databases.""" databases = [database.to_dict() for database in TARGET_MANAGER.databases] return Response( response=json.dumps(obj=databases), @@ -200,9 +190,7 @@ def create_database() -> Response: ) @beartype def create_target(database_name: str) -> Response: - """ - Create a new target in a given database. - """ + """Create a new target in a given database.""" (database,) = ( database for database in TARGET_MANAGER.databases @@ -238,9 +226,7 @@ def create_target(database_name: str) -> Response: ) @beartype def delete_target(database_name: str, target_id: str) -> Response: - """ - Delete a target. - """ + """Delete a target.""" (database,) = ( database for database in TARGET_MANAGER.databases @@ -266,9 +252,7 @@ def delete_target(database_name: str, target_id: str) -> Response: methods=[HTTPMethod.PUT], ) def update_target(database_name: str, target_id: str) -> Response: - """ - Update a target. - """ + """Update a target.""" (database,) = ( database for database in TARGET_MANAGER.databases diff --git a/src/mock_vws/_flask_server/vwq.py b/src/mock_vws/_flask_server/vwq.py index dee12db42..910d71124 100644 --- a/src/mock_vws/_flask_server/vwq.py +++ b/src/mock_vws/_flask_server/vwq.py @@ -33,17 +33,13 @@ @beartype class _ImageMatcherChoice(StrEnum): - """ - Image matcher choices. - """ + """Image matcher choices.""" EXACT = auto() STRUCTURAL_SIMILARITY = auto() def to_image_matcher(self) -> ImageMatcher: - """ - Get the image matcher. - """ + """Get the image matcher.""" match self: case self.EXACT: return ExactMatcher() @@ -55,9 +51,7 @@ def to_image_matcher(self) -> ImageMatcher: @beartype class VWQSettings(BaseSettings): - """ - Settings for the VWQ Flask app. - """ + """Settings for the VWQ Flask app.""" vwq_host: str = "" target_manager_base_url: str @@ -68,9 +62,7 @@ class VWQSettings(BaseSettings): @beartype def get_all_databases() -> set[VuforiaDatabase]: - """ - Get all database objects from the target manager back-end. - """ + """Get all database objects from the target manager back-end.""" settings = VWQSettings.model_validate(obj={}) response = requests.get( url=f"{settings.target_manager_base_url}/databases", @@ -111,9 +103,7 @@ def set_terminate_wsgi_input() -> None: @CLOUDRECO_FLASK_APP.errorhandler(code_or_exception=ValidatorError) def handle_exceptions(exc: ValidatorError) -> Response: - """ - Return the error response associated with the given exception. - """ + """Return the error response associated with the given exception.""" response = Response( status=exc.status_code.value, response=exc.response_text, @@ -127,9 +117,7 @@ def handle_exceptions(exc: ValidatorError) -> Response: @CLOUDRECO_FLASK_APP.route(rule="/v1/query", methods=[HTTPMethod.POST]) def query() -> Response: - """ - Perform an image recognition query. - """ + """Perform an image recognition query.""" settings = VWQSettings.model_validate(obj={}) query_match_checker = settings.query_image_matcher.to_image_matcher() diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index 2d2f9ac9f..6463b928f 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -47,17 +47,13 @@ @beartype class _ImageMatcherChoice(StrEnum): - """ - Image matcher choices. - """ + """Image matcher choices.""" EXACT = auto() STRUCTURAL_SIMILARITY = auto() def to_image_matcher(self) -> ImageMatcher: - """ - Get the image matcher. - """ + """Get the image matcher.""" match self: case self.EXACT: return ExactMatcher() @@ -69,9 +65,7 @@ def to_image_matcher(self) -> ImageMatcher: @beartype class VWSSettings(BaseSettings): - """ - Settings for the VWS Flask app. - """ + """Settings for the VWS Flask app.""" target_manager_base_url: str processing_time_seconds: float = 2.0 @@ -83,9 +77,7 @@ class VWSSettings(BaseSettings): @beartype def get_all_databases() -> set[VuforiaDatabase]: - """ - Get all database objects from the task manager back-end. - """ + """Get all database objects from the task manager back-end.""" settings = VWSSettings.model_validate(obj={}) timeout_seconds = 30 response = requests.get( @@ -127,9 +119,7 @@ def set_terminate_wsgi_input() -> None: @VWS_FLASK_APP.before_request @beartype def validate_request() -> None: - """ - Run validators on the request. - """ + """Run validators on the request.""" databases = get_all_databases() run_services_validators( request_headers=dict(request.headers), @@ -142,9 +132,7 @@ def validate_request() -> None: @VWS_FLASK_APP.errorhandler(code_or_exception=ValidatorError) def handle_exceptions(exc: ValidatorError) -> Response: - """ - Return the error response associated with the given exception. - """ + """Return the error response associated with the given exception.""" response = Response( status=exc.status_code.value, response=exc.response_text, diff --git a/src/mock_vws/_mock_common.py b/src/mock_vws/_mock_common.py index 7ef5d7502..0ebbd379b 100644 --- a/src/mock_vws/_mock_common.py +++ b/src/mock_vws/_mock_common.py @@ -1,6 +1,4 @@ -""" -Common utilities for creating mock routes. -""" +"""Common utilities for creating mock routes.""" import json from collections.abc import Iterable diff --git a/src/mock_vws/_query_tools.py b/src/mock_vws/_query_tools.py index 520afd9ab..b73f6c616 100644 --- a/src/mock_vws/_query_tools.py +++ b/src/mock_vws/_query_tools.py @@ -1,6 +1,4 @@ -""" -Tools for making Vuforia queries. -""" +"""Tools for making Vuforia queries.""" import base64 import io diff --git a/src/mock_vws/_query_validators/__init__.py b/src/mock_vws/_query_validators/__init__.py index 9fc608c78..54e458e80 100644 --- a/src/mock_vws/_query_validators/__init__.py +++ b/src/mock_vws/_query_validators/__init__.py @@ -1,6 +1,4 @@ -""" -Input validators to use in the mock query API. -""" +"""Input validators to use in the mock query API.""" from collections.abc import Iterable, Mapping diff --git a/src/mock_vws/_query_validators/accept_header_validators.py b/src/mock_vws/_query_validators/accept_header_validators.py index baeb735fe..fe3e966f6 100644 --- a/src/mock_vws/_query_validators/accept_header_validators.py +++ b/src/mock_vws/_query_validators/accept_header_validators.py @@ -1,6 +1,4 @@ -""" -Validators for the ``Accept`` header. -""" +"""Validators for the ``Accept`` header.""" import logging from collections.abc import Mapping diff --git a/src/mock_vws/_query_validators/auth_validators.py b/src/mock_vws/_query_validators/auth_validators.py index a8a139691..a90273909 100644 --- a/src/mock_vws/_query_validators/auth_validators.py +++ b/src/mock_vws/_query_validators/auth_validators.py @@ -1,6 +1,4 @@ -""" -Authorization validators to use in the mock query API. -""" +"""Authorization validators to use in the mock query API.""" import logging from collections.abc import Iterable, Mapping @@ -41,7 +39,8 @@ def validate_auth_header_number_of_parts( *, request_headers: Mapping[str, str], ) -> None: - """Validate the authorization header includes text either side of a space. + """Validate the authorization header includes text either side of a + space. Args: request_headers: The headers sent with the request. @@ -66,7 +65,8 @@ def validate_client_key_exists( request_headers: Mapping[str, str], databases: Iterable[VuforiaDatabase], ) -> None: - """Validate the authorization header includes a client key for a database. + """Validate the authorization header includes a client key for a + database. Args: request_headers: The headers sent with the request. diff --git a/src/mock_vws/_query_validators/content_length_validators.py b/src/mock_vws/_query_validators/content_length_validators.py index f7cbac25b..4cb799fb5 100644 --- a/src/mock_vws/_query_validators/content_length_validators.py +++ b/src/mock_vws/_query_validators/content_length_validators.py @@ -1,6 +1,4 @@ -""" -Content-Length header validators to use in the mock. -""" +"""Content-Length header validators to use in the mock.""" import logging from collections.abc import Mapping diff --git a/src/mock_vws/_query_validators/content_type_validators.py b/src/mock_vws/_query_validators/content_type_validators.py index bd474c6ab..3e7dc4792 100644 --- a/src/mock_vws/_query_validators/content_type_validators.py +++ b/src/mock_vws/_query_validators/content_type_validators.py @@ -1,6 +1,4 @@ -""" -Validators for the ``Content-Type`` header. -""" +"""Validators for the ``Content-Type`` header.""" import logging from collections.abc import Mapping diff --git a/src/mock_vws/_query_validators/date_validators.py b/src/mock_vws/_query_validators/date_validators.py index 4048238c9..4151b0d31 100644 --- a/src/mock_vws/_query_validators/date_validators.py +++ b/src/mock_vws/_query_validators/date_validators.py @@ -1,6 +1,4 @@ -""" -Validators of the date header to use in the mock query API. -""" +"""Validators of the date header to use in the mock query API.""" import contextlib import datetime diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py index 3d225297c..6417595f4 100644 --- a/src/mock_vws/_query_validators/exceptions.py +++ b/src/mock_vws/_query_validators/exceptions.py @@ -1,6 +1,4 @@ -""" -Exceptions to raise from validators. -""" +"""Exceptions to raise from validators.""" import email.utils import textwrap @@ -17,7 +15,8 @@ @beartype class ValidatorError(Exception): """ - A base class for exceptions thrown from mock Vuforia cloud recognition + A base class for exceptions thrown from mock Vuforia cloud + recognition client endpoints. """ @@ -28,16 +27,15 @@ class ValidatorError(Exception): @beartype class DateHeaderNotGivenError(ValidatorError): - """ - Exception raised when a date header is not given. - """ + """Exception raised when a date header is not given.""" def __init__(self) -> None: """ Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -59,16 +57,15 @@ def __init__(self) -> None: @beartype class DateFormatNotValidError(ValidatorError): - """ - Exception raised when the date format is not valid. - """ + """Exception raised when the date format is not valid.""" def __init__(self) -> None: """ Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -91,8 +88,7 @@ def __init__(self) -> None: @beartype class RequestTimeTooSkewedError(ValidatorError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'RequestTimeTooSkewed'. """ @@ -101,7 +97,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -127,8 +124,7 @@ def __init__(self) -> None: @beartype class BadImageError(ValidatorError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'BadImage'. """ @@ -137,7 +133,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -170,8 +167,7 @@ def __init__(self) -> None: @beartype class AuthenticationFailureError(ValidatorError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'AuthenticationFailure'. """ @@ -180,7 +176,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -213,8 +210,7 @@ def __init__(self) -> None: @beartype class AuthenticationFailureGoodFormattingError(ValidatorError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'AuthenticationFailure' with a standard JSON formatting. """ @@ -223,7 +219,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -251,16 +248,15 @@ def __init__(self) -> None: @beartype class ImageNotGivenError(ValidatorError): - """ - Exception raised when an image is not given. - """ + """Exception raised when an image is not given.""" def __init__(self) -> None: """ Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -283,16 +279,15 @@ def __init__(self) -> None: @beartype class AuthHeaderMissingError(ValidatorError): - """ - Exception raised when an auth header is not given. - """ + """Exception raised when an auth header is not given.""" def __init__(self) -> None: """ Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -316,16 +311,15 @@ def __init__(self) -> None: @beartype class MalformedAuthHeaderError(ValidatorError): - """ - Exception raised when an auth header is not given. - """ + """Exception raised when an auth header is not given.""" def __init__(self) -> None: """ Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. www_authenticate: The WWW-Authenticate header value. """ @@ -350,16 +344,15 @@ def __init__(self) -> None: @beartype class UnknownParametersError(ValidatorError): - """ - Exception raised when unknown parameters are given. - """ + """Exception raised when unknown parameters are given.""" def __init__(self) -> None: """ Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -382,8 +375,7 @@ def __init__(self) -> None: @beartype class InactiveProjectError(ValidatorError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'InactiveProject'. """ @@ -392,7 +384,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -424,8 +417,8 @@ def __init__(self) -> None: @beartype class InvalidMaxNumResultsError(ValidatorError): - """ - Exception raised when an invalid value is given as the "max_num_results" + """Exception raised when an invalid value is given as the + "max_num_results" field. """ @@ -434,7 +427,8 @@ def __init__(self, given_value: str) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -461,8 +455,8 @@ def __init__(self, given_value: str) -> None: @beartype class MaxNumResultsOutOfRangeError(ValidatorError): - """ - Exception raised when an integer value is given as the "max_num_results" + """Exception raised when an integer value is given as the + "max_num_results" field which is out of range. """ @@ -471,7 +465,8 @@ def __init__(self, given_value: str) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -498,8 +493,7 @@ def __init__(self, given_value: str) -> None: @beartype class InvalidIncludeTargetDataError(ValidatorError): - """ - Exception raised when an invalid value is given as the + """Exception raised when an invalid value is given as the "include_target_data" field. """ @@ -508,7 +502,8 @@ def __init__(self, given_value: str) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -537,16 +532,15 @@ def __init__(self, given_value: str) -> None: @beartype class UnsupportedMediaTypeError(ValidatorError): - """ - Exception raised when no boundary is found for multipart data. - """ + """Exception raised when no boundary is found for multipart data.""" def __init__(self) -> None: """ Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -568,16 +562,15 @@ def __init__(self) -> None: @beartype class InvalidAcceptHeaderError(ValidatorError): - """ - Exception raised when there is an invalid accept header given. - """ + """Exception raised when there is an invalid accept header given.""" def __init__(self) -> None: """ Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -599,16 +592,15 @@ def __init__(self) -> None: @beartype class NoBoundaryFoundError(ValidatorError): - """ - Exception raised when an invalid media type is given. - """ + """Exception raised when an invalid media type is given.""" def __init__(self) -> None: """ Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -634,7 +626,8 @@ def __init__(self) -> None: @beartype class ContentLengthHeaderTooLargeError(ValidatorError): """ - Exception raised when the given content length header is too large. + Exception raised when the given content length header is too + large. """ # We skip coverage here as running a test to cover this is very slow. @@ -643,7 +636,8 @@ def __init__(self) -> None: # pragma: no cover Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -658,7 +652,8 @@ def __init__(self) -> None: # pragma: no cover @beartype class ContentLengthHeaderNotIntError(ValidatorError): """ - Exception raised when the given content length header is not an integer. + Exception raised when the given content length header is not an + integer. """ def __init__(self) -> None: @@ -666,7 +661,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -680,9 +676,7 @@ def __init__(self) -> None: @beartype class RequestEntityTooLargeError(ValidatorError): - """ - Exception raised when the given image file size is too large. - """ + """Exception raised when the given image file size is too large.""" # Ignore coverage on this as there is a bug in urllib3 which means that we # do not trigger this exception. @@ -692,7 +686,8 @@ def __init__(self) -> None: # pragma: no cover Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -725,7 +720,8 @@ def __init__(self) -> None: # pragma: no cover @beartype class NoContentTypeError(ValidatorError): """ - Exception raised when a content type is either not given or is empty. + Exception raised when a content type is either not given or is + empty. """ def __init__(self) -> None: @@ -733,7 +729,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() diff --git a/src/mock_vws/_query_validators/fields_validators.py b/src/mock_vws/_query_validators/fields_validators.py index 3b91fb9fb..b9e78fecd 100644 --- a/src/mock_vws/_query_validators/fields_validators.py +++ b/src/mock_vws/_query_validators/fields_validators.py @@ -1,6 +1,4 @@ -""" -Validators for the fields given. -""" +"""Validators for the fields given.""" import io import logging diff --git a/src/mock_vws/_query_validators/image_validators.py b/src/mock_vws/_query_validators/image_validators.py index 203c606de..827c636d2 100644 --- a/src/mock_vws/_query_validators/image_validators.py +++ b/src/mock_vws/_query_validators/image_validators.py @@ -1,6 +1,4 @@ -""" -Input validators for the image field use in the mock query API. -""" +"""Input validators for the image field use in the mock query API.""" import io import logging diff --git a/src/mock_vws/_query_validators/include_target_data_validators.py b/src/mock_vws/_query_validators/include_target_data_validators.py index 5f8277ade..b3719aad8 100644 --- a/src/mock_vws/_query_validators/include_target_data_validators.py +++ b/src/mock_vws/_query_validators/include_target_data_validators.py @@ -1,6 +1,4 @@ -""" -Validators for the ``include_target_data`` field. -""" +"""Validators for the ``include_target_data`` field.""" import io import logging @@ -20,7 +18,8 @@ def validate_include_target_data( request_headers: Mapping[str, str], request_body: bytes, ) -> None: - """Validate the ``include_target_data`` field is either an accepted value + """Validate the ``include_target_data`` field is either an accepted + value or not given. Args: diff --git a/src/mock_vws/_query_validators/num_results_validators.py b/src/mock_vws/_query_validators/num_results_validators.py index ff4b99ae1..31ead620b 100644 --- a/src/mock_vws/_query_validators/num_results_validators.py +++ b/src/mock_vws/_query_validators/num_results_validators.py @@ -1,6 +1,4 @@ -""" -Validators for the ``max_num_results`` fields. -""" +"""Validators for the ``max_num_results`` fields.""" import io import logging @@ -24,7 +22,8 @@ def validate_max_num_results( request_headers: Mapping[str, str], request_body: bytes, ) -> None: - """Validate the ``max_num_results`` field is either an integer within range + """Validate the ``max_num_results`` field is either an integer within + range or not given. Args: diff --git a/src/mock_vws/_query_validators/project_state_validators.py b/src/mock_vws/_query_validators/project_state_validators.py index bd44f4fd8..5a3517bde 100644 --- a/src/mock_vws/_query_validators/project_state_validators.py +++ b/src/mock_vws/_query_validators/project_state_validators.py @@ -1,6 +1,4 @@ -""" -Validators for the project state. -""" +"""Validators for the project state.""" import logging from collections.abc import Iterable, Mapping diff --git a/src/mock_vws/_requests_mock_server/__init__.py b/src/mock_vws/_requests_mock_server/__init__.py index 81677dca3..79758e3ce 100644 --- a/src/mock_vws/_requests_mock_server/__init__.py +++ b/src/mock_vws/_requests_mock_server/__init__.py @@ -1,3 +1 @@ -""" -An interface to the mock Vuforia which uses ``responses``. -""" +"""An interface to the mock Vuforia which uses ``responses``.""" diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 6acdd899d..6572d593d 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -1,6 +1,4 @@ -""" -Decorators for using the mock. -""" +"""Decorators for using the mock.""" import re from contextlib import ContextDecorator @@ -32,9 +30,7 @@ class MissingSchemeError(Exception): - """ - Raised when a URL is missing a schema. - """ + """Raised when a URL is missing a schema.""" def __init__(self, url: str) -> None: """ @@ -46,7 +42,8 @@ def __init__(self, url: str) -> None: def __str__(self) -> str: """ - Give a string representation of this error with a suggestion. + Give a string representation of this error with a + suggestion. """ return ( f'Invalid URL "{self.url}": No scheme supplied. ' @@ -56,9 +53,7 @@ def __str__(self) -> str: @beartype(conf=BeartypeConf(is_pep484_tower=True)) class MockVWS(ContextDecorator): - """ - Route requests to Vuforia's Web Service APIs to fakes of those APIs. - """ + """Route requests to Vuforia's Web Service APIs to fakes of those APIs.""" def __init__( self, @@ -71,7 +66,8 @@ def __init__( target_tracking_rater: TargetTrackingRater = _BRISQUE_TRACKING_RATER, real_http: bool = False, ) -> None: - """Route requests to Vuforia's Web Service APIs to fakes of those APIs. + """Route requests to Vuforia's Web Service APIs to fakes of those + APIs. Args: real_http: Whether or not to forward requests to the real diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py index 545ecfe03..8206a6d10 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py @@ -31,16 +31,12 @@ @runtime_checkable class _RouteMethod(Protocol[_P]): - """ - Callable used for routing which also exposes ``__name__``. - """ + """Callable used for routing which also exposes ``__name__``.""" __name__: str def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _ResponseType: - """ - Return a mock response. - """ + """Return a mock response.""" ... # pylint: disable=unnecessary-ellipsis @@ -63,7 +59,8 @@ def route( def decorator( method: _RouteMethod[_P], ) -> _RouteMethod[_P]: - """Register a decorated method so that it can be recognized as a route. + """Register a decorated method so that it can be recognized as a + route. Returns: The given `method` with multiple changes, including added @@ -83,9 +80,7 @@ def decorator( @beartype def _body_bytes(request: PreparedRequest) -> bytes: - """ - Return the body of a request as bytes. - """ + """Return the body of a request as bytes.""" if request.body is None or isinstance(request.body, str): return b"" @@ -107,7 +102,8 @@ def __init__( """ Args: target_manager: The target manager which holds all databases. - query_match_checker: A callable which takes two image values and + query_match_checker: A callable which takes two image values + and returns whether they match. Attributes: @@ -119,9 +115,7 @@ def __init__( @route(path_pattern="/v1/query", http_methods={HTTPMethod.POST}) def query(self, request: PreparedRequest) -> _ResponseType: - """ - Perform an image recognition query. - """ + """Perform an image recognition query.""" try: run_query_validators( request_path=request.path_url, diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index a4c2065dc..ddee6ef03 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -44,16 +44,12 @@ @runtime_checkable class _RouteMethod(Protocol[_P]): - """ - Callable used for routing which also exposes ``__name__``. - """ + """Callable used for routing which also exposes ``__name__``.""" __name__: str def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _ResponseType: - """ - Return a mock response. - """ + """Return a mock response.""" ... # pylint: disable=unnecessary-ellipsis @@ -77,7 +73,8 @@ def route( def decorator( method: _RouteMethod[_P], ) -> _RouteMethod[_P]: - """Register a decorated method so that it can be recognized as a route. + """Register a decorated method so that it can be recognized as a + route. Returns: The given `method` with multiple changes, including added @@ -97,9 +94,7 @@ def decorator( @beartype def _body_bytes(request: PreparedRequest) -> bytes: - """ - Return the body of a request as bytes. - """ + """Return the body of a request as bytes.""" if request.body is None: return b"" @@ -130,9 +125,11 @@ def __init__( processing_time_seconds: The number of seconds to process each image for. In the real Vuforia Web Services, this is not deterministic. - duplicate_match_checker: A callable which takes two image values + duplicate_match_checker: A callable which takes two image + values and returns whether they are duplicates. - target_tracking_rater: A callable for rating targets for tracking. + target_tracking_rater: A callable for rating targets for + tracking. Attributes: routes: The `Route`s to be used in the mock. @@ -475,7 +472,8 @@ def get_target(self, request: PreparedRequest) -> _ResponseType: http_methods={HTTPMethod.GET}, ) def get_duplicates(self, request: PreparedRequest) -> _ResponseType: - """Get targets which may be considered duplicates of a given target. + """Get targets which may be considered duplicates of a given + target. Fake implementation of https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#check diff --git a/src/mock_vws/_services_validators/__init__.py b/src/mock_vws/_services_validators/__init__.py index d8bbf0c78..44487b365 100644 --- a/src/mock_vws/_services_validators/__init__.py +++ b/src/mock_vws/_services_validators/__init__.py @@ -1,6 +1,4 @@ -""" -Input validators to use in the mock. -""" +"""Input validators to use in the mock.""" from collections.abc import Iterable, Mapping diff --git a/src/mock_vws/_services_validators/active_flag_validators.py b/src/mock_vws/_services_validators/active_flag_validators.py index f4864bcc7..66a446945 100644 --- a/src/mock_vws/_services_validators/active_flag_validators.py +++ b/src/mock_vws/_services_validators/active_flag_validators.py @@ -1,6 +1,4 @@ -""" -Validators for the active flag. -""" +"""Validators for the active flag.""" import json import logging diff --git a/src/mock_vws/_services_validators/auth_validators.py b/src/mock_vws/_services_validators/auth_validators.py index 0fd8d6757..f47164085 100644 --- a/src/mock_vws/_services_validators/auth_validators.py +++ b/src/mock_vws/_services_validators/auth_validators.py @@ -1,6 +1,4 @@ -""" -Authorization header validators to use in the mock. -""" +"""Authorization header validators to use in the mock.""" import logging from collections.abc import Iterable, Mapping @@ -20,7 +18,8 @@ @beartype def validate_auth_header_exists(*, request_headers: Mapping[str, str]) -> None: - """Validate that there is an authorization header given to a VWS endpoint. + """Validate that there is an authorization header given to a VWS + endpoint. Args: request_headers: The headers sent with the request. @@ -39,7 +38,8 @@ def validate_access_key_exists( request_headers: Mapping[str, str], databases: Iterable[VuforiaDatabase], ) -> None: - """Validate the authorization header includes an access key for a database. + """Validate the authorization header includes an access key for a + database. Args: request_headers: The headers sent with the request. diff --git a/src/mock_vws/_services_validators/content_length_validators.py b/src/mock_vws/_services_validators/content_length_validators.py index 9e2cc966b..eaa41b2af 100644 --- a/src/mock_vws/_services_validators/content_length_validators.py +++ b/src/mock_vws/_services_validators/content_length_validators.py @@ -1,6 +1,4 @@ -""" -Content-Length header validators to use in the mock. -""" +"""Content-Length header validators to use in the mock.""" import logging from collections.abc import Mapping diff --git a/src/mock_vws/_services_validators/content_type_validators.py b/src/mock_vws/_services_validators/content_type_validators.py index 4c97c4cdf..12913fa6f 100644 --- a/src/mock_vws/_services_validators/content_type_validators.py +++ b/src/mock_vws/_services_validators/content_type_validators.py @@ -1,6 +1,4 @@ -""" -Content-Type header validators to use in the mock. -""" +"""Content-Type header validators to use in the mock.""" import logging from collections.abc import Mapping diff --git a/src/mock_vws/_services_validators/date_validators.py b/src/mock_vws/_services_validators/date_validators.py index 4f6bda3ed..f5f773d97 100644 --- a/src/mock_vws/_services_validators/date_validators.py +++ b/src/mock_vws/_services_validators/date_validators.py @@ -1,6 +1,4 @@ -""" -Validators of the date header to use in the mock services API. -""" +"""Validators of the date header to use in the mock services API.""" import datetime import logging diff --git a/src/mock_vws/_services_validators/exceptions.py b/src/mock_vws/_services_validators/exceptions.py index 6a582a9fc..4bbc5dab8 100644 --- a/src/mock_vws/_services_validators/exceptions.py +++ b/src/mock_vws/_services_validators/exceptions.py @@ -1,6 +1,4 @@ -""" -Exceptions to raise from validators. -""" +"""Exceptions to raise from validators.""" import email.utils import textwrap @@ -17,7 +15,8 @@ @beartype class ValidatorError(Exception): """ - A base class for exceptions thrown from mock Vuforia services endpoints. + A base class for exceptions thrown from mock Vuforia services + endpoints. """ status_code: HTTPStatus @@ -27,8 +26,7 @@ class ValidatorError(Exception): @beartype class UnknownTargetError(ValidatorError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'UnknownTarget'. """ @@ -37,7 +35,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -67,8 +66,7 @@ def __init__(self) -> None: @beartype class ProjectInactiveError(ValidatorError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'ProjectInactive'. """ @@ -77,7 +75,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -107,8 +106,7 @@ def __init__(self) -> None: @beartype class AuthenticationFailureError(ValidatorError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'AuthenticationFailure'. """ @@ -117,7 +115,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -147,8 +146,8 @@ def __init__(self) -> None: @beartype class FailError(ValidatorError): - """ - Exception raised when Vuforia returns a response with a result code 'Fail'. + """Exception raised when Vuforia returns a response with a result code + 'Fail'. """ def __init__(self, *, status_code: HTTPStatus) -> None: @@ -156,7 +155,8 @@ def __init__(self, *, status_code: HTTPStatus) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -186,8 +186,7 @@ def __init__(self, *, status_code: HTTPStatus) -> None: @beartype class MetadataTooLargeError(ValidatorError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'MetadataTooLarge'. """ @@ -196,7 +195,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -226,8 +226,7 @@ def __init__(self) -> None: @beartype class TargetNameExistError(ValidatorError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'TargetNameExist'. """ @@ -236,7 +235,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -266,8 +266,7 @@ def __init__(self) -> None: @beartype class BadImageError(ValidatorError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'BadImage'. """ @@ -276,7 +275,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -306,8 +306,7 @@ def __init__(self) -> None: @beartype class ImageTooLargeError(ValidatorError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'ImageTooLarge'. """ @@ -316,7 +315,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -346,8 +346,7 @@ def __init__(self) -> None: @beartype class RequestTimeTooSkewedError(ValidatorError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'RequestTimeTooSkewed'. """ @@ -356,7 +355,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -387,7 +387,8 @@ def __init__(self) -> None: @beartype class ContentLengthHeaderTooLargeError(ValidatorError): """ - Exception raised when the given content length header is too large. + Exception raised when the given content length header is too + large. """ # We skip coverage here as running a test to cover this is very slow. @@ -396,7 +397,8 @@ def __init__(self) -> None: # pragma: no cover Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -419,7 +421,8 @@ def __init__(self) -> None: # pragma: no cover @beartype class ContentLengthHeaderNotIntError(ValidatorError): """ - Exception raised when the given content length header is not an integer. + Exception raised when the given content length header is not an + integer. """ def __init__(self) -> None: @@ -427,7 +430,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -458,16 +462,15 @@ def __init__(self) -> None: @beartype class UnnecessaryRequestBodyError(ValidatorError): - """ - Exception raised when a request body is given but not necessary. - """ + """Exception raised when a request body is given but not necessary.""" def __init__(self) -> None: """ Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -498,7 +501,8 @@ def __init__(self) -> None: Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() @@ -528,16 +532,15 @@ def __init__(self) -> None: @beartype class TargetStatusProcessingError(ValidatorError): - """ - Exception raised when trying to delete a target which is processing. - """ + """Exception raised when trying to delete a target which is processing.""" def __init__(self) -> None: """ Attributes: status_code: The status code to use in a response if this is raised. - response_text: The response text to use in a response if this is + response_text: The response text to use in a response if this + is raised. """ super().__init__() diff --git a/src/mock_vws/_services_validators/image_validators.py b/src/mock_vws/_services_validators/image_validators.py index 2dd703391..c5744efff 100644 --- a/src/mock_vws/_services_validators/image_validators.py +++ b/src/mock_vws/_services_validators/image_validators.py @@ -1,6 +1,4 @@ -""" -Image validators to use in the mock. -""" +"""Image validators to use in the mock.""" import binascii import io diff --git a/src/mock_vws/_services_validators/json_validators.py b/src/mock_vws/_services_validators/json_validators.py index 1f97f2784..9d04eec29 100644 --- a/src/mock_vws/_services_validators/json_validators.py +++ b/src/mock_vws/_services_validators/json_validators.py @@ -1,6 +1,4 @@ -""" -Validators for given JSON. -""" +"""Validators for given JSON.""" import json import logging diff --git a/src/mock_vws/_services_validators/key_validators.py b/src/mock_vws/_services_validators/key_validators.py index 85f4a3f77..b07533fe0 100644 --- a/src/mock_vws/_services_validators/key_validators.py +++ b/src/mock_vws/_services_validators/key_validators.py @@ -1,6 +1,4 @@ -""" -Validators for JSON keys. -""" +"""Validators for JSON keys.""" import json import logging diff --git a/src/mock_vws/_services_validators/metadata_validators.py b/src/mock_vws/_services_validators/metadata_validators.py index 7ad3a1851..0695de3db 100644 --- a/src/mock_vws/_services_validators/metadata_validators.py +++ b/src/mock_vws/_services_validators/metadata_validators.py @@ -1,6 +1,4 @@ -""" -Validators for application metadata. -""" +"""Validators for application metadata.""" import binascii import json @@ -20,7 +18,8 @@ @beartype def validate_metadata_size(*, request_body: bytes) -> None: - """Validate that the given application metadata is a string or 1024 * 1024 + """Validate that the given application metadata is a string or 1024 * + 1024 bytes or fewer. Args: diff --git a/src/mock_vws/_services_validators/name_validators.py b/src/mock_vws/_services_validators/name_validators.py index 604c8a1a0..37e511931 100644 --- a/src/mock_vws/_services_validators/name_validators.py +++ b/src/mock_vws/_services_validators/name_validators.py @@ -1,6 +1,4 @@ -""" -Validators for target names. -""" +"""Validators for target names.""" import json import logging @@ -26,7 +24,8 @@ def validate_name_characters_in_range( request_method: str, request_path: str, ) -> None: - """Validate the characters in the name argument given to a VWS endpoint. + """Validate the characters in the name argument given to a VWS + endpoint. Args: request_body: The body of the request. @@ -179,7 +178,8 @@ def validate_name_does_not_exist_existing_target( request_path: str, databases: Iterable[VuforiaDatabase], ) -> None: - """Validate that the name does not exist for any existing target apart from + """Validate that the name does not exist for any existing target apart + from the one being updated. Args: diff --git a/src/mock_vws/_services_validators/project_state_validators.py b/src/mock_vws/_services_validators/project_state_validators.py index 09fed3d92..468e6188a 100644 --- a/src/mock_vws/_services_validators/project_state_validators.py +++ b/src/mock_vws/_services_validators/project_state_validators.py @@ -1,6 +1,4 @@ -""" -Validators for the project state. -""" +"""Validators for the project state.""" import logging from collections.abc import Iterable, Mapping diff --git a/src/mock_vws/_services_validators/target_validators.py b/src/mock_vws/_services_validators/target_validators.py index 1f6a9e0a2..4dbee04b1 100644 --- a/src/mock_vws/_services_validators/target_validators.py +++ b/src/mock_vws/_services_validators/target_validators.py @@ -1,6 +1,4 @@ -""" -Validators for given target IDs. -""" +"""Validators for given target IDs.""" import logging from collections.abc import Iterable, Mapping diff --git a/src/mock_vws/_services_validators/width_validators.py b/src/mock_vws/_services_validators/width_validators.py index e1d77c596..ab47947d2 100644 --- a/src/mock_vws/_services_validators/width_validators.py +++ b/src/mock_vws/_services_validators/width_validators.py @@ -1,6 +1,4 @@ -""" -Validators for the width field. -""" +"""Validators for the width field.""" import json import logging diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py index 1d3b62659..2e28a9f61 100644 --- a/src/mock_vws/database.py +++ b/src/mock_vws/database.py @@ -1,6 +1,4 @@ -""" -Utilities for managing mock Vuforia databases. -""" +"""Utilities for managing mock Vuforia databases.""" import uuid from collections.abc import Iterable @@ -16,9 +14,7 @@ @beartype class DatabaseDict(TypedDict): - """ - A dictionary type which represents a database. - """ + """A dictionary type which represents a database.""" database_name: str server_access_key: str @@ -31,9 +27,7 @@ class DatabaseDict(TypedDict): @beartype def _random_hex() -> str: - """ - Return a random hex value. - """ + """Return a random hex value.""" return uuid.uuid4().hex @@ -78,9 +72,7 @@ class VuforiaDatabase: target_quota: int = 1000 def to_dict(self) -> DatabaseDict: - """ - Dump a target to a dictionary which can be loaded as JSON. - """ + """Dump a target to a dictionary which can be loaded as JSON.""" targets = [target.to_dict() for target in self.targets] return { "database_name": self.database_name, @@ -93,9 +85,7 @@ def to_dict(self) -> DatabaseDict: } def get_target(self, target_id: str) -> Target: - """ - Return a target from the database with the given ID. - """ + """Return a target from the database with the given ID.""" (target,) = ( target for target in self.targets if target.target_id == target_id ) @@ -103,9 +93,7 @@ def get_target(self, target_id: str) -> Target: @classmethod def from_dict(cls, database_dict: DatabaseDict) -> Self: - """ - Load a database from a dictionary. - """ + """Load a database from a dictionary.""" return cls( database_name=database_dict["database_name"], server_access_key=database_dict["server_access_key"], @@ -121,16 +109,12 @@ def from_dict(cls, database_dict: DatabaseDict) -> Self: @property def not_deleted_targets(self) -> set[Target]: - """ - All targets which have not been deleted. - """ + """All targets which have not been deleted.""" return {target for target in self.targets if not target.delete_date} @property def active_targets(self) -> set[Target]: - """ - All active targets. - """ + """All active targets.""" return { target for target in self.not_deleted_targets @@ -140,9 +124,7 @@ def active_targets(self) -> set[Target]: @property def inactive_targets(self) -> set[Target]: - """ - All inactive targets. - """ + """All inactive targets.""" return { target for target in self.not_deleted_targets @@ -152,9 +134,7 @@ def inactive_targets(self) -> set[Target]: @property def failed_targets(self) -> set[Target]: - """ - All failed targets. - """ + """All failed targets.""" return { target for target in self.not_deleted_targets @@ -163,9 +143,7 @@ def failed_targets(self) -> set[Target]: @property def processing_targets(self) -> set[Target]: - """ - All processing targets. - """ + """All processing targets.""" return { target for target in self.not_deleted_targets diff --git a/src/mock_vws/image_matchers.py b/src/mock_vws/image_matchers.py index eba39996a..2aa954794 100644 --- a/src/mock_vws/image_matchers.py +++ b/src/mock_vws/image_matchers.py @@ -1,6 +1,4 @@ -""" -Matchers for query and duplicate requests. -""" +"""Matchers for query and duplicate requests.""" import io from typing import Protocol, runtime_checkable @@ -16,9 +14,7 @@ @runtime_checkable class ImageMatcher(Protocol): - """ - Protocol for a matcher for query and duplicate requests. - """ + """Protocol for a matcher for query and duplicate requests.""" def __call__( self, @@ -38,9 +34,7 @@ def __call__( @beartype class ExactMatcher: - """ - A matcher which returns whether two images are exactly equal. - """ + """A matcher which returns whether two images are exactly equal.""" def __call__( self, @@ -59,7 +53,8 @@ def __call__( @beartype class StructuralSimilarityMatcher: """ - A matcher which returns whether two images are similar using SSIM. + A matcher which returns whether two images are similar using + SSIM. """ def __call__( diff --git a/src/mock_vws/states.py b/src/mock_vws/states.py index 7233fd8c9..e57a09734 100644 --- a/src/mock_vws/states.py +++ b/src/mock_vws/states.py @@ -1,6 +1,4 @@ -""" -Vuforia database states. -""" +"""Vuforia database states.""" from enum import StrEnum, auto, unique @@ -10,9 +8,7 @@ @beartype @unique class States(StrEnum): - """ - Constants representing various web service states. - """ + """Constants representing various web service states.""" WORKING = auto() diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py index b04c3ff52..afd8f20b1 100644 --- a/src/mock_vws/target.py +++ b/src/mock_vws/target.py @@ -1,6 +1,4 @@ -""" -A fake implementation of a target for the Vuforia Web Services API. -""" +"""A fake implementation of a target for the Vuforia Web Services API.""" import base64 import datetime @@ -22,9 +20,7 @@ class TargetDict(TypedDict): - """ - A dictionary type which represents a target. - """ + """A dictionary type which represents a target.""" name: str width: float @@ -41,17 +37,13 @@ class TargetDict(TypedDict): @beartype def _random_hex() -> str: - """ - Return a random hex value. - """ + """Return a random hex value.""" return uuid.uuid4().hex @beartype def _time_now() -> datetime.datetime: - """ - Return the current time in the GMT time zone. - """ + """Return the current time in the GMT time zone.""" gmt = ZoneInfo(key="GMT") return datetime.datetime.now(tz=gmt) @@ -82,7 +74,8 @@ class Target: @property def _post_processing_status(self) -> TargetStatuses: - """Return the status of the target, or what it will be when processing + """Return the status of the target, or what it will be when + processing is finished. The status depends on the standard deviation of the color bands. @@ -128,16 +121,12 @@ def status(self) -> str: @property def _post_processing_target_rating(self) -> int: - """ - The rating of the target after processing. - """ + """The rating of the target after processing.""" return self.target_tracking_rater(image_content=self.image_value) @property def tracking_rating(self) -> int: - """ - Return the tracking rating of the target recognition image. - """ + """Return the tracking rating of the target recognition image.""" pre_rating_time = datetime.timedelta( # That this is half of the total processing time is unrealistic. # In VWS it is not a constant percentage. @@ -157,9 +146,7 @@ def tracking_rating(self) -> int: @classmethod def from_dict(cls, target_dict: TargetDict) -> Self: - """ - Load a target from a dictionary. - """ + """Load a target from a dictionary.""" timezone = ZoneInfo(key="GMT") name = target_dict["name"] active_flag = target_dict["active_flag"] @@ -201,9 +188,7 @@ def from_dict(cls, target_dict: TargetDict) -> Self: ) def to_dict(self) -> TargetDict: - """ - Dump a target to a dictionary which can be loaded as JSON. - """ + """Dump a target to a dictionary which can be loaded as JSON.""" delete_date: str | None = None if self.delete_date: delete_date = self.delete_date.isoformat() diff --git a/src/mock_vws/target_manager.py b/src/mock_vws/target_manager.py index 9dc08870a..8042f588e 100644 --- a/src/mock_vws/target_manager.py +++ b/src/mock_vws/target_manager.py @@ -1,6 +1,4 @@ -""" -A fake implementation of a Vuforia target manager. -""" +"""A fake implementation of a Vuforia target manager.""" from typing import TYPE_CHECKING @@ -15,13 +13,13 @@ @beartype class TargetManager: """ - A target manager as per https://developer.vuforia.com/target-manager. + A target manager. + + See https://developer.vuforia.com/target-manager. """ def __init__(self) -> None: - """ - Create a target manager with no databases. - """ + """Create a target manager with no databases.""" self._databases: Iterable[VuforiaDatabase] = set() def remove_database(self, database: VuforiaDatabase) -> None: @@ -85,7 +83,5 @@ def add_database(self, database: VuforiaDatabase) -> None: @property def databases(self) -> set[VuforiaDatabase]: - """ - All cloud databases. - """ + """All cloud databases.""" return set(self._databases) diff --git a/src/mock_vws/target_raters.py b/src/mock_vws/target_raters.py index d52c48122..ad75099fb 100644 --- a/src/mock_vws/target_raters.py +++ b/src/mock_vws/target_raters.py @@ -1,6 +1,4 @@ -""" -Raters for target quality. -""" +"""Raters for target quality.""" import functools import io @@ -46,9 +44,7 @@ def _get_brisque_target_tracking_rating(*, image_content: bytes) -> int: @runtime_checkable class TargetTrackingRater(Protocol): - """ - Protocol for a rater of target quality. - """ + """Protocol for a rater of target quality.""" def __call__(self, image_content: bytes) -> int: """The target tracking rating. @@ -63,9 +59,7 @@ def __call__(self, image_content: bytes) -> int: @beartype class RandomTargetTrackingRater: - """ - A rater which returns a random number. - """ + """A rater which returns a random number.""" def __call__(self, image_content: bytes) -> int: """A random target tracking rating. @@ -79,9 +73,7 @@ def __call__(self, image_content: bytes) -> int: @beartype class HardcodedTargetTrackingRater: - """ - A rater which returns a hardcoded number. - """ + """A rater which returns a hardcoded number.""" def __init__(self, rating: int) -> None: """ @@ -102,9 +94,7 @@ def __call__(self, image_content: bytes) -> int: @beartype class BrisqueTargetTrackingRater: - """ - A rater which returns a rating based on a BRISQUE score. - """ + """A rater which returns a rating based on a BRISQUE score.""" def __call__(self, image_content: bytes) -> int: """A rating based on a BRISQUE score. diff --git a/tests/__init__.py b/tests/__init__.py index c7e38a862..3502d86d5 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1 @@ -""" -Tests for ``vws``. -""" +"""Tests for ``vws``.""" diff --git a/tests/conftest.py b/tests/conftest.py index ce7b979b7..64ef1427f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,4 @@ -""" -Configuration, plugins and fixtures for `pytest`. -""" +"""Configuration, plugins and fixtures for `pytest`.""" import base64 import binascii @@ -22,9 +20,7 @@ @pytest.fixture(name="vws_client") def fixture_vws_client(vuforia_database: VuforiaDatabase) -> VWS: - """ - A VWS client for an active VWS database. - """ + """A VWS client for an active VWS database.""" return VWS( server_access_key=vuforia_database.server_access_key, server_secret_key=vuforia_database.server_secret_key, @@ -33,9 +29,7 @@ def fixture_vws_client(vuforia_database: VuforiaDatabase) -> VWS: @pytest.fixture def cloud_reco_client(vuforia_database: VuforiaDatabase) -> CloudRecoService: - """ - A query client for an active VWS database. - """ + """A query client for an active VWS database.""" return CloudRecoService( client_access_key=vuforia_database.client_access_key, client_secret_key=vuforia_database.client_secret_key, @@ -44,9 +38,7 @@ def cloud_reco_client(vuforia_database: VuforiaDatabase) -> CloudRecoService: @pytest.fixture(name="inactive_vws_client") def fixture_inactive_vws_client(inactive_database: VuforiaDatabase) -> VWS: - """ - A client for an inactive VWS database. - """ + """A client for an inactive VWS database.""" return VWS( server_access_key=inactive_database.server_access_key, server_secret_key=inactive_database.server_secret_key, @@ -57,9 +49,7 @@ def fixture_inactive_vws_client(inactive_database: VuforiaDatabase) -> VWS: def inactive_cloud_reco_client( inactive_database: VuforiaDatabase, ) -> CloudRecoService: - """ - A query client for an inactive VWS database. - """ + """A query client for an inactive VWS database.""" return CloudRecoService( client_access_key=inactive_database.client_access_key, client_secret_key=inactive_database.client_secret_key, @@ -99,7 +89,8 @@ def target_id( ) def endpoint(request: pytest.FixtureRequest) -> Endpoint: """ - Return details of an endpoint for the Target API or the Query API. + Return details of an endpoint for the Target API or the Query + API. """ endpoint_fixture: Endpoint = request.getfixturevalue(argname=request.param) return endpoint_fixture @@ -126,7 +117,8 @@ def endpoint(request: pytest.FixtureRequest) -> Endpoint: ], ) def not_base64_encoded_processable(request: pytest.FixtureRequest) -> str: - """Return a string which is not decodable as base64 data, but Vuforia will + """Return a string which is not decodable as base64 data, but Vuforia + will respond as if this is valid base64 data. ``UNPROCESSABLE_ENTITY`` when this is given. @@ -150,7 +142,8 @@ def not_base64_encoded_processable(request: pytest.FixtureRequest) -> str: ) def not_base64_encoded_not_processable(request: pytest.FixtureRequest) -> str: """ - Return a string which is not decodable as base64 data, and Vuforia will + Return a string which is not decodable as base64 data, and Vuforia + will return an ``UNPROCESSABLE_ENTITY`` response when this is given. """ not_base64_encoded_string: str = request.param diff --git a/tests/mock_vws/__init__.py b/tests/mock_vws/__init__.py index bb7d2138d..55becaf36 100644 --- a/tests/mock_vws/__init__.py +++ b/tests/mock_vws/__init__.py @@ -1,6 +1,4 @@ -""" -A mock implementation of Vuforia Web Services. -""" +"""A mock implementation of Vuforia Web Services.""" import pytest diff --git a/tests/mock_vws/fixtures/__init__.py b/tests/mock_vws/fixtures/__init__.py index fd67d619b..3ab52191a 100644 --- a/tests/mock_vws/fixtures/__init__.py +++ b/tests/mock_vws/fixtures/__init__.py @@ -1,3 +1 @@ -""" -Common fixtures. -""" +"""Common fixtures.""" diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index 5fbc1ed9d..90fe125b4 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -1,6 +1,4 @@ -""" -Fixtures for credentials for Vuforia databases. -""" +"""Fixtures for credentials for Vuforia databases.""" from pathlib import Path @@ -12,9 +10,7 @@ class _VuforiaDatabaseSettings(BaseSettings): - """ - Settings for a Vuforia database. - """ + """Settings for a Vuforia database.""" target_manager_database_name: str server_access_key: str @@ -30,9 +26,7 @@ class _VuforiaDatabaseSettings(BaseSettings): class _InactiveVuforiaDatabaseSettings(_VuforiaDatabaseSettings): - """ - Settings for an inactive Vuforia database. - """ + """Settings for an inactive Vuforia database.""" model_config = SettingsConfigDict( env_prefix="INACTIVE_VUFORIA_", @@ -43,9 +37,7 @@ class _InactiveVuforiaDatabaseSettings(_VuforiaDatabaseSettings): @pytest.fixture def vuforia_database() -> VuforiaDatabase: - """ - Return VWS credentials from environment variables. - """ + """Return VWS credentials from environment variables.""" settings = _VuforiaDatabaseSettings.model_validate(obj={}) return VuforiaDatabase( database_name=settings.target_manager_database_name, @@ -60,7 +52,8 @@ def vuforia_database() -> VuforiaDatabase: @pytest.fixture def inactive_database() -> VuforiaDatabase: """ - Return VWS credentials for an inactive project from environment variables. + Return VWS credentials for an inactive project from environment + variables. """ settings = _InactiveVuforiaDatabaseSettings.model_validate(obj={}) return VuforiaDatabase( diff --git a/tests/mock_vws/fixtures/prepared_requests.py b/tests/mock_vws/fixtures/prepared_requests.py index ac813c943..6a1d93aa8 100644 --- a/tests/mock_vws/fixtures/prepared_requests.py +++ b/tests/mock_vws/fixtures/prepared_requests.py @@ -1,6 +1,4 @@ -""" -Fixtures which prepare requests. -""" +"""Fixtures which prepare requests.""" import base64 import io @@ -40,9 +38,7 @@ def add_target( vuforia_database: VuforiaDatabase, image_file_failed_state: io.BytesIO, ) -> Endpoint: - """ - Return details of the endpoint for adding a target. - """ + """Return details of the endpoint for adding a target.""" image_data = image_file_failed_state.getvalue() image_data_encoded = base64.b64encode(s=image_data).decode( encoding="ascii" @@ -97,9 +93,7 @@ def delete_target( target_id: str, vws_client: VWS, ) -> Endpoint: - """ - Return details of the endpoint for deleting a target. - """ + """Return details of the endpoint for deleting a target.""" _wait_for_target_processed(vws_client=vws_client, target_id=target_id) date = rfc_1123_date() request_path = f"/targets/{target_id}" @@ -140,7 +134,8 @@ def delete_target( @pytest.fixture def database_summary(vuforia_database: VuforiaDatabase) -> Endpoint: """ - Return details of the endpoint for getting details about the database. + Return details of the endpoint for getting details about the + database. """ date = rfc_1123_date() request_path = "/summary" @@ -233,9 +228,7 @@ def get_target( target_id: str, vws_client: VWS, ) -> Endpoint: - """ - Return details of the endpoint for getting details of a target. - """ + """Return details of the endpoint for getting details of a target.""" _wait_for_target_processed(vws_client=vws_client, target_id=target_id) date = rfc_1123_date() request_path = f"/targets/{target_id}" @@ -276,9 +269,7 @@ def get_target( @pytest.fixture def target_list(vuforia_database: VuforiaDatabase) -> Endpoint: - """ - Return details of the endpoint for getting a list of targets. - """ + """Return details of the endpoint for getting a list of targets.""" date = rfc_1123_date() request_path = "/targets" method = HTTPMethod.GET @@ -323,7 +314,8 @@ def target_summary( vws_client: VWS, ) -> Endpoint: """ - Return details of the endpoint for getting a summary report of a target. + Return details of the endpoint for getting a summary report of a + target. """ _wait_for_target_processed(vws_client=vws_client, target_id=target_id) date = rfc_1123_date() @@ -369,9 +361,7 @@ def update_target( target_id: str, vws_client: VWS, ) -> Endpoint: - """ - Return details of the endpoint for updating a target. - """ + """Return details of the endpoint for updating a target.""" _wait_for_target_processed(vws_client=vws_client, target_id=target_id) data: dict[str, Any] = {} request_path = f"/targets/{target_id}" @@ -419,7 +409,8 @@ def query( high_quality_image: io.BytesIO, ) -> Endpoint: """ - Return details of the endpoint for making an image recognition query. + Return details of the endpoint for making an image recognition + query. """ image_content = high_quality_image.getvalue() date = rfc_1123_date() diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index ddfdbf16c..39013b5b9 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -1,6 +1,4 @@ -""" -Choose which backends to use for the tests. -""" +"""Choose which backends to use for the tests.""" import contextlib import logging @@ -66,9 +64,7 @@ def _enable_use_real_vuforia( inactive_database: VuforiaDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: - """ - Test against the real Vuforia. - """ + """Test against the real Vuforia.""" assert monkeypatch assert inactive_database _delete_all_targets(database_keys=working_database) @@ -82,9 +78,7 @@ def _enable_use_mock_vuforia( inactive_database: VuforiaDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: - """ - Test against the in-memory mock Vuforia. - """ + """Test against the in-memory mock Vuforia.""" assert monkeypatch working_database = VuforiaDatabase( database_name=working_database.database_name, @@ -116,9 +110,7 @@ def _enable_use_docker_in_memory( inactive_database: VuforiaDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: - """ - Test against mock Vuforia created to be run in a container. - """ + """Test against mock Vuforia created to be run in a container.""" # We set ``wsgi.input_terminated`` to ``True`` so that when going through # ``requests`` in our tests, the Flask applications # have the given ``Content-Length`` headers and the given data in @@ -183,9 +175,7 @@ def _enable_use_docker_in_memory( class VuforiaBackend(Enum): - """ - Backends for tests. - """ + """Backends for tests.""" REAL = "Real Vuforia" MOCK = "In Memory Mock Vuforia" @@ -195,7 +185,8 @@ class VuforiaBackend(Enum): @beartype def pytest_addoption(parser: pytest.Parser) -> None: """ - Add options to the pytest command line for skipping tests with particular + Add options to the pytest command line for skipping tests with + particular backends. """ for backend in VuforiaBackend: @@ -219,9 +210,7 @@ def pytest_collection_modifyitems( config: pytest.Config, items: list[pytest.Function], ) -> None: - """ - Skip Docker tests if requested. - """ + """Skip Docker tests if requested.""" skip_docker_build_tests_option = "--skip-docker_build_tests" skip_docker_build_tests_marker = pytest.mark.skip( reason=( @@ -246,7 +235,8 @@ def fixture_verify_mock_vuforia( inactive_database: VuforiaDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: - """Test functions which use this fixture are run multiple times. Once with + """Test functions which use this fixture are run multiple times. Once + with the real Vuforia, and once with each mock. This is useful for verifying the mocks. @@ -289,7 +279,8 @@ def mock_only_vuforia( inactive_database: VuforiaDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: - """Test functions which use this fixture are run multiple times. Once with + """Test functions which use this fixture are run multiple times. Once + with the each mock. This is useful for testing the mock using fixtures which connect to diff --git a/tests/mock_vws/test_add_target.py b/tests/mock_vws/test_add_target.py index 7134b994b..d732dc94f 100644 --- a/tests/mock_vws/test_add_target.py +++ b/tests/mock_vws/test_add_target.py @@ -1,6 +1,4 @@ -""" -Tests for the mock of the add target endpoint. -""" +"""Tests for the mock of the add target endpoint.""" import base64 import io @@ -88,9 +86,7 @@ def assert_success(response: Response) -> None: @pytest.mark.usefixtures("verify_mock_vuforia") class TestContentTypes: - """ - Tests for the `Content-Type` header. - """ + """Tests for the `Content-Type` header.""" @staticmethod @pytest.mark.parametrize( @@ -111,9 +107,7 @@ def test_content_types( image_file_failed_state: io.BytesIO, content_type: str, ) -> None: - """ - Any non-empty ``Content-Type`` header is allowed. - """ + """Any non-empty ``Content-Type`` header is allowed.""" image_data = image_file_failed_state.getvalue() image_data_encoded = base64.b64encode(s=image_data).decode( encoding="ascii" @@ -139,7 +133,8 @@ def test_empty_content_type( image_file_failed_state: io.BytesIO, ) -> None: """ - An ``UNAUTHORIZED`` response is given if an empty ``Content-Type`` + An ``UNAUTHORIZED`` response is given if an empty ``Content- + Type`` header is given. """ image_data = image_file_failed_state.getvalue() @@ -171,9 +166,7 @@ def test_empty_content_type( @pytest.mark.usefixtures("verify_mock_vuforia") class TestMissingData: - """ - Tests for giving incomplete data. - """ + """Tests for giving incomplete data.""" @staticmethod @pytest.mark.parametrize( @@ -185,9 +178,7 @@ def test_missing_data( image_file_failed_state: io.BytesIO, data_to_remove: str, ) -> None: - """ - `name`, `width` and `image` are all required. - """ + """`name`, `width` and `image` are all required.""" image_data = image_file_failed_state.getvalue() image_data_encoded = base64.b64encode(s=image_data).decode( encoding="ascii", @@ -212,9 +203,7 @@ def test_missing_data( @pytest.mark.usefixtures("verify_mock_vuforia") class TestWidth: - """ - Tests for the target width field. - """ + """Tests for the target width field.""" @staticmethod @pytest.mark.parametrize( @@ -227,9 +216,7 @@ def test_width_invalid( image_file_failed_state: io.BytesIO, width: int | str | None, ) -> None: - """ - The width must be a number greater than zero. - """ + """The width must be a number greater than zero.""" image_data = image_file_failed_state.getvalue() image_data_encoded = base64.b64encode(s=image_data).decode( encoding="ascii" @@ -255,9 +242,7 @@ def test_width_valid( vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: - """ - Positive numbers are valid widths. - """ + """Positive numbers are valid widths.""" vws_client.add_target( name="example", width=0.01, @@ -269,9 +254,7 @@ def test_width_valid( @pytest.mark.usefixtures("verify_mock_vuforia") class TestTargetName: - """ - Tests for the target name field. - """ + """Tests for the target name field.""" _MAX_CHAR_VALUE = 65535 _MAX_NAME_LENGTH = 64 @@ -294,9 +277,7 @@ def test_name_valid( image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: - """ - Names between 1 and 64 characters in length are valid. - """ + """Names between 1 and 64 characters in length are valid.""" vws_client.add_target( name=name, width=1, @@ -335,7 +316,8 @@ def test_name_invalid( vws_client: VWS, ) -> None: """ - A target's name must be a string of length 0 < N < 65, with characters + A target's name must be a string of length 0 < N < 65, with + characters in a particular range. """ image_data = image_file_failed_state.getvalue() @@ -370,9 +352,7 @@ def test_existing_target_name( image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: - """ - Only one target can have a given name. - """ + """Only one target can have a given name.""" vws_client.add_target( name="example_name", width=1, @@ -401,9 +381,7 @@ def test_deleted_existing_target_name( image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: - """ - A target can be added with the name of a deleted target. - """ + """A target can be added with the name of a deleted target.""" target_id = vws_client.add_target( name="example_name", width=1, @@ -437,7 +415,8 @@ def test_image_valid( image_files_failed_state: io.BytesIO, ) -> None: """ - JPEG and PNG files in the RGB and greyscale color spaces are allowed. + JPEG and PNG files in the RGB and greyscale color spaces are + allowed. """ vws_client.add_target( name="example_name", @@ -453,7 +432,8 @@ def test_bad_image_format_or_color_space( vws_client: VWS, ) -> None: """ - An `UNPROCESSABLE_ENTITY` response is returned if an image which is not + An `UNPROCESSABLE_ENTITY` response is returned if an image which + is not a JPEG or PNG file is given, or if the given image is not in the greyscale or RGB color space. """ @@ -477,9 +457,7 @@ def test_corrupted( corrupted_image_file: io.BytesIO, vws_client: VWS, ) -> None: - """ - An error is returned when the given image is corrupted. - """ + """An error is returned when the given image is corrupted.""" with pytest.raises(expected_exception=BadImageError) as exc: vws_client.add_target( name="example_name", @@ -498,7 +476,8 @@ def test_corrupted( @staticmethod def test_image_file_size_too_large(vws_client: VWS) -> None: """ - An ``ImageTooLargeError`` result is returned if the image file size is + An ``ImageTooLargeError`` result is returned if the image file + size is above a certain threshold. """ max_bytes = 2.3 * 1024 * 1024 @@ -567,7 +546,8 @@ def test_not_base64_encoded_processable( vws_client: VWS, not_base64_encoded_processable: str, ) -> None: - """Some strings which are not valid base64 encoded strings are allowed + """Some strings which are not valid base64 encoded strings are + allowed as an image without getting a "Fail" response. This is because Vuforia treats them as valid base64, but then @@ -595,7 +575,8 @@ def test_not_base64_encoded_not_processable( ) -> None: """ Some strings which are not valid base64 encoded strings are not - processable by Vuforia, and then when given as an image Vuforia returns + processable by Vuforia, and then when given as an image Vuforia + returns a "Fail" response. """ data = { @@ -616,7 +597,8 @@ def test_not_base64_encoded_not_processable( @staticmethod def test_not_image(vws_client: VWS) -> None: """ - If the given image is not an image file then a `BadImageError` result + If the given image is not an image file then a `BadImageError` + result is returned. """ with pytest.raises(expected_exception=BadImageError) as exc: @@ -643,9 +625,7 @@ def test_invalid_type( invalid_type_image: int | None, vws_client: VWS, ) -> None: - """ - If the given image is not a string, a `Fail` result is returned. - """ + """If the given image is not a string, a `Fail` result is returned.""" data = { "name": "example_name", "width": 1, @@ -664,9 +644,7 @@ def test_invalid_type( @pytest.mark.usefixtures("verify_mock_vuforia") class TestActiveFlag: - """ - Tests for the active flag parameter. - """ + """Tests for the active flag parameter.""" @staticmethod @pytest.mark.parametrize( @@ -679,9 +657,7 @@ def test_valid( image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: - """ - Boolean values and NULL are valid active flags. - """ + """Boolean values and NULL are valid active flags.""" image_data = image_file_failed_state.getvalue() image_data_encoded = base64.b64encode(s=image_data).decode( encoding="ascii", @@ -709,7 +685,8 @@ def test_invalid( vws_client: VWS, ) -> None: """ - Values which are not Boolean values or NULL are not valid active flags. + Values which are not Boolean values or NULL are not valid active + flags. """ active_flag = "string" image_data = image_file_failed_state.getvalue() @@ -743,9 +720,7 @@ def test_not_set( vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: - """ - The active flag defaults to True if it is not set. - """ + """The active flag defaults to True if it is not set.""" image_data = image_file_failed_state.getvalue() image_data_encoded = base64.b64encode(s=image_data).decode( encoding="ascii" @@ -768,9 +743,7 @@ def test_set_to_none( vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: - """ - The active flag defaults to True if it is set to NULL. - """ + """The active flag defaults to True if it is set to NULL.""" image_data = image_file_failed_state.getvalue() image_data_encoded = base64.b64encode(s=image_data).decode( encoding="ascii" @@ -794,7 +767,8 @@ def test_set_to_none( @pytest.mark.usefixtures("verify_mock_vuforia") class TestUnexpectedData: """ - Tests for passing data which is not mandatory or allowed to the endpoint. + Tests for passing data which is not mandatory or allowed to the + endpoint. """ @staticmethod @@ -803,7 +777,8 @@ def test_invalid_extra_data( image_file_failed_state: io.BytesIO, ) -> None: """ - A `BAD_REQUEST` response is returned when unexpected data is given. + A `BAD_REQUEST` response is returned when unexpected data is + given. """ image_data = image_file_failed_state.getvalue() image_data_encoded = base64.b64encode(s=image_data).decode( @@ -829,9 +804,7 @@ def test_invalid_extra_data( @pytest.mark.usefixtures("verify_mock_vuforia") class TestApplicationMetadata: - """ - Tests for the application metadata parameter. - """ + """Tests for the application metadata parameter.""" @staticmethod @pytest.mark.parametrize( @@ -847,9 +820,7 @@ def test_base64_encoded( metadata: bytes, vws_client: VWS, ) -> None: - """ - A base64 encoded string is valid application metadata. - """ + """A base64 encoded string is valid application metadata.""" metadata_encoded = base64.b64encode(s=metadata).decode( encoding="ascii" ) @@ -867,9 +838,7 @@ def test_null( vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: - """ - NULL is valid application metadata. - """ + """NULL is valid application metadata.""" image_data = image_file_failed_state.getvalue() image_data_encoded = base64.b64encode(s=image_data).decode( encoding="ascii" @@ -926,7 +895,8 @@ def test_not_base64_encoded_processable( vws_client: VWS, ) -> None: """ - Some strings which are not valid base64 encoded strings are allowed as + Some strings which are not valid base64 encoded strings are + allowed as application metadata. """ vws_client.add_target( @@ -944,7 +914,8 @@ def test_not_base64_encoded_not_processable( vws_client: VWS, ) -> None: """ - Some strings which are not valid base64 encoded strings are not allowed + Some strings which are not valid base64 encoded strings are not + allowed as application metadata. """ with pytest.raises(expected_exception=FailError) as exc: @@ -968,7 +939,8 @@ def test_metadata_too_large( vws_client: VWS, ) -> None: """ - A base64 encoded string of greater than 1024 * 1024 bytes is too large + A base64 encoded string of greater than 1024 * 1024 bytes is too + large for application metadata. """ metadata = b"a" * (_MAX_METADATA_BYTES + 1) @@ -994,9 +966,7 @@ def test_metadata_too_large( @pytest.mark.usefixtures("verify_mock_vuforia") class TestInactiveProject: - """ - Tests for inactive projects. - """ + """Tests for inactive projects.""" @staticmethod def test_inactive_project( @@ -1004,7 +974,8 @@ def test_inactive_project( inactive_vws_client: VWS, ) -> None: """ - If the project is inactive, a FORBIDDEN response is returned. + If the project is inactive, a FORBIDDEN response is + returned. """ with pytest.raises(expected_exception=ProjectInactiveError) as exc: inactive_vws_client.add_target( diff --git a/tests/mock_vws/test_authorization_header.py b/tests/mock_vws/test_authorization_header.py index 3cde8cd05..c017c41ad 100644 --- a/tests/mock_vws/test_authorization_header.py +++ b/tests/mock_vws/test_authorization_header.py @@ -1,6 +1,4 @@ -""" -Tests for the `Authorization` header. -""" +"""Tests for the `Authorization` header.""" import io import json @@ -28,13 +26,15 @@ @pytest.mark.usefixtures("verify_mock_vuforia") class TestAuthorizationHeader: """ - Tests for what happens when the `Authorization` header is not as expected. + Tests for what happens when the `Authorization` header is not as + expected. """ @staticmethod def test_missing(endpoint: Endpoint) -> None: """ - An `UNAUTHORIZED` response is returned when no `Authorization` header + An `UNAUTHORIZED` response is returned when no `Authorization` + header is given. """ date = rfc_1123_date() @@ -82,13 +82,12 @@ def test_missing(endpoint: Endpoint) -> None: @pytest.mark.usefixtures("verify_mock_vuforia") class TestMalformed: - """ - Tests for passing a malformed ``Authorization`` header. - """ + """Tests for passing a malformed ``Authorization`` header.""" @staticmethod def test_one_part_no_space(endpoint: Endpoint) -> None: - """A valid authorization string is two "parts" when split on a space. + """A valid authorization string is two "parts" when split on a + space. When a string is given which is one "part", a ``BAD_REQUEST`` or @@ -142,7 +141,8 @@ def test_one_part_no_space(endpoint: Endpoint) -> None: @staticmethod def test_one_part_with_space(endpoint: Endpoint) -> None: - """A valid authorization string is two "parts" when split on a space. + """A valid authorization string is two "parts" when split on a + space. When a string is given which is one "part", a ``BAD_REQUEST`` or @@ -242,16 +242,15 @@ def test_missing_signature(endpoint: Endpoint) -> None: @pytest.mark.usefixtures("verify_mock_vuforia") class TestBadKey: - """ - Tests for making requests with incorrect keys. - """ + """Tests for making requests with incorrect keys.""" @staticmethod def test_bad_access_key_services( vuforia_database: VuforiaDatabase, ) -> None: """ - If the server access key given does not match any database, a ``Fail`` + If the server access key given does not match any database, a + ``Fail`` response is returned. """ vws_client = VWS( diff --git a/tests/mock_vws/test_content_length.py b/tests/mock_vws/test_content_length.py index 12160dc2e..41e34fe24 100644 --- a/tests/mock_vws/test_content_length.py +++ b/tests/mock_vws/test_content_length.py @@ -1,6 +1,4 @@ -""" -Tests for the ``Content-Length`` header. -""" +"""Tests for the ``Content-Length`` header.""" import textwrap from http import HTTPStatus @@ -30,7 +28,8 @@ class TestIncorrect: @staticmethod def test_not_integer(endpoint: Endpoint) -> None: """ - A ``BAD_REQUEST`` error is given when the given ``Content-Length`` is + A ``BAD_REQUEST`` error is given when the given ``Content- + Length`` is not an integer. """ if not endpoint.headers.get("Content-Type"): @@ -92,9 +91,7 @@ def test_not_integer(endpoint: Endpoint) -> None: @staticmethod @pytest.mark.skip(reason="It takes too long to run this test.") def test_too_large(endpoint: Endpoint) -> None: # pragma: no cover - """ - An error is given if the given content length is too large. - """ + """An error is given if the given content length is too large.""" if not endpoint.headers.get("Content-Type"): pytest.skip(reason="No Content-Type header for this request") @@ -150,7 +147,8 @@ def test_too_large(endpoint: Endpoint) -> None: # pragma: no cover @staticmethod def test_too_small(endpoint: Endpoint) -> None: """ - An ``UNAUTHORIZED`` response is given if the given content length is + An ``UNAUTHORIZED`` response is given if the given content + length is too small. """ if not endpoint.headers.get("Content-Type"): diff --git a/tests/mock_vws/test_database_summary.py b/tests/mock_vws/test_database_summary.py index d1ce06f32..e0220319f 100644 --- a/tests/mock_vws/test_database_summary.py +++ b/tests/mock_vws/test_database_summary.py @@ -1,6 +1,4 @@ -""" -Tests for the mock of the database summary endpoint. -""" +"""Tests for the mock of the database summary endpoint.""" import io import logging @@ -25,9 +23,7 @@ @beartype def _log_attempt_number(retry_state: RetryCallState) -> None: - """ - Log the attempt number of a retry. - """ + """Log the attempt number of a retry.""" attempt_number: int = retry_state.attempt_number message = f"Attempt number: {attempt_number}" LOGGER.debug(msg=message) @@ -92,7 +88,8 @@ def _wait_for_image_numbers( @pytest.mark.usefixtures("verify_mock_vuforia") class TestDatabaseSummary: """ - Tests for the mock of the database summary endpoint at `GET /summary`. + Tests for the mock of the database summary endpoint at `GET + /summary`. """ @staticmethod @@ -100,9 +97,7 @@ def test_success( vuforia_database: VuforiaDatabase, vws_client: VWS, ) -> None: - """ - It is possible to get a success response. - """ + """It is possible to get a success response.""" report = vws_client.get_database_summary_report() assert report.name == vuforia_database.database_name @@ -116,9 +111,7 @@ def test_success( @staticmethod def test_active_images(vws_client: VWS, target_id: str) -> None: - """ - The number of images in the active state is returned. - """ + """The number of images in the active state is returned.""" vws_client.wait_for_target_processed(target_id=target_id) _wait_for_image_numbers( @@ -134,9 +127,7 @@ def test_failed_images( image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: - """ - The number of images with a 'failed' status is returned. - """ + """The number of images with a 'failed' status is returned.""" target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, @@ -161,7 +152,8 @@ def test_inactive_images( image_file_success_state_low_rating: io.BytesIO, ) -> None: """ - The number of images with a False active_flag and a 'success' status is + The number of images with a False active_flag and a 'success' + status is returned. """ target_id = vws_client.add_target( @@ -187,9 +179,7 @@ def test_inactive_failed( image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: - """ - An image with a 'failed' status does not show as inactive. - """ + """An image with a 'failed' status does not show as inactive.""" target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, @@ -213,9 +203,7 @@ def test_deleted( image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: - """ - Deleted targets are not shown in the summary. - """ + """Deleted targets are not shown in the summary.""" target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, @@ -250,9 +238,7 @@ class TestProcessingImages: def test_processing_images( image_file_success_state_low_rating: io.BytesIO, ) -> None: - """ - The number of images in the processing state is returned. - """ + """The number of images in the processing state is returned.""" database = VuforiaDatabase() vws_client = VWS( server_access_key=database.server_access_key, @@ -280,9 +266,7 @@ def test_processing_images( @pytest.mark.usefixtures("verify_mock_vuforia") class TestQuotas: - """ - Tests for quotas and thresholds. - """ + """Tests for quotas and thresholds.""" @staticmethod def test_quotas(vws_client: VWS) -> None: @@ -301,9 +285,7 @@ def test_quotas(vws_client: VWS) -> None: @pytest.mark.usefixtures("verify_mock_vuforia") class TestRecos: - """ - Tests for the recognition count fields. - """ + """Tests for the recognition count fields.""" @staticmethod def test_query_request( @@ -311,7 +293,8 @@ def test_query_request( high_quality_image: io.BytesIO, vws_client: VWS, ) -> None: - """The ``*_recos`` counts seem to be delayed by a significant amount of + """The ``*_recos`` counts seem to be delayed by a significant + amount of time. We therefore test that they exist, are integers and do not @@ -343,14 +326,13 @@ def test_query_request( @pytest.mark.usefixtures("verify_mock_vuforia") class TestRequestUsage: - """ - Tests for the ``request_usage`` field. - """ + """Tests for the ``request_usage`` field.""" @staticmethod def test_target_request(vws_client: VWS) -> None: """ - The ``request_usage`` count does not increase with each request to the + The ``request_usage`` count does not increase with each request + to the target API. """ report = vws_client.get_database_summary_report() @@ -366,7 +348,8 @@ def test_bad_target_request( vws_client: VWS, ) -> None: """ - The ``request_usage`` count does not increase with each request to the + The ``request_usage`` count does not increase with each request + to the target API, even if it is a bad request. """ report = vws_client.get_database_summary_report() @@ -394,7 +377,8 @@ def test_query_request( vws_client: VWS, ) -> None: """ - The ``request_usage`` count does not increase with each query. + The ``request_usage`` count does not increase with each + query. """ report = vws_client.get_database_summary_report() original_request_usage = report.request_usage @@ -408,15 +392,11 @@ def test_query_request( @pytest.mark.usefixtures("verify_mock_vuforia") class TestInactiveProject: - """ - Tests for inactive projects. - """ + """Tests for inactive projects.""" @staticmethod def test_inactive_project( inactive_vws_client: VWS, ) -> None: - """ - The project's active state does not affect the database summary. - """ + """The project's active state does not affect the database summary.""" inactive_vws_client.get_database_summary_report() diff --git a/tests/mock_vws/test_date_header.py b/tests/mock_vws/test_date_header.py index 9c4c2302d..eea178ac3 100644 --- a/tests/mock_vws/test_date_header.py +++ b/tests/mock_vws/test_date_header.py @@ -1,6 +1,4 @@ -""" -Tests for the `Date` header. -""" +"""Tests for the `Date` header.""" import json from datetime import datetime, timedelta @@ -30,14 +28,13 @@ @pytest.mark.usefixtures("verify_mock_vuforia") class TestMissing: - """ - Tests for what happens when the `Date` header is missing. - """ + """Tests for what happens when the `Date` header is missing.""" @staticmethod def test_no_date_header(endpoint: Endpoint) -> None: """ - A `BAD_REQUEST` response is returned when no `Date` header is given. + A `BAD_REQUEST` response is returned when no `Date` header is + given. """ authorization_string = authorization_header( access_key=endpoint.access_key, @@ -102,7 +99,8 @@ class TestFormat: @staticmethod def test_incorrect_date_format(endpoint: Endpoint) -> None: - """A `BAD_REQUEST` response is returned when the date given in the date + """A `BAD_REQUEST` response is returned when the date given in the + date header is not in the expected format (RFC 1123) to VWS API. An `UNAUTHORIZED` response is returned to the VWQ API. @@ -166,14 +164,16 @@ def test_incorrect_date_format(endpoint: Endpoint) -> None: @pytest.mark.usefixtures("verify_mock_vuforia") class TestSkewedTime: """ - Tests for what happens when the `Date` header is given with an unexpected + Tests for what happens when the `Date` header is given with an + unexpected time. """ @staticmethod def test_date_out_of_range_after(endpoint: Endpoint) -> None: """If the date header is more than five minutes (target API) or 65 - minutes (query API) after the request is sent, a `FORBIDDEN` response + minutes (query API) after the request is sent, a `FORBIDDEN` + response is returned. Because there is a small delay in sending requests and Vuforia @@ -249,7 +249,8 @@ def test_date_out_of_range_after(endpoint: Endpoint) -> None: @staticmethod def test_date_out_of_range_before(endpoint: Endpoint) -> None: """If the date header is more than five minutes (target API) or 65 - minutes (query API) before the request is sent, a `FORBIDDEN` response + minutes (query API) before the request is sent, a `FORBIDDEN` + response is returned. Because there is a small delay in sending requests and Vuforia @@ -324,7 +325,8 @@ def test_date_out_of_range_before(endpoint: Endpoint) -> None: @staticmethod def test_date_in_range_after(endpoint: Endpoint) -> None: - """If a date header is within five minutes after the request is sent, + """If a date header is within five minutes after the request is + sent, no error is returned. Because there is a small delay in sending requests and Vuforia @@ -387,7 +389,8 @@ def test_date_in_range_after(endpoint: Endpoint) -> None: @staticmethod def test_date_in_range_before(endpoint: Endpoint) -> None: - """If a date header is within five minutes before the request is sent, + """If a date header is within five minutes before the request is + sent, no error is returned. Because there is a small delay in sending requests and Vuforia diff --git a/tests/mock_vws/test_delete_target.py b/tests/mock_vws/test_delete_target.py index 755d9b24c..9c3f68d1e 100644 --- a/tests/mock_vws/test_delete_target.py +++ b/tests/mock_vws/test_delete_target.py @@ -1,6 +1,4 @@ -""" -Tests for deleting targets. -""" +"""Tests for deleting targets.""" from http import HTTPStatus @@ -18,13 +16,12 @@ @pytest.mark.usefixtures("verify_mock_vuforia") class TestDelete: - """ - Tests for deleting targets. - """ + """Tests for deleting targets.""" @staticmethod def test_no_wait(target_id: str, vws_client: VWS) -> None: - """When attempting to delete a target immediately after creating it, a + """When attempting to delete a target immediately after creating + it, a `FORBIDDEN` response is returned. This is because the target goes into a processing state. @@ -45,9 +42,7 @@ def test_no_wait(target_id: str, vws_client: VWS) -> None: @staticmethod def test_processed(target_id: str, vws_client: VWS) -> None: - """ - When a target has finished processing, it can be deleted. - """ + """When a target has finished processing, it can be deleted.""" vws_client.wait_for_target_processed(target_id=target_id) vws_client.delete_target(target_id=target_id) @@ -57,14 +52,13 @@ def test_processed(target_id: str, vws_client: VWS) -> None: @pytest.mark.usefixtures("verify_mock_vuforia") class TestInactiveProject: - """ - Tests for inactive projects. - """ + """Tests for inactive projects.""" @staticmethod def test_inactive_project(inactive_vws_client: VWS) -> None: """ - If the project is inactive, a FORBIDDEN response is returned. + If the project is inactive, a FORBIDDEN response is + returned. """ target_id = "abc12345a" with pytest.raises(expected_exception=ProjectInactiveError) as exc: diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py index d3d18c16a..0484cb253 100644 --- a/tests/mock_vws/test_docker.py +++ b/tests/mock_vws/test_docker.py @@ -1,6 +1,4 @@ -""" -Tests for running the mock server in Docker. -""" +"""Tests for running the mock server in Docker.""" import io import uuid @@ -37,9 +35,7 @@ ) @beartype def wait_for_health_check(container: Container) -> None: - """ - Wait for a container to pass its health check. - """ + """Wait for a container to pass its health check.""" container.reload() health_status = container.attrs["State"]["Health"]["Status"] # In theory this might not be hit by coverage. @@ -97,7 +93,8 @@ def test_build_and_run( request: pytest.FixtureRequest, ) -> None: """ - It is possible to build Docker images which combine to make a working mock + It is possible to build Docker images which combine to make a + working mock application. """ repository_root = request.config.rootpath diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index d79b998fe..fffd06804 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -1,6 +1,4 @@ -""" -Tests for the usage of the mock Flask application. -""" +"""Tests for the usage of the mock Flask application.""" import io import json @@ -28,9 +26,7 @@ @pytest.fixture(autouse=True) def _(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]: - """ - Enable a mock service backed by the Flask applications. - """ + """Enable a mock service backed by the Flask applications.""" with responses.RequestsMock( assert_all_requests_are_fired=False, ) as mock_obj: @@ -61,9 +57,7 @@ def _(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]: class TestProcessingTime: - """ - Tests for the time taken to process targets in the mock. - """ + """Tests for the time taken to process targets in the mock.""" # There is a race condition in this test type - if tests start to # fail, consider increasing the leeway. @@ -73,9 +67,7 @@ def test_default( self, image_file_failed_state: io.BytesIO, ) -> None: - """ - By default, targets in the mock takes 2 seconds to be processed. - """ + """By default, targets in the mock takes 2 seconds to be processed.""" database = VuforiaDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) @@ -93,9 +85,7 @@ def test_custom( image_file_failed_state: io.BytesIO, monkeypatch: pytest.MonkeyPatch, ) -> None: - """ - It is possible to set a custom processing time. - """ + """It is possible to set a custom processing time.""" seconds = 5.0 monkeypatch.setenv( name="PROCESSING_TIME_SECONDS", @@ -115,14 +105,13 @@ def test_custom( class TestAddDatabase: - """ - Tests for adding databases to the mock. - """ + """Tests for adding databases to the mock.""" @staticmethod def test_duplicate_keys() -> None: """ - It is not possible to have multiple databases with matching keys. + It is not possible to have multiple databases with matching + keys. """ database = VuforiaDatabase( server_access_key="1", @@ -180,9 +169,7 @@ def test_duplicate_keys() -> None: @staticmethod def test_give_no_details(high_quality_image: io.BytesIO) -> None: - """ - It is possible to create a database without giving any data. - """ + """It is possible to create a database without giving any data.""" databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" response = requests.post(url=databases_url, json={}, timeout=30) assert response.status_code == HTTPStatus.CREATED @@ -208,14 +195,13 @@ def test_give_no_details(high_quality_image: io.BytesIO) -> None: class TestDeleteDatabase: - """ - Tests for deleting databases from the mock. - """ + """Tests for deleting databases from the mock.""" @staticmethod def test_not_found() -> None: """ - A 404 error is returned when trying to delete a database which does not + A 404 error is returned when trying to delete a database which + does not exist. """ databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" @@ -225,9 +211,7 @@ def test_not_found() -> None: @staticmethod def test_delete_database() -> None: - """ - It is possible to delete a database. - """ + """It is possible to delete a database.""" databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" response = requests.post(url=databases_url, json={}, timeout=30) assert response.status_code == HTTPStatus.CREATED @@ -242,18 +226,14 @@ def test_delete_database() -> None: class TestQueryImageMatchers: - """ - Tests for query image matchers. - """ + """Tests for query image matchers.""" @staticmethod def test_exact_match( high_quality_image: io.BytesIO, monkeypatch: pytest.MonkeyPatch, ) -> None: - """ - The exact matcher matches only exactly the same images. - """ + """The exact matcher matches only exactly the same images.""" monkeypatch.setenv(name="QUERY_IMAGE_MATCHER", value="exact") database = VuforiaDatabase() @@ -297,9 +277,7 @@ def test_structural_similarity_matcher( different_high_quality_image: io.BytesIO, monkeypatch: pytest.MonkeyPatch, ) -> None: - """ - The structural similarity matcher matches similar images. - """ + """The structural similarity matcher matches similar images.""" monkeypatch.setenv( name="QUERY_IMAGE_MATCHER", value="structural_similarity", @@ -346,18 +324,14 @@ def test_structural_similarity_matcher( class TestDuplicatesImageMatchers: - """ - Tests for duplicates image matchers. - """ + """Tests for duplicates image matchers.""" @staticmethod def test_exact_match( high_quality_image: io.BytesIO, monkeypatch: pytest.MonkeyPatch, ) -> None: - """ - The exact matcher matches only exactly the same images. - """ + """The exact matcher matches only exactly the same images.""" monkeypatch.setenv(name="DUPLICATES_IMAGE_MATCHER", value="exact") database = VuforiaDatabase() vws_client = VWS( @@ -406,9 +380,7 @@ def test_structural_similarity_matcher( high_quality_image: io.BytesIO, monkeypatch: pytest.MonkeyPatch, ) -> None: - """ - The structural similarity matcher matches similar images. - """ + """The structural similarity matcher matches similar images.""" monkeypatch.setenv( name="DUPLICATES_IMAGE_MATCHER", value="structural_similarity", @@ -447,18 +419,14 @@ def test_structural_similarity_matcher( class TestTargetRaters: - """ - Tests for using target raters. - """ + """Tests for using target raters.""" @staticmethod def test_default( image_file_success_state_low_rating: io.BytesIO, high_quality_image: io.BytesIO, ) -> None: - """ - By default, the BRISQUE target rater is used. - """ + """By default, the BRISQUE target rater is used.""" database = VuforiaDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) @@ -507,9 +475,7 @@ def test_brisque( image_file_success_state_low_rating: io.BytesIO, high_quality_image: io.BytesIO, ) -> None: - """ - It is possible to use the BRISQUE target rater. - """ + """It is possible to use the BRISQUE target rater.""" monkeypatch.setenv(name="TARGET_RATER", value="brisque") database = VuforiaDatabase() @@ -559,9 +525,7 @@ def test_perfect( monkeypatch: pytest.MonkeyPatch, high_quality_image: io.BytesIO, ) -> None: - """ - It is possible to use the perfect target rater. - """ + """It is possible to use the perfect target rater.""" monkeypatch.setenv(name="TARGET_RATER", value="perfect") database = VuforiaDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" @@ -600,9 +564,7 @@ def test_random( monkeypatch: pytest.MonkeyPatch, high_quality_image: io.BytesIO, ) -> None: - """ - It is possible to use the random target rater. - """ + """It is possible to use the random target rater.""" monkeypatch.setenv(name="TARGET_RATER", value="random") database = VuforiaDatabase() diff --git a/tests/mock_vws/test_get_duplicates.py b/tests/mock_vws/test_get_duplicates.py index c52b64730..5634e8737 100644 --- a/tests/mock_vws/test_get_duplicates.py +++ b/tests/mock_vws/test_get_duplicates.py @@ -1,6 +1,4 @@ -""" -Tests for the mock of the get duplicates endpoint. -""" +"""Tests for the mock of the get duplicates endpoint.""" import copy import io @@ -15,9 +13,7 @@ @pytest.mark.usefixtures("verify_mock_vuforia") class TestDuplicates: - """ - Tests for the mock of the target duplicates endpoint. - """ + """Tests for the mock of the target duplicates endpoint.""" @staticmethod def test_duplicates( @@ -25,9 +21,7 @@ def test_duplicates( image_file_success_state_low_rating: io.BytesIO, vws_client: VWS, ) -> None: - """ - Target IDs of the exact same targets are returned. - """ + """Target IDs of the exact same targets are returned.""" image_data = high_quality_image different_image_data = image_file_success_state_low_rating @@ -70,9 +64,7 @@ def test_duplicates_not_same( high_quality_image: io.BytesIO, vws_client: VWS, ) -> None: - """ - Target IDs of similar targets are returned. - """ + """Target IDs of similar targets are returned.""" image_data = high_quality_image similar_image_data = copy.copy(x=image_data) similar_image_buffer = io.BytesIO() @@ -111,9 +103,7 @@ def test_status( image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: - """ - Targets are not duplicates if the status is not 'success'. - """ + """Targets are not duplicates if the status is not 'success'.""" original_target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, @@ -147,9 +137,7 @@ def test_status( @pytest.mark.usefixtures("verify_mock_vuforia") class TestActiveFlag: - """ - Tests for the effects of the active flag on duplicate matching. - """ + """Tests for the effects of the active flag on duplicate matching.""" @staticmethod def test_active_flag( @@ -157,7 +145,8 @@ def test_active_flag( vws_client: VWS, ) -> None: """Targets with `active_flag` set to `False` can have duplicates. - Targets with `active_flag` set to `False` are not found as duplicates. + Targets with `active_flag` set to `False` are not found as + duplicates. https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#check says: @@ -201,9 +190,7 @@ def test_active_flag( @pytest.mark.usefixtures("verify_mock_vuforia") class TestProcessing: - """ - Tests for targets in the processing stage. - """ + """Tests for targets in the processing stage.""" @staticmethod def test_processing( @@ -254,14 +241,13 @@ def test_processing( @pytest.mark.usefixtures("verify_mock_vuforia") class TestInactiveProject: - """ - Tests for inactive projects. - """ + """Tests for inactive projects.""" @staticmethod def test_inactive_project(inactive_vws_client: VWS) -> None: """ - If the project is inactive, a FORBIDDEN response is returned. + If the project is inactive, a FORBIDDEN response is + returned. """ with pytest.raises(expected_exception=ProjectInactiveError): inactive_vws_client.get_duplicate_targets( diff --git a/tests/mock_vws/test_get_target.py b/tests/mock_vws/test_get_target.py index 435982bc5..e0d22f634 100644 --- a/tests/mock_vws/test_get_target.py +++ b/tests/mock_vws/test_get_target.py @@ -14,18 +14,14 @@ @pytest.mark.usefixtures("verify_mock_vuforia") class TestGetRecord: - """ - Tests for getting a target record. - """ + """Tests for getting a target record.""" @staticmethod def test_get_vws_target( vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: - """ - Details of a target are returned. - """ + """Details of a target are returned.""" name = "my_example_name" width = 1234 @@ -61,7 +57,8 @@ def test_fail_status( image_file_failed_state: io.BytesIO, ) -> None: """ - When a 1x1 image is given, the status changes from 'processing' to + When a 1x1 image is given, the status changes from 'processing' + to 'failed' after some time. """ target_id = vws_client.add_target( @@ -83,7 +80,8 @@ def test_success_status( image_file_success_state_low_rating: io.BytesIO, vws_client: VWS, ) -> None: - """When a random, large enough image is given, the status changes from + """When a random, large enough image is given, the status changes + from 'processing' to 'success' after some time. The mock is much more lenient than the real implementation of @@ -118,9 +116,7 @@ def _get_target_tracking_rating( vws_client: VWS, image_file: io.BytesIO, ) -> int: - """ - Get the tracking rating of a target with the given image. - """ + """Get the tracking rating of a target with the given image.""" target_id = vws_client.add_target( name=f"example_{uuid.uuid4().hex}", width=1, @@ -147,9 +143,7 @@ def test_target_quality( high_quality_image: io.BytesIO, image_file_success_state_low_rating: io.BytesIO, ) -> None: - """ - The target tracking rating is as expected. - """ + """The target tracking rating is as expected.""" high_quality_image_tracking_rating = _get_target_tracking_rating( vws_client=vws_client, image_file=high_quality_image, @@ -166,14 +160,10 @@ def test_target_quality( @pytest.mark.usefixtures("verify_mock_vuforia") class TestInactiveProject: - """ - Tests for inactive projects. - """ + """Tests for inactive projects.""" @staticmethod def test_inactive_project(inactive_vws_client: VWS) -> None: - """ - The project's active state does not affect getting a target. - """ + """The project's active state does not affect getting a target.""" with pytest.raises(expected_exception=UnknownTargetError): inactive_vws_client.get_target_record(target_id=uuid.uuid4().hex) diff --git a/tests/mock_vws/test_invalid_given_id.py b/tests/mock_vws/test_invalid_given_id.py index 7857868e8..067867f75 100644 --- a/tests/mock_vws/test_invalid_given_id.py +++ b/tests/mock_vws/test_invalid_given_id.py @@ -1,5 +1,6 @@ """ -Tests for passing invalid target IDs to endpoints which require a target ID to +Tests for passing invalid target IDs to endpoints which require a target +ID to be given. """ @@ -17,7 +18,8 @@ @pytest.mark.usefixtures("verify_mock_vuforia") class TestInvalidGivenID: """ - Tests for giving an invalid ID to endpoints which require a target ID to be + Tests for giving an invalid ID to endpoints which require a target + ID to be given. """ @@ -28,7 +30,8 @@ def test_not_real_id( target_id: str, ) -> None: """ - A `NOT_FOUND` error is returned when an endpoint is given a target ID + A `NOT_FOUND` error is returned when an endpoint is given a + target ID of a target which does not exist. """ if not endpoint.path_url.endswith(target_id): diff --git a/tests/mock_vws/test_invalid_json.py b/tests/mock_vws/test_invalid_json.py index afc3b75ea..55d01ff6d 100644 --- a/tests/mock_vws/test_invalid_json.py +++ b/tests/mock_vws/test_invalid_json.py @@ -1,6 +1,4 @@ -""" -Tests for giving invalid JSON to endpoints. -""" +"""Tests for giving invalid JSON to endpoints.""" import json from datetime import datetime, timedelta @@ -25,15 +23,11 @@ @pytest.mark.usefixtures("verify_mock_vuforia") class TestInvalidJSON: - """ - Tests for giving invalid JSON to endpoints. - """ + """Tests for giving invalid JSON to endpoints.""" @staticmethod def test_invalid_json(endpoint: Endpoint) -> None: - """ - Giving invalid JSON to endpoints returns error responses. - """ + """Giving invalid JSON to endpoints returns error responses.""" content = b"a" gmt = ZoneInfo(key="GMT") now = datetime.now(tz=gmt) @@ -108,9 +102,7 @@ def test_invalid_json(endpoint: Endpoint) -> None: @staticmethod def test_invalid_json_with_skewed_time(endpoint: Endpoint) -> None: - """ - Giving invalid JSON to endpoints returns error responses. - """ + """Giving invalid JSON to endpoints returns error responses.""" # We use a skew of 70 because the maximum allowed skew for services is # 5 minutes, and for query is 65 minutes. 70 is comfortably larger than # the max of these two. diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index f2a34c16b..49151ccdb 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -147,9 +147,7 @@ def _query( @pytest.mark.usefixtures("verify_mock_vuforia") class TestContentType: - """ - Tests for the Content-Type header. - """ + """Tests for the Content-Type header.""" @staticmethod @pytest.mark.parametrize( @@ -208,9 +206,7 @@ def test_incorrect_no_boundary( resp_cache_control: str | None, resp_text: str, ) -> None: - """ - With bad Content-Type headers we get a variety of results. - """ + """With bad Content-Type headers we get a variety of results.""" image_content = high_quality_image.getvalue() date = rfc_1123_date() request_path = "/v1/query" @@ -272,8 +268,10 @@ def test_incorrect_with_boundary( vuforia_database: VuforiaDatabase, ) -> None: """ - If a Content-Type header which is not ``multipart/form-data`` is given - with the correct boundary, an ``UNSUPPORTED_MEDIA_TYPE`` response is + If a Content-Type header which is not ``multipart/form-data`` is + given + with the correct boundary, an ``UNSUPPORTED_MEDIA_TYPE`` response + is given. """ image_content = high_quality_image.getvalue() @@ -348,7 +346,8 @@ def test_no_boundary( content_type: str, ) -> None: """ - If no boundary is given, an ``INTERNAL_SERVER_ERROR`` is returned. + If no boundary is given, an ``INTERNAL_SERVER_ERROR`` is + returned. """ image_content = high_quality_image.getvalue() date = rfc_1123_date() @@ -408,9 +407,7 @@ def test_bogus_boundary( high_quality_image: io.BytesIO, vuforia_database: VuforiaDatabase, ) -> None: - """ - If a bogus boundary is given, a ``BAD_REQUEST`` is returned. - """ + """If a bogus boundary is given, a ``BAD_REQUEST`` is returned.""" image_content = high_quality_image.getvalue() date = rfc_1123_date() request_path = "/v1/query" @@ -472,7 +469,8 @@ def test_extra_section( vuforia_database: VuforiaDatabase, ) -> None: """ - If sections that are not the boundary section are given in the header, + If sections that are not the boundary section are given in the + header, that is fine. """ image_content = high_quality_image.getvalue() @@ -525,9 +523,7 @@ def test_extra_section( @pytest.mark.usefixtures("verify_mock_vuforia") class TestSuccess: - """ - Tests for successful calls to the query endpoint. - """ + """Tests for successful calls to the query endpoint.""" @staticmethod def test_no_results( @@ -535,7 +531,8 @@ def test_no_results( cloud_reco_client: CloudRecoService, ) -> None: """ - When there are no matching images in the database, an empty list of + When there are no matching images in the database, an empty list + of results is returned. """ results = cloud_reco_client.query(image=high_quality_image) @@ -548,7 +545,8 @@ def test_match_exact( vws_client: VWS, ) -> None: """ - If the exact high quality image that was added is queried for, target + If the exact high quality image that was added is queried for, + target data is shown. """ image_file = high_quality_image @@ -626,7 +624,8 @@ def test_match_similar( cloud_reco_client: CloudRecoService, ) -> None: """ - If a similar image to one that was added is queried for, target data is + If a similar image to one that was added is queried for, target + data is shown. """ metadata_encoded = base64.b64encode(s=b"example").decode( @@ -675,8 +674,10 @@ def test_not_base64_encoded_processable( cloud_reco_client: CloudRecoService, ) -> None: """ - Vuforia accepts some metadata strings which are not valid base64. - When a target with such a string is matched by a query, Vuforia returns + Vuforia accepts some metadata strings which are not valid + base64. + When a target with such a string is matched by a query, Vuforia + returns an interesting result: * If the metadata string is a length one greater than a multiple of 4, @@ -717,14 +718,13 @@ def test_not_base64_encoded_processable( @pytest.mark.usefixtures("verify_mock_vuforia") class TestIncorrectFields: - """ - Tests for incorrect and unexpected fields. - """ + """Tests for incorrect and unexpected fields.""" @staticmethod def test_missing_image(vuforia_database: VuforiaDatabase) -> None: """ - If an image is not given, a ``BAD_REQUEST`` response is returned. + If an image is not given, a ``BAD_REQUEST`` response is + returned. """ response = _query(vuforia_database=vuforia_database, body={}) @@ -744,7 +744,8 @@ def test_extra_fields( vuforia_database: VuforiaDatabase, ) -> None: """ - If extra fields are given, a ``BAD_REQUEST`` response is returned. + If extra fields are given, a ``BAD_REQUEST`` response is + returned. """ image_content = high_quality_image.getvalue() body = { @@ -792,9 +793,7 @@ def test_missing_image_and_extra_fields( @pytest.mark.usefixtures("verify_mock_vuforia") class TestMaxNumResults: - """ - Tests for the ``max_num_results`` parameter. - """ + """Tests for the ``max_num_results`` parameter.""" @staticmethod def test_default( @@ -802,9 +801,7 @@ def test_default( vuforia_database: VuforiaDatabase, vws_client: VWS, ) -> None: - """ - The default ``max_num_results`` is 1. - """ + """The default ``max_num_results`` is 1.""" image_content = high_quality_image.getvalue() target_id_1 = vws_client.add_target( @@ -871,9 +868,7 @@ def test_valid_works( vws_client: VWS, cloud_reco_client: CloudRecoService, ) -> None: - """ - A maximum of ``max_num_results`` results are returned. - """ + """A maximum of ``max_num_results`` results are returned.""" _add_and_wait_for_targets( image=high_quality_image, vws_client=vws_client, @@ -895,7 +890,8 @@ def test_out_of_range( num_results: int, cloud_reco_client: CloudRecoService, ) -> None: - """An error is returned if ``max_num_results`` is given as an integer + """An error is returned if ``max_num_results`` is given as an + integer out of the range (1, 50). The documentation at @@ -935,7 +931,8 @@ def test_invalid_type( vuforia_database: VuforiaDatabase, num_results: bytes, ) -> None: - """An error is returned if ``max_num_results`` is given as something + """An error is returned if ``max_num_results`` is given as + something other than an integer. Integers greater than 2147483647 are not considered integers @@ -970,9 +967,7 @@ def _add_and_wait_for_targets( vws_client: VWS, num_targets: int, ) -> None: - """ - Add targets with the given image. - """ + """Add targets with the given image.""" target_ids: Iterable[str] = set() for _ in range(num_targets): target_id = vws_client.add_target( @@ -990,9 +985,7 @@ def _add_and_wait_for_targets( @pytest.mark.usefixtures("verify_mock_vuforia") class TestIncludeTargetData: - """ - Tests for the ``include_target_data`` parameter. - """ + """Tests for the ``include_target_data`` parameter.""" @staticmethod def test_default( @@ -1000,9 +993,7 @@ def test_default( vws_client: VWS, vuforia_database: VuforiaDatabase, ) -> None: - """ - The default ``include_target_data`` is 'top'. - """ + """The default ``include_target_data`` is 'top'.""" _add_and_wait_for_targets( image=high_quality_image, vws_client=vws_client, @@ -1034,7 +1025,8 @@ def test_top( vws_client: VWS, ) -> None: """ - When ``include_target_data`` is set to "top" (case insensitive), only + When ``include_target_data`` is set to "top" (case insensitive), + only the first result includes target data. """ _add_and_wait_for_targets( @@ -1069,7 +1061,8 @@ def test_none( vws_client: VWS, ) -> None: """ - When ``include_target_data`` is set to "none" (case insensitive), no + When ``include_target_data`` is set to "none" (case + insensitive), no results include target data. """ _add_and_wait_for_targets( @@ -1104,7 +1097,8 @@ def test_all( vws_client: VWS, ) -> None: """ - When ``include_target_data`` is set to "all" (case insensitive), all + When ``include_target_data`` is set to "all" (case insensitive), + all results include target data. """ _add_and_wait_for_targets( @@ -1139,7 +1133,8 @@ def test_invalid_value( include_target_data: str | bool | int, ) -> None: """ - A ``BAD_REQUEST`` error is given when a string that is not one of + A ``BAD_REQUEST`` error is given when a string that is not one + of 'none', 'top' or 'all' (case insensitive). """ image_content = high_quality_image.getvalue() @@ -1168,9 +1163,7 @@ def test_invalid_value( @pytest.mark.usefixtures("verify_mock_vuforia") class TestAcceptHeader: - """ - Tests for the ``Accept`` header. - """ + """Tests for the ``Accept`` header.""" @staticmethod @pytest.mark.parametrize( @@ -1187,8 +1180,8 @@ def test_valid( vuforia_database: VuforiaDatabase, extra_headers: dict[str, str], ) -> None: - """ - An ``Accept`` header can be given iff its value is "application/json". + """An ``Accept`` header can be given iff its value is + "application/json". """ image_content = high_quality_image.getvalue() date = rfc_1123_date() @@ -1242,7 +1235,8 @@ def test_invalid( vuforia_database: VuforiaDatabase, ) -> None: """ - A NOT_ACCEPTABLE response is returned if an ``Accept`` header is given + A NOT_ACCEPTABLE response is returned if an ``Accept`` header is + given with a value which is not "application/json". """ image_content = high_quality_image.getvalue() @@ -1302,9 +1296,7 @@ def test_invalid( @pytest.mark.usefixtures("verify_mock_vuforia") class TestActiveFlag: - """ - Tests for active versus inactive targets. - """ + """Tests for active versus inactive targets.""" @staticmethod def test_inactive( @@ -1312,9 +1304,7 @@ def test_inactive( vws_client: VWS, cloud_reco_client: CloudRecoService, ) -> None: - """ - Images which are not active are not matched. - """ + """Images which are not active are not matched.""" target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, @@ -1330,25 +1320,22 @@ def test_inactive( @pytest.mark.usefixtures("verify_mock_vuforia") class TestBadImage: - """ - Tests for bad images. - """ + """Tests for bad images.""" @staticmethod def test_corrupted( corrupted_image_file: io.BytesIO, cloud_reco_client: CloudRecoService, ) -> None: - """ - No error is returned when a corrupted image is given. - """ + """No error is returned when a corrupted image is given.""" results = cloud_reco_client.query(image=corrupted_image_file) assert results == [] @staticmethod def test_not_image(cloud_reco_client: CloudRecoService) -> None: """ - An ``UNPROCESSABLE_ENTITY`` response is returned when a non-image is + An ``UNPROCESSABLE_ENTITY`` response is returned when a non- + image is given. """ not_image_data = b"not_image_data" @@ -1384,15 +1371,14 @@ def test_not_image(cloud_reco_client: CloudRecoService) -> None: @pytest.mark.usefixtures("verify_mock_vuforia") class TestMaximumImageFileSize: - """ - Tests for maximum image file sizes. - """ + """Tests for maximum image file sizes.""" @staticmethod def test_png(cloud_reco_client: CloudRecoService) -> None: """ According to - https://developer.vuforia.com/library/web-api/vuforia-query-web-api. + https://developer.vuforia.com/library/web-api/vuforia-query-web- + api. the maximum file size is "2MiB for PNG". Above this limit, a ``REQUEST_ENTITY_TOO_LARGE`` response is returned. @@ -1462,7 +1448,8 @@ def test_png(cloud_reco_client: CloudRecoService) -> None: def test_jpeg(cloud_reco_client: CloudRecoService) -> None: """ According to - https://developer.vuforia.com/library/web-api/vuforia-query-web-api. + https://developer.vuforia.com/library/web-api/vuforia-query-web- + api. the maximum file size is "512 KiB for JPEG". However, this test shows that the maximum size for JPEG is 2 MiB. @@ -1532,16 +1519,15 @@ def test_jpeg(cloud_reco_client: CloudRecoService) -> None: @pytest.mark.usefixtures("verify_mock_vuforia") class TestMaximumImageDimensions: - """ - Tests for maximum image dimensions. - """ + """Tests for maximum image dimensions.""" @staticmethod def test_max_height( cloud_reco_client: CloudRecoService, ) -> None: """ - An error is returned when an image with a height greater than 30000 is + An error is returned when an image with a height greater than + 30000 is given. """ width = 1 @@ -1593,7 +1579,8 @@ def test_max_height( @staticmethod def test_max_width(cloud_reco_client: CloudRecoService) -> None: """ - An error is returned when an image with a width greater than 30000 is + An error is returned when an image with a width greater than + 30000 is given. """ height = 1 @@ -1644,9 +1631,7 @@ def test_max_width(cloud_reco_client: CloudRecoService) -> None: @staticmethod def test_max_pixels(cloud_reco_client: CloudRecoService) -> None: - """ - No error is returned for an 835 x 835 image. - """ + """No error is returned for an 835 x 835 image.""" # If we make this 836 then we hit REQUEST_ENTITY_TOO_LARGE errors. max_height = max_width = 835 png_not_too_wide = make_image_file( @@ -1662,9 +1647,7 @@ def test_max_pixels(cloud_reco_client: CloudRecoService) -> None: @pytest.mark.usefixtures("verify_mock_vuforia") class TestImageFormats: - """ - Tests for various image formats. - """ + """Tests for various image formats.""" @staticmethod @pytest.mark.parametrize(argnames="file_format", argvalues=["png", "jpeg"]) @@ -1673,9 +1656,7 @@ def test_supported( file_format: str, cloud_reco_client: CloudRecoService, ) -> None: - """ - PNG and JPEG formats are supported. - """ + """PNG and JPEG formats are supported.""" image_buffer = io.BytesIO() pil_image = Image.open(fp=high_quality_image) pil_image.save(fp=image_buffer, format=file_format) @@ -1690,9 +1671,7 @@ def test_unsupported( high_quality_image: io.BytesIO, cloud_reco_client: CloudRecoService, ) -> None: - """ - File formats which are not PNG or JPEG are not supported. - """ + """File formats which are not PNG or JPEG are not supported.""" file_format = "tiff" image_buffer = io.BytesIO() pil_image = Image.open(fp=high_quality_image) @@ -1730,9 +1709,7 @@ def test_unsupported( @pytest.mark.usefixtures("verify_mock_vuforia") class TestProcessing: - """ - Tests for targets in the processing state. - """ + """Tests for targets in the processing state.""" @staticmethod @pytest.mark.parametrize(argnames="active_flag", argvalues=[True, False]) @@ -1744,7 +1721,8 @@ def test_processing( active_flag: bool, ) -> None: """ - When a target with a matching image is in the processing state it is + When a target with a matching image is in the processing state + it is not matched. """ target_id = vws_client.add_target( @@ -1776,9 +1754,7 @@ def test_processing( @pytest.mark.usefixtures("verify_mock_vuforia") class TestUpdate: - """ - Tests for updated targets. - """ + """Tests for updated targets.""" @staticmethod def test_updated_target( @@ -1855,9 +1831,7 @@ def test_updated_target( @pytest.mark.usefixtures("verify_mock_vuforia") class TestDeleted: - """ - Tests for matching deleted targets. - """ + """Tests for matching deleted targets.""" @staticmethod def test_deleted_active( @@ -1865,9 +1839,7 @@ def test_deleted_active( vws_client: VWS, cloud_reco_client: CloudRecoService, ) -> None: - """ - Deleted targets are not matched. - """ + """Deleted targets are not matched.""" target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, @@ -1904,7 +1876,8 @@ def test_deleted_inactive( cloud_reco_client: CloudRecoService, ) -> None: """ - No error is returned when querying for an image of recently deleted, + No error is returned when querying for an image of recently + deleted, inactive target. """ target_id = vws_client.add_target( @@ -1922,9 +1895,7 @@ def test_deleted_inactive( @pytest.mark.usefixtures("verify_mock_vuforia") class TestTargetStatusFailed: - """ - Tests for targets with the status "failed". - """ + """Tests for targets with the status "failed".""" @staticmethod def test_status_failed( @@ -1932,9 +1903,7 @@ def test_status_failed( vws_client: VWS, cloud_reco_client: CloudRecoService, ) -> None: - """ - Targets with the status "failed" are not found in query results. - """ + """Targets with the status "failed" are not found in query results.""" target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, @@ -2041,9 +2010,7 @@ def test_date_formats( @pytest.mark.usefixtures("verify_mock_vuforia") class TestInactiveProject: - """ - Tests for inactive projects. - """ + """Tests for inactive projects.""" @staticmethod def test_inactive_project( @@ -2051,7 +2018,8 @@ def test_inactive_project( inactive_cloud_reco_client: CloudRecoService, ) -> None: """ - If the project is inactive, a FORBIDDEN response is returned. + If the project is inactive, a FORBIDDEN response is + returned. """ with pytest.raises( expected_exception=InactiveProjectError diff --git a/tests/mock_vws/test_requests_mock_usage.py b/tests/mock_vws/test_requests_mock_usage.py index 3cb3471f6..f80b3c078 100644 --- a/tests/mock_vws/test_requests_mock_usage.py +++ b/tests/mock_vws/test_requests_mock_usage.py @@ -1,6 +1,4 @@ -""" -Tests for the usage of the mock for ``requests``. -""" +"""Tests for the usage of the mock for ``requests``.""" import datetime import email.utils @@ -32,9 +30,7 @@ def _not_exact_matcher( first_image_content: bytes, second_image_content: bytes, ) -> bool: - """ - A matcher which returns True if the images are not the same. - """ + """A matcher which returns True if the images are not the same.""" return first_image_content != second_image_content @@ -59,7 +55,8 @@ def request_unmocked_address() -> None: @beartype def request_mocked_address() -> None: """ - Make a request, using `requests` to an address that is mocked by `MockVWS`. + Make a request, using `requests` to an address that is mocked by + `MockVWS`. """ requests.get( url="https://vws.vuforia.com/summary", @@ -73,14 +70,13 @@ def request_mocked_address() -> None: class TestRealHTTP: - """ - Tests for making requests to mocked and unmocked addresses. - """ + """Tests for making requests to mocked and unmocked addresses.""" @staticmethod def test_default() -> None: """ - By default, the mock stops any requests made with `requests` to non- + By default, the mock stops any requests made with `requests` to + non- Vuforia addresses, but not to mocked Vuforia endpoints. """ with MockVWS(): @@ -102,7 +98,8 @@ def test_default() -> None: @staticmethod def test_real_http() -> None: """ - When the `real_http` parameter given to the context manager is set to + When the `real_http` parameter given to the context manager is + set to `True`, requests made to unmocked addresses are not stopped. """ with ( @@ -115,18 +112,14 @@ def test_real_http() -> None: class TestProcessingTime: - """ - Tests for the time taken to process targets in the mock. - """ + """Tests for the time taken to process targets in the mock.""" # There is a race condition in this test type - if tests start to # fail, consider increasing the leeway. LEEWAY = 0.5 def test_default(self, image_file_failed_state: io.BytesIO) -> None: - """ - By default, targets in the mock takes 2 seconds to be processed. - """ + """By default, targets in the mock takes 2 seconds to be processed.""" database = VuforiaDatabase() with MockVWS() as mock: mock.add_database(database=database) @@ -139,9 +132,7 @@ def test_default(self, image_file_failed_state: io.BytesIO) -> None: assert expected - self.LEEWAY < time_taken < expected + self.LEEWAY def test_custom(self, image_file_failed_state: io.BytesIO) -> None: - """ - It is possible to set a custom processing time. - """ + """It is possible to set a custom processing time.""" database = VuforiaDatabase() seconds = 5 with MockVWS(processing_time_seconds=seconds) as mock: @@ -156,15 +147,11 @@ def test_custom(self, image_file_failed_state: io.BytesIO) -> None: class TestDatabaseName: - """ - Tests for the database name. - """ + """Tests for the database name.""" @staticmethod def test_default() -> None: - """ - By default, the database has a random name. - """ + """By default, the database has a random name.""" database_details = VuforiaDatabase() other_database_details = VuforiaDatabase() assert ( @@ -174,23 +161,17 @@ def test_default() -> None: @staticmethod def test_custom_name() -> None: - """ - It is possible to set a custom database name. - """ + """It is possible to set a custom database name.""" database_details = VuforiaDatabase(database_name="foo") assert database_details.database_name == "foo" class TestCustomBaseURLs: - """ - Tests for using custom base URLs. - """ + """Tests for using custom base URLs.""" @staticmethod def test_custom_base_vws_url() -> None: - """ - It is possible to use a custom base VWS URL. - """ + """It is possible to use a custom base VWS URL.""" with MockVWS( base_vws_url="https://vuforia.vws.example.com", real_http=False, @@ -211,9 +192,7 @@ def test_custom_base_vws_url() -> None: @staticmethod def test_custom_base_vwq_url() -> None: - """ - It is possible to use a custom base cloud recognition URL. - """ + """It is possible to use a custom base cloud recognition URL.""" with MockVWS( base_vwq_url="https://vuforia.vwq.example.com", real_http=False, @@ -237,9 +216,7 @@ def test_custom_base_vwq_url() -> None: @staticmethod def test_no_scheme() -> None: - """ - An error if raised if a URL is given with no scheme. - """ + """An error if raised if a URL is given with no scheme.""" with pytest.raises(expected_exception=MissingSchemeError) as vws_exc: MockVWS(base_vws_url="vuforia.vws.example.com") @@ -258,14 +235,13 @@ def test_no_scheme() -> None: class TestTargets: - """ - Tests for target representations. - """ + """Tests for target representations.""" @staticmethod def test_to_dict(high_quality_image: io.BytesIO) -> None: """ - It is possible to dump a target to a dictionary and load it back. + It is possible to dump a target to a dictionary and load it + back. """ database = VuforiaDatabase() @@ -297,7 +273,8 @@ def test_to_dict(high_quality_image: io.BytesIO) -> None: @staticmethod def test_to_dict_deleted(high_quality_image: io.BytesIO) -> None: """ - It is possible to dump a deleted target to a dictionary and load it + It is possible to dump a deleted target to a dictionary and load + it back. """ database = VuforiaDatabase() @@ -331,14 +308,13 @@ def test_to_dict_deleted(high_quality_image: io.BytesIO) -> None: class TestDatabaseToDict: - """ - Tests for dumping a database to a dictionary. - """ + """Tests for dumping a database to a dictionary.""" @staticmethod def test_to_dict(high_quality_image: io.BytesIO) -> None: """ - It is possible to dump a database to a dictionary and load it back. + It is possible to dump a database to a dictionary and load it + back. """ database = VuforiaDatabase() vws_client = VWS( @@ -366,14 +342,13 @@ def test_to_dict(high_quality_image: io.BytesIO) -> None: class TestDateHeader: - """ - Tests for the date header in responses from mock routes. - """ + """Tests for the date header in responses from mock routes.""" @staticmethod def test_date_changes() -> None: """ - The date that the response is sent is in the response Date header. + The date that the response is sent is in the response Date + header. """ new_year = 2012 new_time = datetime.datetime( @@ -396,14 +371,13 @@ def test_date_changes() -> None: class TestAddDatabase: - """ - Tests for adding databases to the mock. - """ + """Tests for adding databases to the mock.""" @staticmethod def test_duplicate_keys() -> None: """ - It is not possible to have multiple databases with matching keys. + It is not possible to have multiple databases with matching + keys. """ database = VuforiaDatabase( server_access_key="1", @@ -457,15 +431,11 @@ def test_duplicate_keys() -> None: class TestQueryImageMatchers: - """ - Tests for query image matchers. - """ + """Tests for query image matchers.""" @staticmethod def test_exact_match(high_quality_image: io.BytesIO) -> None: - """ - The exact matcher matches only exactly the same images. - """ + """The exact matcher matches only exactly the same images.""" database = VuforiaDatabase() vws_client = VWS( server_access_key=database.server_access_key, @@ -501,9 +471,7 @@ def test_exact_match(high_quality_image: io.BytesIO) -> None: @staticmethod def test_custom_matcher(high_quality_image: io.BytesIO) -> None: - """ - It is possible to use a custom matcher. - """ + """It is possible to use a custom matcher.""" database = VuforiaDatabase() vws_client = VWS( server_access_key=database.server_access_key, @@ -542,9 +510,7 @@ def test_structural_similarity_matcher( high_quality_image: io.BytesIO, different_high_quality_image: io.BytesIO, ) -> None: - """ - The structural similarity matcher matches similar images. - """ + """The structural similarity matcher matches similar images.""" database = VuforiaDatabase() vws_client = VWS( server_access_key=database.server_access_key, @@ -587,15 +553,11 @@ def test_structural_similarity_matcher( class TestDuplicatesImageMatchers: - """ - Tests for duplicates image matchers. - """ + """Tests for duplicates image matchers.""" @staticmethod def test_exact_match(high_quality_image: io.BytesIO) -> None: - """ - The exact matcher matches only exactly the same images. - """ + """The exact matcher matches only exactly the same images.""" database = VuforiaDatabase() vws_client = VWS( server_access_key=database.server_access_key, @@ -639,9 +601,7 @@ def test_exact_match(high_quality_image: io.BytesIO) -> None: @staticmethod def test_custom_matcher(high_quality_image: io.BytesIO) -> None: - """ - It is possible to use a custom matcher. - """ + """It is possible to use a custom matcher.""" database = VuforiaDatabase() vws_client = VWS( server_access_key=database.server_access_key, @@ -687,9 +647,7 @@ def test_custom_matcher(high_quality_image: io.BytesIO) -> None: def test_structural_similarity_matcher( high_quality_image: io.BytesIO, ) -> None: - """ - The structural similarity matcher matches similar images. - """ + """The structural similarity matcher matches similar images.""" database = VuforiaDatabase() vws_client = VWS( server_access_key=database.server_access_key, @@ -728,15 +686,11 @@ def test_structural_similarity_matcher( # Flask app. @pytest.mark.usefixtures("mock_only_vuforia") class TestDataTypes: - """ - Tests for sending various data types. - """ + """Tests for sending various data types.""" @staticmethod def test_text(endpoint: Endpoint) -> None: - """ - It is possible to send strings to VWS endpoints. - """ + """It is possible to send strings to VWS endpoints.""" netloc = urlparse(url=endpoint.base_url).netloc if netloc == "cloudreco.vuforia.com": diff --git a/tests/mock_vws/test_target_list.py b/tests/mock_vws/test_target_list.py index 3cc4ea27d..b83db3dfd 100644 --- a/tests/mock_vws/test_target_list.py +++ b/tests/mock_vws/test_target_list.py @@ -1,6 +1,4 @@ -""" -Tests for the mock of the target list endpoint. -""" +"""Tests for the mock of the target list endpoint.""" import pytest from vws import VWS @@ -8,18 +6,14 @@ @pytest.mark.usefixtures("verify_mock_vuforia") class TestTargetList: - """ - Tests for the mock of the target list endpoint at `/targets`. - """ + """Tests for the mock of the target list endpoint at `/targets`.""" @staticmethod def test_includes_targets( vws_client: VWS, target_id: str, ) -> None: - """ - Targets in the database are returned in the list. - """ + """Targets in the database are returned in the list.""" assert vws_client.list_targets() == [target_id] @staticmethod @@ -27,9 +21,7 @@ def test_deleted( vws_client: VWS, target_id: str, ) -> None: - """ - Deleted targets are not returned in the list. - """ + """Deleted targets are not returned in the list.""" vws_client.wait_for_target_processed(target_id=target_id) vws_client.delete_target(target_id=target_id) assert not vws_client.list_targets() @@ -37,14 +29,10 @@ def test_deleted( @pytest.mark.usefixtures("verify_mock_vuforia") class TestInactiveProject: - """ - Tests for inactive projects. - """ + """Tests for inactive projects.""" @staticmethod def test_inactive_project(inactive_vws_client: VWS) -> None: - """ - The project's active state does not affect the target list. - """ + """The project's active state does not affect the target list.""" # No exception is raised. inactive_vws_client.list_targets() diff --git a/tests/mock_vws/test_target_raters.py b/tests/mock_vws/test_target_raters.py index 997833f41..27dbc8cb4 100644 --- a/tests/mock_vws/test_target_raters.py +++ b/tests/mock_vws/test_target_raters.py @@ -1,6 +1,4 @@ -""" -Tests for target quality raters. -""" +"""Tests for target quality raters.""" import io @@ -15,7 +13,8 @@ def test_random_target_tracking_rater() -> None: """ - Test that the random target tracking rater returns a random number. + Test that the random target tracking rater returns a random + number. """ rater = RandomTargetTrackingRater() image_content = b"content" @@ -36,7 +35,8 @@ def test_random_target_tracking_rater() -> None: @pytest.mark.parametrize(argnames="rating", argvalues=range(-10, 10)) def test_hardcoded_target_tracking_rater(rating: int) -> None: """ - Test that the hardcoded target tracking rater returns the hardcoded number. + Test that the hardcoded target tracking rater returns the hardcoded + number. """ rater = HardcodedTargetTrackingRater(rating=rating) image_content = b"content" @@ -45,17 +45,13 @@ def test_hardcoded_target_tracking_rater(rating: int) -> None: class TestBrisqueTargetTrackingRater: - """ - Tests for the BRISQUE target tracking rater. - """ + """Tests for the BRISQUE target tracking rater.""" @staticmethod def test_low_quality_image( image_file_success_state_low_rating: io.BytesIO, ) -> None: - """ - Test that a low quality image returns a low rating. - """ + """Test that a low quality image returns a low rating.""" rater = BrisqueTargetTrackingRater() image_content = image_file_success_state_low_rating.getvalue() rating = rater(image_content=image_content) @@ -63,9 +59,7 @@ def test_low_quality_image( @staticmethod def test_high_quality_image(high_quality_image: io.BytesIO) -> None: - """ - Test that a high quality image returns a high rating. - """ + """Test that a high quality image returns a high rating.""" rater = BrisqueTargetTrackingRater() image_content = high_quality_image.getvalue() rating = rater(image_content=image_content) @@ -75,9 +69,7 @@ def test_high_quality_image(high_quality_image: io.BytesIO) -> None: def test_different_high_quality_image( different_high_quality_image: io.BytesIO, ) -> None: - """ - Test that a high quality image returns a high rating. - """ + """Test that a high quality image returns a high rating.""" rater = BrisqueTargetTrackingRater() image_content = different_high_quality_image.getvalue() rating = rater(image_content=image_content) diff --git a/tests/mock_vws/test_target_summary.py b/tests/mock_vws/test_target_summary.py index c432e78b4..61fbd76ce 100644 --- a/tests/mock_vws/test_target_summary.py +++ b/tests/mock_vws/test_target_summary.py @@ -1,6 +1,4 @@ -""" -Tests for the mock of the target summary endpoint. -""" +"""Tests for the mock of the target summary endpoint.""" import datetime import io @@ -17,9 +15,7 @@ @pytest.mark.usefixtures("verify_mock_vuforia") class TestTargetSummary: - """ - Tests for the target summary endpoint. - """ + """Tests for the target summary endpoint.""" @staticmethod @pytest.mark.parametrize(argnames="active_flag", argvalues=[True, False]) @@ -30,9 +26,7 @@ def test_target_summary( *, active_flag: bool, ) -> None: - """ - A target summary is returned. - """ + """A target summary is returned.""" name = uuid.uuid4().hex gmt = ZoneInfo(key="GMT") date_before_add_target = datetime.datetime.now(tz=gmt).date() @@ -81,7 +75,8 @@ def test_after_processing( image_fixture_name: str, expected_status: TargetStatuses, ) -> None: - """After processing is completed, the tracking rating is in the range + """After processing is completed, the tracking rating is in the + range of 0 to 5. The documentation says: @@ -122,9 +117,7 @@ def test_after_processing( @pytest.mark.usefixtures("verify_mock_vuforia") class TestRecognitionCounts: - """ - Tests for the recognition counts in the summary. - """ + """Tests for the recognition counts in the summary.""" @staticmethod def test_recognition( @@ -132,9 +125,7 @@ def test_recognition( cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, ) -> None: - """ - The recognition counts stay at 0 even after recognitions. - """ + """The recognition counts stay at 0 even after recognitions.""" target_id = vws_client.add_target( name="example", width=1, @@ -158,15 +149,11 @@ def test_recognition( @pytest.mark.usefixtures("verify_mock_vuforia") class TestInactiveProject: - """ - Tests for inactive projects. - """ + """Tests for inactive projects.""" @staticmethod def test_inactive_project(inactive_vws_client: VWS) -> None: - """ - The project's active state does not affect getting a target. - """ + """The project's active state does not affect getting a target.""" with pytest.raises(expected_exception=UnknownTargetError): inactive_vws_client.get_target_summary_report( target_id=uuid.uuid4().hex, diff --git a/tests/mock_vws/test_unexpected_json.py b/tests/mock_vws/test_unexpected_json.py index c7f96856e..497119268 100644 --- a/tests/mock_vws/test_unexpected_json.py +++ b/tests/mock_vws/test_unexpected_json.py @@ -1,6 +1,4 @@ -""" -Tests for giving JSON data to endpoints which do not expect it. -""" +"""Tests for giving JSON data to endpoints which do not expect it.""" import json from http import HTTPStatus @@ -16,14 +14,13 @@ @pytest.mark.usefixtures("verify_mock_vuforia") class TestUnexpectedJSON: - """ - Tests for giving JSON to endpoints which do not expect it. - """ + """Tests for giving JSON to endpoints which do not expect it.""" @staticmethod def test_does_not_take_data(endpoint: Endpoint) -> None: """ - Giving JSON to endpoints which do not take any JSON data returns error + Giving JSON to endpoints which do not take any JSON data returns + error responses. """ if ( diff --git a/tests/mock_vws/test_update_target.py b/tests/mock_vws/test_update_target.py index 3b6a0db1e..1dddaffcc 100644 --- a/tests/mock_vws/test_update_target.py +++ b/tests/mock_vws/test_update_target.py @@ -1,6 +1,4 @@ -""" -Tests for the mock of the update target endpoint. -""" +"""Tests for the mock of the update target endpoint.""" import base64 import io @@ -65,9 +63,7 @@ def _update_target( @pytest.mark.usefixtures("verify_mock_vuforia") class TestUpdate: - """ - Tests for updating targets. - """ + """Tests for updating targets.""" @staticmethod @pytest.mark.parametrize( @@ -86,7 +82,8 @@ def test_content_types( content_type: str, ) -> None: """ - The ``Content-Type`` header does not change the response as long as it + The ``Content-Type`` header does not change the response as long + as it is not empty. """ target_id = vws_client.add_target( @@ -120,7 +117,8 @@ def test_empty_content_type( image_file_failed_state: io.BytesIO, ) -> None: """ - An ``UNAUTHORIZED`` response is given if an empty ``Content-Type`` + An ``UNAUTHORIZED`` response is given if an empty ``Content- + Type`` header is given. """ target_id = vws_client.add_target( @@ -152,9 +150,7 @@ def test_no_fields_given( vws_client: VWS, target_id: str, ) -> None: - """ - No data fields are required. - """ + """No data fields are required.""" vws_client.wait_for_target_processed(target_id=target_id) response = _update_target( @@ -185,9 +181,7 @@ def test_no_fields_given( @pytest.mark.usefixtures("verify_mock_vuforia") class TestUnexpectedData: - """ - Tests for passing data which is not allowed to the endpoint. - """ + """Tests for passing data which is not allowed to the endpoint.""" @staticmethod def test_invalid_extra_data( @@ -195,7 +189,8 @@ def test_invalid_extra_data( target_id: str, ) -> None: """ - A `BAD_REQUEST` response is returned when unexpected data is given. + A `BAD_REQUEST` response is returned when unexpected data is + given. """ vws_client.wait_for_target_processed(target_id=target_id) @@ -215,9 +210,7 @@ def test_invalid_extra_data( @pytest.mark.usefixtures("verify_mock_vuforia") class TestWidth: - """ - Tests for the target width field. - """ + """Tests for the target width field.""" @staticmethod @pytest.mark.parametrize( @@ -230,9 +223,7 @@ def test_width_invalid( width: int | str | None, target_id: str, ) -> None: - """ - The width must be a number greater than zero. - """ + """The width must be a number greater than zero.""" vws_client.wait_for_target_processed(target_id=target_id) target_details = vws_client.get_target_record(target_id=target_id) @@ -256,9 +247,7 @@ def test_width_invalid( @staticmethod def test_width_valid(vws_client: VWS, target_id: str) -> None: - """ - Positive numbers are valid widths. - """ + """Positive numbers are valid widths.""" vws_client.wait_for_target_processed(target_id=target_id) width = 0.01 @@ -269,9 +258,7 @@ def test_width_valid(vws_client: VWS, target_id: str) -> None: @pytest.mark.usefixtures("verify_mock_vuforia") class TestActiveFlag: - """ - Tests for the active flag parameter. - """ + """Tests for the active flag parameter.""" @staticmethod @pytest.mark.parametrize( @@ -289,9 +276,7 @@ def test_active_flag( initial_active_flag: bool, desired_active_flag: bool, ) -> None: - """ - Setting the active flag to a Boolean value changes it. - """ + """Setting the active flag to a Boolean value changes it.""" target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, @@ -320,7 +305,8 @@ def test_invalid( desired_active_flag: str | None, ) -> None: """ - Values which are not Boolean values are not valid active flags. + Values which are not Boolean values are not valid active + flags. """ vws_client.wait_for_target_processed(target_id=target_id) @@ -340,9 +326,7 @@ def test_invalid( @pytest.mark.usefixtures("verify_mock_vuforia") class TestApplicationMetadata: - """ - Tests for the application metadata parameter. - """ + """Tests for the application metadata parameter.""" @staticmethod @pytest.mark.parametrize( @@ -358,9 +342,7 @@ def test_base64_encoded( metadata: bytes, vws_client: VWS, ) -> None: - """ - A base64 encoded string is valid application metadata. - """ + """A base64 encoded string is valid application metadata.""" metadata_encoded = base64.b64encode(s=metadata).decode( encoding="ascii" ) @@ -377,9 +359,7 @@ def test_invalid_type( target_id: str, invalid_metadata: int | None, ) -> None: - """ - Non-string values cannot be given as valid application metadata. - """ + """Non-string values cannot be given as valid application metadata.""" vws_client.wait_for_target_processed(target_id=target_id) with pytest.raises(expected_exception=FailError) as exc: @@ -402,7 +382,8 @@ def test_not_base64_encoded_processable( not_base64_encoded_processable: str, ) -> None: """ - Some strings which are not valid base64 encoded strings are allowed as + Some strings which are not valid base64 encoded strings are + allowed as application metadata. """ vws_client.wait_for_target_processed(target_id=target_id) @@ -419,7 +400,8 @@ def test_not_base64_encoded_not_processable( not_base64_encoded_not_processable: str, ) -> None: """ - Some strings which are not valid base64 encoded strings are not allowed + Some strings which are not valid base64 encoded strings are not + allowed as application metadata. """ vws_client.wait_for_target_processed(target_id=target_id) @@ -439,7 +421,8 @@ def test_not_base64_encoded_not_processable( @staticmethod def test_metadata_too_large(vws_client: VWS, target_id: str) -> None: """ - A base64 encoded string of greater than 1024 * 1024 bytes is too large + A base64 encoded string of greater than 1024 * 1024 bytes is too + large for application metadata. """ metadata = b"a" * (_MAX_METADATA_BYTES + 1) @@ -463,9 +446,7 @@ def test_metadata_too_large(vws_client: VWS, target_id: str) -> None: @pytest.mark.usefixtures("verify_mock_vuforia") class TestTargetName: - """ - Tests for the target name field. - """ + """Tests for the target name field.""" _MAX_CHAR_VALUE = 65535 _MAX_NAME_LENGTH = 64 @@ -537,9 +518,7 @@ def test_name_invalid( status_code: int, result_code: ResultCodes, ) -> None: - """ - A target's name must be a string of length 0 < N < 65. - """ + """A target's name must be a string of length 0 < N < 65.""" vws_client.wait_for_target_processed(target_id=target_id) with pytest.raises(expected_exception=VWSError) as exc: @@ -560,9 +539,7 @@ def test_existing_target_name( image_file_success_state_low_rating: io.BytesIO, vws_client: VWS, ) -> None: - """ - Only one target can have a given name. - """ + """Only one target can have a given name.""" first_target_name = "example_name" second_target_name = "another_example_name" @@ -602,9 +579,7 @@ def test_same_name_given( image_file_success_state_low_rating: io.BytesIO, vws_client: VWS, ) -> None: - """ - Updating a target with its own name does not give an error. - """ + """Updating a target with its own name does not give an error.""" name = "example" target_id = vws_client.add_target( @@ -636,7 +611,8 @@ def test_image_valid( vws_client: VWS, ) -> None: """ - JPEG and PNG files in the RGB and greyscale color spaces are allowed. + JPEG and PNG files in the RGB and greyscale color spaces are + allowed. """ vws_client.wait_for_target_processed(target_id=target_id) @@ -652,8 +628,10 @@ def test_bad_image_format_or_color_space( vws_client: VWS, ) -> None: """ - A `BAD_IMAGE` response is returned if an image which is not a JPEG or - PNG file is given, or if the given image is not in the greyscale or RGB + A `BAD_IMAGE` response is returned if an image which is not a + JPEG or + PNG file is given, or if the given image is not in the greyscale or + RGB color space. """ vws_client.wait_for_target_processed(target_id=target_id) @@ -669,9 +647,7 @@ def test_corrupted( corrupted_image_file: io.BytesIO, target_id: str, ) -> None: - """ - An error is returned when the given image is corrupted. - """ + """An error is returned when the given image is corrupted.""" vws_client.wait_for_target_processed(target_id=target_id) with pytest.raises(expected_exception=BadImageError) as exc: vws_client.update_target( @@ -688,7 +664,8 @@ def test_corrupted( @staticmethod def test_image_too_large(target_id: str, vws_client: VWS) -> None: """ - An `ImageTooLargeError` result is returned if the image is above a + An `ImageTooLargeError` result is returned if the image is above + a certain threshold. """ max_bytes = 2.3 * 1024 * 1024 @@ -750,7 +727,8 @@ def test_not_base64_encoded_processable( target_id: str, not_base64_encoded_processable: str, ) -> None: - """Some strings which are not valid base64 encoded strings are allowed + """Some strings which are not valid base64 encoded strings are + allowed as an image without getting a "Fail" response. This is because Vuforia treats them as valid base64, but then @@ -779,7 +757,8 @@ def test_not_base64_encoded_not_processable( ) -> None: """ Some strings which are not valid base64 encoded strings are not - processable by Vuforia, and then when given as an image Vuforia returns + processable by Vuforia, and then when given as an image Vuforia + returns a "Fail" response. """ vws_client.wait_for_target_processed(target_id=target_id) @@ -800,7 +779,8 @@ def test_not_base64_encoded_not_processable( @staticmethod def test_not_image(target_id: str, vws_client: VWS) -> None: """ - If the given image is not an image file then a `BadImageError` result + If the given image is not an image file then a `BadImageError` + result is returned. """ vws_client.wait_for_target_processed(target_id=target_id) @@ -827,9 +807,7 @@ def test_invalid_type( target_id: str, vws_client: VWS, ) -> None: - """ - If the given image is not a string, a `Fail` result is returned. - """ + """If the given image is not a string, a `Fail` result is returned.""" vws_client.wait_for_target_processed(target_id=target_id) with pytest.raises(expected_exception=FailError) as exc: @@ -888,14 +866,13 @@ def test_rating_can_change( @pytest.mark.usefixtures("verify_mock_vuforia") class TestInactiveProject: - """ - Tests for inactive projects. - """ + """Tests for inactive projects.""" @staticmethod def test_inactive_project(inactive_vws_client: VWS) -> None: """ - If the project is inactive, a FORBIDDEN response is returned. + If the project is inactive, a FORBIDDEN response is + returned. """ with pytest.raises(expected_exception=ProjectInactiveError): inactive_vws_client.update_target(target_id=uuid.uuid4().hex) diff --git a/tests/mock_vws/utils/__init__.py b/tests/mock_vws/utils/__init__.py index 241c13ec4..c554e4571 100644 --- a/tests/mock_vws/utils/__init__.py +++ b/tests/mock_vws/utils/__init__.py @@ -1,6 +1,4 @@ -""" -Utilities for tests. -""" +"""Utilities for tests.""" import io import secrets @@ -55,9 +53,7 @@ class Endpoint: secret_key: str def send(self) -> Response: - """ - Send the request. - """ + """Send the request.""" request = requests.Request( method=self.method, url=urljoin(base=self.base_url, url=self.path_url), @@ -79,9 +75,7 @@ def send(self) -> Response: @property def auth_header_content_type(self) -> str: - """ - The content type to use for the `Authorization` header. - """ + """The content type to use for the `Authorization` header.""" full_content_type = dict(self.headers).get("Content-Type", "") return full_content_type.split(sep=";")[0] diff --git a/tests/mock_vws/utils/assertions.py b/tests/mock_vws/utils/assertions.py index 8ea4ea12b..e026fed02 100644 --- a/tests/mock_vws/utils/assertions.py +++ b/tests/mock_vws/utils/assertions.py @@ -1,6 +1,4 @@ -""" -Assertion helpers. -""" +"""Assertion helpers.""" import copy import datetime @@ -169,7 +167,8 @@ def assert_vws_response( @beartype def assert_query_success(*, response: Response) -> None: - """Assert that the given response is a success response for performing an + """Assert that the given response is a success response for performing + an image recognition query. Raises: diff --git a/tests/mock_vws/utils/retries.py b/tests/mock_vws/utils/retries.py index 6d7a7d491..02e980e0b 100644 --- a/tests/mock_vws/utils/retries.py +++ b/tests/mock_vws/utils/retries.py @@ -1,6 +1,4 @@ -""" -Helpers for retrying requests to VWS. -""" +"""Helpers for retrying requests to VWS.""" from tenacity import retry from tenacity.retry import retry_if_exception_type diff --git a/tests/mock_vws/utils/too_many_requests.py b/tests/mock_vws/utils/too_many_requests.py index aae3742af..c35cb54d1 100644 --- a/tests/mock_vws/utils/too_many_requests.py +++ b/tests/mock_vws/utils/too_many_requests.py @@ -1,6 +1,4 @@ -""" -Helpers for handling too many requests errors. -""" +"""Helpers for handling too many requests errors.""" from http import HTTPStatus diff --git a/tests/mock_vws/utils/usage_test_helpers.py b/tests/mock_vws/utils/usage_test_helpers.py index b3012312e..6c85dc0f6 100644 --- a/tests/mock_vws/utils/usage_test_helpers.py +++ b/tests/mock_vws/utils/usage_test_helpers.py @@ -1,6 +1,4 @@ -""" -Helpers for testing the usage of the mocks. -""" +"""Helpers for testing the usage of the mocks.""" import datetime import io @@ -16,9 +14,7 @@ def processing_time_seconds( vuforia_database: VuforiaDatabase, image: io.BytesIO, ) -> float: - """ - Return the time taken to process a target in the database. - """ + """Return the time taken to process a target in the database.""" vws_client = VWS( server_access_key=vuforia_database.server_access_key, server_secret_key=vuforia_database.server_secret_key, From 9d75c2af7e2a97d5ea9df4b66e3054a0b466affe Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 26 Jan 2026 13:03:30 +0000 Subject: [PATCH 2161/2467] Remove --example-workers 0 from mypy-docs hook (#2869) * Remove --example-workers 0 from mypy-docs hook See https://github.com/python/mypy/issues/18283 * [pre-commit.ci lite] apply automatic fixes * Add comment explaining why --example-workers 0 is not used for mypy-docs --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 083d2a28d..d259b2428 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -171,11 +171,11 @@ repos: pass_filenames: false additional_dependencies: [uv==0.9.5] + # We do not use --example-workers 0 due to https://github.com/python/mypy/issues/18283 - id: mypy-docs name: mypy-docs stages: [pre-push] - entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python - --command="mypy" + entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="mypy" language: python types_or: [markdown, rst] From 2c0ed6b249031810bf173f3f7153185ed0d8f09e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 26 Jan 2026 13:21:54 +0000 Subject: [PATCH 2162/2467] Remove --example-workers 0 from mypy-docs (#2870) * Remove --example-workers 0 from mypy-docs See https://github.com/python/mypy/issues/18283 * [pre-commit.ci lite] apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d259b2428..dc9bf5224 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -138,8 +138,8 @@ repos: - id: shellcheck-docs name: shellcheck-docs # We exclude SC2215 as it is a false positive for an unknown reason on Windows. - entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=shell - --language=console --command="shellcheck --shell=bash --exclude=SC2215" + entry: uv run --extra=dev doccmd --no-write-to-file --language=shell --language=console + --command="shellcheck --shell=bash --exclude=SC2215" language: python types_or: [markdown, rst] additional_dependencies: [uv==0.9.5] @@ -199,8 +199,7 @@ repos: - id: pyright-docs name: pyright-docs stages: [pre-push] - entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python - --command="pyright" + entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="pyright" language: python types_or: [markdown, rst] @@ -225,8 +224,8 @@ repos: - id: ty-docs name: ty-docs stages: [pre-push] - entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python - --command="ty check" + entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="ty + check" language: python types_or: [markdown, rst] additional_dependencies: [uv==0.9.5] @@ -242,8 +241,7 @@ repos: - id: vulture-docs name: vulture docs - entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python - --command="vulture" + entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="vulture" language: python types_or: [python] pass_filenames: false @@ -277,8 +275,7 @@ repos: - id: pylint-docs name: pylint-docs - entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python - --command="pylint" + entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="pylint" language: python stages: [manual] types_or: [markdown, rst] @@ -334,8 +331,7 @@ repos: - id: interrogate-docs name: interrogate docs - entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python - --command="interrogate" + entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="interrogate" language: python types_or: [markdown, rst] additional_dependencies: [uv==0.9.5] @@ -414,8 +410,8 @@ repos: - id: pyrefly-docs name: pyrefly-docs stages: [pre-push] - entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python - --command="pyrefly check" + entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="pyrefly + check" language: python types_or: [markdown, rst] additional_dependencies: [uv==0.9.5] From 626a691044fabc121eee525e4f2a8c3f31a2fa2c Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 27 Jan 2026 04:57:00 +0000 Subject: [PATCH 2163/2467] Bump doc8 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3b2feaf22..92ee0e46f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "coverage==7.13.2", "deptry==0.24.0", "dirty-equals==0.11", - "doc8==1.1.1", + "doc8==2.0.0", "doccmd==2026.1.25", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", From cf547d5b6c576e36df8ca6ee214cee195857589e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 05:02:02 +0000 Subject: [PATCH 2164/2467] Bump pyrefly from 0.49.0 to 0.50.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.49.0 to 0.50.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.49.0...0.50.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.50.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3b2feaf22..f0958c0c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", - "pyrefly==0.49.0", + "pyrefly==0.50.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 8c3f5abc3febdfc9503db1bc09d333d9b0a775e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 05:02:10 +0000 Subject: [PATCH 2165/2467] Bump pydocstringformatter from 0.7.3 to 0.7.5 Bumps [pydocstringformatter](https://github.com/DanielNoord/pydocstringformatter) from 0.7.3 to 0.7.5. - [Release notes](https://github.com/DanielNoord/pydocstringformatter/releases) - [Commits](https://github.com/DanielNoord/pydocstringformatter/compare/v0.7.3...v0.7.5) --- updated-dependencies: - dependency-name: pydocstringformatter dependency-version: 0.7.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3b2feaf22..85d8df832 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional-dependencies.dev = [ "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2026.1.12", "prek==0.3.0", - "pydocstringformatter==0.7.3", + "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", From 5bc1b72224fe4386e5fd52cc1a8e84936f0e99a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 05:14:39 +0000 Subject: [PATCH 2166/2467] Bump ty from 0.0.13 to 0.0.14 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.13 to 0.0.14. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.13...0.0.14) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.14 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ff3f1c7f6..6241c2aee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.2", - "ty==0.0.13", + "ty==0.0.14", "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", From 8e7227cf0955149b7cc3ecdc8339ce8a7e24dff8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 05:04:03 +0000 Subject: [PATCH 2167/2467] Bump doccmd from 2026.1.25 to 2026.1.27.4 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.1.25 to 2026.1.27.4. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2026.01.25...2026.01.27.4) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.1.27.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6241c2aee..10f8cbc1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==2.0.0", - "doccmd==2026.1.25", + "doccmd==2026.1.27.4", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", "freezegun==1.5.5", From 89b1780c9c980f5e15e82bd538ca5fb6098d3108 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 05:04:06 +0000 Subject: [PATCH 2168/2467] Bump doccmd from 2026.1.27.4 to 2026.1.28 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.1.27.4 to 2026.1.28. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2026.01.27.4...2026.01.28) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.1.28 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 10f8cbc1f..eada66bd7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==2.0.0", - "doccmd==2026.1.27.4", + "doccmd==2026.1.28", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", "freezegun==1.5.5", From a3d0ec4e44af7d173832da11a3e046cff5776933 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 05:04:23 +0000 Subject: [PATCH 2169/2467] Bump pyrefly from 0.50.0 to 0.50.1 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.50.0 to 0.50.1. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.50.0...0.50.1) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.50.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 10f8cbc1f..6f4bfd688 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", - "pyrefly==0.50.0", + "pyrefly==0.50.1", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From f7035e4fac46c49062b12091ee20599f469304c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 05:04:34 +0000 Subject: [PATCH 2170/2467] Bump doccmd from 2026.1.28 to 2026.1.31.3 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.1.28 to 2026.1.31.3. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2026.01.28...2026.01.31.3) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.1.31.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a6f8cc1b9..54a7772c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==2.0.0", - "doccmd==2026.1.28", + "doccmd==2026.1.31.3", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", "freezegun==1.5.5", From 01eafc6616001abe997cfe591cad26e202dce360 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 05:05:03 +0000 Subject: [PATCH 2171/2467] Bump prek from 0.3.0 to 0.3.1 Bumps [prek](https://github.com/j178/prek) from 0.3.0 to 0.3.1. - [Release notes](https://github.com/j178/prek/releases) - [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md) - [Commits](https://github.com/j178/prek/compare/v0.3.0...v0.3.1) --- updated-dependencies: - dependency-name: prek dependency-version: 0.3.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a6f8cc1b9..2e792d08b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2026.1.12", - "prek==0.3.0", + "prek==0.3.1", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pylint[spelling]==4.0.4", From 96578e1502503eff564831a205fef4432f6730fc Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 2 Feb 2026 12:24:49 +0000 Subject: [PATCH 2172/2467] Add response_delay_seconds parameter to MockVWS for testing timeouts This adds support for simulating slow HTTP responses in MockVWS to enable testing of request timeout handling. When response_delay_seconds is set higher than a client's timeout, requests.exceptions.Timeout is raised. The delay is applied at the HTTP response level so requests' native timeout handling triggers naturally. Also includes 5 new tests covering timeout behavior with both raw requests and VWS client integration. Co-Authored-By: Claude Haiku 4.5 --- pyproject.toml | 3 + .../_requests_mock_server/decorators.py | 68 ++++++++++++- tests/mock_vws/test_requests_mock_usage.py | 98 +++++++++++++++++++ 3 files changed, 165 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cd328a850..68d96a4ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -495,3 +495,6 @@ ignore_decorators = [ [tool.yamlfix] section_whitelines = 1 whitelines = 1 + +[tool.uv.sources] +vws-python = { path = "../vws-python", editable = true } diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 6572d593d..9a96cd19c 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -1,10 +1,13 @@ """Decorators for using the mock.""" import re +import threading +import time from contextlib import ContextDecorator -from typing import TYPE_CHECKING, Literal, Self +from typing import TYPE_CHECKING, Any, Literal, Self from urllib.parse import urljoin, urlparse +import requests as requests_lib from beartype import BeartypeConf, beartype from responses import RequestsMock @@ -23,7 +26,16 @@ from .mock_web_services_api import MockVuforiaWebServicesAPI if TYPE_CHECKING: - from collections.abc import Iterable + from collections.abc import Callable, Iterable, Mapping + + from requests import PreparedRequest + from requests.adapters import HTTPAdapter # noqa: F401 + + ResponseType = tuple[int, Mapping[str, str], str] + Callback = Callable[[PreparedRequest], ResponseType] # noqa: F841 + +# Thread-local storage to capture the request timeout +_timeout_storage = threading.local() _STRUCTURAL_SIMILARITY_MATCHER = StructuralSimilarityMatcher() _BRISQUE_TRACKING_RATER = BrisqueTargetTrackingRater() @@ -65,6 +77,7 @@ def __init__( processing_time_seconds: float = 2.0, target_tracking_rater: TargetTrackingRater = _BRISQUE_TRACKING_RATER, real_http: bool = False, + response_delay_seconds: float = 0.0, ) -> None: """Route requests to Vuforia's Web Service APIs to fakes of those APIs. @@ -84,12 +97,15 @@ def __init__( duplicate_match_checker: A callable which takes two image values and returns whether they are duplicates. target_tracking_rater: A callable for rating targets for tracking. + response_delay_seconds: The number of seconds to delay each + response by. This can be used to test timeout handling. Raises: MissingSchemeError: There is no scheme in a given URL. """ super().__init__() self._real_http = real_http + self._response_delay_seconds = response_delay_seconds self._mock: RequestsMock self._target_manager = TargetManager() @@ -131,8 +147,46 @@ def __enter__(self) -> Self: ``self``. """ compiled_url_patterns: Iterable[re.Pattern[str]] = set() + delay_seconds = self._response_delay_seconds + + def wrap_callback(callback: "Callback") -> "Callback": + """Wrap a callback to add a response delay.""" + + def wrapped(request: "PreparedRequest") -> "ResponseType": + # Check if the delay would exceed the request timeout + timeout = getattr(_timeout_storage, "timeout", None) + if timeout is not None and delay_seconds > 0: + # timeout can be a float or a tuple (connect, read) + if isinstance(timeout, tuple): + effective_timeout: float | None = timeout[1] # read timeout + else: + effective_timeout = timeout + if ( + effective_timeout is not None + and delay_seconds > effective_timeout + ): + raise requests_lib.exceptions.Timeout + + result = callback(request) + time.sleep(delay_seconds) + return result + + return wrapped mock = RequestsMock(assert_all_requests_are_fired=False) + + # Patch _on_request to capture the timeout parameter + original_on_request = mock._on_request # noqa: SLF001 + + def patched_on_request( + adapter: "HTTPAdapter", + request: "PreparedRequest", + **kwargs: Any, # noqa: ANN401 + ) -> Any: # noqa: ANN401 + _timeout_storage.timeout = kwargs.get("timeout") + return original_on_request(adapter, request, **kwargs) # type: ignore[misc] + + mock._on_request = patched_on_request # type: ignore[method-assign] # noqa: SLF001 for vws_route in self._mock_vws_api.routes: url_pattern = urljoin( base=self._base_vws_url, @@ -145,10 +199,13 @@ def __enter__(self) -> Self: } for vws_http_method in vws_route.http_methods: + original_callback = getattr( + self._mock_vws_api, vws_route.route_name + ) mock.add_callback( method=vws_http_method, url=compiled_url_pattern, - callback=getattr(self._mock_vws_api, vws_route.route_name), + callback=wrap_callback(callback=original_callback), content_type=None, ) @@ -164,10 +221,13 @@ def __enter__(self) -> Self: } for vwq_http_method in vwq_route.http_methods: + original_callback = getattr( + self._mock_vwq_api, vwq_route.route_name + ) mock.add_callback( method=vwq_http_method, url=compiled_url_pattern, - callback=getattr(self._mock_vwq_api, vwq_route.route_name), + callback=wrap_callback(callback=original_callback), content_type=None, ) diff --git a/tests/mock_vws/test_requests_mock_usage.py b/tests/mock_vws/test_requests_mock_usage.py index f80b3c078..dd9c04b5c 100644 --- a/tests/mock_vws/test_requests_mock_usage.py +++ b/tests/mock_vws/test_requests_mock_usage.py @@ -111,6 +111,104 @@ def test_real_http() -> None: request_unmocked_address() +class TestResponseDelay: + """Tests for the response delay feature.""" + + @staticmethod + def test_default_no_delay() -> None: + """By default, there is no response delay.""" + with MockVWS(): + # With a very short timeout, the request should still succeed + # because there is no delay + response = requests.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": rfc_1123_date(), + "Authorization": "bad_auth_token", + }, + data=b"", + timeout=0.5, + ) + # We just care that no timeout occurred, not the response content + assert response.status_code is not None + + @staticmethod + def test_delay_causes_timeout() -> None: + """ + When response_delay_seconds is set higher than the client + timeout, + a Timeout exception is raised. + """ + with ( + MockVWS(response_delay_seconds=0.5), + pytest.raises(expected_exception=requests.exceptions.Timeout), + ): + requests.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": rfc_1123_date(), + "Authorization": "bad_auth_token", + }, + data=b"", + timeout=0.1, + ) + + @staticmethod + def test_delay_allows_completion() -> None: + """ + When response_delay_seconds is set lower than the client + timeout, + the request completes successfully. + """ + with MockVWS(response_delay_seconds=0.1): + # This should succeed because timeout > delay + response = requests.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": rfc_1123_date(), + "Authorization": "bad_auth_token", + }, + data=b"", + timeout=2.0, + ) + assert response.status_code is not None + + @staticmethod + def test_vws_client_with_timeout() -> None: + """ + The VWS client's request_timeout_seconds parameter works with + response_delay_seconds. + """ + database = VuforiaDatabase() + with MockVWS(response_delay_seconds=0.5) as mock: + mock.add_database(database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + request_timeout_seconds=0.1, + ) + with pytest.raises(expected_exception=requests.exceptions.Timeout): + vws_client.list_targets() + + @staticmethod + def test_vws_client_without_timeout() -> None: + """ + The VWS client completes successfully when the timeout exceeds + the response delay. + """ + database = VuforiaDatabase() + with MockVWS(response_delay_seconds=0.1) as mock: + mock.add_database(database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + request_timeout_seconds=2.0, + ) + # This should succeed + targets = vws_client.list_targets() + assert targets == [] + + class TestProcessingTime: """Tests for the time taken to process targets in the mock.""" From d21ed4c33a278906c3ca7e9ffc1d9b6fa44e2e8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 07:01:07 +0000 Subject: [PATCH 2173/2467] Bump pyrefly from 0.50.1 to 0.51.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.50.1 to 0.51.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.50.1...0.51.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.51.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cd328a850..e4fe051b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", - "pyrefly==0.50.1", + "pyrefly==0.51.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 0c630c1f71bc087a4484d25fb454d669564c7550 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 05:04:22 +0000 Subject: [PATCH 2174/2467] Bump ruff from 0.14.14 to 0.15.0 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.14 to 0.15.0. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.14.14...0.15.0) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e4fe051b4..3cb828404 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ "python-dotenv==1.2.1", "pyyaml==6.0.3", "requests-mock-flask==2026.1.12", - "ruff==0.14.14", + "ruff==0.15.0", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 69d4b6a86e720a68c7f7370612640cae00cc1ee2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 05:04:37 +0000 Subject: [PATCH 2175/2467] Bump coverage from 7.13.2 to 7.13.3 Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.13.2 to 7.13.3. - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](https://github.com/coveragepy/coveragepy/compare/7.13.2...7.13.3) --- updated-dependencies: - dependency-name: coverage dependency-version: 7.13.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e4fe051b4..b8f58f109 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ optional-dependencies.dev = [ "actionlint-py==1.7.10.24", "check-manifest==0.51", "check-wheel-contents==0.6.3", - "coverage==7.13.2", + "coverage==7.13.3", "deptry==0.24.0", "dirty-equals==0.11", "doc8==2.0.0", From 383b144e60377b1ad0d61bc42b0013fbdcee687d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 05:03:47 +0000 Subject: [PATCH 2176/2467] Bump sphinxcontrib-httpdomain from 1.8.1 to 2.0.0 Bumps [sphinxcontrib-httpdomain](https://github.com/sphinx-contrib/httpdomain) from 1.8.1 to 2.0.0. - [Release notes](https://github.com/sphinx-contrib/httpdomain/releases) - [Changelog](https://github.com/sphinx-contrib/httpdomain/blob/main/CHANGELOG.rst) - [Commits](https://github.com/sphinx-contrib/httpdomain/compare/1.8.1...2.0.0) --- updated-dependencies: - dependency-name: sphinxcontrib-httpdomain dependency-version: 2.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3cb828404..406763480 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,7 +92,7 @@ optional-dependencies.dev = [ "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2026.1.12", "sphinx-toolbox==4.1.2", - "sphinxcontrib-httpdomain==1.8.1", + "sphinxcontrib-httpdomain==2.0.0", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.2", From d6f756f8a04f1c666a3eab159e3a356a4a03d7d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 05:16:48 +0000 Subject: [PATCH 2177/2467] Bump ty from 0.0.14 to 0.0.15 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.14 to 0.0.15. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.14...0.0.15) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.15 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 406763480..ad4179c7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.2", - "ty==0.0.14", + "ty==0.0.15", "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", From f90aba046c4a6be862949afbf8f45deffb698c2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 05:04:20 +0000 Subject: [PATCH 2178/2467] Bump pyrefly from 0.51.0 to 0.51.1 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.51.0 to 0.51.1. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.51.0...0.51.1) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.51.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ad4179c7b..0807a325e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.11.1", - "pyrefly==0.51.0", + "pyrefly==0.51.1", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 027d9591c1eb028cdf07d2d66864a09e4b13865c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 05:04:33 +0000 Subject: [PATCH 2179/2467] Bump tenacity from 9.1.2 to 9.1.3 Bumps [tenacity](https://github.com/jd/tenacity) from 9.1.2 to 9.1.3. - [Release notes](https://github.com/jd/tenacity/releases) - [Commits](https://github.com/jd/tenacity/compare/9.1.2...9.1.3) --- updated-dependencies: - dependency-name: tenacity dependency-version: 9.1.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ad4179c7b..528a686da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ optional-dependencies.dev = [ "sphinxcontrib-httpdomain==2.0.0", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "tenacity==9.1.2", + "tenacity==9.1.3", "ty==0.0.15", "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", From 3b28ec7dea8c8b2763c5430d7a3d1a82d93b0ca2 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 8 Feb 2026 13:26:19 +0000 Subject: [PATCH 2180/2467] Work around toml-fmt array comment bugs Avoid tox-dev/toml-fmt#184 (double quotes in comments corrupt arrays) by using single quotes in comments, and avoid tox-dev/toml-fmt#186 (single-quoted strings in arrays with comments get corrupted) by converting to double quotes. Co-Authored-By: Claude Opus 4.6 --- pyproject.toml | 70 +++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f5ddc8276..018f15491 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -159,7 +159,7 @@ lint.ignore = [ "D415", # Ruff warns that this conflicts with the formatter. "ISC001", - # Ignore "too-many-*" errors as they seem to get in the way more than + # Ignore 'too-many-*' errors as they seem to get in the way more than # helping. "PLR0913", ] @@ -213,21 +213,21 @@ jobs = 0 # as they seemed to get in the way. load-plugins = [ "pylint_per_file_ignores", - 'pylint.extensions.bad_builtin', - 'pylint.extensions.comparison_placement', - 'pylint.extensions.consider_refactoring_into_while_condition', - 'pylint.extensions.docparams', - 'pylint.extensions.dunder', - 'pylint.extensions.eq_without_hash', - 'pylint.extensions.for_any_all', - 'pylint.extensions.mccabe', - 'pylint.extensions.no_self_use', - 'pylint.extensions.overlapping_exceptions', - 'pylint.extensions.private_import', - 'pylint.extensions.redefined_loop_name', - 'pylint.extensions.redefined_variable_type', - 'pylint.extensions.set_membership', - 'pylint.extensions.typing', + "pylint.extensions.bad_builtin", + "pylint.extensions.comparison_placement", + "pylint.extensions.consider_refactoring_into_while_condition", + "pylint.extensions.docparams", + "pylint.extensions.dunder", + "pylint.extensions.eq_without_hash", + "pylint.extensions.for_any_all", + "pylint.extensions.mccabe", + "pylint.extensions.no_self_use", + "pylint.extensions.overlapping_exceptions", + "pylint.extensions.private_import", + "pylint.extensions.redefined_loop_name", + "pylint.extensions.redefined_variable_type", + "pylint.extensions.set_membership", + "pylint.extensions.typing", ] # We ignore invalid names because: @@ -246,12 +246,12 @@ per-file-ignores = [ # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. enable = [ - 'bad-inline-option', - 'deprecated-pragma', - 'file-ignored', - 'spelling', - 'use-symbolic-message-instead', - 'useless-suppression', + "bad-inline-option", + "deprecated-pragma", + "file-ignored", + "spelling", + "use-symbolic-message-instead", + "useless-suppression", ] # Disable the message, report, category or checker with the given id(s). You @@ -266,25 +266,25 @@ enable = [ disable = [ # Style issues that we can deal with ourselves - 'too-few-public-methods', - 'too-many-locals', - 'too-many-arguments', - 'too-many-instance-attributes', - 'too-many-lines', - 'locally-disabled', + "too-few-public-methods", + "too-many-locals", + "too-many-arguments", + "too-many-instance-attributes", + "too-many-lines", + "locally-disabled", # Let ruff handle long lines - 'line-too-long', + "line-too-long", # Let ruff handle unused imports - 'unused-import', + "unused-import", # Let ruff deal with sorting - 'ungrouped-imports', + "ungrouped-imports", # We don't need everything to be documented because of mypy - 'missing-type-doc', - 'missing-return-type-doc', + "missing-type-doc", + "missing-return-type-doc", # Too difficult to please - 'duplicate-code', + "duplicate-code", # Let ruff handle imports - 'wrong-import-order', + "wrong-import-order", ] [tool.pylint.'FORMAT'] From 7b4b2d2128c60949ee050c2a5acd2923045a4e52 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 8 Feb 2026 13:26:19 +0000 Subject: [PATCH 2181/2467] Bump pyproject-fmt to 2.14.0 and apply formatting Co-Authored-By: Claude Opus 4.6 --- pyproject.toml | 141 +++++++++++++++++-------------------------------- 1 file changed, 49 insertions(+), 92 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 018f15491..73ea8bc88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.11.1", + "pyproject-fmt==2.14.0", "pyrefly==0.51.1", "pyright==1.1.408", "pyroma==5.0.1", @@ -80,10 +80,6 @@ optional-dependencies.dev = [ "pyyaml==6.0.3", "requests-mock-flask==2026.1.12", "ruff==0.15.0", - # We add shellcheck-py not only for shell scripts and shell code blocks, - # but also because having it installed means that ``actionlint-py`` will - # use it to lint shell commands in GitHub workflow files. - "shellcheck-py==0.11.0.1", "shfmt-py==3.12.0.2", "sphinx==8.2.3", "sphinx-copybutton==0.5.2", @@ -105,6 +101,10 @@ optional-dependencies.dev = [ "vws-python==2025.3.10.1", "vws-test-fixtures==2023.3.5", "vws-web-tools==2024.10.6.1", + # We add shellcheck-py not only for shell scripts and shell code blocks, + # but also because having it installed means that ``actionlint-py`` will + # use it to lint shell commands in GitHub workflow files. + "shellcheck-py==0.11.0.1", "yamlfix==1.19.1", "zizmor==1.22.0", ] @@ -114,29 +114,23 @@ urls.Source = "https://github.com/VWS-Python/vws-python-mock" [tool.setuptools] zip-safe = false - -[tool.setuptools.packages.find] -where = [ - "src", -] - -[tool.setuptools.package-data] -mock_vws = [ +package-data.mock_vws = [ "py.typed", ] +packages.find.where = [ + "src", +] -[tool.distutils.bdist_wheel] -universal = true +[tool.distutils] +bdist_wheel.universal = true [tool.setuptools_scm] - # We use a fallback version like # https://github.com/pypa/setuptools_scm/issues/77 so that we do not # error in the Docker build stage of the release pipeline. # # This must be a PEP 440 compliant version. fallback_version = "0.0.0" - # This keeps the start of the version the same as the last release. # This is useful for our documentation to include e.g. binary links # to the latest released binary. @@ -150,33 +144,30 @@ lint.select = [ "ALL", ] lint.ignore = [ - # Ruff warns that this conflicts with the formatter. - "COM812", # Allow our chosen docstring line-style - pydocstringformatter handles formatting # but doesn't enforce D205 (blank line after summary) or D212 (summary on first line). "D205", - "D212", - "D415", - # Ruff warns that this conflicts with the formatter. - "ISC001", # Ignore 'too-many-*' errors as they seem to get in the way more than # helping. "PLR0913", + # Ruff warns that this conflicts with the formatter. + "COM812", + # Ruff warns that this conflicts with the formatter. + "ISC001", + "D212", + "D415", ] - lint.per-file-ignores."ci/test_custom_linters.py" = [ # Allow asserts in tests. "S101", ] - lint.per-file-ignores."doccmd_*.py" = [ + # Allow asserts in docs. + "S101", # Allow our chosen docstring line-style - pydocstringformatter handles # formatting but docstrings in docs may not match this style. "D200", - # Allow asserts in docs. - "S101", ] - lint.per-file-ignores."tests/**" = [ # Allow asserts in tests. "S101", @@ -184,7 +175,6 @@ lint.per-file-ignores."tests/**" = [ "S105", "S106", ] - # Do not automatically remove commented out code. # We comment out code during development, and with VSCode auto-save, this code # is sometimes annoyingly removed. @@ -194,15 +184,13 @@ lint.unfixable = [ lint.pydocstyle.convention = "google" [tool.pylint] - -[tool.pylint.'MASTER'] - +# Allow the body of an if to be on the same line as the test if there is no +# else. +"FORMAT".single-line-if-stmt = false # Pickle collected data for later comparisons. -persistent = true - +"MASTER".persistent = true # Use multiple processes to speed up Pylint. -jobs = 0 - +"MASTER".jobs = 0 # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. # See https://chezsoi.org/lucas/blog/pylint-strict-base-configuration.html. @@ -211,7 +199,7 @@ jobs = 0 # - pylint.extensions.magic_value # - pylint.extensions.while_used # as they seemed to get in the way. -load-plugins = [ +"MASTER".load-plugins = [ "pylint_per_file_ignores", "pylint.extensions.bad_builtin", "pylint.extensions.comparison_placement", @@ -229,23 +217,19 @@ load-plugins = [ "pylint.extensions.set_membership", "pylint.extensions.typing", ] - # We ignore invalid names because: # - We want to use generated module names, which may not be valid, but are never seen. # - We want to use global variables in documentation, which may not be uppercase -per-file-ignores = [ +"MASTER".per-file-ignores = [ "docs/source/conf.py:invalid-name", "docs/source/doccmd_*.py:invalid-name", "doccmd_README_rst_*.py:invalid-name", ] - -[tool.pylint.'MESSAGES CONTROL'] - # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. -enable = [ +"MESSAGES CONTROL".enable = [ "bad-inline-option", "deprecated-pragma", "file-ignored", @@ -253,7 +237,6 @@ enable = [ "use-symbolic-message-instead", "useless-suppression", ] - # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration @@ -263,8 +246,7 @@ enable = [ # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" - -disable = [ +"MESSAGES CONTROL".disable = [ # Style issues that we can deal with ourselves "too-few-public-methods", "too-many-locals", @@ -286,35 +268,22 @@ disable = [ # Let ruff handle imports "wrong-import-order", ] - -[tool.pylint.'FORMAT'] - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt = false - -[tool.pylint.'SPELLING'] - # Spelling dictionary name. Available dictionaries: none. To make it working # install python-enchant package. -spelling-dict = 'en_US' - +"SPELLING".spelling-dict = "en_US" # A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file = 'spelling_private_dict.txt' - +"SPELLING".spelling-private-dict-file = "spelling_private_dict.txt" # Tells whether to store unknown words to indicated private dictionary in # --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words = 'no' +"SPELLING".spelling-store-unknown-words = "no" [tool.check-manifest] - ignore = [ ".checkmake-config.ini", ".prettierrc", ".yamlfmt", "*.enc", "admin/**", - "CHANGELOG.rst", "CODE_OF_CONDUCT.rst", "CONTRIBUTING.rst", @@ -339,9 +308,7 @@ pep621_dev_dependency_groups = [ "dev", "release", ] - -[tool.deptry.per_rule_ignores] -DEP002 = [ +per_rule_ignores.DEP002 = [ # tzdata is needed on Windows for zoneinfo to work. # See https://docs.python.org/3/library/zoneinfo.html#data-sources. "tzdata", @@ -352,41 +319,34 @@ indent = 4 keep_full_version = true max_supported_python = "3.13" -[tool.pytest.ini_options] - -xfail_strict = true -log_cli = true -addopts = [ +[tool.pytest] +ini_options.xfail_strict = true +ini_options.log_cli = true +ini_options.addopts = [ "--strict-markers", ] -markers = [ +ini_options.markers = [ "requires_docker_build", ] - # Options for pytest-retry. -retries = 10 -retry_delay = 10 -cumulative_timing = false - -[tool.coverage.run] +ini_options.retries = 10 +ini_options.retry_delay = 10 +ini_options.cumulative_timing = false -branch = true -omit = [ - "src/mock_vws/_flask_server/healthcheck.py", -] -parallel = true -source = [ "src/", "tests/" ] - -[tool.coverage.report] - -exclude_also = [ +[tool.coverage] +report.exclude_also = [ "if TYPE_CHECKING:", "class .*\\bProtocol\\):", ] -fail_under = 100 +report.fail_under = 100 +run.branch = true +run.omit = [ + "src/mock_vws/_flask_server/healthcheck.py", +] +run.parallel = true +run.source = [ "src/", "tests/" ] [tool.mypy] - strict = true files = [ "." ] exclude = [ "build" ] @@ -403,7 +363,6 @@ search_path = [ ] [tool.pyright] - enableTypeIgnoreComments = false reportUnnecessaryTypeIgnoreComment = true typeCheckingMode = "strict" @@ -426,7 +385,6 @@ warn_required_dynamic_aliases = true warn_untyped_fields = true [tool.doc8] - max_line_length = 2000 ignore_path = [ "./.eggs", @@ -481,7 +439,6 @@ ignore_names = [ # pydantic-settings "model_config", ] - # Duplicate some of .gitignore exclude = [ ".venv" ] ignore_decorators = [ From 063ae62180b01cc2053d26758110296adec007ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 05:04:46 +0000 Subject: [PATCH 2182/2467] Bump tenacity from 9.1.3 to 9.1.4 Bumps [tenacity](https://github.com/jd/tenacity) from 9.1.3 to 9.1.4. - [Release notes](https://github.com/jd/tenacity/releases) - [Commits](https://github.com/jd/tenacity/compare/9.1.3...9.1.4) --- updated-dependencies: - dependency-name: tenacity dependency-version: 9.1.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2a728fad6..1642d7f1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,7 @@ optional-dependencies.dev = [ "sphinxcontrib-httpdomain==2.0.0", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "tenacity==9.1.3", + "tenacity==9.1.4", "ty==0.0.15", "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", From aa22ad737c4babe160aa17dc136ea4b493a068c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 05:04:58 +0000 Subject: [PATCH 2183/2467] Bump pyrefly from 0.51.1 to 0.51.2 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.51.1 to 0.51.2. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/commits) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.51.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2a728fad6..4734c46f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.14.0", - "pyrefly==0.51.1", + "pyrefly==0.51.2", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 052ede43d36fbc1ceb1ba70ba87fb0d40527c976 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 05:05:26 +0000 Subject: [PATCH 2184/2467] Bump prek from 0.3.1 to 0.3.2 Bumps [prek](https://github.com/j178/prek) from 0.3.1 to 0.3.2. - [Release notes](https://github.com/j178/prek/releases) - [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md) - [Commits](https://github.com/j178/prek/compare/v0.3.1...v0.3.2) --- updated-dependencies: - dependency-name: prek dependency-version: 0.3.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2a728fad6..2f38d8306 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2026.1.12", - "prek==0.3.1", + "prek==0.3.2", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pylint[spelling]==4.0.4", From 2f7fa4c4a287d4273a4ce1e6fdf65042ea174553 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 9 Feb 2026 10:47:33 +0000 Subject: [PATCH 2185/2467] Bump pyproject-fmt to 2.14.2 --- pyproject.toml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e29030381..3efcdfde5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.14.0", + "pyproject-fmt==2.14.2", "pyrefly==0.51.2", "pyright==1.1.408", "pyroma==5.0.1", @@ -80,6 +80,10 @@ optional-dependencies.dev = [ "pyyaml==6.0.3", "requests-mock-flask==2026.1.12", "ruff==0.15.0", + # We add shellcheck-py not only for shell scripts and shell code blocks, + # but also because having it installed means that ``actionlint-py`` will + # use it to lint shell commands in GitHub workflow files. + "shellcheck-py==0.11.0.1", "shfmt-py==3.12.0.2", "sphinx==8.2.3", "sphinx-copybutton==0.5.2", @@ -101,10 +105,6 @@ optional-dependencies.dev = [ "vws-python==2025.3.10.1", "vws-test-fixtures==2023.3.5", "vws-web-tools==2024.10.6.1", - # We add shellcheck-py not only for shell scripts and shell code blocks, - # but also because having it installed means that ``actionlint-py`` will - # use it to lint shell commands in GitHub workflow files. - "shellcheck-py==0.11.0.1", "yamlfix==1.19.1", "zizmor==1.22.0", ] @@ -144,29 +144,29 @@ lint.select = [ "ALL", ] lint.ignore = [ + # Ruff warns that this conflicts with the formatter. + "COM812", # Allow our chosen docstring line-style - pydocstringformatter handles formatting # but doesn't enforce D205 (blank line after summary) or D212 (summary on first line). "D205", + "D212", + "D415", + # Ruff warns that this conflicts with the formatter. + "ISC001", # Ignore 'too-many-*' errors as they seem to get in the way more than # helping. "PLR0913", - # Ruff warns that this conflicts with the formatter. - "COM812", - # Ruff warns that this conflicts with the formatter. - "ISC001", - "D212", - "D415", ] lint.per-file-ignores."ci/test_custom_linters.py" = [ # Allow asserts in tests. "S101", ] lint.per-file-ignores."doccmd_*.py" = [ - # Allow asserts in docs. - "S101", # Allow our chosen docstring line-style - pydocstringformatter handles # formatting but docstrings in docs may not match this style. "D200", + # Allow asserts in docs. + "S101", ] lint.per-file-ignores."tests/**" = [ # Allow asserts in tests. From d43728e3de9785b0bca4d23c5677118b30f18199 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 05:03:52 +0000 Subject: [PATCH 2186/2467] Bump pyrefly from 0.51.2 to 0.52.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.51.2 to 0.52.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/commits/0.52.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.52.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3efcdfde5..e911007c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.14.2", - "pyrefly==0.51.2", + "pyrefly==0.52.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 1c1924cdea1932748957f4c16b2940784bc679d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 05:04:25 +0000 Subject: [PATCH 2187/2467] Bump coverage from 7.13.3 to 7.13.4 Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.13.3 to 7.13.4. - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](https://github.com/coveragepy/coveragepy/compare/7.13.3...7.13.4) --- updated-dependencies: - dependency-name: coverage dependency-version: 7.13.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3efcdfde5..03bb51082 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ optional-dependencies.dev = [ "actionlint-py==1.7.10.24", "check-manifest==0.51", "check-wheel-contents==0.6.3", - "coverage==7.13.3", + "coverage==7.13.4", "deptry==0.24.0", "dirty-equals==0.11", "doc8==2.0.0", From f7aab973631d535788317eb6ce2f32ec50952410 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 05:20:21 +0000 Subject: [PATCH 2188/2467] Bump pyproject-fmt from 2.14.2 to 2.15.1 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.14.2 to 2.15.1. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.14.2...pyproject-fmt/2.15.1) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.15.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e911007c9..df17105a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.14.2", + "pyproject-fmt==2.15.1", "pyrefly==0.52.0", "pyright==1.1.408", "pyroma==5.0.1", From 623246fc767f7cadd8f242df1eb1e60c63043f18 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 05:03:57 +0000 Subject: [PATCH 2189/2467] Bump ty from 0.0.15 to 0.0.16 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.15 to 0.0.16. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.15...0.0.16) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.16 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3c5433d21..5aa6b8f94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.4", - "ty==0.0.15", + "ty==0.0.16", "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", From f1e350e7ea7c9d7b6f3d2a328b0f16fac4e83116 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 05:04:26 +0000 Subject: [PATCH 2190/2467] Bump pyproject-fmt from 2.15.1 to 2.15.2 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.15.1 to 2.15.2. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.15.1...pyproject-fmt/2.15.2) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.15.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3c5433d21..ee3c4e465 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.15.1", + "pyproject-fmt==2.15.2", "pyrefly==0.52.0", "pyright==1.1.408", "pyroma==5.0.1", From 1ae4929d9413be2ab9c7d8b0fd88690172fcd845 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 11 Feb 2026 09:32:02 +0000 Subject: [PATCH 2191/2467] Update pyproject-fmt to 2.15.2 (#2903) --- pyproject.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e105b47bc..f46f47994 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -334,17 +334,17 @@ ini_options.retry_delay = 10 ini_options.cumulative_timing = false [tool.coverage] -report.exclude_also = [ - "if TYPE_CHECKING:", - "class .*\\bProtocol\\):", -] -report.fail_under = 100 run.branch = true run.omit = [ "src/mock_vws/_flask_server/healthcheck.py", ] run.parallel = true run.source = [ "src/", "tests/" ] +report.exclude_also = [ + "class .*\\bProtocol\\):", + "if TYPE_CHECKING:", +] +report.fail_under = 100 [tool.mypy] strict = true From c4d8cb85deeccf338994017dcfd97e47e451292d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 05:03:39 +0000 Subject: [PATCH 2192/2467] Bump docker/build-push-action from 6.18.0 to 6.19.1 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.18.0 to 6.19.1. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v6.18.0...v6.19.1) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: 6.19.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-build.yml | 2 +- .github/workflows/release.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index ad39dd8a2..f037eef33 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -42,7 +42,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Build Docker image - uses: docker/build-push-action@v6.18.0 + uses: docker/build-push-action@v6.19.1 with: platforms: linux/amd64,linux/arm64 file: src/mock_vws/_flask_server/Dockerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 474c6649a..0b5a3068f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -123,7 +123,7 @@ jobs: uses: docker/setup-qemu-action@v3 - name: Build and push target manager Docker image - uses: docker/build-push-action@v6.18.0 + uses: docker/build-push-action@v6.19.1 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 @@ -134,7 +134,7 @@ jobs: adamtheturtle/vuforia-target-manager-mock:${{ steps.calver.outputs.release }} - name: Build and push VWS Docker image - uses: docker/build-push-action@v6.18.0 + uses: docker/build-push-action@v6.19.1 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 @@ -145,7 +145,7 @@ jobs: adamtheturtle/vuforia-vws-mock:${{ steps.calver.outputs.release }} - name: Build and push VWQ Docker image - uses: docker/build-push-action@v6.18.0 + uses: docker/build-push-action@v6.19.1 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 From c235ca5df275710140c0fc679935f9275cd9333b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 05:04:18 +0000 Subject: [PATCH 2193/2467] Bump pyproject-fmt from 2.15.2 to 2.15.3 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.15.2 to 2.15.3. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.15.2...pyproject-fmt/2.15.3) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.15.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f46f47994..e4114824e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.15.2", + "pyproject-fmt==2.15.3", "pyrefly==0.52.0", "pyright==1.1.408", "pyroma==5.0.1", From 3e8040d2fb7d9c308b0f342ef3d6ce7555def02b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 05:03:38 +0000 Subject: [PATCH 2194/2467] Bump docker/build-push-action from 6.19.1 to 6.19.2 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.19.1 to 6.19.2. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v6.19.1...v6.19.2) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: 6.19.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-build.yml | 2 +- .github/workflows/release.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index f037eef33..eb1bca888 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -42,7 +42,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Build Docker image - uses: docker/build-push-action@v6.19.1 + uses: docker/build-push-action@v6.19.2 with: platforms: linux/amd64,linux/arm64 file: src/mock_vws/_flask_server/Dockerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0b5a3068f..6ab93f1af 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -123,7 +123,7 @@ jobs: uses: docker/setup-qemu-action@v3 - name: Build and push target manager Docker image - uses: docker/build-push-action@v6.19.1 + uses: docker/build-push-action@v6.19.2 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 @@ -134,7 +134,7 @@ jobs: adamtheturtle/vuforia-target-manager-mock:${{ steps.calver.outputs.release }} - name: Build and push VWS Docker image - uses: docker/build-push-action@v6.19.1 + uses: docker/build-push-action@v6.19.2 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 @@ -145,7 +145,7 @@ jobs: adamtheturtle/vuforia-vws-mock:${{ steps.calver.outputs.release }} - name: Build and push VWQ Docker image - uses: docker/build-push-action@v6.19.1 + uses: docker/build-push-action@v6.19.2 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 From a4542862b2a57c227aca55325d0525ea1785a339 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 05:04:25 +0000 Subject: [PATCH 2195/2467] Bump ruff from 0.15.0 to 0.15.1 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.0 to 0.15.1. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.0...0.15.1) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e4114824e..b64ad8343 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ "python-dotenv==1.2.1", "pyyaml==6.0.3", "requests-mock-flask==2026.1.12", - "ruff==0.15.0", + "ruff==0.15.1", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 01cd54e81c8ef37806263fdf46771e17b2727621 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 05:04:32 +0000 Subject: [PATCH 2196/2467] Bump pyproject-fmt from 2.15.3 to 2.16.0 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.15.3 to 2.16.0. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.15.3...pyproject-fmt/2.16.0) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.16.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e4114824e..63ce3ba14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.15.3", + "pyproject-fmt==2.16.0", "pyrefly==0.52.0", "pyright==1.1.408", "pyroma==5.0.1", From d5d69e23e6bf7fe405cea4e25f98f49be946496f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 13 Feb 2026 16:33:04 +0000 Subject: [PATCH 2197/2467] More flexible Windows checks --- tests/mock_vws/test_docker.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py index 0484cb253..dfac6cdba 100644 --- a/tests/mock_vws/test_docker.py +++ b/tests/mock_vws/test_docker.py @@ -121,9 +121,16 @@ def test_build_and_run( full_log = "\n".join( [item["stream"] for item in exc.build_log if "stream" in item], ) + windows_message_substrings = ( + "no matching manifest for windows/amd64", + "no matching manifest for windows(10.0.26100)/amd64", + ) # If this assertion fails, it may be useful to look at the other # properties of ``exc``. - if "no matching manifest for windows/amd64" not in exc.msg: + if not any( + windows_message_substring in exc.msg + for windows_message_substring in windows_message_substrings + ): raise AssertionError(full_log) from exc pytest.skip( reason="We do not currently support using Windows containers." From 3af80120dd86b99b072d930429913fd6c0cc29aa Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 2 Feb 2026 12:32:27 +0000 Subject: [PATCH 2198/2467] Fix CI: remove local vws-python dependency Remove the [tool.uv.sources] section that pointed to a local vws-python path which doesn't exist on CI runners. Replace tests that depended on unreleased vws-python features (request_timeout_seconds) with a test that uses tuple timeouts directly with the requests library. Co-Authored-By: Claude Opus 4.5 --- pyproject.toml | 3 - .../_requests_mock_server/decorators.py | 60 ++++++++----------- tests/mock_vws/test_requests_mock_usage.py | 47 ++++++--------- 3 files changed, 42 insertions(+), 68 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 68d96a4ee..cd328a850 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -495,6 +495,3 @@ ignore_decorators = [ [tool.yamlfix] section_whitelines = 1 whitelines = 1 - -[tool.uv.sources] -vws-python = { path = "../vws-python", editable = true } diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 9a96cd19c..4241d1faa 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -1,13 +1,12 @@ """Decorators for using the mock.""" import re -import threading import time from contextlib import ContextDecorator -from typing import TYPE_CHECKING, Any, Literal, Self +from typing import TYPE_CHECKING, Literal, Self from urllib.parse import urljoin, urlparse -import requests as requests_lib +import requests from beartype import BeartypeConf, beartype from responses import RequestsMock @@ -29,13 +28,9 @@ from collections.abc import Callable, Iterable, Mapping from requests import PreparedRequest - from requests.adapters import HTTPAdapter # noqa: F401 ResponseType = tuple[int, Mapping[str, str], str] - Callback = Callable[[PreparedRequest], ResponseType] # noqa: F841 - -# Thread-local storage to capture the request timeout -_timeout_storage = threading.local() + Callback = Callable[[PreparedRequest], ResponseType] _STRUCTURAL_SIMILARITY_MATCHER = StructuralSimilarityMatcher() _BRISQUE_TRACKING_RATER = BrisqueTargetTrackingRater() @@ -152,20 +147,28 @@ def __enter__(self) -> Self: def wrap_callback(callback: "Callback") -> "Callback": """Wrap a callback to add a response delay.""" - def wrapped(request: "PreparedRequest") -> "ResponseType": - # Check if the delay would exceed the request timeout - timeout = getattr(_timeout_storage, "timeout", None) - if timeout is not None and delay_seconds > 0: - # timeout can be a float or a tuple (connect, read) - if isinstance(timeout, tuple): - effective_timeout: float | None = timeout[1] # read timeout - else: - effective_timeout = timeout - if ( - effective_timeout is not None - and delay_seconds > effective_timeout - ): - raise requests_lib.exceptions.Timeout + def wrapped( + request: "PreparedRequest", + ) -> "ResponseType": + # req_kwargs is added dynamically by the responses + # library onto PreparedRequest objects - it is not + # in the requests type stubs. + timeout = request.req_kwargs.get("timeout") # type: ignore[attr-defined] + # requests allows timeout as a (connect, read) + # tuple. The delay simulates server response + # time, so compare against the read timeout. + effective: float | None = None + if isinstance(timeout, tuple): + effective = timeout[1] + elif isinstance(timeout, (int, float)): + effective = timeout + + if ( + effective is not None + and delay_seconds > effective + ): + time.sleep(effective) + raise requests.exceptions.Timeout result = callback(request) time.sleep(delay_seconds) @@ -174,19 +177,6 @@ def wrapped(request: "PreparedRequest") -> "ResponseType": return wrapped mock = RequestsMock(assert_all_requests_are_fired=False) - - # Patch _on_request to capture the timeout parameter - original_on_request = mock._on_request # noqa: SLF001 - - def patched_on_request( - adapter: "HTTPAdapter", - request: "PreparedRequest", - **kwargs: Any, # noqa: ANN401 - ) -> Any: # noqa: ANN401 - _timeout_storage.timeout = kwargs.get("timeout") - return original_on_request(adapter, request, **kwargs) # type: ignore[misc] - - mock._on_request = patched_on_request # type: ignore[method-assign] # noqa: SLF001 for vws_route in self._mock_vws_api.routes: url_pattern = urljoin( base=self._base_vws_url, diff --git a/tests/mock_vws/test_requests_mock_usage.py b/tests/mock_vws/test_requests_mock_usage.py index dd9c04b5c..46e22112d 100644 --- a/tests/mock_vws/test_requests_mock_usage.py +++ b/tests/mock_vws/test_requests_mock_usage.py @@ -174,39 +174,26 @@ def test_delay_allows_completion() -> None: assert response.status_code is not None @staticmethod - def test_vws_client_with_timeout() -> None: + def test_delay_with_tuple_timeout() -> None: """ - The VWS client's request_timeout_seconds parameter works with - response_delay_seconds. + The response delay works correctly with tuple timeouts + (connect_timeout, read_timeout). """ - database = VuforiaDatabase() - with MockVWS(response_delay_seconds=0.5) as mock: - mock.add_database(database=database) - vws_client = VWS( - server_access_key=database.server_access_key, - server_secret_key=database.server_secret_key, - request_timeout_seconds=0.1, + with ( + MockVWS(response_delay_seconds=0.5), + pytest.raises(expected_exception=requests.exceptions.Timeout), + ): + # Tuple timeout: (connect_timeout, read_timeout) + # The read timeout (0.1) is less than the delay (0.5) + requests.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": rfc_1123_date(), + "Authorization": "bad_auth_token", + }, + data=b"", + timeout=(5.0, 0.1), ) - with pytest.raises(expected_exception=requests.exceptions.Timeout): - vws_client.list_targets() - - @staticmethod - def test_vws_client_without_timeout() -> None: - """ - The VWS client completes successfully when the timeout exceeds - the response delay. - """ - database = VuforiaDatabase() - with MockVWS(response_delay_seconds=0.1) as mock: - mock.add_database(database=database) - vws_client = VWS( - server_access_key=database.server_access_key, - server_secret_key=database.server_secret_key, - request_timeout_seconds=2.0, - ) - # This should succeed - targets = vws_client.list_targets() - assert targets == [] class TestProcessingTime: From 844d38e0062fa51e1989a94306765ea422dba5ff Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 07:41:15 +0000 Subject: [PATCH 2199/2467] Refactor __enter__ to reduce duplication and move wrap_callback to static method Consolidate the duplicate VWS/VWQ route registration loops into a single loop and extract wrap_callback as a static method. Move type aliases out of TYPE_CHECKING block. Co-Authored-By: Claude Opus 4.6 --- .../_requests_mock_server/decorators.py | 143 ++++++++---------- 1 file changed, 59 insertions(+), 84 deletions(-) diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 4241d1faa..2b45de518 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -2,12 +2,14 @@ import re import time +from collections.abc import Callable, Mapping from contextlib import ContextDecorator -from typing import TYPE_CHECKING, Literal, Self +from typing import Literal, Self from urllib.parse import urljoin, urlparse import requests from beartype import BeartypeConf, beartype +from requests import PreparedRequest from responses import RequestsMock from mock_vws.database import VuforiaDatabase @@ -24,13 +26,8 @@ from .mock_web_query_api import MockVuforiaWebQueryAPI from .mock_web_services_api import MockVuforiaWebServicesAPI -if TYPE_CHECKING: - from collections.abc import Callable, Iterable, Mapping - - from requests import PreparedRequest - - ResponseType = tuple[int, Mapping[str, str], str] - Callback = Callable[[PreparedRequest], ResponseType] +_ResponseType = tuple[int, Mapping[str, str], str] +_Callback = Callable[[PreparedRequest], _ResponseType] _STRUCTURAL_SIMILARITY_MATCHER = StructuralSimilarityMatcher() _BRISQUE_TRACKING_RATER = BrisqueTargetTrackingRater() @@ -135,91 +132,69 @@ def add_database(self, database: VuforiaDatabase) -> None: """ self._target_manager.add_database(database=database) + @staticmethod + def _wrap_callback( + callback: _Callback, + delay_seconds: float, + ) -> _Callback: + """Wrap a callback to add a response delay.""" + + def wrapped( + request: "PreparedRequest", + ) -> "_ResponseType": + # req_kwargs is added dynamically by the responses + # library onto PreparedRequest objects - it is not + # in the requests type stubs. + timeout = request.req_kwargs.get("timeout") # type: ignore[attr-defined] + # requests allows timeout as a (connect, read) + # tuple. The delay simulates server response + # time, so compare against the read timeout. + effective: float | None = None + if isinstance(timeout, tuple): + effective = timeout[1] + elif isinstance(timeout, (int, float)): + effective = timeout + + if effective is not None and delay_seconds > effective: + time.sleep(effective) + raise requests.exceptions.Timeout + + result = callback(request) + time.sleep(delay_seconds) + return result + + return wrapped + def __enter__(self) -> Self: """Start an instance of a Vuforia mock. Returns: ``self``. """ - compiled_url_patterns: Iterable[re.Pattern[str]] = set() - delay_seconds = self._response_delay_seconds - - def wrap_callback(callback: "Callback") -> "Callback": - """Wrap a callback to add a response delay.""" - - def wrapped( - request: "PreparedRequest", - ) -> "ResponseType": - # req_kwargs is added dynamically by the responses - # library onto PreparedRequest objects - it is not - # in the requests type stubs. - timeout = request.req_kwargs.get("timeout") # type: ignore[attr-defined] - # requests allows timeout as a (connect, read) - # tuple. The delay simulates server response - # time, so compare against the read timeout. - effective: float | None = None - if isinstance(timeout, tuple): - effective = timeout[1] - elif isinstance(timeout, (int, float)): - effective = timeout - - if ( - effective is not None - and delay_seconds > effective - ): - time.sleep(effective) - raise requests.exceptions.Timeout - - result = callback(request) - time.sleep(delay_seconds) - return result - - return wrapped - mock = RequestsMock(assert_all_requests_are_fired=False) - for vws_route in self._mock_vws_api.routes: - url_pattern = urljoin( - base=self._base_vws_url, - url=f"{vws_route.path_pattern}$", - ) - compiled_url_pattern = re.compile(pattern=url_pattern) - compiled_url_patterns = { - *compiled_url_patterns, - compiled_url_pattern, - } - - for vws_http_method in vws_route.http_methods: - original_callback = getattr( - self._mock_vws_api, vws_route.route_name - ) - mock.add_callback( - method=vws_http_method, - url=compiled_url_pattern, - callback=wrap_callback(callback=original_callback), - content_type=None, - ) - for vwq_route in self._mock_vwq_api.routes: - url_pattern = urljoin( - base=self._base_vwq_url, - url=f"{vwq_route.path_pattern}$", - ) - compiled_url_pattern = re.compile(pattern=url_pattern) - compiled_url_patterns = { - *compiled_url_patterns, - compiled_url_pattern, - } - - for vwq_http_method in vwq_route.http_methods: - original_callback = getattr( - self._mock_vwq_api, vwq_route.route_name - ) - mock.add_callback( - method=vwq_http_method, - url=compiled_url_pattern, - callback=wrap_callback(callback=original_callback), - content_type=None, + for api, base_url in ( + (self._mock_vws_api, self._base_vws_url), + (self._mock_vwq_api, self._base_vwq_url), + ): + for route in api.routes: + url_pattern = urljoin( + base=base_url, + url=f"{route.path_pattern}$", ) + compiled_url_pattern = re.compile(pattern=url_pattern) + + for http_method in route.http_methods: + original_callback = getattr(api, route.route_name) + mock.add_callback( + method=http_method, + url=compiled_url_pattern, + callback=self._wrap_callback( + callback=original_callback, + delay_seconds=self._response_delay_seconds, + ), + content_type=None, + ) if self._real_http: all_requests_pattern = re.compile(pattern=".*") From 8d907b924df758cd48d66d758daec9da2b442207 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 07:44:45 +0000 Subject: [PATCH 2200/2467] Remove unnecessary string annotations for runtime imports Co-Authored-By: Claude Opus 4.6 --- src/mock_vws/_requests_mock_server/decorators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 2b45de518..0ae03f1fa 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -140,8 +140,8 @@ def _wrap_callback( """Wrap a callback to add a response delay.""" def wrapped( - request: "PreparedRequest", - ) -> "_ResponseType": + request: PreparedRequest, + ) -> _ResponseType: # req_kwargs is added dynamically by the responses # library onto PreparedRequest objects - it is not # in the requests type stubs. From 8cf27b73a314993643092546dab1dfccf7367835 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 07:47:05 +0000 Subject: [PATCH 2201/2467] Remove unused compiled_url_patterns variable The variable was accumulated in both route-processing loops but never read, so it served no purpose. Co-Authored-By: Claude Haiku 4.5 --- src/mock_vws/_requests_mock_server/decorators.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 6572d593d..475b8c313 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -2,7 +2,7 @@ import re from contextlib import ContextDecorator -from typing import TYPE_CHECKING, Literal, Self +from typing import Literal, Self from urllib.parse import urljoin, urlparse from beartype import BeartypeConf, beartype @@ -22,9 +22,6 @@ from .mock_web_query_api import MockVuforiaWebQueryAPI from .mock_web_services_api import MockVuforiaWebServicesAPI -if TYPE_CHECKING: - from collections.abc import Iterable - _STRUCTURAL_SIMILARITY_MATCHER = StructuralSimilarityMatcher() _BRISQUE_TRACKING_RATER = BrisqueTargetTrackingRater() @@ -130,8 +127,6 @@ def __enter__(self) -> Self: Returns: ``self``. """ - compiled_url_patterns: Iterable[re.Pattern[str]] = set() - mock = RequestsMock(assert_all_requests_are_fired=False) for vws_route in self._mock_vws_api.routes: url_pattern = urljoin( @@ -139,10 +134,6 @@ def __enter__(self) -> Self: url=f"{vws_route.path_pattern}$", ) compiled_url_pattern = re.compile(pattern=url_pattern) - compiled_url_patterns = { - *compiled_url_patterns, - compiled_url_pattern, - } for vws_http_method in vws_route.http_methods: mock.add_callback( @@ -158,10 +149,6 @@ def __enter__(self) -> Self: url=f"{vwq_route.path_pattern}$", ) compiled_url_pattern = re.compile(pattern=url_pattern) - compiled_url_patterns = { - *compiled_url_patterns, - compiled_url_pattern, - } for vwq_http_method in vwq_route.http_methods: mock.add_callback( From 900fed77b4b50e68fbf1f53c85474adea73d6944 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 07:52:57 +0000 Subject: [PATCH 2202/2467] Add missing docstring to wrapped function Co-Authored-By: Claude Opus 4.6 --- src/mock_vws/_requests_mock_server/decorators.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 0ae03f1fa..925515a1f 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -142,6 +142,7 @@ def _wrap_callback( def wrapped( request: PreparedRequest, ) -> _ResponseType: + """Handle the response delay and timeout logic.""" # req_kwargs is added dynamically by the responses # library onto PreparedRequest objects - it is not # in the requests type stubs. From 68c5958485a7cc9b6fc4fd44051e0f4fe098d844 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 07:58:18 +0000 Subject: [PATCH 2203/2467] Add changelog entry for response_delay_seconds Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a5e8f60f5..84f11dfd1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,8 @@ Changelog Next ---- +- Add ``response_delay_seconds`` parameter to ``MockVWS`` for simulating slow server responses and testing timeout handling. + 2025.03.10.1 ------------ From 205acc292e0575524c82d994bf8ae79abca34429 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 08:00:53 +0000 Subject: [PATCH 2204/2467] Matrix lint workflow by hook stage Refactor the lint workflow to parallelize hook stage execution by adding hook-stage to the build matrix. This runs all three hook stages (pre-commit, pre-push, manual) in parallel across each platform instead of sequentially. Co-Authored-By: Claude Haiku 4.5 --- .github/workflows/lint.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 36158fc56..51da7df5a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,6 +20,7 @@ jobs: matrix: python-version: ['3.13'] platform: [ubuntu-latest, windows-latest] + hook-stage: [pre-commit, pre-push, manual] runs-on: ${{ matrix.platform }} @@ -38,10 +39,8 @@ jobs: # Use bash to ensure the step fails if any command fails. # PowerShell does not fail on intermediate command failures by default. shell: bash - run: | - uv run --extra=dev prek run --all-files --hook-stage pre-commit --verbose - uv run --extra=dev prek run --all-files --hook-stage pre-push --verbose - uv run --extra=dev prek run --all-files --hook-stage manual --verbose + run: uv run --extra=dev prek run --all-files --hook-stage ${{ matrix.hook-stage }} + --verbose env: UV_PYTHON: ${{ matrix.python-version }} From 216fc9f24467e2f551e255de13a6e4103690ce46 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 08:05:15 +0000 Subject: [PATCH 2205/2467] Fix pyright strict mode errors by using getattr for dynamic attribute access Co-Authored-By: Claude Opus 4.6 --- src/mock_vws/_requests_mock_server/decorators.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 925515a1f..08f63f982 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -4,7 +4,7 @@ import time from collections.abc import Callable, Mapping from contextlib import ContextDecorator -from typing import Literal, Self +from typing import Any, Literal, Self from urllib.parse import urljoin, urlparse import requests @@ -146,15 +146,17 @@ def wrapped( # req_kwargs is added dynamically by the responses # library onto PreparedRequest objects - it is not # in the requests type stubs. - timeout = request.req_kwargs.get("timeout") # type: ignore[attr-defined] + req_kwargs: dict[str, Any] = getattr(request, "req_kwargs", {}) + timeout = req_kwargs.get("timeout") # requests allows timeout as a (connect, read) # tuple. The delay simulates server response # time, so compare against the read timeout. effective: float | None = None if isinstance(timeout, tuple): - effective = timeout[1] + if isinstance(timeout[1], (int, float)): + effective = float(timeout[1]) elif isinstance(timeout, (int, float)): - effective = timeout + effective = float(timeout) if effective is not None and delay_seconds > effective: time.sleep(effective) From 0ac5ad29b6dff93f2ea8d9e00648b635729745aa Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 08:30:05 +0000 Subject: [PATCH 2206/2467] Fix release workflow: persist credentials for git-auto-commit-action --- .github/workflows/release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6ab93f1af..0016bdd5c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,9 @@ jobs: # Also, avoids # https://github.com/stefanzweifel/git-auto-commit-action/issues/99. fetch-depth: 0 - persist-credentials: false + # Credentials need to persist for stefanzweifel/git-auto-commit-action. + # zizmor: ignore[artipacked] + persist-credentials: true - name: Install uv uses: astral-sh/setup-uv@v7 From 35b3e9f976973ada72b7031b507c0c543e2a5308 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 08:39:24 +0000 Subject: [PATCH 2207/2467] Use PAT to bypass ruleset in release workflow --- .github/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0016bdd5c..2beefeeb7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,6 +31,10 @@ jobs: # Credentials need to persist for stefanzweifel/git-auto-commit-action. # zizmor: ignore[artipacked] persist-credentials: true + # Use a PAT so that the push from git-auto-commit-action + # can bypass repository ruleset required status checks. + # The default GITHUB_TOKEN cannot bypass rulesets. + token: ${{ secrets.RELEASE_PAT }} - name: Install uv uses: astral-sh/setup-uv@v7 From 4877930e57627754809a7b9c4fe098733ada89cc Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sun, 15 Feb 2026 08:42:33 +0000 Subject: [PATCH 2208/2467] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 84f11dfd1..e0e8358f9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.15 +---------- + + - Add ``response_delay_seconds`` parameter to ``MockVWS`` for simulating slow server responses and testing timeout handling. 2025.03.10.1 From b4c8c4bf9483cc8d6e4ad6653e4ece39520dcb1c Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sun, 15 Feb 2026 08:42:33 +0000 Subject: [PATCH 2209/2467] Bump CHANGELOG --- .github/workflows/release.yml | 68 ++++++++++++++++++++++++----------- CHANGELOG.rst | 4 +++ 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2beefeeb7..f05691575 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,21 +4,18 @@ name: Release on: workflow_dispatch jobs: - build: - name: Publish a release + release: + name: Create release runs-on: ubuntu-latest - # Specifying an environment is strongly recommended by PyPI. - # See https://github.com/pypa/gh-action-pypi-publish/tree/release/v1/?tab=readme-ov-file#trusted-publishing. - environment: release - permissions: - # This is needed for PyPI publishing. - # See https://github.com/pypa/gh-action-pypi-publish/tree/release/v1/?tab=readme-ov-file#trusted-publishing. - id-token: write # This is needed for https://github.com/stefanzweifel/git-auto-commit-action. contents: write + outputs: + version: ${{ steps.calver.outputs.release }} + tag: ${{ steps.tag_version.outputs.new_tag }} + steps: - uses: actions/checkout@v6 with: @@ -36,12 +33,6 @@ jobs: # The default GITHUB_TOKEN cannot bypass rulesets. token: ${{ secrets.RELEASE_PAT }} - - name: Install uv - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - cache-dependency-glob: '**/pyproject.toml' - - name: Calver calculate version uses: StephaneBour/actions-calver@master id: calver @@ -102,10 +93,34 @@ jobs: name: Release ${{ steps.tag_version.outputs.new_tag }} body: ${{ steps.tag_version.outputs.changelog }} + pypi: + name: Publish to PyPI + needs: release + runs-on: ubuntu-latest + + # Specifying an environment is strongly recommended by PyPI. + # See https://github.com/pypa/gh-action-pypi-publish/tree/release/v1/?tab=readme-ov-file#trusted-publishing. + environment: release + + permissions: + # This is needed for PyPI publishing. + # See https://github.com/pypa/gh-action-pypi-publish/tree/release/v1/?tab=readme-ov-file#trusted-publishing. + id-token: write + + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ needs.release.outputs.tag }} + persist-credentials: false + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + cache-dependency-glob: '**/pyproject.toml' + - name: Build a binary wheel and a source tarball run: | - git fetch --tags - git checkout ${{ steps.tag_version.outputs.new_tag }} uv build --sdist --wheel --out-dir dist/ uv run --extra=release check-wheel-contents dist/*.whl @@ -116,6 +131,19 @@ jobs: with: verbose: true + docker: + name: Publish Docker images + needs: release + runs-on: ubuntu-latest + + permissions: {} + + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ needs.release.outputs.tag }} + persist-credentials: false + - name: Login to DockerHub uses: docker/login-action@v3 with: @@ -137,7 +165,7 @@ jobs: target: target-manager tags: | adamtheturtle/vuforia-target-manager-mock:latest - adamtheturtle/vuforia-target-manager-mock:${{ steps.calver.outputs.release }} + adamtheturtle/vuforia-target-manager-mock:${{ needs.release.outputs.version }} - name: Build and push VWS Docker image uses: docker/build-push-action@v6.19.2 @@ -148,7 +176,7 @@ jobs: target: vws tags: | adamtheturtle/vuforia-vws-mock:latest - adamtheturtle/vuforia-vws-mock:${{ steps.calver.outputs.release }} + adamtheturtle/vuforia-vws-mock:${{ needs.release.outputs.version }} - name: Build and push VWQ Docker image uses: docker/build-push-action@v6.19.2 @@ -159,4 +187,4 @@ jobs: target: vwq tags: |- adamtheturtle/vuforia-vwq-mock:latest - adamtheturtle/vuforia-vwq-mock:${{ steps.calver.outputs.release }} + adamtheturtle/vuforia-vwq-mock:${{ needs.release.outputs.version }} diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 84f11dfd1..e0e8358f9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.15 +---------- + + - Add ``response_delay_seconds`` parameter to ``MockVWS`` for simulating slow server responses and testing timeout handling. 2025.03.10.1 From be4083bab9305b3b84020b425db246ce35fef857 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 09:06:57 +0000 Subject: [PATCH 2210/2467] Add response_delay_seconds to Flask mock settings Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.rst | 1 + pyproject.toml | 1 + src/mock_vws/_flask_server/vwq.py | 11 +++++ src/mock_vws/_flask_server/vws.py | 11 +++++ tests/mock_vws/test_flask_app_usage.py | 62 ++++++++++++++++++++++++++ 5 files changed, 86 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e0e8358f9..edb135638 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,7 @@ Next - Add ``response_delay_seconds`` parameter to ``MockVWS`` for simulating slow server responses and testing timeout handling. +- Add ``response_delay_seconds`` setting to the Flask mock (``VWSSettings`` and ``VWQSettings``) for simulating slow server responses. 2025.03.10.1 ------------ diff --git a/pyproject.toml b/pyproject.toml index 5b30c0aab..c8d642664 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -445,6 +445,7 @@ ignore_decorators = [ "@pytest.fixture", # Flask "@*APP.route", + "@*APP.after_request", "@*APP.before_request", "@*APP.errorhandler", ] diff --git a/src/mock_vws/_flask_server/vwq.py b/src/mock_vws/_flask_server/vwq.py index 910d71124..157552a2c 100644 --- a/src/mock_vws/_flask_server/vwq.py +++ b/src/mock_vws/_flask_server/vwq.py @@ -5,6 +5,7 @@ """ import email.utils +import time from enum import StrEnum, auto from http import HTTPMethod, HTTPStatus @@ -58,6 +59,7 @@ class VWQSettings(BaseSettings): query_image_matcher: _ImageMatcherChoice = ( _ImageMatcherChoice.STRUCTURAL_SIMILARITY ) + response_delay_seconds: float = 0.0 @beartype @@ -101,6 +103,15 @@ def set_terminate_wsgi_input() -> None: request.environ["wsgi.input_terminated"] = True +@CLOUDRECO_FLASK_APP.after_request +@beartype +def add_response_delay(response: Response) -> Response: + """Add a delay to each response.""" + settings = VWQSettings.model_validate(obj={}) + time.sleep(settings.response_delay_seconds) + return response + + @CLOUDRECO_FLASK_APP.errorhandler(code_or_exception=ValidatorError) def handle_exceptions(exc: ValidatorError) -> Response: """Return the error response associated with the given exception.""" diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index 6463b928f..87d4ae618 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -8,6 +8,7 @@ import email.utils import json import logging +import time import uuid from enum import StrEnum, auto from http import HTTPMethod, HTTPStatus @@ -73,6 +74,7 @@ class VWSSettings(BaseSettings): duplicates_image_matcher: _ImageMatcherChoice = ( _ImageMatcherChoice.STRUCTURAL_SIMILARITY ) + response_delay_seconds: float = 0.0 @beartype @@ -130,6 +132,15 @@ def validate_request() -> None: ) +@VWS_FLASK_APP.after_request +@beartype +def add_response_delay(response: Response) -> Response: + """Add a delay to each response.""" + settings = VWSSettings.model_validate(obj={}) + time.sleep(settings.response_delay_seconds) + return response + + @VWS_FLASK_APP.errorhandler(code_or_exception=ValidatorError) def handle_exceptions(exc: ValidatorError) -> Response: """Return the error response associated with the given exception.""" diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index fffd06804..a77e85fd7 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -1,7 +1,9 @@ """Tests for the usage of the mock Flask application.""" +import email.utils import io import json +import time import uuid from collections.abc import Iterator from http import HTTPStatus @@ -605,3 +607,63 @@ def test_random( assert lowest_rating >= minimum_rating assert highest_rating <= maximum_rating assert lowest_rating != highest_rating + + +class TestResponseDelay: + """Tests for the response delay feature.""" + + @staticmethod + def test_default_no_delay() -> None: + """By default, there is no response delay.""" + database = VuforiaDatabase() + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + requests.post(url=databases_url, json=database.to_dict(), timeout=30) + + start = time.monotonic() + requests.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": email.utils.formatdate( + timeval=None, + localtime=False, + usegmt=True, + ), + "Authorization": "bad_auth_token", + }, + data=b"", + timeout=30, + ) + elapsed = time.monotonic() - start + # With no delay, the response should be fast + assert elapsed < 1.0 + + @staticmethod + def test_delay_is_applied( + monkeypatch: pytest.MonkeyPatch, + ) -> None: + """When response_delay_seconds is set, the response is delayed.""" + delay = 0.5 + monkeypatch.setenv( + name="RESPONSE_DELAY_SECONDS", + value=str(object=delay), + ) + database = VuforiaDatabase() + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + requests.post(url=databases_url, json=database.to_dict(), timeout=30) + + start = time.monotonic() + requests.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": email.utils.formatdate( + timeval=None, + localtime=False, + usegmt=True, + ), + "Authorization": "bad_auth_token", + }, + data=b"", + timeout=30, + ) + elapsed = time.monotonic() - start + assert elapsed >= delay From 381c12ac55671872a0b8d147af0f7108a6282125 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 09:11:51 +0000 Subject: [PATCH 2211/2467] Tighten response delay tests to use non-overlapping bounds Co-Authored-By: Claude Opus 4.6 --- tests/mock_vws/test_flask_app_usage.py | 44 +++++++++++--------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index a77e85fd7..e0a09f3c8 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -612,14 +612,11 @@ def test_random( class TestResponseDelay: """Tests for the response delay feature.""" - @staticmethod - def test_default_no_delay() -> None: - """By default, there is no response delay.""" - database = VuforiaDatabase() - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" - requests.post(url=databases_url, json=database.to_dict(), timeout=30) + DELAY_SECONDS = 0.5 - start = time.monotonic() + @staticmethod + def _make_request() -> None: + """Make a request to the VWS API.""" requests.get( url="https://vws.vuforia.com/summary", headers={ @@ -633,37 +630,32 @@ def test_default_no_delay() -> None: data=b"", timeout=30, ) + + def test_default_no_delay(self) -> None: + """By default, there is no response delay.""" + database = VuforiaDatabase() + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + requests.post(url=databases_url, json=database.to_dict(), timeout=30) + + start = time.monotonic() + self._make_request() elapsed = time.monotonic() - start - # With no delay, the response should be fast - assert elapsed < 1.0 + assert elapsed < self.DELAY_SECONDS - @staticmethod def test_delay_is_applied( + self, monkeypatch: pytest.MonkeyPatch, ) -> None: """When response_delay_seconds is set, the response is delayed.""" - delay = 0.5 monkeypatch.setenv( name="RESPONSE_DELAY_SECONDS", - value=str(object=delay), + value=str(object=self.DELAY_SECONDS), ) database = VuforiaDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) start = time.monotonic() - requests.get( - url="https://vws.vuforia.com/summary", - headers={ - "Date": email.utils.formatdate( - timeval=None, - localtime=False, - usegmt=True, - ), - "Authorization": "bad_auth_token", - }, - data=b"", - timeout=30, - ) + self._make_request() elapsed = time.monotonic() - start - assert elapsed >= delay + assert elapsed >= self.DELAY_SECONDS From fc0571278721d0b44198c0b38fbb337f752c278a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 09:22:36 +0000 Subject: [PATCH 2212/2467] Make pyrefly non-exhaustive-match an error - Configure pyrefly to treat non-exhaustive-match as an error instead of a warning so it fails the build. - Add wildcard case branches to match statements to make them exhaustive. --- pyproject.toml | 1 + src/mock_vws/_flask_server/target_manager.py | 4 ++-- src/mock_vws/_flask_server/vwq.py | 4 ++-- src/mock_vws/_flask_server/vws.py | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5b30c0aab..c5ab1ec44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -361,6 +361,7 @@ search_path = [ ".", "src", ] +errors.non-exhaustive-match = "error" [tool.pyright] enableTypeIgnoreComments = false diff --git a/src/mock_vws/_flask_server/target_manager.py b/src/mock_vws/_flask_server/target_manager.py index 90f3341eb..e317549b1 100644 --- a/src/mock_vws/_flask_server/target_manager.py +++ b/src/mock_vws/_flask_server/target_manager.py @@ -45,8 +45,8 @@ def to_target_rater(self) -> TargetTrackingRater: return HardcodedTargetTrackingRater(rating=5) case self.RANDOM: return RandomTargetTrackingRater() - - raise ValueError # pragma: no cover + case _: # pragma: no cover + raise ValueError @beartype diff --git a/src/mock_vws/_flask_server/vwq.py b/src/mock_vws/_flask_server/vwq.py index 910d71124..262274e35 100644 --- a/src/mock_vws/_flask_server/vwq.py +++ b/src/mock_vws/_flask_server/vwq.py @@ -45,8 +45,8 @@ def to_image_matcher(self) -> ImageMatcher: return ExactMatcher() case self.STRUCTURAL_SIMILARITY: return StructuralSimilarityMatcher() - - raise ValueError # pragma: no cover + case _: # pragma: no cover + raise ValueError @beartype diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index 6463b928f..8616bbd36 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -59,8 +59,8 @@ def to_image_matcher(self) -> ImageMatcher: return ExactMatcher() case self.STRUCTURAL_SIMILARITY: return StructuralSimilarityMatcher() - - raise ValueError # pragma: no cover + case _: # pragma: no cover + raise ValueError @beartype From 10ce6686a960e3aa502cf2aa2187255323246c79 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 09:26:39 +0000 Subject: [PATCH 2213/2467] Document that client timeouts are not enforced in tests Co-Authored-By: Claude Opus 4.6 --- tests/mock_vws/test_flask_app_usage.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index e0a09f3c8..f49ab9582 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -610,7 +610,15 @@ def test_random( class TestResponseDelay: - """Tests for the response delay feature.""" + """Tests for the response delay feature. + + These tests run through the ``responses`` library, which intercepts + requests in-process. Because of this, the client ``timeout`` parameter + is not enforced — the delay blocks but never raises + ``requests.exceptions.Timeout``. When running the Flask app as a real + server (e.g. in Docker), the delay causes a genuinely slow HTTP + response and the ``requests`` client will raise ``Timeout`` on its own. + """ DELAY_SECONDS = 0.5 From 4e0a8e8e78e1c13c7dbea71dfe055a592a204687 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 09:30:37 +0000 Subject: [PATCH 2214/2467] Fix pypi job: fetch full history for setuptools-scm --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f05691575..379cc4c14 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -111,6 +111,9 @@ jobs: - uses: actions/checkout@v6 with: ref: ${{ needs.release.outputs.tag }} + # Fetch all history including tags. + # Needed for setuptools-scm version detection. + fetch-depth: 0 persist-credentials: false - name: Install uv From 49db70c32842e1d476e99ed784a4465cde6db11f Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sun, 15 Feb 2026 09:54:20 +0000 Subject: [PATCH 2215/2467] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e0e8358f9..052c7e14d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.15.1 +------------ + + 2026.02.15 ---------- From 6631301e0cc08aad6358e75fe827887e20cf4e80 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 10:01:09 +0000 Subject: [PATCH 2216/2467] Parallelize Docker image builds in release workflow --- .github/workflows/release.yml | 42 +++++++++++++---------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 379cc4c14..a5d074c96 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -135,10 +135,20 @@ jobs: verbose: true docker: - name: Publish Docker images + name: Publish Docker image (${{ matrix.image.target }}) needs: release runs-on: ubuntu-latest + strategy: + matrix: + image: + - target: target-manager + repo: adamtheturtle/vuforia-target-manager-mock + - target: vws + repo: adamtheturtle/vuforia-vws-mock + - target: vwq + repo: adamtheturtle/vuforia-vwq-mock + permissions: {} steps: @@ -159,35 +169,13 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v3 - - name: Build and push target manager Docker image - uses: docker/build-push-action@v6.19.2 - with: - file: src/mock_vws/_flask_server/Dockerfile - platforms: linux/amd64,linux/arm64 - push: true - target: target-manager - tags: | - adamtheturtle/vuforia-target-manager-mock:latest - adamtheturtle/vuforia-target-manager-mock:${{ needs.release.outputs.version }} - - - name: Build and push VWS Docker image - uses: docker/build-push-action@v6.19.2 - with: - file: src/mock_vws/_flask_server/Dockerfile - platforms: linux/amd64,linux/arm64 - push: true - target: vws - tags: | - adamtheturtle/vuforia-vws-mock:latest - adamtheturtle/vuforia-vws-mock:${{ needs.release.outputs.version }} - - - name: Build and push VWQ Docker image + - name: Build and push Docker image uses: docker/build-push-action@v6.19.2 with: file: src/mock_vws/_flask_server/Dockerfile platforms: linux/amd64,linux/arm64 push: true - target: vwq + target: ${{ matrix.image.target }} tags: |- - adamtheturtle/vuforia-vwq-mock:latest - adamtheturtle/vuforia-vwq-mock:${{ needs.release.outputs.version }} + ${{ matrix.image.repo }}:latest + ${{ matrix.image.repo }}:${{ needs.release.outputs.version }} From d5173e9fdef51df2b30ca1f601742e1915cfc6b7 Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sun, 15 Feb 2026 10:04:10 +0000 Subject: [PATCH 2217/2467] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 052c7e14d..fecd1a373 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.15.2 +------------ + + 2026.02.15.1 ------------ From 34124461b91666be70e6a0032ddab136a550dde6 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 10:09:18 +0000 Subject: [PATCH 2218/2467] Move changelog entry under Next --- CHANGELOG.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index edb135638..a398cc866 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,10 +4,6 @@ Changelog Next ---- -2026.02.15 ----------- - - - Add ``response_delay_seconds`` parameter to ``MockVWS`` for simulating slow server responses and testing timeout handling. - Add ``response_delay_seconds`` setting to the Flask mock (``VWSSettings`` and ``VWQSettings``) for simulating slow server responses. From 0e09b211c9b2e2edf33a34981f45b7186839bc34 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 10:25:04 +0000 Subject: [PATCH 2219/2467] Fix response delay env var string conversion --- tests/mock_vws/test_flask_app_usage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index f49ab9582..af33fcde9 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -657,7 +657,7 @@ def test_delay_is_applied( """When response_delay_seconds is set, the response is delayed.""" monkeypatch.setenv( name="RESPONSE_DELAY_SECONDS", - value=str(object=self.DELAY_SECONDS), + value=f"{self.DELAY_SECONDS}", ) database = VuforiaDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" From 825d2062b8b3844998318d5d297ad8e5daf9d684 Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sun, 15 Feb 2026 10:27:30 +0000 Subject: [PATCH 2220/2467] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a398cc866..1d4dff57f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.15.3 +------------ + + - Add ``response_delay_seconds`` parameter to ``MockVWS`` for simulating slow server responses and testing timeout handling. - Add ``response_delay_seconds`` setting to the Flask mock (``VWSSettings`` and ``VWQSettings``) for simulating slow server responses. From b3c1cc319f44114e30b2b4985ad0afd8d7dc3daf Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 11:37:07 +0000 Subject: [PATCH 2221/2467] Add injectable sleep_fn parameter to MockVWS Enable deterministic test-time delay handling by allowing callers to inject a custom sleep strategy. This avoids the need for monkeypatching and enables virtual time control with libraries like freezegun. - Add sleep_fn parameter to MockVWS.__init__ with default time.sleep - Pass sleep_fn through to _wrap_callback for both timeout and delay paths - Add tests verifying injected sleep_fn is called on both success and timeout paths - Update changelog Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.rst | 2 + .../_requests_mock_server/decorators.py | 12 ++++- tests/mock_vws/test_requests_mock_usage.py | 48 +++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1d4dff57f..0ca58e988 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,8 @@ Changelog Next ---- +- Add ``sleep_fn`` parameter to ``MockVWS`` for injecting a custom delay strategy, enabling deterministic and fast tests without monkey-patching. + 2026.02.15.3 ------------ diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 08f63f982..411cc3298 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -70,6 +70,7 @@ def __init__( target_tracking_rater: TargetTrackingRater = _BRISQUE_TRACKING_RATER, real_http: bool = False, response_delay_seconds: float = 0.0, + sleep_fn: Callable[[float], None] = time.sleep, ) -> None: """Route requests to Vuforia's Web Service APIs to fakes of those APIs. @@ -91,6 +92,10 @@ def __init__( target_tracking_rater: A callable for rating targets for tracking. response_delay_seconds: The number of seconds to delay each response by. This can be used to test timeout handling. + sleep_fn: The function to use for sleeping during response + delays. Defaults to ``time.sleep``. Inject a custom + function to control virtual time in tests without + monkey-patching. Raises: MissingSchemeError: There is no scheme in a given URL. @@ -98,6 +103,7 @@ def __init__( super().__init__() self._real_http = real_http self._response_delay_seconds = response_delay_seconds + self._sleep_fn = sleep_fn self._mock: RequestsMock self._target_manager = TargetManager() @@ -136,6 +142,7 @@ def add_database(self, database: VuforiaDatabase) -> None: def _wrap_callback( callback: _Callback, delay_seconds: float, + sleep_fn: Callable[[float], None], ) -> _Callback: """Wrap a callback to add a response delay.""" @@ -159,11 +166,11 @@ def wrapped( effective = float(timeout) if effective is not None and delay_seconds > effective: - time.sleep(effective) + sleep_fn(effective) raise requests.exceptions.Timeout result = callback(request) - time.sleep(delay_seconds) + sleep_fn(delay_seconds) return result return wrapped @@ -195,6 +202,7 @@ def __enter__(self) -> Self: callback=self._wrap_callback( callback=original_callback, delay_seconds=self._response_delay_seconds, + sleep_fn=self._sleep_fn, ), content_type=None, ) diff --git a/tests/mock_vws/test_requests_mock_usage.py b/tests/mock_vws/test_requests_mock_usage.py index 46e22112d..4b9cf1f5c 100644 --- a/tests/mock_vws/test_requests_mock_usage.py +++ b/tests/mock_vws/test_requests_mock_usage.py @@ -195,6 +195,54 @@ def test_delay_with_tuple_timeout() -> None: timeout=(5.0, 0.1), ) + @staticmethod + def test_custom_sleep_fn_called_on_delay() -> None: + """ + When a custom ``sleep_fn`` is provided, it is called instead of + ``time.sleep`` for the non-timeout delay path. + """ + calls: list[float] = [] + with MockVWS( + response_delay_seconds=5.0, + sleep_fn=calls.append, + ): + requests.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": rfc_1123_date(), + "Authorization": "bad_auth_token", + }, + data=b"", + timeout=30, + ) + assert calls == [5.0] + + @staticmethod + def test_custom_sleep_fn_called_on_timeout() -> None: + """ + When a custom ``sleep_fn`` is provided, it is called instead of + ``time.sleep`` for the timeout path. + """ + calls: list[float] = [] + with ( + MockVWS( + response_delay_seconds=5.0, + sleep_fn=calls.append, + ), + pytest.raises(expected_exception=requests.exceptions.Timeout), + ): + requests.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": rfc_1123_date(), + "Authorization": "bad_auth_token", + }, + data=b"", + timeout=1.0, + ) + # sleep_fn should have been called with the effective timeout + assert calls == [1.0] + class TestProcessingTime: """Tests for the time taken to process targets in the mock.""" From 1778e87963504db4585d86e00292432667358237 Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sun, 15 Feb 2026 11:52:36 +0000 Subject: [PATCH 2222/2467] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0ca58e988..9588916d5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.15.4 +------------ + + - Add ``sleep_fn`` parameter to ``MockVWS`` for injecting a custom delay strategy, enabling deterministic and fast tests without monkey-patching. 2026.02.15.3 From 43baf019c65325348ea8768d7c78476a55462e93 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 14:52:17 +0000 Subject: [PATCH 2223/2467] Use docker/bake-action to share base layer across images Replace the matrix strategy with docker/bake-action, which builds all three targets (vws, vwq, target-manager) in a single invocation. This allows the shared base stage to be built once instead of three times, improving build performance on multi-platform releases. Co-Authored-By: Claude Haiku 4.5 --- .github/workflows/release.yml | 24 +++++---------------- docker-bake.hcl | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 19 deletions(-) create mode 100644 docker-bake.hcl diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5d074c96..365d226bc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -135,20 +135,10 @@ jobs: verbose: true docker: - name: Publish Docker image (${{ matrix.image.target }}) + name: Publish Docker images needs: release runs-on: ubuntu-latest - strategy: - matrix: - image: - - target: target-manager - repo: adamtheturtle/vuforia-target-manager-mock - - target: vws - repo: adamtheturtle/vuforia-vws-mock - - target: vwq - repo: adamtheturtle/vuforia-vwq-mock - permissions: {} steps: @@ -169,13 +159,9 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v3 - - name: Build and push Docker image - uses: docker/build-push-action@v6.19.2 + - name: Build and push Docker images + uses: docker/bake-action@v6.10.0 with: - file: src/mock_vws/_flask_server/Dockerfile - platforms: linux/amd64,linux/arm64 push: true - target: ${{ matrix.image.target }} - tags: |- - ${{ matrix.image.repo }}:latest - ${{ matrix.image.repo }}:${{ needs.release.outputs.version }} + env: + VERSION: ${{ needs.release.outputs.version }} diff --git a/docker-bake.hcl b/docker-bake.hcl new file mode 100644 index 000000000..46abc827e --- /dev/null +++ b/docker-bake.hcl @@ -0,0 +1,39 @@ +variable "VERSION" { + default = "latest" +} + +group "default" { + targets = ["vws", "vwq", "target-manager"] +} + +target "_base" { + dockerfile = "src/mock_vws/_flask_server/Dockerfile" + platforms = ["linux/amd64", "linux/arm64"] +} + +target "vws" { + inherits = ["_base"] + target = "vws" + tags = [ + "adamtheturtle/vuforia-vws-mock:latest", + "adamtheturtle/vuforia-vws-mock:${VERSION}", + ] +} + +target "vwq" { + inherits = ["_base"] + target = "vwq" + tags = [ + "adamtheturtle/vuforia-vwq-mock:latest", + "adamtheturtle/vuforia-vwq-mock:${VERSION}", + ] +} + +target "target-manager" { + inherits = ["_base"] + target = "target-manager" + tags = [ + "adamtheturtle/vuforia-target-manager-mock:latest", + "adamtheturtle/vuforia-target-manager-mock:${VERSION}", + ] +} From 01daa0551a166bd1ae6fb9d82bbac7071df492f2 Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sun, 15 Feb 2026 21:40:37 +0000 Subject: [PATCH 2224/2467] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9588916d5..2a5183345 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.15.5 +------------ + + 2026.02.15.4 ------------ From 67f3913ec14d871c3c555a3a1f4a6c5afc2d2c54 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 21:48:21 +0000 Subject: [PATCH 2225/2467] Enable coverage report.show_missing --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 95648ba09..e6f937a3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -346,6 +346,7 @@ report.exclude_also = [ ] report.fail_under = 100 +report.show_missing = true [tool.mypy] strict = true files = [ "." ] From 4bc5537a7f46291903f2ebc84c4dc196344a5a21 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 21:54:49 +0000 Subject: [PATCH 2226/2467] Add hclfmt pre-commit hook and update CI Docker build to use bake Co-Authored-By: Claude Opus 4.6 --- .github/workflows/docker-build.yml | 33 +++++++----------------------- .pre-commit-config.yaml | 8 ++++++++ 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index eb1bca888..b7828a7dc 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -23,13 +23,6 @@ jobs: name: Build Docker images runs-on: ubuntu-latest - strategy: - matrix: - image: - - name: target-manager - - name: vws - - name: vwq - steps: - uses: actions/checkout@v6 with: @@ -41,24 +34,12 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Build Docker image - uses: docker/build-push-action@v6.19.2 + - name: Check Docker bake definition + uses: docker/bake-action@v6.10.0 with: - platforms: linux/amd64,linux/arm64 - file: src/mock_vws/_flask_server/Dockerfile - push: false - target: ${{ matrix.image.name }} - tags: |- - adamtheturtle/vuforia-${{ matrix.image.name }}-mock:latest + call: check - completion-docker: - needs: build - runs-on: ubuntu-latest - if: always() # Run even if one matrix job fails - steps: - - name: Check matrix job status - run: |- - if ! ${{ needs.build.result == 'success' }}; then - echo "One or more matrix jobs failed" - exit 1 - fi + - name: Build Docker images + uses: docker/bake-action@v6.10.0 + with: + push: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dc9bf5224..5993b1976 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -415,3 +415,11 @@ repos: language: python types_or: [markdown, rst] additional_dependencies: [uv==0.9.5] + + - id: hclfmt + name: hclfmt + entry: hclfmt -w + language: golang + types: [hcl] + additional_dependencies: [github.com/hashicorp/hcl/v2/cmd/hclfmt@v2.24.0] + stages: [pre-commit] From 91693d89fa1222a210945d1de5f4584ce292e47a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 22:06:40 +0000 Subject: [PATCH 2227/2467] Fix TOML section spacing for coverage setting --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e6f937a3a..e3b687a23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -345,8 +345,8 @@ report.exclude_also = [ "if TYPE_CHECKING:", ] report.fail_under = 100 - report.show_missing = true + [tool.mypy] strict = true files = [ "." ] From 358652437e2b27ed22b13a061150bca09068cd3a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 22:04:11 +0000 Subject: [PATCH 2228/2467] Remove redundant isinstance check in timeout handling The isinstance check for tuple[1] is always true when timeout is a tuple, as the requests library always provides numeric values. Removing this redundant check eliminates uncovered branch warnings during coverage analysis. Co-Authored-By: Claude Opus 4.6 --- src/mock_vws/_requests_mock_server/decorators.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 411cc3298..a613fb53f 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -154,15 +154,16 @@ def wrapped( # library onto PreparedRequest objects - it is not # in the requests type stubs. req_kwargs: dict[str, Any] = getattr(request, "req_kwargs", {}) - timeout = req_kwargs.get("timeout") + timeout: tuple[float, float] | float | int | None = req_kwargs.get( + "timeout" + ) # requests allows timeout as a (connect, read) # tuple. The delay simulates server response # time, so compare against the read timeout. - effective: float | None = None if isinstance(timeout, tuple): - if isinstance(timeout[1], (int, float)): - effective = float(timeout[1]) - elif isinstance(timeout, (int, float)): + timeout = timeout[1] + effective: float | None = None + if isinstance(timeout, (int, float)): effective = float(timeout) if effective is not None and delay_seconds > effective: From 736d98576aa3d51e0b24e6fc1076e7d89cedcd89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 05:04:14 +0000 Subject: [PATCH 2229/2467] Bump prek from 0.3.2 to 0.3.3 Bumps [prek](https://github.com/j178/prek) from 0.3.2 to 0.3.3. - [Release notes](https://github.com/j178/prek/releases) - [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md) - [Commits](https://github.com/j178/prek/compare/v0.3.2...v0.3.3) --- updated-dependencies: - dependency-name: prek dependency-version: 0.3.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e3b687a23..872727b07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2026.1.12", - "prek==0.3.2", + "prek==0.3.3", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pylint[spelling]==4.0.4", From 016db6777b155086971ec03ba501faec3aaabd59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 05:04:47 +0000 Subject: [PATCH 2230/2467] Bump doccmd from 2026.1.31.3 to 2026.2.15 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.1.31.3 to 2026.2.15. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2026.01.31.3...2026.02.15) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.2.15 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e3b687a23..8215c05f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==2.0.0", - "doccmd==2026.1.31.3", + "doccmd==2026.2.15", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", "freezegun==1.5.5", From 6e8d81fba2a537336f6a28ee0e9e38ff7cfb25e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 05:04:57 +0000 Subject: [PATCH 2231/2467] Bump actionlint-py from 1.7.10.24 to 1.7.11.24 Bumps [actionlint-py](https://github.com/Mateusz-Grzelinski/actionlint-py) from 1.7.10.24 to 1.7.11.24. - [Commits](https://github.com/Mateusz-Grzelinski/actionlint-py/compare/v1.7.10.24...v1.7.11.24) --- updated-dependencies: - dependency-name: actionlint-py dependency-version: 1.7.11.24 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e3b687a23..d2128acda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dependencies = [ "werkzeug>=3.1.2", ] optional-dependencies.dev = [ - "actionlint-py==1.7.10.24", + "actionlint-py==1.7.11.24", "check-manifest==0.51", "check-wheel-contents==0.6.3", "coverage==7.13.4", From 0baa1b752818f14e84b8ef5ccb7c62b7ea9981f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 05:05:05 +0000 Subject: [PATCH 2232/2467] Bump vws-python from 2025.3.10.1 to 2026.2.15 Bumps [vws-python](https://github.com/VWS-Python/vws-python) from 2025.3.10.1 to 2026.2.15. - [Release notes](https://github.com/VWS-Python/vws-python/releases) - [Changelog](https://github.com/VWS-Python/vws-python/blob/main/CHANGELOG.rst) - [Commits](https://github.com/VWS-Python/vws-python/compare/2025.03.10.1...2026.02.15) --- updated-dependencies: - dependency-name: vws-python dependency-version: 2026.2.15 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e3b687a23..8e904bf63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,7 +102,7 @@ optional-dependencies.dev = [ "types-requests==2.32.4.20260107", "urllib3==2.6.3", "vulture==2.14", - "vws-python==2025.3.10.1", + "vws-python==2026.2.15", "vws-test-fixtures==2023.3.5", "vws-web-tools==2024.10.6.1", "yamlfix==1.19.1", From 5c90ce827282919541c2f9648495f21736c97d91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 05:28:13 +0000 Subject: [PATCH 2233/2467] Bump ty from 0.0.16 to 0.0.17 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.16 to 0.0.17. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.16...0.0.17) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.17 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 872727b07..763aaf85b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.4", - "ty==0.0.16", + "ty==0.0.17", "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", From a4d23e0ed601b467d0ccc8f439745e4f63e876c0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 13:44:51 +0000 Subject: [PATCH 2234/2467] Bump vws-web-tools to 2026.2.16 and update create_secrets_files.py to use driver helper Migrate admin/create_secrets_files.py to use the create_chrome_driver() helper from vws-web-tools instead of directly instantiating webdriver.Chrome(). This provides consistent configuration and headless browser setup. Also update create_database() call to create_cloud_database() to match the renamed API in the new version. Co-Authored-By: Claude Haiku 4.5 --- admin/create_secrets_files.py | 12 +++--------- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 4f1b7183c..ca5e9a9de 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -8,16 +8,11 @@ import sys import textwrap from pathlib import Path -from typing import TYPE_CHECKING import vws_web_tools from dotenv import load_dotenv -from selenium import webdriver from selenium.common.exceptions import TimeoutException -if TYPE_CHECKING: - from selenium.webdriver.remote.webdriver import WebDriver - def main() -> None: """Create secrets files.""" @@ -39,12 +34,11 @@ def main() -> None: for i in range(num_databases) ] files_to_create = [file for file in required_files if not file.exists()] - driver: WebDriver | None = None + driver: vws_web_tools.WebDriver | None = None while files_to_create: if driver is None: - # With Safari we get a bunch of errors / timeouts. - driver = webdriver.Chrome() + driver = vws_web_tools.create_chrome_driver() file = files_to_create[-1] sys.stdout.write(f"Creating database {file.name}\n") time = datetime.datetime.now(tz=datetime.UTC).strftime( @@ -69,7 +63,7 @@ def main() -> None: driver = None continue - vws_web_tools.create_database( + vws_web_tools.create_cloud_database( driver=driver, database_name=database_name, license_name=license_name, diff --git a/pyproject.toml b/pyproject.toml index 18e5e8400..642054c2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,7 +104,7 @@ optional-dependencies.dev = [ "vulture==2.14", "vws-python==2026.2.15", "vws-test-fixtures==2023.3.5", - "vws-web-tools==2024.10.6.1", + "vws-web-tools==2026.2.16", "yamlfix==1.19.1", "zizmor==1.22.0", ] From fd1ce9b2ddb6b25e65064e966a2d25032e242c31 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 13:47:31 +0000 Subject: [PATCH 2235/2467] Fix mypy type checking for WebDriver Use TYPE_CHECKING guard for WebDriver import to satisfy mypy while keeping the runtime implementation using vws_web_tools.create_chrome_driver(). Co-Authored-By: Claude Haiku 4.5 --- admin/create_secrets_files.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index ca5e9a9de..4e7afd0d6 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -8,11 +8,15 @@ import sys import textwrap from pathlib import Path +from typing import TYPE_CHECKING import vws_web_tools from dotenv import load_dotenv from selenium.common.exceptions import TimeoutException +if TYPE_CHECKING: + from selenium.webdriver.remote.webdriver import WebDriver + def main() -> None: """Create secrets files.""" @@ -34,7 +38,7 @@ def main() -> None: for i in range(num_databases) ] files_to_create = [file for file in required_files if not file.exists()] - driver: vws_web_tools.WebDriver | None = None + driver: WebDriver | None = None while files_to_create: if driver is None: From ef76dd50f6b278fb6b0dc71e7d9dc6e45fdefa70 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 14:53:05 +0000 Subject: [PATCH 2236/2467] Remove INACTIVE_VUMARK_VUFORIA_SERVER_ACCESS_KEY from secrets Co-Authored-By: Claude Opus 4.6 --- admin/create_secrets_files.py | 1 - vuforia_secrets.env.example | 1 - 2 files changed, 2 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 8a066a337..11eb94dca 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -111,7 +111,6 @@ def _generate_secrets_file_content( VUMARK_VUFORIA_SERVER_SECRET_KEY={vumark_details["server_secret_key"]} INACTIVE_VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME={os.environ["INACTIVE_VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME"]} - INACTIVE_VUMARK_VUFORIA_SERVER_ACCESS_KEY={os.environ["INACTIVE_VUMARK_VUFORIA_SERVER_ACCESS_KEY"]} INACTIVE_VUMARK_VUFORIA_SERVER_SECRET_KEY={os.environ["INACTIVE_VUMARK_VUFORIA_SERVER_SECRET_KEY"]} """, ) diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index b3bce61af..e30717dfc 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -21,5 +21,4 @@ VUMARK_VUFORIA_SERVER_SECRET_KEY= INACTIVE_VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= -INACTIVE_VUMARK_VUFORIA_SERVER_ACCESS_KEY= INACTIVE_VUMARK_VUFORIA_SERVER_SECRET_KEY= From b1e0e8d1e925e585d712dfc8693929e3ad137160 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 15:02:59 +0000 Subject: [PATCH 2237/2467] Remove inactive VuMark secrets Co-Authored-By: Claude Opus 4.6 --- admin/create_secrets_files.py | 3 --- vuforia_secrets.env.example | 4 ---- 2 files changed, 7 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 11eb94dca..5e6cc43c5 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -109,9 +109,6 @@ def _generate_secrets_file_content( VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME={vumark_details["database_name"]} VUMARK_VUFORIA_SERVER_ACCESS_KEY={vumark_details["server_access_key"]} VUMARK_VUFORIA_SERVER_SECRET_KEY={vumark_details["server_secret_key"]} - - INACTIVE_VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME={os.environ["INACTIVE_VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME"]} - INACTIVE_VUMARK_VUFORIA_SERVER_SECRET_KEY={os.environ["INACTIVE_VUMARK_VUFORIA_SERVER_SECRET_KEY"]} """, ) diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index e30717dfc..5e843a117 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -18,7 +18,3 @@ VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= VUMARK_VUFORIA_SERVER_ACCESS_KEY= VUMARK_VUFORIA_SERVER_SECRET_KEY= - -INACTIVE_VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= - -INACTIVE_VUMARK_VUFORIA_SERVER_SECRET_KEY= From d2b383ad622073398eb530ba5fe72ac10f8738e0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 22:35:26 +0000 Subject: [PATCH 2238/2467] Add minimal VuMark API interaction test --- tests/mock_vws/fixtures/credentials.py | 30 ++++++++++++ tests/mock_vws/test_vumark_generation_api.py | 48 ++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 tests/mock_vws/test_vumark_generation_api.py diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index 90fe125b4..f31c9a904 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -3,6 +3,7 @@ from pathlib import Path import pytest +from pydantic import ValidationError from pydantic_settings import BaseSettings, SettingsConfigDict from mock_vws.database import VuforiaDatabase @@ -35,6 +36,20 @@ class _InactiveVuforiaDatabaseSettings(_VuforiaDatabaseSettings): ) +class _VuMarkVuforiaDatabaseSettings(BaseSettings): + """Settings for a VuMark Vuforia database.""" + + target_manager_database_name: str + server_access_key: str + server_secret_key: str + + model_config = SettingsConfigDict( + env_prefix="VUMARK_VUFORIA_", + env_file=Path("vuforia_secrets.env"), + extra="allow", + ) + + @pytest.fixture def vuforia_database() -> VuforiaDatabase: """Return VWS credentials from environment variables.""" @@ -64,3 +79,18 @@ def inactive_database() -> VuforiaDatabase: client_secret_key=settings.client_secret_key, state=States.PROJECT_INACTIVE, ) + + +@pytest.fixture +def vumark_vuforia_database() -> VuforiaDatabase: + """Return VuMark VWS credentials from environment variables.""" + try: + settings = _VuMarkVuforiaDatabaseSettings.model_validate(obj={}) + except ValidationError: + pytest.skip(reason="VuMark credentials are not configured.") + + return VuforiaDatabase( + database_name=settings.target_manager_database_name, + server_access_key=settings.server_access_key, + server_secret_key=settings.server_secret_key, + ) diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py new file mode 100644 index 000000000..1196563b1 --- /dev/null +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -0,0 +1,48 @@ +"""Tests for the VuMark generation web API.""" + +import json +import uuid +from http import HTTPMethod, HTTPStatus + +import requests +from vws_auth_tools import authorization_header, rfc_1123_date + +from mock_vws.database import VuforiaDatabase + +_VWS_HOST = "https://vws.vuforia.com" + + +def test_generate_instance_for_missing_target( + vumark_vuforia_database: VuforiaDatabase, +) -> None: + """The VuMark generation API can be called with signed credentials.""" + request_path = f"/targets/{uuid.uuid4().hex}/instances" + content_type = "application/json" + content = json.dumps(obj={"instance_id": "1"}).encode(encoding="utf-8") + date = rfc_1123_date() + authorization_string = authorization_header( + access_key=vumark_vuforia_database.server_access_key, + secret_key=vumark_vuforia_database.server_secret_key, + method=HTTPMethod.POST, + content=content, + content_type=content_type, + date=date, + request_path=request_path, + ) + + response = requests.post( + url=_VWS_HOST + request_path, + headers={ + "Authorization": authorization_string, + "Content-Length": str(object=len(content)), + "Content-Type": content_type, + "Date": date, + }, + data=content, + timeout=30, + ) + + assert response.status_code == HTTPStatus.NOT_FOUND + response_json = json.loads(s=response.text) + assert isinstance(response_json, dict) + assert response_json["result_code"] in {"NoSuchTarget", "UnknownTarget"} From a1335853c8f843910d73acf8aa3924be224f334d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 22:46:05 +0000 Subject: [PATCH 2239/2467] Share VuMark credentials across secrets files --- admin/create_secrets_files.py | 20 +++++++++++--------- docs/source/contributing.rst | 8 +++++++- tests/mock_vws/fixtures/credentials.py | 6 +----- vuforia_secrets.env.example | 7 ++++--- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 5e6cc43c5..103ca0e53 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -134,6 +134,7 @@ def main() -> None: ] files_to_create = [file for file in required_files if not file.exists()] driver: WebDriver | None = None + shared_vumark_details: VuMarkDatabaseDict | None = None while files_to_create: if driver is None: @@ -159,21 +160,22 @@ def main() -> None: driver = None continue - vumark_details = _create_and_get_vumark_details( - driver=driver, - vumark_database_name=vumark_database_name, - ) - if vumark_details is None: - driver.quit() - driver = None - continue + if shared_vumark_details is None: + shared_vumark_details = _create_and_get_vumark_details( + driver=driver, + vumark_database_name=vumark_database_name, + ) + if shared_vumark_details is None: + driver.quit() + driver = None + continue driver.quit() driver = None file_contents = _generate_secrets_file_content( database_details=database_details, - vumark_details=vumark_details, + vumark_details=shared_vumark_details, ) file.write_text(data=file_contents) sys.stdout.write(f"Created database {file.name}\n") diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index b93e5f20b..09e1f4ca9 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -84,10 +84,14 @@ Then, add a database from the `Vuforia Target Manager`_. To find the environment variables to set in the :file:`vuforia_secrets.env` file, visit the Target Database in the `Vuforia Target Manager`_ and view the "Database Access Keys". -Two databases are necessary in order to run all the tests. +Two Cloud databases are necessary in order to run all the Cloud Target tests. One of those must be an inactive project. To create an inactive project, delete the license key associated with a database. +VuMark tests require one VuMark database. +When creating multiple credentials files, the same inactive database and the +same VuMark database can be reused across all files. + Targets sometimes get stuck at the "Processing" stage meaning that they cannot be deleted. When this happens, create a new target database to use for testing. @@ -101,6 +105,8 @@ To create databases without using the browser, use :file:`admin/create_secrets_f $ export EXISTING_SECRETS_FILE=/existing/file/with/inactive/db/creds # You may have to run this a few times, but it is idempotent. $ python admin/create_secrets_files.py + # Each generated file gets its own Cloud database credentials and shares + # one VuMark database credential set. # After creating the secrets, update the encrypted archive: $ tar cvf secrets.tar "${NEW_SECRETS_DIR}" $ gpg \ diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index f31c9a904..e64e20940 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -3,7 +3,6 @@ from pathlib import Path import pytest -from pydantic import ValidationError from pydantic_settings import BaseSettings, SettingsConfigDict from mock_vws.database import VuforiaDatabase @@ -84,10 +83,7 @@ def inactive_database() -> VuforiaDatabase: @pytest.fixture def vumark_vuforia_database() -> VuforiaDatabase: """Return VuMark VWS credentials from environment variables.""" - try: - settings = _VuMarkVuforiaDatabaseSettings.model_validate(obj={}) - except ValidationError: - pytest.skip(reason="VuMark credentials are not configured.") + settings = _VuMarkVuforiaDatabaseSettings.model_validate(obj={}) return VuforiaDatabase( database_name=settings.target_manager_database_name, diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index 5e843a117..3bd0e64ac 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -14,7 +14,8 @@ INACTIVE_VUFORIA_SERVER_SECRET_KEY= INACTIVE_VUFORIA_CLIENT_ACCESS_KEY= INACTIVE_VUFORIA_CLIENT_SECRET_KEY= -VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= +# Shared across all generated secrets files. +VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= -VUMARK_VUFORIA_SERVER_ACCESS_KEY= -VUMARK_VUFORIA_SERVER_SECRET_KEY= +VUMARK_VUFORIA_SERVER_ACCESS_KEY= +VUMARK_VUFORIA_SERVER_SECRET_KEY= From 00a5951dbbb82829b9909ce17fefdfc88e12189a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 22:54:00 +0000 Subject: [PATCH 2240/2467] Set VuMark test Accept header --- tests/mock_vws/test_vumark_generation_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 1196563b1..acd7a420c 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -33,6 +33,7 @@ def test_generate_instance_for_missing_target( response = requests.post( url=_VWS_HOST + request_path, headers={ + "Accept": "image/png", "Authorization": authorization_string, "Content-Length": str(object=len(content)), "Content-Type": content_type, From f8c083c52d98a10228ec0f99791257aae6850008 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 22:59:44 +0000 Subject: [PATCH 2241/2467] Make VuMark test assert successful generation --- tests/mock_vws/test_vumark_generation_api.py | 65 +++++++++++++++++--- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index acd7a420c..e18e08f62 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -1,24 +1,71 @@ """Tests for the VuMark generation web API.""" import json -import uuid from http import HTTPMethod, HTTPStatus +from pathlib import Path +import pytest import requests +from pydantic import ValidationError +from pydantic_settings import BaseSettings, SettingsConfigDict from vws_auth_tools import authorization_header, rfc_1123_date from mock_vws.database import VuforiaDatabase _VWS_HOST = "https://vws.vuforia.com" +_PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" -def test_generate_instance_for_missing_target( +class _VuMarkGenerationSettings(BaseSettings): + """Settings needed for VuMark instance generation tests.""" + + target_id: str + instance_id: str + + model_config = SettingsConfigDict( + env_prefix="VUMARK_VUFORIA_", + env_file=Path("vuforia_secrets.env"), + extra="allow", + ) + + +def _get_vumark_generation_settings() -> _VuMarkGenerationSettings: + """Return generation settings, skipping if they are not configured.""" + try: + settings = _VuMarkGenerationSettings.model_validate(obj={}) + except ValidationError: + pytest.skip( + reason=( + "VuMark generation settings are not configured. " + "Set VUMARK_VUFORIA_TARGET_ID and " + "VUMARK_VUFORIA_INSTANCE_ID." + ), + ) + + if settings.target_id.startswith("<") or settings.instance_id.startswith( + "<" + ): + pytest.skip( + reason=( + "VuMark generation settings are placeholders. " + "Set VUMARK_VUFORIA_TARGET_ID and " + "VUMARK_VUFORIA_INSTANCE_ID." + ), + ) + + return settings + + +def test_generate_instance_success( vumark_vuforia_database: VuforiaDatabase, ) -> None: - """The VuMark generation API can be called with signed credentials.""" - request_path = f"/targets/{uuid.uuid4().hex}/instances" + """A VuMark instance can be generated with valid template settings.""" + settings = _get_vumark_generation_settings() + request_path = f"/targets/{settings.target_id}/instances" content_type = "application/json" - content = json.dumps(obj={"instance_id": "1"}).encode(encoding="utf-8") + content = json.dumps(obj={"instance_id": settings.instance_id}).encode( + encoding="utf-8" + ) date = rfc_1123_date() authorization_string = authorization_header( access_key=vumark_vuforia_database.server_access_key, @@ -43,7 +90,7 @@ def test_generate_instance_for_missing_target( timeout=30, ) - assert response.status_code == HTTPStatus.NOT_FOUND - response_json = json.loads(s=response.text) - assert isinstance(response_json, dict) - assert response_json["result_code"] in {"NoSuchTarget", "UnknownTarget"} + assert response.status_code == HTTPStatus.OK + assert response.headers["Content-Type"].split(sep=";")[0] == "image/png" + assert response.content.startswith(_PNG_SIGNATURE) + assert len(response.content) > len(_PNG_SIGNATURE) From 9efcf5ea58ea01d4aba6c62715e54b6c26382817 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 23:21:31 +0000 Subject: [PATCH 2242/2467] Use VuMark fixture fields in generation test --- tests/mock_vws/fixtures/credentials.py | 22 ++++++-- tests/mock_vws/test_vumark_generation_api.py | 55 ++++---------------- vuforia_secrets.env.example | 2 + 3 files changed, 32 insertions(+), 47 deletions(-) diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index e64e20940..142b6f5d6 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -1,5 +1,6 @@ """Fixtures for credentials for Vuforia databases.""" +from dataclasses import dataclass from pathlib import Path import pytest @@ -41,6 +42,8 @@ class _VuMarkVuforiaDatabaseSettings(BaseSettings): target_manager_database_name: str server_access_key: str server_secret_key: str + target_id: str = "" + instance_id: str = "" model_config = SettingsConfigDict( env_prefix="VUMARK_VUFORIA_", @@ -49,6 +52,17 @@ class _VuMarkVuforiaDatabaseSettings(BaseSettings): ) +@dataclass(frozen=True) +class VuMarkVuforiaDatabase: + """Credentials for the VuMark generation API.""" + + target_manager_database_name: str + server_access_key: str + server_secret_key: str + target_id: str + instance_id: str + + @pytest.fixture def vuforia_database() -> VuforiaDatabase: """Return VWS credentials from environment variables.""" @@ -81,12 +95,14 @@ def inactive_database() -> VuforiaDatabase: @pytest.fixture -def vumark_vuforia_database() -> VuforiaDatabase: +def vumark_vuforia_database() -> VuMarkVuforiaDatabase: """Return VuMark VWS credentials from environment variables.""" settings = _VuMarkVuforiaDatabaseSettings.model_validate(obj={}) - return VuforiaDatabase( - database_name=settings.target_manager_database_name, + return VuMarkVuforiaDatabase( + target_manager_database_name=settings.target_manager_database_name, server_access_key=settings.server_access_key, server_secret_key=settings.server_secret_key, + target_id=settings.target_id, + instance_id=settings.instance_id, ) diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index e18e08f62..bb8d7d5a5 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -2,49 +2,24 @@ import json from http import HTTPMethod, HTTPStatus -from pathlib import Path import pytest import requests -from pydantic import ValidationError -from pydantic_settings import BaseSettings, SettingsConfigDict from vws_auth_tools import authorization_header, rfc_1123_date -from mock_vws.database import VuforiaDatabase +from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase _VWS_HOST = "https://vws.vuforia.com" _PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" -class _VuMarkGenerationSettings(BaseSettings): - """Settings needed for VuMark instance generation tests.""" - - target_id: str - instance_id: str - - model_config = SettingsConfigDict( - env_prefix="VUMARK_VUFORIA_", - env_file=Path("vuforia_secrets.env"), - extra="allow", - ) - - -def _get_vumark_generation_settings() -> _VuMarkGenerationSettings: - """Return generation settings, skipping if they are not configured.""" - try: - settings = _VuMarkGenerationSettings.model_validate(obj={}) - except ValidationError: - pytest.skip( - reason=( - "VuMark generation settings are not configured. " - "Set VUMARK_VUFORIA_TARGET_ID and " - "VUMARK_VUFORIA_INSTANCE_ID." - ), - ) - - if settings.target_id.startswith("<") or settings.instance_id.startswith( +def test_generate_instance_success( + vumark_vuforia_database: VuMarkVuforiaDatabase, +) -> None: + """A VuMark instance can be generated with valid template settings.""" + if vumark_vuforia_database.target_id.startswith( "<" - ): + ) or vumark_vuforia_database.instance_id.startswith("<"): pytest.skip( reason=( "VuMark generation settings are placeholders. " @@ -53,19 +28,11 @@ def _get_vumark_generation_settings() -> _VuMarkGenerationSettings: ), ) - return settings - - -def test_generate_instance_success( - vumark_vuforia_database: VuforiaDatabase, -) -> None: - """A VuMark instance can be generated with valid template settings.""" - settings = _get_vumark_generation_settings() - request_path = f"/targets/{settings.target_id}/instances" + request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" content_type = "application/json" - content = json.dumps(obj={"instance_id": settings.instance_id}).encode( - encoding="utf-8" - ) + content = json.dumps( + obj={"instance_id": vumark_vuforia_database.instance_id} + ).encode(encoding="utf-8") date = rfc_1123_date() authorization_string = authorization_header( access_key=vumark_vuforia_database.server_access_key, diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index 3bd0e64ac..fde084a82 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -19,3 +19,5 @@ VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= VUMARK_VUFORIA_SERVER_ACCESS_KEY= VUMARK_VUFORIA_SERVER_SECRET_KEY= +VUMARK_VUFORIA_TARGET_ID= +VUMARK_VUFORIA_INSTANCE_ID= From 18cedb6acfe2376cef2318c1292a599942024e36 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 23:38:17 +0000 Subject: [PATCH 2243/2467] Update CI workflow and remove VuMark test skip logic --- .github/workflows/test.yml | 1 + tests/mock_vws/test_vumark_generation_api.py | 12 ------------ 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 386db8031..cc06d4323 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -113,6 +113,7 @@ jobs: - tests/mock_vws/test_update_target.py::TestInactiveProject - tests/mock_vws/test_requests_mock_usage.py - tests/mock_vws/test_flask_app_usage.py + - tests/mock_vws/test_vumark_generation_api.py - tests/mock_vws/test_docker.py - README.rst - docs/source/basic-example.rst diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index bb8d7d5a5..9a1b30234 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -3,7 +3,6 @@ import json from http import HTTPMethod, HTTPStatus -import pytest import requests from vws_auth_tools import authorization_header, rfc_1123_date @@ -17,17 +16,6 @@ def test_generate_instance_success( vumark_vuforia_database: VuMarkVuforiaDatabase, ) -> None: """A VuMark instance can be generated with valid template settings.""" - if vumark_vuforia_database.target_id.startswith( - "<" - ) or vumark_vuforia_database.instance_id.startswith("<"): - pytest.skip( - reason=( - "VuMark generation settings are placeholders. " - "Set VUMARK_VUFORIA_TARGET_ID and " - "VUMARK_VUFORIA_INSTANCE_ID." - ), - ) - request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" content_type = "application/json" content = json.dumps( From 6d87404038ad44d0b5acc957e48f28c2628b4c20 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 23:43:44 +0000 Subject: [PATCH 2244/2467] Generate VuMark instance IDs in test instead of env settings --- tests/mock_vws/fixtures/credentials.py | 3 --- tests/mock_vws/test_vumark_generation_api.py | 8 +++++--- vuforia_secrets.env.example | 1 - 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index 142b6f5d6..819ad2635 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -43,7 +43,6 @@ class _VuMarkVuforiaDatabaseSettings(BaseSettings): server_access_key: str server_secret_key: str target_id: str = "" - instance_id: str = "" model_config = SettingsConfigDict( env_prefix="VUMARK_VUFORIA_", @@ -60,7 +59,6 @@ class VuMarkVuforiaDatabase: server_access_key: str server_secret_key: str target_id: str - instance_id: str @pytest.fixture @@ -104,5 +102,4 @@ def vumark_vuforia_database() -> VuMarkVuforiaDatabase: server_access_key=settings.server_access_key, server_secret_key=settings.server_secret_key, target_id=settings.target_id, - instance_id=settings.instance_id, ) diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 9a1b30234..6c2004e1c 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -2,6 +2,7 @@ import json from http import HTTPMethod, HTTPStatus +from uuid import uuid4 import requests from vws_auth_tools import authorization_header, rfc_1123_date @@ -18,9 +19,10 @@ def test_generate_instance_success( """A VuMark instance can be generated with valid template settings.""" request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" content_type = "application/json" - content = json.dumps( - obj={"instance_id": vumark_vuforia_database.instance_id} - ).encode(encoding="utf-8") + generated_instance_id = uuid4().hex + content = json.dumps(obj={"instance_id": generated_instance_id}).encode( + encoding="utf-8" + ) date = rfc_1123_date() authorization_string = authorization_header( access_key=vumark_vuforia_database.server_access_key, diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index fde084a82..af7001f55 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -20,4 +20,3 @@ VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= VUMARK_VUFORIA_SERVER_ACCESS_KEY= VUMARK_VUFORIA_SERVER_SECRET_KEY= VUMARK_VUFORIA_TARGET_ID= -VUMARK_VUFORIA_INSTANCE_ID= From 9ef3b36be375abcb3c8927fd8aa7075962c7cbc4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 05:04:16 +0000 Subject: [PATCH 2245/2467] Bump requests-mock-flask from 2026.1.12 to 2026.2.16 Bumps [requests-mock-flask](https://github.com/adamtheturtle/requests-mock-flask) from 2026.1.12 to 2026.2.16. - [Release notes](https://github.com/adamtheturtle/requests-mock-flask/releases) - [Changelog](https://github.com/adamtheturtle/requests-mock-flask/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/requests-mock-flask/compare/2026.01.12...2026.02.16) --- updated-dependencies: - dependency-name: requests-mock-flask dependency-version: 2026.2.16 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dead7dba2..533d913be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ optional-dependencies.dev = [ "pytest-xdist==3.8.0", "python-dotenv==1.2.1", "pyyaml==6.0.3", - "requests-mock-flask==2026.1.12", + "requests-mock-flask==2026.2.16", "ruff==0.15.1", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will From dfd0bde66d5f812d54a29932a018e1c67d28a5bb Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 07:06:13 +0000 Subject: [PATCH 2246/2467] Refactor secrets generation to initialize VuMark data once --- admin/create_secrets_files.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 103ca0e53..144d828ff 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -133,9 +133,27 @@ def main() -> None: for i in range(num_databases) ] files_to_create = [file for file in required_files if not file.exists()] - driver: WebDriver | None = None shared_vumark_details: VuMarkDatabaseDict | None = None + while shared_vumark_details is None: + driver = vws_web_tools.create_chrome_driver() + time = datetime.datetime.now(tz=datetime.UTC).strftime( + format="%Y-%m-%d-%H-%M-%S", + ) + vumark_database_name = f"my-vumark-database-{time}" + vws_web_tools.log_in( + driver=driver, + email_address=email_address, + password=password, + ) + vws_web_tools.wait_for_logged_in(driver=driver) + shared_vumark_details = _create_and_get_vumark_details( + driver=driver, + vumark_database_name=vumark_database_name, + ) + driver.quit() + + driver: WebDriver | None = None while files_to_create: if driver is None: driver = vws_web_tools.create_chrome_driver() @@ -146,7 +164,6 @@ def main() -> None: ) license_name = f"my-license-{time}" database_name = f"my-database-{time}" - vumark_database_name = f"my-vumark-database-{time}" database_details = _create_and_get_database_details( driver=driver, @@ -160,19 +177,12 @@ def main() -> None: driver = None continue - if shared_vumark_details is None: - shared_vumark_details = _create_and_get_vumark_details( - driver=driver, - vumark_database_name=vumark_database_name, - ) - if shared_vumark_details is None: - driver.quit() - driver = None - continue - driver.quit() driver = None + if shared_vumark_details is None: + msg = "Failed to create shared VuMark database details." + raise RuntimeError(msg) file_contents = _generate_secrets_file_content( database_details=database_details, vumark_details=shared_vumark_details, From d65bccf9c8ddb020768bc6c78d02971c48647140 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 07:07:14 +0000 Subject: [PATCH 2247/2467] Fix typing in VuMark setup refactor --- admin/create_secrets_files.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 144d828ff..08f8976dd 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -136,22 +136,26 @@ def main() -> None: shared_vumark_details: VuMarkDatabaseDict | None = None while shared_vumark_details is None: - driver = vws_web_tools.create_chrome_driver() + vumark_driver = vws_web_tools.create_chrome_driver() time = datetime.datetime.now(tz=datetime.UTC).strftime( format="%Y-%m-%d-%H-%M-%S", ) vumark_database_name = f"my-vumark-database-{time}" vws_web_tools.log_in( - driver=driver, + driver=vumark_driver, email_address=email_address, password=password, ) - vws_web_tools.wait_for_logged_in(driver=driver) + vws_web_tools.wait_for_logged_in(driver=vumark_driver) shared_vumark_details = _create_and_get_vumark_details( - driver=driver, + driver=vumark_driver, vumark_database_name=vumark_database_name, ) - driver.quit() + vumark_driver.quit() + + if shared_vumark_details is None: + msg = "Failed to create shared VuMark database details." + raise RuntimeError(msg) driver: WebDriver | None = None while files_to_create: @@ -180,9 +184,6 @@ def main() -> None: driver.quit() driver = None - if shared_vumark_details is None: - msg = "Failed to create shared VuMark database details." - raise RuntimeError(msg) file_contents = _generate_secrets_file_content( database_details=database_details, vumark_details=shared_vumark_details, From a762e69157e827a212e40e85fb7b8b60575f677d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 07:08:09 +0000 Subject: [PATCH 2248/2467] Remove redundant VuMark None check for pyright --- admin/create_secrets_files.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 08f8976dd..78097f445 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -153,10 +153,6 @@ def main() -> None: ) vumark_driver.quit() - if shared_vumark_details is None: - msg = "Failed to create shared VuMark database details." - raise RuntimeError(msg) - driver: WebDriver | None = None while files_to_create: if driver is None: From ee59318ad5a4f99e46a14cf9e61b8acb8ecfef60 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 07:19:34 +0000 Subject: [PATCH 2249/2467] Skip VuMark provisioning when no secrets files are missing --- admin/create_secrets_files.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 78097f445..0571cbe98 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -133,6 +133,10 @@ def main() -> None: for i in range(num_databases) ] files_to_create = [file for file in required_files if not file.exists()] + if not files_to_create: + sys.stdout.write("No secrets files need to be created.\n") + return + shared_vumark_details: VuMarkDatabaseDict | None = None while shared_vumark_details is None: From a6b5d038fce90a9fa4b6e638b372ebc98770764a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 07:35:11 +0000 Subject: [PATCH 2250/2467] Use tenacity retries for secret file creation --- admin/create_secrets_files.py | 137 ++++++++++++++++++++++------------ 1 file changed, 88 insertions(+), 49 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 5e6cc43c5..1092b27a3 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -13,6 +13,12 @@ import vws_web_tools from dotenv import load_dotenv from selenium.common.exceptions import TimeoutException +from tenacity import ( + retry, + retry_if_exception_type, + stop_after_attempt, + wait_exponential, +) if TYPE_CHECKING: from selenium.webdriver.remote.webdriver import WebDriver @@ -25,10 +31,10 @@ def _create_and_get_database_details( password: str, license_name: str, database_name: str, -) -> "DatabaseDict | None": +) -> "DatabaseDict": """Create a cloud database and get its details. - Returns database details or None if a timeout occurs. + Returns database details. """ vws_web_tools.log_in( driver=driver, @@ -36,11 +42,7 @@ def _create_and_get_database_details( password=password, ) vws_web_tools.wait_for_logged_in(driver=driver) - try: - vws_web_tools.create_license(driver=driver, license_name=license_name) - except TimeoutException: - sys.stderr.write("Timed out waiting for license creation\n") - return None + vws_web_tools.create_license(driver=driver, license_name=license_name) vws_web_tools.create_cloud_database( driver=driver, @@ -48,43 +50,71 @@ def _create_and_get_database_details( license_name=license_name, ) - try: - return vws_web_tools.get_database_details( - driver=driver, - database_name=database_name, - ) - except TimeoutException: - sys.stderr.write("Timed out waiting for database to be created\n") - return None + return vws_web_tools.get_database_details( + driver=driver, + database_name=database_name, + ) def _create_and_get_vumark_details( driver: "WebDriver", vumark_database_name: str, -) -> "VuMarkDatabaseDict | None": +) -> "VuMarkDatabaseDict": """Create a VuMark database and get its details. - Returns VuMark database details or None if a timeout occurs. + Returns VuMark database details. """ - try: - vws_web_tools.create_vumark_database( - driver=driver, - database_name=vumark_database_name, - ) - except TimeoutException: - sys.stderr.write("Timed out waiting for VuMark database creation\n") - return None - - try: - return vws_web_tools.get_vumark_database_details( - driver=driver, - database_name=vumark_database_name, - ) - except TimeoutException: - sys.stderr.write( - "Timed out waiting for VuMark database to be created\n" - ) - return None + vws_web_tools.create_vumark_database( + driver=driver, + database_name=vumark_database_name, + ) + + return vws_web_tools.get_vumark_database_details( + driver=driver, + database_name=vumark_database_name, + ) + + +@retry( + retry=retry_if_exception_type(TimeoutException), + stop=stop_after_attempt(3), + wait=wait_exponential(multiplier=2, min=5, max=30), + reraise=True, +) +def _create_and_get_database_details_with_retries( + driver: "WebDriver", + email_address: str, + password: str, + license_name: str, + database_name: str, +) -> "DatabaseDict": + """Create a cloud database and return details with retries on + timeout. + """ + return _create_and_get_database_details( + driver=driver, + email_address=email_address, + password=password, + license_name=license_name, + database_name=database_name, + ) + + +@retry( + retry=retry_if_exception_type(TimeoutException), + stop=stop_after_attempt(3), + wait=wait_exponential(multiplier=2, min=5, max=30), + reraise=True, +) +def _create_and_get_vumark_details_with_retries( + driver: "WebDriver", + vumark_database_name: str, +) -> "VuMarkDatabaseDict": + """Create a VuMark database and return details with retries on timeout.""" + return _create_and_get_vumark_details( + driver=driver, + vumark_database_name=vumark_database_name, + ) def _generate_secrets_file_content( @@ -147,23 +177,32 @@ def main() -> None: database_name = f"my-database-{time}" vumark_database_name = f"my-vumark-database-{time}" - database_details = _create_and_get_database_details( - driver=driver, - email_address=email_address, - password=password, - license_name=license_name, - database_name=database_name, - ) - if database_details is None: + try: + database_details = _create_and_get_database_details_with_retries( + driver=driver, + email_address=email_address, + password=password, + license_name=license_name, + database_name=database_name, + ) + except TimeoutException: + sys.stderr.write( + "Timed out waiting for license/database creation " + "after retries\n" + ) driver.quit() driver = None continue - vumark_details = _create_and_get_vumark_details( - driver=driver, - vumark_database_name=vumark_database_name, - ) - if vumark_details is None: + try: + vumark_details = _create_and_get_vumark_details_with_retries( + driver=driver, + vumark_database_name=vumark_database_name, + ) + except TimeoutException: + sys.stderr.write( + "Timed out waiting for VuMark creation after retries\n" + ) driver.quit() driver = None continue From 3de872f6d49ee7cbc6be66d60ff4a4eec42be5f2 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 07:36:59 +0000 Subject: [PATCH 2251/2467] Fix tenacity calls for strict mypy kwargs --- admin/create_secrets_files.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 1092b27a3..b077f6a91 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -76,8 +76,8 @@ def _create_and_get_vumark_details( @retry( - retry=retry_if_exception_type(TimeoutException), - stop=stop_after_attempt(3), + retry=retry_if_exception_type(exception_types=TimeoutException), + stop=stop_after_attempt(max_attempt_number=3), wait=wait_exponential(multiplier=2, min=5, max=30), reraise=True, ) @@ -101,8 +101,8 @@ def _create_and_get_database_details_with_retries( @retry( - retry=retry_if_exception_type(TimeoutException), - stop=stop_after_attempt(3), + retry=retry_if_exception_type(exception_types=TimeoutException), + stop=stop_after_attempt(max_attempt_number=3), wait=wait_exponential(multiplier=2, min=5, max=30), reraise=True, ) From 1dd41f60ebcd067d80e78fd6e42e51ba59c5261e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 07:40:50 +0000 Subject: [PATCH 2252/2467] Simplify VuMark retry function usage --- admin/create_secrets_files.py | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index b077f6a91..50119f48f 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -56,6 +56,12 @@ def _create_and_get_database_details( ) +@retry( + retry=retry_if_exception_type(exception_types=TimeoutException), + stop=stop_after_attempt(max_attempt_number=3), + wait=wait_exponential(multiplier=2, min=5, max=30), + reraise=True, +) def _create_and_get_vumark_details( driver: "WebDriver", vumark_database_name: str, @@ -100,23 +106,6 @@ def _create_and_get_database_details_with_retries( ) -@retry( - retry=retry_if_exception_type(exception_types=TimeoutException), - stop=stop_after_attempt(max_attempt_number=3), - wait=wait_exponential(multiplier=2, min=5, max=30), - reraise=True, -) -def _create_and_get_vumark_details_with_retries( - driver: "WebDriver", - vumark_database_name: str, -) -> "VuMarkDatabaseDict": - """Create a VuMark database and return details with retries on timeout.""" - return _create_and_get_vumark_details( - driver=driver, - vumark_database_name=vumark_database_name, - ) - - def _generate_secrets_file_content( database_details: "DatabaseDict", vumark_details: "VuMarkDatabaseDict", @@ -195,7 +184,7 @@ def main() -> None: continue try: - vumark_details = _create_and_get_vumark_details_with_retries( + vumark_details = _create_and_get_vumark_details( driver=driver, vumark_database_name=vumark_database_name, ) From 8e9ddd0307737d439df018ad93f01aa34dc2a526 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 07:46:34 +0000 Subject: [PATCH 2253/2467] Retry only detail fetches to avoid duplicate creates --- admin/create_secrets_files.py | 50 ++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 50119f48f..76840f34f 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -25,6 +25,23 @@ from vws_web_tools import DatabaseDict, VuMarkDatabaseDict +@retry( + retry=retry_if_exception_type(exception_types=TimeoutException), + stop=stop_after_attempt(max_attempt_number=3), + wait=wait_exponential(multiplier=2, min=5, max=30), + reraise=True, +) +def _get_database_details_with_retries( + driver: "WebDriver", + database_name: str, +) -> "DatabaseDict": + """Get cloud database details with retries on timeout.""" + return vws_web_tools.get_database_details( + driver=driver, + database_name=database_name, + ) + + def _create_and_get_database_details( driver: "WebDriver", email_address: str, @@ -50,18 +67,12 @@ def _create_and_get_database_details( license_name=license_name, ) - return vws_web_tools.get_database_details( + return _get_database_details_with_retries( driver=driver, database_name=database_name, ) -@retry( - retry=retry_if_exception_type(exception_types=TimeoutException), - stop=stop_after_attempt(max_attempt_number=3), - wait=wait_exponential(multiplier=2, min=5, max=30), - reraise=True, -) def _create_and_get_vumark_details( driver: "WebDriver", vumark_database_name: str, @@ -75,7 +86,7 @@ def _create_and_get_vumark_details( database_name=vumark_database_name, ) - return vws_web_tools.get_vumark_database_details( + return _get_vumark_details_with_retries( driver=driver, database_name=vumark_database_name, ) @@ -87,21 +98,13 @@ def _create_and_get_vumark_details( wait=wait_exponential(multiplier=2, min=5, max=30), reraise=True, ) -def _create_and_get_database_details_with_retries( +def _get_vumark_details_with_retries( driver: "WebDriver", - email_address: str, - password: str, - license_name: str, database_name: str, -) -> "DatabaseDict": - """Create a cloud database and return details with retries on - timeout. - """ - return _create_and_get_database_details( +) -> "VuMarkDatabaseDict": + """Get VuMark database details with retries on timeout.""" + return vws_web_tools.get_vumark_database_details( driver=driver, - email_address=email_address, - password=password, - license_name=license_name, database_name=database_name, ) @@ -167,7 +170,7 @@ def main() -> None: vumark_database_name = f"my-vumark-database-{time}" try: - database_details = _create_and_get_database_details_with_retries( + database_details = _create_and_get_database_details( driver=driver, email_address=email_address, password=password, @@ -176,8 +179,7 @@ def main() -> None: ) except TimeoutException: sys.stderr.write( - "Timed out waiting for license/database creation " - "after retries\n" + "Timed out waiting for database setup/details after retries\n" ) driver.quit() driver = None @@ -190,7 +192,7 @@ def main() -> None: ) except TimeoutException: sys.stderr.write( - "Timed out waiting for VuMark creation after retries\n" + "Timed out waiting for VuMark setup/details after retries\n" ) driver.quit() driver = None From 7604f0fd1b5abd4f01da67bb2e41bbccabf6b18e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 07:55:02 +0000 Subject: [PATCH 2254/2467] Reuse one retry callable inline --- admin/create_secrets_files.py | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 76840f34f..48a86c636 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -25,21 +25,12 @@ from vws_web_tools import DatabaseDict, VuMarkDatabaseDict -@retry( +RETRY_ON_TIMEOUT = retry( retry=retry_if_exception_type(exception_types=TimeoutException), stop=stop_after_attempt(max_attempt_number=3), wait=wait_exponential(multiplier=2, min=5, max=30), reraise=True, ) -def _get_database_details_with_retries( - driver: "WebDriver", - database_name: str, -) -> "DatabaseDict": - """Get cloud database details with retries on timeout.""" - return vws_web_tools.get_database_details( - driver=driver, - database_name=database_name, - ) def _create_and_get_database_details( @@ -67,7 +58,7 @@ def _create_and_get_database_details( license_name=license_name, ) - return _get_database_details_with_retries( + return RETRY_ON_TIMEOUT(vws_web_tools.get_database_details)( driver=driver, database_name=database_name, ) @@ -86,29 +77,12 @@ def _create_and_get_vumark_details( database_name=vumark_database_name, ) - return _get_vumark_details_with_retries( + return RETRY_ON_TIMEOUT(vws_web_tools.get_vumark_database_details)( driver=driver, database_name=vumark_database_name, ) -@retry( - retry=retry_if_exception_type(exception_types=TimeoutException), - stop=stop_after_attempt(max_attempt_number=3), - wait=wait_exponential(multiplier=2, min=5, max=30), - reraise=True, -) -def _get_vumark_details_with_retries( - driver: "WebDriver", - database_name: str, -) -> "VuMarkDatabaseDict": - """Get VuMark database details with retries on timeout.""" - return vws_web_tools.get_vumark_database_details( - driver=driver, - database_name=database_name, - ) - - def _generate_secrets_file_content( database_details: "DatabaseDict", vumark_details: "VuMarkDatabaseDict", From 708204df1dc2f60e1edb595fe0d320c2248965ed Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 08:14:46 +0000 Subject: [PATCH 2255/2467] Pass env details from main to helpers --- admin/create_secrets_files.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 48a86c636..2f70a3bb8 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -86,6 +86,7 @@ def _create_and_get_vumark_details( def _generate_secrets_file_content( database_details: "DatabaseDict", vumark_details: "VuMarkDatabaseDict", + inactive_database_details: dict[str, str], ) -> str: """Generate the content of a secrets file.""" return textwrap.dedent( @@ -96,11 +97,11 @@ def _generate_secrets_file_content( VUFORIA_CLIENT_ACCESS_KEY={database_details["client_access_key"]} VUFORIA_CLIENT_SECRET_KEY={database_details["client_secret_key"]} - INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME={os.environ["INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME"]} - INACTIVE_VUFORIA_SERVER_ACCESS_KEY={os.environ["INACTIVE_VUFORIA_SERVER_ACCESS_KEY"]} - INACTIVE_VUFORIA_SERVER_SECRET_KEY={os.environ["INACTIVE_VUFORIA_SERVER_SECRET_KEY"]} - INACTIVE_VUFORIA_CLIENT_ACCESS_KEY={os.environ["INACTIVE_VUFORIA_CLIENT_ACCESS_KEY"]} - INACTIVE_VUFORIA_CLIENT_SECRET_KEY={os.environ["INACTIVE_VUFORIA_CLIENT_SECRET_KEY"]} + INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME={inactive_database_details["INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME"]} + INACTIVE_VUFORIA_SERVER_ACCESS_KEY={inactive_database_details["INACTIVE_VUFORIA_SERVER_ACCESS_KEY"]} + INACTIVE_VUFORIA_SERVER_SECRET_KEY={inactive_database_details["INACTIVE_VUFORIA_SERVER_SECRET_KEY"]} + INACTIVE_VUFORIA_CLIENT_ACCESS_KEY={inactive_database_details["INACTIVE_VUFORIA_CLIENT_ACCESS_KEY"]} + INACTIVE_VUFORIA_CLIENT_SECRET_KEY={inactive_database_details["INACTIVE_VUFORIA_CLIENT_SECRET_KEY"]} VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME={vumark_details["database_name"]} VUMARK_VUFORIA_SERVER_ACCESS_KEY={vumark_details["server_access_key"]} @@ -121,6 +122,23 @@ def main() -> None: msg = f"Existing secrets file does not exist: {existing_secrets_file}" raise FileNotFoundError(msg) load_dotenv(dotenv_path=existing_secrets_file) + inactive_database_details = { + "INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME": os.environ[ + "INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME" + ], + "INACTIVE_VUFORIA_SERVER_ACCESS_KEY": os.environ[ + "INACTIVE_VUFORIA_SERVER_ACCESS_KEY" + ], + "INACTIVE_VUFORIA_SERVER_SECRET_KEY": os.environ[ + "INACTIVE_VUFORIA_SERVER_SECRET_KEY" + ], + "INACTIVE_VUFORIA_CLIENT_ACCESS_KEY": os.environ[ + "INACTIVE_VUFORIA_CLIENT_ACCESS_KEY" + ], + "INACTIVE_VUFORIA_CLIENT_SECRET_KEY": os.environ[ + "INACTIVE_VUFORIA_CLIENT_SECRET_KEY" + ], + } new_secrets_dir.mkdir(exist_ok=True) num_databases = 100 @@ -178,6 +196,7 @@ def main() -> None: file_contents = _generate_secrets_file_content( database_details=database_details, vumark_details=vumark_details, + inactive_database_details=inactive_database_details, ) file.write_text(data=file_contents) sys.stdout.write(f"Created database {file.name}\n") From d473c783d157216c9746174e4b6781bc1b480169 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 08:25:23 +0000 Subject: [PATCH 2256/2467] Use DatabaseDict for inactive details --- admin/create_secrets_files.py | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 2f70a3bb8..a5c68238a 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -86,7 +86,7 @@ def _create_and_get_vumark_details( def _generate_secrets_file_content( database_details: "DatabaseDict", vumark_details: "VuMarkDatabaseDict", - inactive_database_details: dict[str, str], + inactive_database_details: "DatabaseDict", ) -> str: """Generate the content of a secrets file.""" return textwrap.dedent( @@ -97,11 +97,11 @@ def _generate_secrets_file_content( VUFORIA_CLIENT_ACCESS_KEY={database_details["client_access_key"]} VUFORIA_CLIENT_SECRET_KEY={database_details["client_secret_key"]} - INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME={inactive_database_details["INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME"]} - INACTIVE_VUFORIA_SERVER_ACCESS_KEY={inactive_database_details["INACTIVE_VUFORIA_SERVER_ACCESS_KEY"]} - INACTIVE_VUFORIA_SERVER_SECRET_KEY={inactive_database_details["INACTIVE_VUFORIA_SERVER_SECRET_KEY"]} - INACTIVE_VUFORIA_CLIENT_ACCESS_KEY={inactive_database_details["INACTIVE_VUFORIA_CLIENT_ACCESS_KEY"]} - INACTIVE_VUFORIA_CLIENT_SECRET_KEY={inactive_database_details["INACTIVE_VUFORIA_CLIENT_SECRET_KEY"]} + INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME={inactive_database_details["database_name"]} + INACTIVE_VUFORIA_SERVER_ACCESS_KEY={inactive_database_details["server_access_key"]} + INACTIVE_VUFORIA_SERVER_SECRET_KEY={inactive_database_details["server_secret_key"]} + INACTIVE_VUFORIA_CLIENT_ACCESS_KEY={inactive_database_details["client_access_key"]} + INACTIVE_VUFORIA_CLIENT_SECRET_KEY={inactive_database_details["client_secret_key"]} VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME={vumark_details["database_name"]} VUMARK_VUFORIA_SERVER_ACCESS_KEY={vumark_details["server_access_key"]} @@ -122,22 +122,14 @@ def main() -> None: msg = f"Existing secrets file does not exist: {existing_secrets_file}" raise FileNotFoundError(msg) load_dotenv(dotenv_path=existing_secrets_file) - inactive_database_details = { - "INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME": os.environ[ + inactive_database_details: DatabaseDict = { + "database_name": os.environ[ "INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME" ], - "INACTIVE_VUFORIA_SERVER_ACCESS_KEY": os.environ[ - "INACTIVE_VUFORIA_SERVER_ACCESS_KEY" - ], - "INACTIVE_VUFORIA_SERVER_SECRET_KEY": os.environ[ - "INACTIVE_VUFORIA_SERVER_SECRET_KEY" - ], - "INACTIVE_VUFORIA_CLIENT_ACCESS_KEY": os.environ[ - "INACTIVE_VUFORIA_CLIENT_ACCESS_KEY" - ], - "INACTIVE_VUFORIA_CLIENT_SECRET_KEY": os.environ[ - "INACTIVE_VUFORIA_CLIENT_SECRET_KEY" - ], + "server_access_key": os.environ["INACTIVE_VUFORIA_SERVER_ACCESS_KEY"], + "server_secret_key": os.environ["INACTIVE_VUFORIA_SERVER_SECRET_KEY"], + "client_access_key": os.environ["INACTIVE_VUFORIA_CLIENT_ACCESS_KEY"], + "client_secret_key": os.environ["INACTIVE_VUFORIA_CLIENT_SECRET_KEY"], } new_secrets_dir.mkdir(exist_ok=True) From 0826fc00957be76048b1fb7c4262c222ccdde12d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 08:37:38 +0000 Subject: [PATCH 2257/2467] Use upload_vumark_template for VuMark target ID --- admin/create_secrets_files.py | 91 ++++++++++++++++++++++++++++++++--- pyproject.toml | 2 +- vuforia_secrets.env.example | 1 + 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index a5c68238a..30e1b7d67 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -6,6 +6,7 @@ import datetime import os import sys +import tempfile import textwrap from pathlib import Path from typing import TYPE_CHECKING @@ -32,6 +33,15 @@ reraise=True, ) +VUMARK_TEMPLATE_SVG = textwrap.dedent( + text="""\ + + + + + """, +) + def _create_and_get_database_details( driver: "WebDriver", @@ -87,6 +97,7 @@ def _generate_secrets_file_content( database_details: "DatabaseDict", vumark_details: "VuMarkDatabaseDict", inactive_database_details: "DatabaseDict", + vumark_target_id: str, ) -> str: """Generate the content of a secrets file.""" return textwrap.dedent( @@ -104,12 +115,65 @@ def _generate_secrets_file_content( INACTIVE_VUFORIA_CLIENT_SECRET_KEY={inactive_database_details["client_secret_key"]} VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME={vumark_details["database_name"]} + VUMARK_VUFORIA_TARGET_ID={vumark_target_id} VUMARK_VUFORIA_SERVER_ACCESS_KEY={vumark_details["server_access_key"]} VUMARK_VUFORIA_SERVER_SECRET_KEY={vumark_details["server_secret_key"]} """, ) +def _create_and_get_vumark_target_id( + driver: "WebDriver", + vumark_database_name: str, + vumark_template_name: str, +) -> str: + """Upload a VuMark template and get its target ID.""" + with tempfile.TemporaryDirectory() as temporary_directory: + svg_file_path = Path(temporary_directory) / "template.svg" + svg_file_path.write_text( + data=VUMARK_TEMPLATE_SVG, + encoding="utf-8", + ) + upload_result = RETRY_ON_TIMEOUT(vws_web_tools.upload_vumark_template)( + driver=driver, + database_name=vumark_database_name, + svg_file_path=svg_file_path, + template_name=vumark_template_name, + width=100.0, + ) + + if isinstance(upload_result, str): + return upload_result + + if isinstance(upload_result, dict): + target_id = upload_result.get("target_id") + if isinstance(target_id, str): + return target_id + + target_id = getattr(upload_result, "target_id", None) + if isinstance(target_id, str): + return target_id + + msg = ( + "Expected `upload_vumark_template` to return a target ID. " + "Upgrade `vws-web-tools` to a version that returns one." + ) + raise RuntimeError(msg) + + +def _create_vuforia_resource_names() -> tuple[str, str, str, str]: + """Create names for Vuforia resources.""" + time = datetime.datetime.now(tz=datetime.UTC).strftime( + format="%Y-%m-%d-%H-%M-%S", + ) + return ( + f"my-license-{time}", + f"my-database-{time}", + f"my-vumark-database-{time}", + f"my-vumark-template-{time}", + ) + + def main() -> None: """Create secrets files.""" email_address = os.environ["VWS_EMAIL_ADDRESS"] @@ -146,12 +210,12 @@ def main() -> None: driver = vws_web_tools.create_chrome_driver() file = files_to_create[-1] sys.stdout.write(f"Creating database {file.name}\n") - time = datetime.datetime.now(tz=datetime.UTC).strftime( - format="%Y-%m-%d-%H-%M-%S", - ) - license_name = f"my-license-{time}" - database_name = f"my-database-{time}" - vumark_database_name = f"my-vumark-database-{time}" + ( + license_name, + database_name, + vumark_database_name, + vumark_template_name, + ) = _create_vuforia_resource_names() try: database_details = _create_and_get_database_details( @@ -182,6 +246,20 @@ def main() -> None: driver = None continue + try: + vumark_target_id = _create_and_get_vumark_target_id( + driver=driver, + vumark_database_name=vumark_database_name, + vumark_template_name=vumark_template_name, + ) + except TimeoutException: + sys.stderr.write( + "Timed out waiting for VuMark template upload after retries\n" + ) + driver.quit() + driver = None + continue + driver.quit() driver = None @@ -189,6 +267,7 @@ def main() -> None: database_details=database_details, vumark_details=vumark_details, inactive_database_details=inactive_database_details, + vumark_target_id=vumark_target_id, ) file.write_text(data=file_contents) sys.stdout.write(f"Created database {file.name}\n") diff --git a/pyproject.toml b/pyproject.toml index dead7dba2..9948bb41a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,7 +104,7 @@ optional-dependencies.dev = [ "vulture==2.14", "vws-python==2026.2.15", "vws-test-fixtures==2023.3.5", - "vws-web-tools==2026.2.16.1", + "vws-web-tools==2026.2.17", "yamlfix==1.19.1", "zizmor==1.22.0", ] diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index 5e843a117..ea7273354 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -15,6 +15,7 @@ INACTIVE_VUFORIA_CLIENT_ACCESS_KEY= INACTIVE_VUFORIA_CLIENT_SECRET_KEY= VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= +VUMARK_VUFORIA_TARGET_ID= VUMARK_VUFORIA_SERVER_ACCESS_KEY= VUMARK_VUFORIA_SERVER_SECRET_KEY= From 72292913f84686e19f9e46abb4675208ab3f8bc3 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 09:02:53 +0000 Subject: [PATCH 2258/2467] Use checked-in VuMark SVG template --- admin/create_secrets_files.py | 30 ++++++++---------------------- admin/vumark_template.svg | 4 ++++ 2 files changed, 12 insertions(+), 22 deletions(-) create mode 100644 admin/vumark_template.svg diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 30e1b7d67..dbb4b980e 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -6,7 +6,6 @@ import datetime import os import sys -import tempfile import textwrap from pathlib import Path from typing import TYPE_CHECKING @@ -33,14 +32,7 @@ reraise=True, ) -VUMARK_TEMPLATE_SVG = textwrap.dedent( - text="""\ - - - - - """, -) +VUMARK_TEMPLATE_SVG_FILE_PATH = Path(__file__).with_name("vumark_template.svg") def _create_and_get_database_details( @@ -128,19 +120,13 @@ def _create_and_get_vumark_target_id( vumark_template_name: str, ) -> str: """Upload a VuMark template and get its target ID.""" - with tempfile.TemporaryDirectory() as temporary_directory: - svg_file_path = Path(temporary_directory) / "template.svg" - svg_file_path.write_text( - data=VUMARK_TEMPLATE_SVG, - encoding="utf-8", - ) - upload_result = RETRY_ON_TIMEOUT(vws_web_tools.upload_vumark_template)( - driver=driver, - database_name=vumark_database_name, - svg_file_path=svg_file_path, - template_name=vumark_template_name, - width=100.0, - ) + upload_result = RETRY_ON_TIMEOUT(vws_web_tools.upload_vumark_template)( + driver=driver, + database_name=vumark_database_name, + svg_file_path=VUMARK_TEMPLATE_SVG_FILE_PATH, + template_name=vumark_template_name, + width=100.0, + ) if isinstance(upload_result, str): return upload_result diff --git a/admin/vumark_template.svg b/admin/vumark_template.svg new file mode 100644 index 000000000..3d3c3d89e --- /dev/null +++ b/admin/vumark_template.svg @@ -0,0 +1,4 @@ + + + + From 222ad7209ed759b695ce7f1f22c695043b851db8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 10:01:44 +0000 Subject: [PATCH 2259/2467] Remove local retry wrapper and simplify VuMark upload return handling --- admin/create_secrets_files.py | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index dbb4b980e..06db5dfc5 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -13,25 +13,12 @@ import vws_web_tools from dotenv import load_dotenv from selenium.common.exceptions import TimeoutException -from tenacity import ( - retry, - retry_if_exception_type, - stop_after_attempt, - wait_exponential, -) if TYPE_CHECKING: from selenium.webdriver.remote.webdriver import WebDriver from vws_web_tools import DatabaseDict, VuMarkDatabaseDict -RETRY_ON_TIMEOUT = retry( - retry=retry_if_exception_type(exception_types=TimeoutException), - stop=stop_after_attempt(max_attempt_number=3), - wait=wait_exponential(multiplier=2, min=5, max=30), - reraise=True, -) - VUMARK_TEMPLATE_SVG_FILE_PATH = Path(__file__).with_name("vumark_template.svg") @@ -60,7 +47,7 @@ def _create_and_get_database_details( license_name=license_name, ) - return RETRY_ON_TIMEOUT(vws_web_tools.get_database_details)( + return vws_web_tools.get_database_details( driver=driver, database_name=database_name, ) @@ -79,7 +66,7 @@ def _create_and_get_vumark_details( database_name=vumark_database_name, ) - return RETRY_ON_TIMEOUT(vws_web_tools.get_vumark_database_details)( + return vws_web_tools.get_vumark_database_details( driver=driver, database_name=vumark_database_name, ) @@ -120,30 +107,17 @@ def _create_and_get_vumark_target_id( vumark_template_name: str, ) -> str: """Upload a VuMark template and get its target ID.""" - upload_result = RETRY_ON_TIMEOUT(vws_web_tools.upload_vumark_template)( + target_id = vws_web_tools.upload_vumark_template( driver=driver, database_name=vumark_database_name, svg_file_path=VUMARK_TEMPLATE_SVG_FILE_PATH, template_name=vumark_template_name, width=100.0, ) - - if isinstance(upload_result, str): - return upload_result - - if isinstance(upload_result, dict): - target_id = upload_result.get("target_id") - if isinstance(target_id, str): - return target_id - - target_id = getattr(upload_result, "target_id", None) if isinstance(target_id, str): return target_id - msg = ( - "Expected `upload_vumark_template` to return a target ID. " - "Upgrade `vws-web-tools` to a version that returns one." - ) + msg = "Expected `upload_vumark_template` to return a string target ID." raise RuntimeError(msg) From a2107881489236c0608b64459984c7546166edcd Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 10:02:48 +0000 Subject: [PATCH 2260/2467] Fix mypy call style for Path.with_name --- admin/create_secrets_files.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 06db5dfc5..4d9210b37 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -19,7 +19,9 @@ from vws_web_tools import DatabaseDict, VuMarkDatabaseDict -VUMARK_TEMPLATE_SVG_FILE_PATH = Path(__file__).with_name("vumark_template.svg") +VUMARK_TEMPLATE_SVG_FILE_PATH = Path(__file__).with_name( + name="vumark_template.svg", +) def _create_and_get_database_details( From 097b16262d0c39791c7d1686f65e30acfd8414f2 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 10:04:17 +0000 Subject: [PATCH 2261/2467] Align VuMark target ID helper with strict static typing --- admin/create_secrets_files.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 4d9210b37..396c513e0 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -8,7 +8,7 @@ import sys import textwrap from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast import vws_web_tools from dotenv import load_dotenv @@ -109,18 +109,16 @@ def _create_and_get_vumark_target_id( vumark_template_name: str, ) -> str: """Upload a VuMark template and get its target ID.""" - target_id = vws_web_tools.upload_vumark_template( - driver=driver, - database_name=vumark_database_name, - svg_file_path=VUMARK_TEMPLATE_SVG_FILE_PATH, - template_name=vumark_template_name, - width=100.0, + return cast( + "str", + vws_web_tools.upload_vumark_template( + driver=driver, + database_name=vumark_database_name, + svg_file_path=VUMARK_TEMPLATE_SVG_FILE_PATH, + template_name=vumark_template_name, + width=100.0, + ), ) - if isinstance(target_id, str): - return target_id - - msg = "Expected `upload_vumark_template` to return a string target ID." - raise RuntimeError(msg) def _create_vuforia_resource_names() -> tuple[str, str, str, str]: From b181c991939537f705e465dac73a33910deda3df Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 10:29:19 +0000 Subject: [PATCH 2262/2467] Bump vws-web-tools and remove local retry --- admin/create_secrets_files.py | 18 ++---------------- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index a5c68238a..5582a4616 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -13,26 +13,12 @@ import vws_web_tools from dotenv import load_dotenv from selenium.common.exceptions import TimeoutException -from tenacity import ( - retry, - retry_if_exception_type, - stop_after_attempt, - wait_exponential, -) if TYPE_CHECKING: from selenium.webdriver.remote.webdriver import WebDriver from vws_web_tools import DatabaseDict, VuMarkDatabaseDict -RETRY_ON_TIMEOUT = retry( - retry=retry_if_exception_type(exception_types=TimeoutException), - stop=stop_after_attempt(max_attempt_number=3), - wait=wait_exponential(multiplier=2, min=5, max=30), - reraise=True, -) - - def _create_and_get_database_details( driver: "WebDriver", email_address: str, @@ -58,7 +44,7 @@ def _create_and_get_database_details( license_name=license_name, ) - return RETRY_ON_TIMEOUT(vws_web_tools.get_database_details)( + return vws_web_tools.get_database_details( driver=driver, database_name=database_name, ) @@ -77,7 +63,7 @@ def _create_and_get_vumark_details( database_name=vumark_database_name, ) - return RETRY_ON_TIMEOUT(vws_web_tools.get_vumark_database_details)( + return vws_web_tools.get_vumark_database_details( driver=driver, database_name=vumark_database_name, ) diff --git a/pyproject.toml b/pyproject.toml index 533d913be..d0c04be1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,7 +104,7 @@ optional-dependencies.dev = [ "vulture==2.14", "vws-python==2026.2.15", "vws-test-fixtures==2023.3.5", - "vws-web-tools==2026.2.16.1", + "vws-web-tools==2026.2.17", "yamlfix==1.19.1", "zizmor==1.22.0", ] From 206f3796344b61033f9182d9e12a6a829c3b91ee Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 17:56:19 +0000 Subject: [PATCH 2263/2467] Use get_vumark_target_id and pin vws-web-tools 2026.2.17.1 --- admin/create_secrets_files.py | 22 ++++++++++++---------- pyproject.toml | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 396c513e0..13d2310d0 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -8,7 +8,7 @@ import sys import textwrap from pathlib import Path -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING import vws_web_tools from dotenv import load_dotenv @@ -109,15 +109,17 @@ def _create_and_get_vumark_target_id( vumark_template_name: str, ) -> str: """Upload a VuMark template and get its target ID.""" - return cast( - "str", - vws_web_tools.upload_vumark_template( - driver=driver, - database_name=vumark_database_name, - svg_file_path=VUMARK_TEMPLATE_SVG_FILE_PATH, - template_name=vumark_template_name, - width=100.0, - ), + vws_web_tools.upload_vumark_template( + driver=driver, + database_name=vumark_database_name, + svg_file_path=VUMARK_TEMPLATE_SVG_FILE_PATH, + template_name=vumark_template_name, + width=100.0, + ) + return vws_web_tools.get_vumark_target_id( + driver=driver, + database_name=vumark_database_name, + target_name=vumark_template_name, ) diff --git a/pyproject.toml b/pyproject.toml index d0c04be1a..83b732734 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,7 +104,7 @@ optional-dependencies.dev = [ "vulture==2.14", "vws-python==2026.2.15", "vws-test-fixtures==2023.3.5", - "vws-web-tools==2026.2.17", + "vws-web-tools==2026.2.17.1", "yamlfix==1.19.1", "zizmor==1.22.0", ] From f706b1a0fe31a0735ebc8e4013dc17d7dcedcf42 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 18:43:34 +0000 Subject: [PATCH 2264/2467] Reset VuMark secrets files to origin/main --- admin/create_secrets_files.py | 91 ++++++++++++----------------------- vuforia_secrets.env.example | 8 +-- 2 files changed, 36 insertions(+), 63 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 5f3eef287..13d2310d0 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -136,50 +136,6 @@ def _create_vuforia_resource_names() -> tuple[str, str, str, str]: ) -def _create_shared_vumark_resources( - email_address: str, - password: str, -) -> tuple["VuMarkDatabaseDict", str]: - """Create shared VuMark resources used by all generated secrets - files. - """ - shared_vumark_details: VuMarkDatabaseDict | None = None - shared_vumark_target_id: str | None = None - - while shared_vumark_details is None or shared_vumark_target_id is None: - vumark_driver = vws_web_tools.create_chrome_driver() - ( - _license_name, - _database_name, - vumark_database_name, - vumark_template_name, - ) = _create_vuforia_resource_names() - try: - vws_web_tools.log_in( - driver=vumark_driver, - email_address=email_address, - password=password, - ) - vws_web_tools.wait_for_logged_in(driver=vumark_driver) - shared_vumark_details = _create_and_get_vumark_details( - driver=vumark_driver, - vumark_database_name=vumark_database_name, - ) - shared_vumark_target_id = _create_and_get_vumark_target_id( - driver=vumark_driver, - vumark_database_name=vumark_database_name, - vumark_template_name=vumark_template_name, - ) - except TimeoutException: - sys.stderr.write( - "Timed out waiting for shared VuMark setup/details after " - "retries\n" - ) - vumark_driver.quit() - - return shared_vumark_details, shared_vumark_target_id - - def main() -> None: """Create secrets files.""" email_address = os.environ["VWS_EMAIL_ADDRESS"] @@ -209,18 +165,8 @@ def main() -> None: for i in range(num_databases) ] files_to_create = [file for file in required_files if not file.exists()] - if not files_to_create: - sys.stdout.write("No secrets files need to be created.\n") - return - - shared_vumark_details, shared_vumark_target_id = ( - _create_shared_vumark_resources( - email_address=email_address, - password=password, - ) - ) - driver: WebDriver | None = None + while files_to_create: if driver is None: driver = vws_web_tools.create_chrome_driver() @@ -229,8 +175,8 @@ def main() -> None: ( license_name, database_name, - _vumark_database_name, - _vumark_template_name, + vumark_database_name, + vumark_template_name, ) = _create_vuforia_resource_names() try: @@ -249,14 +195,41 @@ def main() -> None: driver = None continue + try: + vumark_details = _create_and_get_vumark_details( + driver=driver, + vumark_database_name=vumark_database_name, + ) + except TimeoutException: + sys.stderr.write( + "Timed out waiting for VuMark setup/details after retries\n" + ) + driver.quit() + driver = None + continue + + try: + vumark_target_id = _create_and_get_vumark_target_id( + driver=driver, + vumark_database_name=vumark_database_name, + vumark_template_name=vumark_template_name, + ) + except TimeoutException: + sys.stderr.write( + "Timed out waiting for VuMark template upload after retries\n" + ) + driver.quit() + driver = None + continue + driver.quit() driver = None file_contents = _generate_secrets_file_content( database_details=database_details, - vumark_details=shared_vumark_details, + vumark_details=vumark_details, inactive_database_details=inactive_database_details, - vumark_target_id=shared_vumark_target_id, + vumark_target_id=vumark_target_id, ) file.write_text(data=file_contents) sys.stdout.write(f"Created database {file.name}\n") diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index 1920c860a..ea7273354 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -14,8 +14,8 @@ INACTIVE_VUFORIA_SERVER_SECRET_KEY= INACTIVE_VUFORIA_CLIENT_ACCESS_KEY= INACTIVE_VUFORIA_CLIENT_SECRET_KEY= -VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= - -VUMARK_VUFORIA_SERVER_ACCESS_KEY= -VUMARK_VUFORIA_SERVER_SECRET_KEY= +VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= VUMARK_VUFORIA_TARGET_ID= + +VUMARK_VUFORIA_SERVER_ACCESS_KEY= +VUMARK_VUFORIA_SERVER_SECRET_KEY= From 1527b62d6e38468aabca9df566e94edb1cbc4c60 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 18:55:02 +0000 Subject: [PATCH 2265/2467] Use upstream VuMark template SVG --- admin/vumark_template.svg | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/admin/vumark_template.svg b/admin/vumark_template.svg index 3d3c3d89e..4b97b6667 100644 --- a/admin/vumark_template.svg +++ b/admin/vumark_template.svg @@ -1,4 +1 @@ - - - - + From 7dcfeef45c05f03814cfa19f8ca0fbdf77f47ec0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 18:56:45 +0000 Subject: [PATCH 2266/2467] Add mock VuMark instance generation support and class-based test --- pyproject.toml | 1 + src/mock_vws/_flask_server/vws.py | 37 ++++++ .../_requests_mock_server/decorators.py | 2 +- .../mock_web_services_api.py | 44 ++++++- .../_services_validators/key_validators.py | 8 ++ .../_services_validators/target_validators.py | 2 + tests/mock_vws/fixtures/vuforia_backends.py | 10 +- tests/mock_vws/test_vumark_generation_api.py | 118 ++++++++++++------ 8 files changed, 183 insertions(+), 39 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 83b732734..620ab8d6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -448,6 +448,7 @@ ignore_names = [ exclude = [ ".venv" ] ignore_decorators = [ "@pytest.fixture", + "@route", # Flask "@*APP.route", "@*APP.after_request", diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index 00ae09a12..6d0d94e1d 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -44,6 +44,12 @@ _LOGGER = logging.getLogger(name=__name__) +_VUMARK_PNG = base64.b64decode( + s=( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8A" + "AwMCAO7Zl6kAAAAASUVORK5CYII=" + ), +) @beartype @@ -338,6 +344,37 @@ def delete_target(target_id: str) -> Response: ) +@VWS_FLASK_APP.route( + rule="/targets//instances", + methods=[HTTPMethod.POST], +) +@beartype +def generate_vumark_instance(target_id: str) -> Response: + """Generate a VuMark instance. + + Fake implementation of + https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#generate-instance + """ + # ``target_id`` is validated by request validators. + del target_id + date = email.utils.formatdate(timeval=None, localtime=False, usegmt=True) + headers = { + "Connection": "keep-alive", + "Content-Type": "image/png", + "server": "envoy", + "Date": date, + "x-envoy-upstream-service-time": "5", + "strict-transport-security": "max-age=31536000", + "x-aws-region": "us-east-2, us-west-2", + "x-content-type-options": "nosniff", + } + return Response( + status=HTTPStatus.OK, + response=_VUMARK_PNG, + headers=headers, + ) + + @VWS_FLASK_APP.route(rule="/summary", methods=[HTTPMethod.GET]) @beartype def database_summary() -> Response: diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index a613fb53f..e8832b726 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -26,7 +26,7 @@ from .mock_web_query_api import MockVuforiaWebQueryAPI from .mock_web_services_api import MockVuforiaWebServicesAPI -_ResponseType = tuple[int, Mapping[str, str], str] +_ResponseType = tuple[int, Mapping[str, str], str | bytes] _Callback = Callable[[PreparedRequest], _ResponseType] _STRUCTURAL_SIMILARITY_MATCHER = StructuralSimilarityMatcher() diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index ddee6ef03..13ee44ca5 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -34,11 +34,17 @@ from mock_vws.target_raters import TargetTrackingRater _TARGET_ID_PATTERN = "[A-Za-z0-9]+" +_VUMARK_PNG = base64.b64decode( + s=( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8A" + "AwMCAO7Zl6kAAAAASUVORK5CYII=" + ), +) _ROUTES: set[Route] = set() -_ResponseType = tuple[int, Mapping[str, str], str] +_ResponseType = tuple[int, Mapping[str, str], str | bytes] _P = ParamSpec("_P") @@ -287,6 +293,42 @@ def delete_target(self, request: PreparedRequest) -> _ResponseType: } return HTTPStatus.OK, headers, body_json + @route( + path_pattern=f"/targets/{_TARGET_ID_PATTERN}/instances", + http_methods={HTTPMethod.POST}, + ) + def generate_vumark_instance( + self, request: PreparedRequest + ) -> _ResponseType: + """Generate a VuMark instance.""" + try: + run_services_validators( + request_headers=request.headers, + request_body=_body_bytes(request=request), + request_method=request.method or "", + request_path=request.path_url, + databases=self._target_manager.databases, + ) + except ValidatorError as exc: + return exc.status_code, exc.headers, exc.response_text + + date = email.utils.formatdate( + timeval=None, + localtime=False, + usegmt=True, + ) + headers = { + "Connection": "keep-alive", + "Content-Type": "image/png", + "Date": date, + "server": "envoy", + "x-envoy-upstream-service-time": "5", + "strict-transport-security": "max-age=31536000", + "x-aws-region": "us-east-2, us-west-2", + "x-content-type-options": "nosniff", + } + return HTTPStatus.OK, headers, _VUMARK_PNG + @route(path_pattern="/summary", http_methods={HTTPMethod.GET}) def database_summary(self, request: PreparedRequest) -> _ResponseType: """Get a database summary report. diff --git a/src/mock_vws/_services_validators/key_validators.py b/src/mock_vws/_services_validators/key_validators.py index b07533fe0..cf4d32fa4 100644 --- a/src/mock_vws/_services_validators/key_validators.py +++ b/src/mock_vws/_services_validators/key_validators.py @@ -114,6 +114,13 @@ def validate_keys( }, ) + generate_instance = _Route( + path_pattern=f"/targets/{target_id_pattern}/instances", + http_methods={HTTPMethod.POST}, + mandatory_keys={"instance_id"}, + optional_keys=set(), + ) + target_summary = _Route( path_pattern=f"/summary/{target_id_pattern}", http_methods={HTTPMethod.GET}, @@ -129,6 +136,7 @@ def validate_keys( get_target, get_duplicates, update_target, + generate_instance, target_summary, ) diff --git a/src/mock_vws/_services_validators/target_validators.py b/src/mock_vws/_services_validators/target_validators.py index 4dbee04b1..58963b891 100644 --- a/src/mock_vws/_services_validators/target_validators.py +++ b/src/mock_vws/_services_validators/target_validators.py @@ -42,6 +42,8 @@ def validate_target_id_exists( return target_id = split_path[-1] + if split_path[-1] == "instances": + target_id = split_path[-2] database = get_database_matching_server_keys( request_headers=request_headers, request_body=request_body, diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 39013b5b9..154c7cb08 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -234,7 +234,7 @@ def fixture_verify_mock_vuforia( vuforia_database: VuforiaDatabase, inactive_database: VuforiaDatabase, monkeypatch: pytest.MonkeyPatch, -) -> Generator[None]: +) -> Generator[VuforiaBackend]: """Test functions which use this fixture are run multiple times. Once with the real Vuforia, and once with each mock. @@ -257,11 +257,17 @@ def fixture_verify_mock_vuforia( VuforiaBackend.DOCKER_IN_MEMORY: _enable_use_docker_in_memory, }[backend] - yield from enable_function( + backend_generator = enable_function( working_database=vuforia_database, inactive_database=inactive_database, monkeypatch=monkeypatch, ) + next(backend_generator) + try: + yield backend + finally: + with contextlib.suppress(StopIteration): + next(backend_generator) @pytest.fixture( diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 6c2004e1c..7cbe9aff1 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -1,53 +1,101 @@ """Tests for the VuMark generation web API.""" +import base64 +import io import json from http import HTTPMethod, HTTPStatus from uuid import uuid4 +import pytest import requests +from vws import VWS from vws_auth_tools import authorization_header, rfc_1123_date +from mock_vws.database import VuforiaDatabase from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase +from tests.mock_vws.fixtures.vuforia_backends import VuforiaBackend _VWS_HOST = "https://vws.vuforia.com" _PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" -def test_generate_instance_success( - vumark_vuforia_database: VuMarkVuforiaDatabase, -) -> None: - """A VuMark instance can be generated with valid template settings.""" - request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" - content_type = "application/json" - generated_instance_id = uuid4().hex - content = json.dumps(obj={"instance_id": generated_instance_id}).encode( - encoding="utf-8" - ) - date = rfc_1123_date() - authorization_string = authorization_header( - access_key=vumark_vuforia_database.server_access_key, - secret_key=vumark_vuforia_database.server_secret_key, - method=HTTPMethod.POST, - content=content, - content_type=content_type, - date=date, - request_path=request_path, - ) +@pytest.mark.usefixtures("verify_mock_vuforia") +class TestGenerateInstance: + """Tests for VuMark instance generation.""" - response = requests.post( - url=_VWS_HOST + request_path, - headers={ - "Accept": "image/png", - "Authorization": authorization_string, - "Content-Length": str(object=len(content)), - "Content-Type": content_type, - "Date": date, - }, - data=content, - timeout=30, + _TINY_PNG = base64.b64decode( + s=( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4" + "2mP8/x8AAwMCAO7Zl6kAAAAASUVORK5CYII=" + ), ) - assert response.status_code == HTTPStatus.OK - assert response.headers["Content-Type"].split(sep=";")[0] == "image/png" - assert response.content.startswith(_PNG_SIGNATURE) - assert len(response.content) > len(_PNG_SIGNATURE) + @staticmethod + def _create_mock_target_id(vuforia_database: VuforiaDatabase) -> str: + """Create and return a target ID for mock backends.""" + vws_client = VWS( + server_access_key=vuforia_database.server_access_key, + server_secret_key=vuforia_database.server_secret_key, + ) + return vws_client.add_target( + name=uuid4().hex, + width=1, + image=io.BytesIO(initial_bytes=TestGenerateInstance._TINY_PNG), + active_flag=True, + application_metadata=None, + ) + + def test_generate_instance_success( + self, + verify_mock_vuforia: VuforiaBackend, + vuforia_database: VuforiaDatabase, + vumark_vuforia_database: VuMarkVuforiaDatabase, + ) -> None: + """A VuMark instance can be generated with valid template settings.""" + if verify_mock_vuforia == VuforiaBackend.REAL: + server_access_key = vumark_vuforia_database.server_access_key + server_secret_key = vumark_vuforia_database.server_secret_key + target_id = vumark_vuforia_database.target_id + else: + server_access_key = vuforia_database.server_access_key + server_secret_key = vuforia_database.server_secret_key + target_id = self._create_mock_target_id( + vuforia_database=vuforia_database + ) + + request_path = f"/targets/{target_id}/instances" + content_type = "application/json" + generated_instance_id = uuid4().hex + content = json.dumps( + obj={"instance_id": generated_instance_id} + ).encode(encoding="utf-8") + date = rfc_1123_date() + authorization_string = authorization_header( + access_key=server_access_key, + secret_key=server_secret_key, + method=HTTPMethod.POST, + content=content, + content_type=content_type, + date=date, + request_path=request_path, + ) + + response = requests.post( + url=_VWS_HOST + request_path, + headers={ + "Accept": "image/png", + "Authorization": authorization_string, + "Content-Length": str(object=len(content)), + "Content-Type": content_type, + "Date": date, + }, + data=content, + timeout=30, + ) + + assert response.status_code == HTTPStatus.OK + assert ( + response.headers["Content-Type"].split(sep=";")[0] == "image/png" + ) + assert response.content.startswith(_PNG_SIGNATURE) + assert len(response.content) > len(_PNG_SIGNATURE) From 495dd6488faeda2eda1072bcb8e0eb2537a78dcc Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 19:05:02 +0000 Subject: [PATCH 2267/2467] Update CI secrets archive for VuMark --- secrets.tar.gpg | Bin 15126 -> 17558 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/secrets.tar.gpg b/secrets.tar.gpg index 62c9a2f8d8ae05a911a2dcee0f10f5547d1dc65c..a21cf43fd538155a3612c7f1b41c19eb17508113 100644 GIT binary patch literal 17558 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 literal 15126 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 Date: Tue, 17 Feb 2026 20:10:46 +0000 Subject: [PATCH 2268/2467] Fix generator teardown pattern in backend fixture --- tests/mock_vws/fixtures/vuforia_backends.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 154c7cb08..a5ab8590e 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -257,17 +257,12 @@ def fixture_verify_mock_vuforia( VuforiaBackend.DOCKER_IN_MEMORY: _enable_use_docker_in_memory, }[backend] - backend_generator = enable_function( + for _ in enable_function( working_database=vuforia_database, inactive_database=inactive_database, monkeypatch=monkeypatch, - ) - next(backend_generator) - try: + ): yield backend - finally: - with contextlib.suppress(StopIteration): - next(backend_generator) @pytest.fixture( From 43e2ac6cfb8bf3febafea3f5888539de0c01b8d1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 22:43:50 +0000 Subject: [PATCH 2269/2467] Add VuMark target setup to mock backends --- tests/mock_vws/fixtures/vuforia_backends.py | 64 +++++++++- tests/mock_vws/test_vumark_generation_api.py | 122 +++++++------------ 2 files changed, 102 insertions(+), 84 deletions(-) diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index a5ab8590e..155a2bb70 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -1,6 +1,7 @@ """Choose which backends to use for the tests.""" import contextlib +import io import logging from collections.abc import Generator from enum import Enum @@ -9,6 +10,7 @@ import requests import responses from beartype import beartype +from PIL import Image from requests_mock_flask import add_flask_app_to_mock from vws import VWS from vws.exceptions.vws_exceptions import ( @@ -21,6 +23,9 @@ from mock_vws._flask_server.vws import VWS_FLASK_APP from mock_vws.database import VuforiaDatabase from mock_vws.states import States +from mock_vws.target import Target +from mock_vws.target_raters import HardcodedTargetTrackingRater +from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase from tests.mock_vws.utils.retries import RETRY_ON_TOO_MANY_REQUESTS LOGGER = logging.getLogger(name=__name__) @@ -57,16 +62,51 @@ def _delete_all_targets(*, database_keys: VuforiaDatabase) -> None: vws_client.delete_target(target_id=target) +@beartype +def _rgb_png_bytes() -> bytes: + """Return a small RGB PNG image.""" + image = Image.new(mode="RGB", size=(8, 8), color=(255, 0, 0)) + image_file = io.BytesIO() + image.save(fp=image_file, format="PNG") + return image_file.getvalue() + + +@beartype +def _vumark_database( + *, + vumark_vuforia_database: VuMarkVuforiaDatabase, +) -> VuforiaDatabase: + """Return a database with a target for VuMark instance generation.""" + vumark_target = Target( + active_flag=True, + application_metadata=None, + image_value=_rgb_png_bytes(), + name="mock-vumark-target", + processing_time_seconds=0, + width=1, + target_tracking_rater=HardcodedTargetTrackingRater(rating=5), + target_id=vumark_vuforia_database.target_id, + ) + return VuforiaDatabase( + database_name=vumark_vuforia_database.target_manager_database_name, + server_access_key=vumark_vuforia_database.server_access_key, + server_secret_key=vumark_vuforia_database.server_secret_key, + targets={vumark_target}, + ) + + @beartype def _enable_use_real_vuforia( *, working_database: VuforiaDatabase, inactive_database: VuforiaDatabase, + vumark_vuforia_database: VuMarkVuforiaDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test against the real Vuforia.""" assert monkeypatch assert inactive_database + assert vumark_vuforia_database _delete_all_targets(database_keys=working_database) yield @@ -76,6 +116,7 @@ def _enable_use_mock_vuforia( *, working_database: VuforiaDatabase, inactive_database: VuforiaDatabase, + vumark_vuforia_database: VuMarkVuforiaDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test against the in-memory mock Vuforia.""" @@ -96,10 +137,14 @@ def _enable_use_mock_vuforia( client_access_key=inactive_database.client_access_key, client_secret_key=inactive_database.client_secret_key, ) + vumark_database = _vumark_database( + vumark_vuforia_database=vumark_vuforia_database, + ) with MockVWS() as mock: mock.add_database(database=working_database) mock.add_database(database=inactive_database) + mock.add_database(database=vumark_database) yield @@ -108,6 +153,7 @@ def _enable_use_docker_in_memory( *, working_database: VuforiaDatabase, inactive_database: VuforiaDatabase, + vumark_vuforia_database: VuMarkVuforiaDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test against mock Vuforia created to be run in a container.""" @@ -170,6 +216,13 @@ def _enable_use_docker_in_memory( json=inactive_database.to_dict(), timeout=30, ) + requests.post( + url=databases_url, + json=_vumark_database( + vumark_vuforia_database=vumark_vuforia_database, + ).to_dict(), + timeout=30, + ) yield @@ -233,8 +286,9 @@ def fixture_verify_mock_vuforia( request: pytest.FixtureRequest, vuforia_database: VuforiaDatabase, inactive_database: VuforiaDatabase, + vumark_vuforia_database: VuMarkVuforiaDatabase, monkeypatch: pytest.MonkeyPatch, -) -> Generator[VuforiaBackend]: +) -> Generator[None]: """Test functions which use this fixture are run multiple times. Once with the real Vuforia, and once with each mock. @@ -257,12 +311,12 @@ def fixture_verify_mock_vuforia( VuforiaBackend.DOCKER_IN_MEMORY: _enable_use_docker_in_memory, }[backend] - for _ in enable_function( + yield from enable_function( working_database=vuforia_database, inactive_database=inactive_database, + vumark_vuforia_database=vumark_vuforia_database, monkeypatch=monkeypatch, - ): - yield backend + ) @pytest.fixture( @@ -278,6 +332,7 @@ def mock_only_vuforia( request: pytest.FixtureRequest, vuforia_database: VuforiaDatabase, inactive_database: VuforiaDatabase, + vumark_vuforia_database: VuMarkVuforiaDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test functions which use this fixture are run multiple times. Once @@ -305,5 +360,6 @@ def mock_only_vuforia( yield from enable_function( working_database=vuforia_database, inactive_database=inactive_database, + vumark_vuforia_database=vumark_vuforia_database, monkeypatch=monkeypatch, ) diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 7cbe9aff1..8c14a72af 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -1,101 +1,63 @@ """Tests for the VuMark generation web API.""" -import base64 -import io import json from http import HTTPMethod, HTTPStatus from uuid import uuid4 import pytest import requests -from vws import VWS from vws_auth_tools import authorization_header, rfc_1123_date -from mock_vws.database import VuforiaDatabase from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase -from tests.mock_vws.fixtures.vuforia_backends import VuforiaBackend _VWS_HOST = "https://vws.vuforia.com" _PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" @pytest.mark.usefixtures("verify_mock_vuforia") -class TestGenerateInstance: - """Tests for VuMark instance generation.""" - - _TINY_PNG = base64.b64decode( - s=( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4" - "2mP8/x8AAwMCAO7Zl6kAAAAASUVORK5CYII=" - ), - ) - - @staticmethod - def _create_mock_target_id(vuforia_database: VuforiaDatabase) -> str: - """Create and return a target ID for mock backends.""" - vws_client = VWS( - server_access_key=vuforia_database.server_access_key, - server_secret_key=vuforia_database.server_secret_key, +def test_generate_instance_success( + vumark_vuforia_database: VuMarkVuforiaDatabase, +) -> None: + """A VuMark instance can be generated with valid template settings.""" + if vumark_vuforia_database.target_id.startswith("<"): + pytest.skip( + reason=( + "VuMark target ID is a placeholder. " + "Set VUMARK_VUFORIA_TARGET_ID." + ), ) - return vws_client.add_target( - name=uuid4().hex, - width=1, - image=io.BytesIO(initial_bytes=TestGenerateInstance._TINY_PNG), - active_flag=True, - application_metadata=None, - ) - - def test_generate_instance_success( - self, - verify_mock_vuforia: VuforiaBackend, - vuforia_database: VuforiaDatabase, - vumark_vuforia_database: VuMarkVuforiaDatabase, - ) -> None: - """A VuMark instance can be generated with valid template settings.""" - if verify_mock_vuforia == VuforiaBackend.REAL: - server_access_key = vumark_vuforia_database.server_access_key - server_secret_key = vumark_vuforia_database.server_secret_key - target_id = vumark_vuforia_database.target_id - else: - server_access_key = vuforia_database.server_access_key - server_secret_key = vuforia_database.server_secret_key - target_id = self._create_mock_target_id( - vuforia_database=vuforia_database - ) - request_path = f"/targets/{target_id}/instances" - content_type = "application/json" - generated_instance_id = uuid4().hex - content = json.dumps( - obj={"instance_id": generated_instance_id} - ).encode(encoding="utf-8") - date = rfc_1123_date() - authorization_string = authorization_header( - access_key=server_access_key, - secret_key=server_secret_key, - method=HTTPMethod.POST, - content=content, - content_type=content_type, - date=date, - request_path=request_path, - ) + request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" + content_type = "application/json" + generated_instance_id = uuid4().hex + content = json.dumps(obj={"instance_id": generated_instance_id}).encode( + encoding="utf-8" + ) + date = rfc_1123_date() + authorization_string = authorization_header( + access_key=vumark_vuforia_database.server_access_key, + secret_key=vumark_vuforia_database.server_secret_key, + method=HTTPMethod.POST, + content=content, + content_type=content_type, + date=date, + request_path=request_path, + ) - response = requests.post( - url=_VWS_HOST + request_path, - headers={ - "Accept": "image/png", - "Authorization": authorization_string, - "Content-Length": str(object=len(content)), - "Content-Type": content_type, - "Date": date, - }, - data=content, - timeout=30, - ) + response = requests.post( + url=_VWS_HOST + request_path, + headers={ + "Accept": "image/png", + "Authorization": authorization_string, + "Content-Length": str(object=len(content)), + "Content-Type": content_type, + "Date": date, + }, + data=content, + timeout=30, + ) - assert response.status_code == HTTPStatus.OK - assert ( - response.headers["Content-Type"].split(sep=";")[0] == "image/png" - ) - assert response.content.startswith(_PNG_SIGNATURE) - assert len(response.content) > len(_PNG_SIGNATURE) + assert response.status_code == HTTPStatus.OK + assert response.headers["Content-Type"].split(sep=";")[0] == "image/png" + assert response.content.startswith(_PNG_SIGNATURE) + assert len(response.content) > len(_PNG_SIGNATURE) From bf8224ae9c8238244f1d0012880bfe60227e4e89 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 23:03:21 +0000 Subject: [PATCH 2270/2467] Fix BugBot issues for VuMark support --- src/mock_vws/_constants.py | 7 +++ src/mock_vws/_flask_server/vws.py | 10 +-- .../mock_web_services_api.py | 10 +-- .../_services_validators/target_validators.py | 7 ++- tests/mock_vws/fixtures/credentials.py | 10 +-- tests/mock_vws/fixtures/vuforia_backends.py | 32 +++++----- tests/mock_vws/test_target_validators.py | 63 +++++++++++++++++++ 7 files changed, 102 insertions(+), 37 deletions(-) create mode 100644 tests/mock_vws/test_target_validators.py diff --git a/src/mock_vws/_constants.py b/src/mock_vws/_constants.py index 1f832af1f..d9af993b5 100644 --- a/src/mock_vws/_constants.py +++ b/src/mock_vws/_constants.py @@ -4,6 +4,13 @@ from beartype import beartype +VUMARK_PNG = ( + b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00" + b"\x01\x08\x04\x00\x00\x00\xb5\x1c\x0c\x02\x00\x00\x00\x0bIDATx\xdac" + b"\xfc\xff\x1f\x00\x03\x03\x02\x00\xee\xd9\x97\xa9\x00\x00\x00\x00IEND" + b"\xaeB`\x82" +) + @beartype @unique diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index 6d0d94e1d..99b8c3b8e 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -18,7 +18,7 @@ from flask import Flask, Response, request from pydantic_settings import BaseSettings -from mock_vws._constants import ResultCodes, TargetStatuses +from mock_vws._constants import VUMARK_PNG, ResultCodes, TargetStatuses from mock_vws._database_matchers import get_database_matching_server_keys from mock_vws._mock_common import json_dump from mock_vws._services_validators import run_services_validators @@ -44,12 +44,6 @@ _LOGGER = logging.getLogger(name=__name__) -_VUMARK_PNG = base64.b64decode( - s=( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8A" - "AwMCAO7Zl6kAAAAASUVORK5CYII=" - ), -) @beartype @@ -370,7 +364,7 @@ def generate_vumark_instance(target_id: str) -> Response: } return Response( status=HTTPStatus.OK, - response=_VUMARK_PNG, + response=VUMARK_PNG, headers=headers, ) diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index 13ee44ca5..5c9f3e4d8 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -18,7 +18,7 @@ from beartype import BeartypeConf, beartype from requests.models import PreparedRequest -from mock_vws._constants import ResultCodes, TargetStatuses +from mock_vws._constants import VUMARK_PNG, ResultCodes, TargetStatuses from mock_vws._database_matchers import get_database_matching_server_keys from mock_vws._mock_common import Route, json_dump from mock_vws._services_validators import run_services_validators @@ -34,12 +34,6 @@ from mock_vws.target_raters import TargetTrackingRater _TARGET_ID_PATTERN = "[A-Za-z0-9]+" -_VUMARK_PNG = base64.b64decode( - s=( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8A" - "AwMCAO7Zl6kAAAAASUVORK5CYII=" - ), -) _ROUTES: set[Route] = set() @@ -327,7 +321,7 @@ def generate_vumark_instance( "x-aws-region": "us-east-2, us-west-2", "x-content-type-options": "nosniff", } - return HTTPStatus.OK, headers, _VUMARK_PNG + return HTTPStatus.OK, headers, VUMARK_PNG @route(path_pattern="/summary", http_methods={HTTPMethod.GET}) def database_summary(self, request: PreparedRequest) -> _ResponseType: diff --git a/src/mock_vws/_services_validators/target_validators.py b/src/mock_vws/_services_validators/target_validators.py index 58963b891..aedfa511e 100644 --- a/src/mock_vws/_services_validators/target_validators.py +++ b/src/mock_vws/_services_validators/target_validators.py @@ -10,6 +10,7 @@ from mock_vws.database import VuforiaDatabase _LOGGER = logging.getLogger(name=__name__) +_TARGETS_WITH_INSTANCE_PATH_LENGTH = 4 @beartype @@ -42,7 +43,11 @@ def validate_target_id_exists( return target_id = split_path[-1] - if split_path[-1] == "instances": + if ( + len(split_path) == _TARGETS_WITH_INSTANCE_PATH_LENGTH + and split_path[-3] == "targets" + and split_path[-1] == "instances" + ): target_id = split_path[-2] database = get_database_matching_server_keys( request_headers=request_headers, diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index 819ad2635..bc37d2f88 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -1,6 +1,6 @@ """Fixtures for credentials for Vuforia databases.""" -from dataclasses import dataclass +from dataclasses import dataclass, field from pathlib import Path import pytest @@ -55,10 +55,10 @@ class _VuMarkVuforiaDatabaseSettings(BaseSettings): class VuMarkVuforiaDatabase: """Credentials for the VuMark generation API.""" - target_manager_database_name: str - server_access_key: str - server_secret_key: str - target_id: str + target_manager_database_name: str = field(repr=False) + server_access_key: str = field(repr=False) + server_secret_key: str = field(repr=False) + target_id: str = field(repr=False) @pytest.fixture diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 155a2bb70..c0e95b08c 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -1,7 +1,6 @@ """Choose which backends to use for the tests.""" import contextlib -import io import logging from collections.abc import Generator from enum import Enum @@ -10,7 +9,6 @@ import requests import responses from beartype import beartype -from PIL import Image from requests_mock_flask import add_flask_app_to_mock from vws import VWS from vws.exceptions.vws_exceptions import ( @@ -26,6 +24,7 @@ from mock_vws.target import Target from mock_vws.target_raters import HardcodedTargetTrackingRater from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase +from tests.mock_vws.utils import make_image_file from tests.mock_vws.utils.retries import RETRY_ON_TOO_MANY_REQUESTS LOGGER = logging.getLogger(name=__name__) @@ -62,15 +61,6 @@ def _delete_all_targets(*, database_keys: VuforiaDatabase) -> None: vws_client.delete_target(target_id=target) -@beartype -def _rgb_png_bytes() -> bytes: - """Return a small RGB PNG image.""" - image = Image.new(mode="RGB", size=(8, 8), color=(255, 0, 0)) - image_file = io.BytesIO() - image.save(fp=image_file, format="PNG") - return image_file.getvalue() - - @beartype def _vumark_database( *, @@ -80,7 +70,12 @@ def _vumark_database( vumark_target = Target( active_flag=True, application_metadata=None, - image_value=_rgb_png_bytes(), + image_value=make_image_file( + file_format="PNG", + color_space="RGB", + width=8, + height=8, + ).getvalue(), name="mock-vumark-target", processing_time_seconds=0, width=1, @@ -177,6 +172,10 @@ def _enable_use_docker_in_memory( name="TARGET_MANAGER_BASE_URL", value=target_manager_base_url, ) + vumark_database = _vumark_database( + vumark_vuforia_database=vumark_vuforia_database, + ) + (vumark_target,) = vumark_database.targets with responses.RequestsMock(assert_all_requests_are_fired=False) as mock: add_flask_app_to_mock( @@ -218,9 +217,12 @@ def _enable_use_docker_in_memory( ) requests.post( url=databases_url, - json=_vumark_database( - vumark_vuforia_database=vumark_vuforia_database, - ).to_dict(), + json=vumark_database.to_dict(), + timeout=30, + ) + requests.post( + url=(f"{databases_url}/{vumark_database.database_name}/targets"), + json=vumark_target.to_dict(), timeout=30, ) diff --git a/tests/mock_vws/test_target_validators.py b/tests/mock_vws/test_target_validators.py new file mode 100644 index 000000000..314b253e0 --- /dev/null +++ b/tests/mock_vws/test_target_validators.py @@ -0,0 +1,63 @@ +"""Tests for target ID validators.""" + +import pytest + +from mock_vws._services_validators.target_validators import ( + validate_target_id_exists, +) +from mock_vws.database import VuforiaDatabase +from mock_vws.target import Target +from mock_vws.target_raters import HardcodedTargetTrackingRater +from tests.mock_vws.utils import make_image_file + + +def _database_with_target(*, target_id: str) -> VuforiaDatabase: + """Create a database containing one target with the given ID.""" + target = Target( + active_flag=True, + application_metadata=None, + image_value=make_image_file( + file_format="PNG", + color_space="RGB", + width=8, + height=8, + ).getvalue(), + name="example", + processing_time_seconds=0, + target_id=target_id, + target_tracking_rater=HardcodedTargetTrackingRater(rating=5), + width=1, + ) + return VuforiaDatabase(targets={target}) + + +@pytest.mark.parametrize( + ("request_path", "target_id"), + [ + ("/targets/instances", "instances"), + ("/targets/target123/instances", "target123"), + ], +) +def test_validate_target_id_exists_uses_correct_path_segment( + *, + request_path: str, + target_id: str, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Validation uses the right target segment for both endpoint + shapes. + """ + database = _database_with_target(target_id=target_id) + monkeypatch.setattr( + "mock_vws._services_validators.target_validators." + "get_database_matching_server_keys", + lambda **_kwargs: database, + ) + + validate_target_id_exists( + request_path=request_path, + request_headers={}, + request_body=b"", + request_method="GET", + databases={database}, + ) From fbae231aa11735814f28ac0984899c307a48cc36 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 23:04:36 +0000 Subject: [PATCH 2271/2467] Fix mypy typing in target validator test --- tests/mock_vws/test_target_validators.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/mock_vws/test_target_validators.py b/tests/mock_vws/test_target_validators.py index 314b253e0..8d68e2b99 100644 --- a/tests/mock_vws/test_target_validators.py +++ b/tests/mock_vws/test_target_validators.py @@ -2,6 +2,7 @@ import pytest +from mock_vws._services_validators import target_validators from mock_vws._services_validators.target_validators import ( validate_target_id_exists, ) @@ -32,8 +33,8 @@ def _database_with_target(*, target_id: str) -> VuforiaDatabase: @pytest.mark.parametrize( - ("request_path", "target_id"), - [ + argnames=("request_path", "target_id"), + argvalues=[ ("/targets/instances", "instances"), ("/targets/target123/instances", "target123"), ], @@ -49,9 +50,9 @@ def test_validate_target_id_exists_uses_correct_path_segment( """ database = _database_with_target(target_id=target_id) monkeypatch.setattr( - "mock_vws._services_validators.target_validators." - "get_database_matching_server_keys", - lambda **_kwargs: database, + target=target_validators, + name="get_database_matching_server_keys", + value=lambda **_kwargs: database, ) validate_target_id_exists( From 6dea443fd320c5fb240f4c7e10b767d8f7180ae8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 23:06:17 +0000 Subject: [PATCH 2272/2467] Resolve pyright typing in validator test --- tests/mock_vws/test_target_validators.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/mock_vws/test_target_validators.py b/tests/mock_vws/test_target_validators.py index 8d68e2b99..04b147422 100644 --- a/tests/mock_vws/test_target_validators.py +++ b/tests/mock_vws/test_target_validators.py @@ -1,5 +1,8 @@ """Tests for target ID validators.""" +from collections.abc import Iterable, Mapping +from functools import partial + import pytest from mock_vws._services_validators import target_validators @@ -32,6 +35,24 @@ def _database_with_target(*, target_id: str) -> VuforiaDatabase: return VuforiaDatabase(targets={target}) +def _always_match_database( + *, + database: VuforiaDatabase, + request_headers: Mapping[str, str], + request_body: bytes | None, + request_method: str, + request_path: str, + databases: Iterable[VuforiaDatabase], +) -> VuforiaDatabase: + """Return the given database regardless of request details.""" + del request_headers + del request_body + del request_method + del request_path + del databases + return database + + @pytest.mark.parametrize( argnames=("request_path", "target_id"), argvalues=[ @@ -49,10 +70,11 @@ def test_validate_target_id_exists_uses_correct_path_segment( shapes. """ database = _database_with_target(target_id=target_id) + monkeypatch.setattr( target=target_validators, name="get_database_matching_server_keys", - value=lambda **_kwargs: database, + value=partial(_always_match_database, database=database), ) validate_target_id_exists( From 941348f09462ce5c9973c8f00044636e33ad4f5e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 23:55:30 +0000 Subject: [PATCH 2273/2467] Remove uncovered branches from generate_vumark_instance See https://github.com/VWS-Python/vws-python-mock/issues/2942 to add back the ValidatorError handling once tests cover those branches. --- .../mock_web_services_api.py | 17 +++++++---------- tests/mock_vws/test_vumark_generation_api.py | 8 -------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index 5c9f3e4d8..303d4a2b4 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -295,16 +295,13 @@ def generate_vumark_instance( self, request: PreparedRequest ) -> _ResponseType: """Generate a VuMark instance.""" - try: - run_services_validators( - request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, - databases=self._target_manager.databases, - ) - except ValidatorError as exc: - return exc.status_code, exc.headers, exc.response_text + run_services_validators( + request_headers=request.headers, + request_body=_body_bytes(request=request), + request_method=request.method or "", + request_path=request.path_url, + databases=self._target_manager.databases, + ) date = email.utils.formatdate( timeval=None, diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 8c14a72af..ac7b2634a 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -19,14 +19,6 @@ def test_generate_instance_success( vumark_vuforia_database: VuMarkVuforiaDatabase, ) -> None: """A VuMark instance can be generated with valid template settings.""" - if vumark_vuforia_database.target_id.startswith("<"): - pytest.skip( - reason=( - "VuMark target ID is a placeholder. " - "Set VUMARK_VUFORIA_TARGET_ID." - ), - ) - request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" content_type = "application/json" generated_instance_id = uuid4().hex From 5442a769e09d1ce8157b1994d270a8c195ec9ef6 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 00:10:48 +0000 Subject: [PATCH 2274/2467] Use valid default target ID for VuMark database settings The previous default '' contained '<' and '>' which are not matched by the target ID URL pattern [A-Za-z0-9]+, causing the mock to fail to route requests in tests. --- tests/mock_vws/fixtures/credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index bc37d2f88..91069d473 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -42,7 +42,7 @@ class _VuMarkVuforiaDatabaseSettings(BaseSettings): target_manager_database_name: str server_access_key: str server_secret_key: str - target_id: str = "" + target_id: str = "MockVuMarkTargetID00" model_config = SettingsConfigDict( env_prefix="VUMARK_VUFORIA_", From 270f9d96c23d460dd9851840c4d1406c5e448d03 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 00:36:23 +0000 Subject: [PATCH 2275/2467] Use valid target ID placeholder in vuforia_secrets.env.example The windows-tests and skip-tests CI jobs copy this file directly, so the VUMARK_VUFORIA_TARGET_ID value must be alphanumeric to match the [A-Za-z0-9]+ URL pattern. --- vuforia_secrets.env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index ea7273354..43847306c 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -15,7 +15,7 @@ INACTIVE_VUFORIA_CLIENT_ACCESS_KEY= INACTIVE_VUFORIA_CLIENT_SECRET_KEY= VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= -VUMARK_VUFORIA_TARGET_ID= +VUMARK_VUFORIA_TARGET_ID=MockVuMarkTargetID00 VUMARK_VUFORIA_SERVER_ACCESS_KEY= VUMARK_VUFORIA_SERVER_SECRET_KEY= From 55a45eea79079de4f2b16ed80d4cf354ed76f536 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 00:57:48 +0000 Subject: [PATCH 2276/2467] Update uv to 0.10.4 in Dockerfile Updates the uv package from 0.1.44 to the latest version 0.10.4 in the Flask server Dockerfile. Co-Authored-By: Claude Haiku 4.5 --- src/mock_vws/_flask_server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mock_vws/_flask_server/Dockerfile b/src/mock_vws/_flask_server/Dockerfile index a08e67063..339ae6b26 100644 --- a/src/mock_vws/_flask_server/Dockerfile +++ b/src/mock_vws/_flask_server/Dockerfile @@ -16,7 +16,7 @@ RUN python3 -m venv $VIRTUAL_ENV ENV PATH="$VIRTUAL_ENV/bin:$PATH" WORKDIR /app -RUN pip install --no-cache-dir uv==0.1.44 && \ +RUN pip install --no-cache-dir uv==0.10.4 && \ uv pip install --no-cache-dir --upgrade --editable . EXPOSE 5000 ENTRYPOINT ["python"] From 5aa3a157f168b4611e3ccf033dd9edca0d9ed6d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 05:04:01 +0000 Subject: [PATCH 2277/2467] Bump pyproject-fmt from 2.16.0 to 2.16.1 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.16.0 to 2.16.1. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.16.0...pyproject-fmt/2.16.1) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.16.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 83b732734..186bec54d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.16.0", + "pyproject-fmt==2.16.1", "pyrefly==0.52.0", "pyright==1.1.408", "pyroma==5.0.1", From 685f3d7ff360aa93c4470873034636f8fccbf206 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 05:30:56 +0000 Subject: [PATCH 2278/2467] Bump pyrefly from 0.52.0 to 0.53.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.52.0 to 0.53.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.52.0...0.53.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.53.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 186bec54d..f3b569763 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.16.1", - "pyrefly==0.52.0", + "pyrefly==0.53.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 80871103734752e55af81b721f20a113d4101268 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 09:20:15 +0000 Subject: [PATCH 2279/2467] Use CPU-only torch for Docker builds to reduce image size Configure uv to install CPU-only torch from PyTorch's dedicated index via [tool.uv.sources] in pyproject.toml. Update Dockerfile to use uv sync (which respects uv.sources) instead of uv pip install, and upgrade uv from 0.1.44 to 0.5.0 for [tool.uv.sources] support. This reduces the Docker image by ~3GB (CUDA libraries are unnecessary for the CPU-only Flask server). Co-Authored-By: Claude Haiku 4.5 --- pyproject.toml | 4 ++++ src/mock_vws/_flask_server/Dockerfile | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 83b732734..29a85c1d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -138,6 +138,10 @@ fallback_version = "0.0.0" # Code to match this is in ``conf.py``. version_scheme = "post-release" +[tool.uv] +sources.torch = { index = "pytorch-cpu" } +index = [ { name = "pytorch-cpu", url = "https://download.pytorch.org/whl/cpu", explicit = true } ] + [tool.ruff] line-length = 79 lint.select = [ diff --git a/src/mock_vws/_flask_server/Dockerfile b/src/mock_vws/_flask_server/Dockerfile index a08e67063..94fe4c6d1 100644 --- a/src/mock_vws/_flask_server/Dockerfile +++ b/src/mock_vws/_flask_server/Dockerfile @@ -12,12 +12,12 @@ COPY --chown=myuser:myuser . /app # See https://pythonspeed.com/articles/activate-virtualenv-dockerfile/ # For why we use this method of activating the virtual environment. ENV VIRTUAL_ENV=/app/docker_venvs/.venv -RUN python3 -m venv $VIRTUAL_ENV +ENV UV_PROJECT_ENVIRONMENT=/app/docker_venvs/.venv ENV PATH="$VIRTUAL_ENV/bin:$PATH" WORKDIR /app -RUN pip install --no-cache-dir uv==0.1.44 && \ - uv pip install --no-cache-dir --upgrade --editable . +RUN pip install --no-cache-dir uv==0.5.0 && \ + uv sync --no-cache EXPOSE 5000 ENTRYPOINT ["python"] HEALTHCHECK --interval=1s --timeout=10s --start-period=5s --retries=3 CMD ["python", "/app/src/mock_vws/_flask_server/healthcheck.py"] From 1410e79736b4cc938dd9844684fd406d4f1d1af5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 09:28:10 +0000 Subject: [PATCH 2280/2467] Fix torchvision CPU index and Docker venv creation Add torchvision to [tool.uv.sources] so it also comes from the CPU index (piq depends on torchvision, which crashes on import with CPU-only torch when installed from PyPI). Restore python3 -m venv in the Dockerfile so pip install uv puts the uv binary into the venv's bin directory (which is on PATH), rather than ~/.local/bin (which is not). Co-Authored-By: Claude Sonnet 4.6 --- pyproject.toml | 1 + src/mock_vws/_flask_server/Dockerfile | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3f261e459..78f9f3c6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -140,6 +140,7 @@ version_scheme = "post-release" [tool.uv] sources.torch = { index = "pytorch-cpu" } +sources.torchvision = { index = "pytorch-cpu" } index = [ { name = "pytorch-cpu", url = "https://download.pytorch.org/whl/cpu", explicit = true } ] [tool.ruff] diff --git a/src/mock_vws/_flask_server/Dockerfile b/src/mock_vws/_flask_server/Dockerfile index cf24f86a3..9565f9d90 100644 --- a/src/mock_vws/_flask_server/Dockerfile +++ b/src/mock_vws/_flask_server/Dockerfile @@ -16,7 +16,8 @@ ENV UV_PROJECT_ENVIRONMENT=/app/docker_venvs/.venv ENV PATH="$VIRTUAL_ENV/bin:$PATH" WORKDIR /app -RUN pip install --no-cache-dir uv==0.10.4 && \ +RUN python3 -m venv $VIRTUAL_ENV && \ + pip install --no-cache-dir uv==0.10.4 && \ uv sync --no-cache EXPOSE 5000 ENTRYPOINT ["python"] From 9f484ea5aa98af328793dcd908d00a97ff389854 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 09:40:43 +0000 Subject: [PATCH 2281/2467] Add torchvision as direct dependency for CPU index source to apply tool.uv.sources only applies to direct dependencies; torchvision was only a transitive dependency via piq, so sources.torchvision was silently ignored and PyPI's torchvision was installed instead. PyPI's torchvision registers torchvision::nms operators that don't exist in CPU-only torch, causing RuntimeError on import. Making torchvision a direct dependency causes uv to route it through the CPU index. Co-Authored-By: Claude Sonnet 4.6 --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 78f9f3c6d..f4e49c096 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ dependencies = [ "responses>=0.25.3", "torch>=2.5.1", "torchmetrics>=1.5.1", + "torchvision>=0.20.1", "tzdata; sys_platform=='win32'", "vws-auth-tools>=2024.7.12", "werkzeug>=3.1.2", @@ -317,6 +318,9 @@ per_rule_ignores.DEP002 = [ # tzdata is needed on Windows for zoneinfo to work. # See https://docs.python.org/3/library/zoneinfo.html#data-sources. "tzdata", + # torchvision is used transitively via piq, but must be a direct dependency + # so that tool.uv.sources can route it to the CPU-only PyTorch index. + "torchvision", ] [tool.pyproject-fmt] From 99fcbf89868d7d0e40609dab6100363ef4a3a04b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 10:07:56 +0000 Subject: [PATCH 2282/2467] Document CPU-only torch installation to speed up pip installs pip users get CUDA torch from PyPI by default (~873 MB). Document how to pre-install torch and torchvision from PyTorch's CPU index (~200 MB) before installing the package, and the equivalent uv pyproject.toml configuration. Co-Authored-By: Claude Sonnet 4.6 --- docs/source/installation.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index ce56603b2..753d68829 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -6,3 +6,29 @@ Installation $ pip install vws-python-mock This requires Python |minimum-python-version|\+. + +Faster installation +~~~~~~~~~~~~~~~~~~~ + +This package depends on `PyTorch`_, which pip installs from PyPI as a large CUDA-enabled build (~873 MB) even on CPU-only machines. +To get a much smaller CPU-only build (~200 MB, no CUDA dependencies), install ``torch`` and ``torchvision`` from PyTorch's CPU index before installing this package: + +.. code-block:: console + + $ pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu + $ pip install vws-python-mock + +If you manage dependencies with ``uv``, add the following to your ``pyproject.toml`` instead: + +.. code-block:: toml + + [[tool.uv.index]] + name = "pytorch-cpu" + url = "https://download.pytorch.org/whl/cpu" + explicit = true + + [tool.uv.sources] + torch = { index = "pytorch-cpu" } + torchvision = { index = "pytorch-cpu" } + +.. _PyTorch: https://pytorch.org From fb8313b857b6410b9fc840bd2b8e1880a8fa794d Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Wed, 18 Feb 2026 10:08:55 +0000 Subject: [PATCH 2283/2467] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2a5183345..4c0fec1f8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.18 +---------- + + 2026.02.15.5 ------------ From b43ad927e9b496ccb6f4ff1c2764187b0a0fef45 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 10:26:31 +0000 Subject: [PATCH 2284/2467] Remove redundant VIRTUAL_ENV variable from Dockerfile Replace VIRTUAL_ENV with UV_PROJECT_ENVIRONMENT, which holds the same value and is already set. This reduces environment variable duplication. Co-Authored-By: Claude Haiku 4.5 --- src/mock_vws/_flask_server/Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/mock_vws/_flask_server/Dockerfile b/src/mock_vws/_flask_server/Dockerfile index 9565f9d90..4981a38f4 100644 --- a/src/mock_vws/_flask_server/Dockerfile +++ b/src/mock_vws/_flask_server/Dockerfile @@ -11,12 +11,11 @@ COPY --chown=myuser:myuser . /app # See https://pythonspeed.com/articles/activate-virtualenv-dockerfile/ # For why we use this method of activating the virtual environment. -ENV VIRTUAL_ENV=/app/docker_venvs/.venv ENV UV_PROJECT_ENVIRONMENT=/app/docker_venvs/.venv -ENV PATH="$VIRTUAL_ENV/bin:$PATH" +ENV PATH="$UV_PROJECT_ENVIRONMENT/bin:$PATH" WORKDIR /app -RUN python3 -m venv $VIRTUAL_ENV && \ +RUN python3 -m venv $UV_PROJECT_ENVIRONMENT && \ pip install --no-cache-dir uv==0.10.4 && \ uv sync --no-cache EXPOSE 5000 From d3f2a362ef365229283bc0ace493b80eaef455a8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 10:33:47 +0000 Subject: [PATCH 2285/2467] Remove unused default VuMark target ID The default "MockVuMarkTargetID00" is never used since vuforia_secrets.env is always required (other settings have no defaults) and always provides VUMARK_VUFORIA_TARGET_ID. Update example env to use placeholder like other fields. Co-Authored-By: Claude Haiku 4.5 --- tests/mock_vws/fixtures/credentials.py | 2 +- vuforia_secrets.env.example | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index 91069d473..bb7172a35 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -42,7 +42,7 @@ class _VuMarkVuforiaDatabaseSettings(BaseSettings): target_manager_database_name: str server_access_key: str server_secret_key: str - target_id: str = "MockVuMarkTargetID00" + target_id: str model_config = SettingsConfigDict( env_prefix="VUMARK_VUFORIA_", diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index 43847306c..4d6655642 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -15,7 +15,7 @@ INACTIVE_VUFORIA_CLIENT_ACCESS_KEY= INACTIVE_VUFORIA_CLIENT_SECRET_KEY= VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= -VUMARK_VUFORIA_TARGET_ID=MockVuMarkTargetID00 +VUMARK_VUFORIA_TARGET_ID= VUMARK_VUFORIA_SERVER_ACCESS_KEY= VUMARK_VUFORIA_SERVER_SECRET_KEY= From abb522f37591d235f77aaf202ac60679c1cfe68e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 10:49:05 +0000 Subject: [PATCH 2286/2467] Update expected Jetty version to 12.1.6 The real Vuforia service upgraded from Jetty 12.0.20 to 12.1.6, causing the hardcoded expected response body to no longer match. Co-Authored-By: Claude Haiku 4.5 --- tests/mock_vws/test_query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index 49151ccdb..bb3962a3e 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -65,7 +65,7 @@ STATUS:400 MESSAGE:Bad Request -
Powered by Jetty:// 12.0.20
+
Powered by Jetty:// 12.1.6
From e092ac09e426c6f5d0db8baea59a35b743cf0269 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 10:56:14 +0000 Subject: [PATCH 2287/2467] Normalize Jetty version when asserting query error response The real Vuforia service load-balances across instances with different Jetty versions, causing the exact-match assertion to flap. Normalize the version string before comparing so the test is stable. Co-Authored-By: Claude Haiku 4.5 --- tests/mock_vws/test_query.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index bb3962a3e..fc9977639 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -9,6 +9,7 @@ import datetime import io import json +import re import textwrap import time import uuid @@ -65,13 +66,15 @@ STATUS:400 MESSAGE:Bad Request -
Powered by Jetty:// 12.1.6
+
Powered by Jetty:// 12.0.20
""", ) +_JETTY_VERSION_RE = re.compile(r"Powered by Jetty:// [\d.]+") + _NGINX_REQUEST_ENTITY_TOO_LARGE_ERROR = textwrap.dedent( text="""\ \r @@ -252,7 +255,9 @@ def test_incorrect_no_boundary( if resp_status_code != HTTPStatus.INTERNAL_SERVER_ERROR: handle_server_errors(response=vws_response) - assert requests_response.text == resp_text + sub = _JETTY_VERSION_RE.sub + jetty = "Powered by Jetty://" + assert sub(jetty, requests_response.text) == sub(jetty, resp_text) assert_vwq_failure( response=vws_response, status_code=resp_status_code, From 7f1ca590d49c9e3820ebe73be89a98dc90513a75 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 10:57:09 +0000 Subject: [PATCH 2288/2467] Use keyword argument for re.compile Co-Authored-By: Claude Haiku 4.5 --- tests/mock_vws/test_query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index fc9977639..e9abb1842 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -73,7 +73,7 @@ """, ) -_JETTY_VERSION_RE = re.compile(r"Powered by Jetty:// [\d.]+") +_JETTY_VERSION_RE = re.compile(pattern=r"Powered by Jetty:// [\d.]+") _NGINX_REQUEST_ENTITY_TOO_LARGE_ERROR = textwrap.dedent( text="""\ From 94ff0925264964b840b90a35abfeabb3f98958c0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 10:58:08 +0000 Subject: [PATCH 2289/2467] Use keyword arguments for re Pattern sub calls Co-Authored-By: Claude Haiku 4.5 --- tests/mock_vws/test_query.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index e9abb1842..716ba627f 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -255,9 +255,11 @@ def test_incorrect_no_boundary( if resp_status_code != HTTPStatus.INTERNAL_SERVER_ERROR: handle_server_errors(response=vws_response) + repl = "Powered by Jetty://" sub = _JETTY_VERSION_RE.sub - jetty = "Powered by Jetty://" - assert sub(jetty, requests_response.text) == sub(jetty, resp_text) + actual = sub(repl=repl, string=requests_response.text) + expected = sub(repl=repl, string=resp_text) + assert actual == expected assert_vwq_failure( response=vws_response, status_code=resp_status_code, From d1f5e50f9a2a094ea4db737ed1fa2117a9fbfcfa Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 12:22:07 +0000 Subject: [PATCH 2290/2467] Replace angle bracket placeholders with alphanumeric examples in env file (#2950) The example env file used angle bracket placeholders (e.g., ) which were causing Windows tests to fail. When these placeholder values were URL-encoded in the VuMark test, the < and > characters became %3C and %3E, which didn't match the mock's [A-Za-z0-9]+ URL pattern. Replacing all placeholders with descriptive alphanumeric examples fixes this issue while maintaining clarity about what each value represents. Co-authored-by: Claude Haiku 4.5 --- vuforia_secrets.env.example | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index 4d6655642..a4099f736 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -1,21 +1,21 @@ -VUFORIA_TARGET_MANAGER_DATABASE_NAME= +VUFORIA_TARGET_MANAGER_DATABASE_NAME=example_database_name -VUFORIA_SERVER_ACCESS_KEY= -VUFORIA_SERVER_SECRET_KEY= +VUFORIA_SERVER_ACCESS_KEY=example_server_access_key +VUFORIA_SERVER_SECRET_KEY=example_server_secret_key -VUFORIA_CLIENT_ACCESS_KEY= -VUFORIA_CLIENT_SECRET_KEY= +VUFORIA_CLIENT_ACCESS_KEY=example_client_access_key +VUFORIA_CLIENT_SECRET_KEY=example_client_secret_key -INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME= +INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME=example_inactive_database_name -INACTIVE_VUFORIA_SERVER_ACCESS_KEY= -INACTIVE_VUFORIA_SERVER_SECRET_KEY= +INACTIVE_VUFORIA_SERVER_ACCESS_KEY=example_inactive_server_access_key +INACTIVE_VUFORIA_SERVER_SECRET_KEY=example_inactive_server_secret_key -INACTIVE_VUFORIA_CLIENT_ACCESS_KEY= -INACTIVE_VUFORIA_CLIENT_SECRET_KEY= +INACTIVE_VUFORIA_CLIENT_ACCESS_KEY=example_inactive_client_access_key +INACTIVE_VUFORIA_CLIENT_SECRET_KEY=example_inactive_client_secret_key -VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= -VUMARK_VUFORIA_TARGET_ID= +VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME=example_vumark_database_name +VUMARK_VUFORIA_TARGET_ID=examplevumarktargetid -VUMARK_VUFORIA_SERVER_ACCESS_KEY= -VUMARK_VUFORIA_SERVER_SECRET_KEY= +VUMARK_VUFORIA_SERVER_ACCESS_KEY=example_vumark_server_access_key +VUMARK_VUFORIA_SERVER_SECRET_KEY=example_vumark_server_secret_key From 9969e817903c0e80d358a3994491476c784cdf8b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 13:30:43 +0000 Subject: [PATCH 2291/2467] Use Debian base image with uv installed from official Docker image (#2952) Replaces python:3.13-slim with debian:bookworm-slim and installs uv directly from ghcr.io/astral-sh/uv:0.10.4, eliminating the need to create a Python venv first and install uv via pip. uv now manages Python installation automatically. This follows the uv Docker integration guide more closely. Co-authored-by: Claude Haiku 4.5 --- src/mock_vws/_flask_server/Dockerfile | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/mock_vws/_flask_server/Dockerfile b/src/mock_vws/_flask_server/Dockerfile index 4981a38f4..bbae92ccc 100644 --- a/src/mock_vws/_flask_server/Dockerfile +++ b/src/mock_vws/_flask_server/Dockerfile @@ -1,10 +1,9 @@ -FROM python:3.13-slim AS base +FROM ghcr.io/astral-sh/uv:0.10.4-python3.13-trixie-slim AS base # We set this pretend version as we do not have Git in our path, and we do # not care enough about having the version correct inside the Docker container # to install it. ENV SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 # Avoid using root user. -# This avoids having to use ``--root-user-action=ignore`` with pip. RUN useradd -ms /bin/bash myuser USER myuser COPY --chown=myuser:myuser . /app @@ -15,9 +14,7 @@ ENV UV_PROJECT_ENVIRONMENT=/app/docker_venvs/.venv ENV PATH="$UV_PROJECT_ENVIRONMENT/bin:$PATH" WORKDIR /app -RUN python3 -m venv $UV_PROJECT_ENVIRONMENT && \ - pip install --no-cache-dir uv==0.10.4 && \ - uv sync --no-cache +RUN uv sync --no-cache EXPOSE 5000 ENTRYPOINT ["python"] HEALTHCHECK --interval=1s --timeout=10s --start-period=5s --retries=3 CMD ["python", "/app/src/mock_vws/_flask_server/healthcheck.py"] From 215912aa3de5feab229fbfe2ca581bb9841e9ed3 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 15:25:43 +0000 Subject: [PATCH 2292/2467] Document VuMark instance image differences from real Vuforia (#2956) Co-authored-by: Claude Sonnet 4.6 --- docs/source/differences-to-vws.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/source/differences-to-vws.rst b/docs/source/differences-to-vws.rst index 86218f4eb..1f4876ea9 100644 --- a/docs/source/differences-to-vws.rst +++ b/docs/source/differences-to-vws.rst @@ -103,6 +103,13 @@ These are: When the given ``Content-Length`` header does not match the length of the given data, the mock server (written with Flask) will not behave as the real Vuforia Web Services behaves. +VuMark instance images +---------------------- + +The mock returns a fixed minimal image in the requested format. +The ``instance_id`` value is not encoded into the response image. +Real Vuforia encodes the instance ID into the VuMark pattern. + Header cases ------------ From 40bd318ff41ab9e7bcf5e126dcfd9c4063e1f557 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 15:44:35 +0000 Subject: [PATCH 2293/2467] Group VuMark generation tests in class (#2957) * Group VuMark generation tests in TestGenerateInstance class Move test_generate_instance_success into a TestGenerateInstance class and apply the @pytest.mark.usefixtures decorator at the class level to reduce duplication. Co-Authored-By: Claude Haiku 4.5 * Make test_generate_instance_success a static method Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Haiku 4.5 --- tests/mock_vws/test_vumark_generation_api.py | 81 +++++++++++--------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index ac7b2634a..f2ec64527 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -15,41 +15,46 @@ @pytest.mark.usefixtures("verify_mock_vuforia") -def test_generate_instance_success( - vumark_vuforia_database: VuMarkVuforiaDatabase, -) -> None: - """A VuMark instance can be generated with valid template settings.""" - request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" - content_type = "application/json" - generated_instance_id = uuid4().hex - content = json.dumps(obj={"instance_id": generated_instance_id}).encode( - encoding="utf-8" - ) - date = rfc_1123_date() - authorization_string = authorization_header( - access_key=vumark_vuforia_database.server_access_key, - secret_key=vumark_vuforia_database.server_secret_key, - method=HTTPMethod.POST, - content=content, - content_type=content_type, - date=date, - request_path=request_path, - ) - - response = requests.post( - url=_VWS_HOST + request_path, - headers={ - "Accept": "image/png", - "Authorization": authorization_string, - "Content-Length": str(object=len(content)), - "Content-Type": content_type, - "Date": date, - }, - data=content, - timeout=30, - ) - - assert response.status_code == HTTPStatus.OK - assert response.headers["Content-Type"].split(sep=";")[0] == "image/png" - assert response.content.startswith(_PNG_SIGNATURE) - assert len(response.content) > len(_PNG_SIGNATURE) +class TestGenerateInstance: + """Tests for the VuMark instance generation endpoint.""" + + @staticmethod + def test_generate_instance_success( + vumark_vuforia_database: VuMarkVuforiaDatabase, + ) -> None: + """A VuMark instance can be generated with valid template settings.""" + target_id = vumark_vuforia_database.target_id + request_path = f"/targets/{target_id}/instances" + content_type = "application/json" + generated_instance_id = uuid4().hex + body_dict = {"instance_id": generated_instance_id} + content = json.dumps(obj=body_dict).encode(encoding="utf-8") + date = rfc_1123_date() + authorization_string = authorization_header( + access_key=vumark_vuforia_database.server_access_key, + secret_key=vumark_vuforia_database.server_secret_key, + method=HTTPMethod.POST, + content=content, + content_type=content_type, + date=date, + request_path=request_path, + ) + + response = requests.post( + url=_VWS_HOST + request_path, + headers={ + "Accept": "image/png", + "Authorization": authorization_string, + "Content-Length": str(object=len(content)), + "Content-Type": content_type, + "Date": date, + }, + data=content, + timeout=30, + ) + + assert response.status_code == HTTPStatus.OK + content_type_value = response.headers["Content-Type"].split(sep=";")[0] + assert content_type_value == "image/png" + assert response.content.startswith(_PNG_SIGNATURE) + assert len(response.content) > len(_PNG_SIGNATURE) From 7bf40c46053844d87e02a7d89d070e7bc6ae54b3 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 15:49:39 +0000 Subject: [PATCH 2294/2467] Extract multipart parsing into _parse_multipart_files helper (#2958) Refactor five image validators to eliminate duplicate multipart parsing code by extracting it into a reusable helper with @beartype decoration. Also simplify validate_image_is_image by directly accessing the stream from the files dict. Co-authored-by: Claude Haiku 4.5 --- .../_query_validators/image_validators.py | 80 ++++++++++--------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/src/mock_vws/_query_validators/image_validators.py b/src/mock_vws/_query_validators/image_validators.py index 827c636d2..e2f8f498f 100644 --- a/src/mock_vws/_query_validators/image_validators.py +++ b/src/mock_vws/_query_validators/image_validators.py @@ -7,6 +7,7 @@ from beartype import beartype from PIL import Image +from werkzeug.datastructures import FileStorage, MultiDict from werkzeug.formparser import MultiPartParser from mock_vws._query_validators.exceptions import ( @@ -19,19 +20,19 @@ @beartype -def validate_image_field_given( +def _parse_multipart_files( *, request_headers: Mapping[str, str], request_body: bytes, -) -> None: - """Validate that the image field is given. +) -> MultiDict[str, FileStorage]: + """Parse the multipart body and return the files section. Args: request_headers: The headers sent with the request. request_body: The body of the request. - Raises: - ImageNotGivenError: The image field is not given. + Returns: + The files parsed from the multipart body. """ email_message = EmailMessage() email_message["Content-Type"] = request_headers["Content-Type"] @@ -42,6 +43,28 @@ def validate_image_field_given( boundary=boundary.encode(encoding="utf-8"), content_length=len(request_body), ) + return files + + +@beartype +def validate_image_field_given( + *, + request_headers: Mapping[str, str], + request_body: bytes, +) -> None: + """Validate that the image field is given. + + Args: + request_headers: The headers sent with the request. + request_body: The body of the request. + + Raises: + ImageNotGivenError: The image field is not given. + """ + files = _parse_multipart_files( + request_headers=request_headers, + request_body=request_body, + ) if files.get(key="image") is not None: return @@ -64,14 +87,9 @@ def validate_image_file_size( Raises: RequestEntityTooLargeError: The image file size is too large. """ - email_message = EmailMessage() - email_message["Content-Type"] = request_headers["Content-Type"] - boundary = email_message.get_boundary(failobj="") - parser = MultiPartParser() - _, files = parser.parse( - stream=io.BytesIO(initial_bytes=request_body), - boundary=boundary.encode(encoding="utf-8"), - content_length=len(request_body), + files = _parse_multipart_files( + request_headers=request_headers, + request_body=request_body, ) image_part = files["image"] image_value = image_part.stream.read() @@ -105,14 +123,9 @@ def validate_image_dimensions( BadImageError: The image is given and is not within the maximum width and height limits. """ - email_message = EmailMessage() - email_message["Content-Type"] = request_headers["Content-Type"] - boundary = email_message.get_boundary(failobj="") - parser = MultiPartParser() - _, files = parser.parse( - stream=io.BytesIO(initial_bytes=request_body), - boundary=boundary.encode(encoding="utf-8"), - content_length=len(request_body), + files = _parse_multipart_files( + request_headers=request_headers, + request_body=request_body, ) image_part = files["image"] image_value = image_part.stream.read() @@ -142,14 +155,9 @@ def validate_image_format( Raises: BadImageError: The image is given and is not either a PNG or a JPEG. """ - email_message = EmailMessage() - email_message["Content-Type"] = request_headers["Content-Type"] - boundary = email_message.get_boundary(failobj="") - parser = MultiPartParser() - _, files = parser.parse( - stream=io.BytesIO(initial_bytes=request_body), - boundary=boundary.encode(encoding="utf-8"), - content_length=len(request_body), + files = _parse_multipart_files( + request_headers=request_headers, + request_body=request_body, ) image_part = files["image"] pil_image = Image.open(fp=image_part.stream) @@ -175,17 +183,11 @@ def validate_image_is_image( Raises: BadImageError: Image data is given and it is not an image file. """ - email_message = EmailMessage() - email_message["Content-Type"] = request_headers["Content-Type"] - boundary = email_message.get_boundary(failobj="") - parser = MultiPartParser() - _, files = parser.parse( - stream=io.BytesIO(initial_bytes=request_body), - boundary=boundary.encode(encoding="utf-8"), - content_length=len(request_body), + files = _parse_multipart_files( + request_headers=request_headers, + request_body=request_body, ) - image_part = files["image"] - image_file = image_part.stream + image_file = files["image"].stream try: Image.open(fp=image_file) From 080b86c5a2c087f2fa8f0b557adcc5170dc58bd8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 16:23:34 +0000 Subject: [PATCH 2295/2467] Add comprehensive VuMark instance generation tests (#2954) * Add comprehensive VuMark instance generation tests and mock support - Add tests for PNG, SVG, and PDF output formats (parametrized) - Add tests for invalid Accept header (returns InvalidAcceptHeader 400) - Add tests for empty instance_id (returns InvalidInstanceId 422) - Organise tests into a class with verify_mock_vuforia on the class - Implement Accept header validation and multi-format responses in mock - Add InvalidAcceptHeader and InvalidInstanceId result codes and exceptions - Add minimal SVG and PDF mock response content - Document VuMark image simplification in differences-to-vws.rst Co-Authored-By: Claude Sonnet 4.6 * Fix mypy errors in VuMark tests and Flask handler - Use argnames=/argvalues= keyword arguments in pytest.mark.parametrize - Use keyword argument key= for Flask Headers.get() call Co-Authored-By: Claude Sonnet 4.6 * Add svg and pdf to spelling private dictionary Co-Authored-By: Claude Sonnet 4.6 * Retry delete_target if TargetStatusProcessingError after update_target Vuforia has a race condition where delete_target can raise TargetStatusProcessingError immediately after wait_for_target_processed returns, because update_target(active_flag=False) triggers a brief reprocessing cycle that may not have started by the time the wait completes. Retry once with another wait if this occurs. Co-Authored-By: Claude Sonnet 4.6 * Complete remaining todos: ResultCodes constants, spelling revert, retry - Use ResultCodes.INVALID_ACCEPT_HEADER.value and ResultCodes.INVALID_INSTANCE_ID.value in test assertions instead of raw strings - Reword docstrings/docs to avoid acronyms that trigger the spell checker, and revert spelling_private_dict.txt to its pre-PR state - Replace one-off try/except retry in _delete_all_targets with a proper tenacity-based _delete_target_when_processed helper function Co-Authored-By: Claude Sonnet 4.6 * Extract multipart parsing helper; revert retry changes to separate issue - Extract repeated multipart parsing logic in image_validators.py into _parse_multipart_files helper, eliminating four copies of the same six-line pattern - Revert _delete_target_when_processed retry changes from vuforia_backends.py; tracked separately as https://github.com/VWS-Python/vws-python-mock/issues/2955 Co-Authored-By: Claude Sonnet 4.6 * Simplify valid_accept_types: content type equals accept key The dict values were (bytes, str) tuples where the str was always identical to the dict key (the Accept MIME type). Use a plain dict[str, bytes] and derive the content type directly from `accept`. Co-Authored-By: Claude Sonnet 4.6 * Remove duplicate _parse_multipart_files introduced by merge Both branches independently added the same helper; keep the @beartype-decorated version from main. Co-Authored-By: Claude Sonnet 4.6 * Fix Docker build on PRs; remove duplicate rst section - Pass files: docker-bake.hcl explicitly to docker/bake-action so it uses the local checked-out file rather than fetching from the PR merge ref (refs/pull/*/merge) via HTTPS, which requires auth not available to the bake action - Remove duplicate 'VuMark instance images' section in differences-to-vws.rst introduced by the merge Co-Authored-By: Claude Sonnet 4.6 * Revert docker-build.yml change; Docker failure was transient The Docker build failures are intermittent infrastructure issues, not a systematic problem. The files: docker-bake.hcl change was unnecessary. Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Sonnet 4.6 --- src/mock_vws/_constants.py | 17 +++ src/mock_vws/_flask_server/vws.py | 31 +++- .../mock_web_services_api.py | 47 ++++-- .../_services_validators/exceptions.py | 76 ++++++++++ tests/mock_vws/test_vumark_generation_api.py | 143 ++++++++++++++---- 5 files changed, 268 insertions(+), 46 deletions(-) diff --git a/src/mock_vws/_constants.py b/src/mock_vws/_constants.py index d9af993b5..68bf9375f 100644 --- a/src/mock_vws/_constants.py +++ b/src/mock_vws/_constants.py @@ -11,6 +11,21 @@ b"\xaeB`\x82" ) +VUMARK_SVG = ( + b'' +) + +VUMARK_PDF = ( + b"%PDF-1.4\n" + b"1 0 obj<>endobj\n" + b"2 0 obj<>endobj\n" + b"3 0 obj<>endobj\n" + b"xref\n0 4\n" + b"0000000000 65535 f \n" + b"trailer<>\n" + b"startxref\n9\n%%EOF" +) + @beartype @unique @@ -45,6 +60,8 @@ class ResultCodes(Enum): PROJECT_INACTIVE = "ProjectInactive" INACTIVE_PROJECT = "InactiveProject" TOO_MANY_REQUESTS = "TooManyRequests" + INVALID_ACCEPT_HEADER = "InvalidAcceptHeader" + INVALID_INSTANCE_ID = "InvalidInstanceId" @beartype diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index 99b8c3b8e..664571b6f 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -18,12 +18,20 @@ from flask import Flask, Response, request from pydantic_settings import BaseSettings -from mock_vws._constants import VUMARK_PNG, ResultCodes, TargetStatuses +from mock_vws._constants import ( + VUMARK_PDF, + VUMARK_PNG, + VUMARK_SVG, + ResultCodes, + TargetStatuses, +) from mock_vws._database_matchers import get_database_matching_server_keys from mock_vws._mock_common import json_dump from mock_vws._services_validators import run_services_validators from mock_vws._services_validators.exceptions import ( FailError, + InvalidAcceptHeaderError, + InvalidInstanceIdError, TargetStatusNotSuccessError, TargetStatusProcessingError, ValidatorError, @@ -351,10 +359,27 @@ def generate_vumark_instance(target_id: str) -> Response: """ # ``target_id`` is validated by request validators. del target_id + + accept = request.headers.get(key="Accept", default="") + valid_accept_types: dict[str, bytes] = { + "image/png": VUMARK_PNG, + "image/svg+xml": VUMARK_SVG, + "application/pdf": VUMARK_PDF, + } + if accept not in valid_accept_types: + raise InvalidAcceptHeaderError + + request_json = json.loads(s=request.data) + instance_id = request_json.get("instance_id", "") + if not instance_id: + raise InvalidInstanceIdError + + response_body = valid_accept_types[accept] + content_type = accept date = email.utils.formatdate(timeval=None, localtime=False, usegmt=True) headers = { "Connection": "keep-alive", - "Content-Type": "image/png", + "Content-Type": content_type, "server": "envoy", "Date": date, "x-envoy-upstream-service-time": "5", @@ -364,7 +389,7 @@ def generate_vumark_instance(target_id: str) -> Response: } return Response( status=HTTPStatus.OK, - response=VUMARK_PNG, + response=response_body, headers=headers, ) diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index 303d4a2b4..320b776ef 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -18,12 +18,20 @@ from beartype import BeartypeConf, beartype from requests.models import PreparedRequest -from mock_vws._constants import VUMARK_PNG, ResultCodes, TargetStatuses +from mock_vws._constants import ( + VUMARK_PDF, + VUMARK_PNG, + VUMARK_SVG, + ResultCodes, + TargetStatuses, +) from mock_vws._database_matchers import get_database_matching_server_keys from mock_vws._mock_common import Route, json_dump from mock_vws._services_validators import run_services_validators from mock_vws._services_validators.exceptions import ( FailError, + InvalidAcceptHeaderError, + InvalidInstanceIdError, TargetStatusNotSuccessError, TargetStatusProcessingError, ValidatorError, @@ -295,14 +303,33 @@ def generate_vumark_instance( self, request: PreparedRequest ) -> _ResponseType: """Generate a VuMark instance.""" - run_services_validators( - request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, - databases=self._target_manager.databases, - ) + valid_accept_types: dict[str, bytes] = { + "image/png": VUMARK_PNG, + "image/svg+xml": VUMARK_SVG, + "application/pdf": VUMARK_PDF, + } + try: + run_services_validators( + request_headers=request.headers, + request_body=_body_bytes(request=request), + request_method=request.method or "", + request_path=request.path_url, + databases=self._target_manager.databases, + ) + + accept = dict(request.headers).get("Accept", "") + if accept not in valid_accept_types: + raise InvalidAcceptHeaderError + + request_json = json.loads(s=_body_bytes(request=request)) + instance_id = request_json.get("instance_id", "") + if not instance_id: + raise InvalidInstanceIdError + except ValidatorError as exc: + return exc.status_code, exc.headers, exc.response_text + response_body = valid_accept_types[accept] + content_type = accept date = email.utils.formatdate( timeval=None, localtime=False, @@ -310,7 +337,7 @@ def generate_vumark_instance( ) headers = { "Connection": "keep-alive", - "Content-Type": "image/png", + "Content-Type": content_type, "Date": date, "server": "envoy", "x-envoy-upstream-service-time": "5", @@ -318,7 +345,7 @@ def generate_vumark_instance( "x-aws-region": "us-east-2, us-west-2", "x-content-type-options": "nosniff", } - return HTTPStatus.OK, headers, VUMARK_PNG + return HTTPStatus.OK, headers, response_body @route(path_pattern="/summary", http_methods={HTTPMethod.GET}) def database_summary(self, request: PreparedRequest) -> _ResponseType: diff --git a/src/mock_vws/_services_validators/exceptions.py b/src/mock_vws/_services_validators/exceptions.py index 4bbc5dab8..f7cbe2439 100644 --- a/src/mock_vws/_services_validators/exceptions.py +++ b/src/mock_vws/_services_validators/exceptions.py @@ -530,6 +530,82 @@ def __init__(self) -> None: } +@beartype +class InvalidAcceptHeaderError(ValidatorError): + """Exception raised when an unsupported Accept header is given.""" + + def __init__(self) -> None: + """ + Attributes: + status_code: The status code to use in a response if this is + raised. + response_text: The response text to use in a response if this + is + raised. + """ + super().__init__() + self.status_code = HTTPStatus.BAD_REQUEST + body = { + "transaction_id": uuid.uuid4().hex, + "result_code": ResultCodes.INVALID_ACCEPT_HEADER.value, + } + self.response_text = json_dump(body=body) + date = email.utils.formatdate( + timeval=None, + localtime=False, + usegmt=True, + ) + self.headers = { + "Connection": "keep-alive", + "Content-Type": "application/json", + "server": "envoy", + "Date": date, + "x-envoy-upstream-service-time": "5", + "Content-Length": str(object=len(self.response_text)), + "strict-transport-security": "max-age=31536000", + "x-aws-region": "us-east-2, us-west-2", + "x-content-type-options": "nosniff", + } + + +@beartype +class InvalidInstanceIdError(ValidatorError): + """Exception raised when an invalid instance_id is given.""" + + def __init__(self) -> None: + """ + Attributes: + status_code: The status code to use in a response if this is + raised. + response_text: The response text to use in a response if this + is + raised. + """ + super().__init__() + self.status_code = HTTPStatus.UNPROCESSABLE_ENTITY + body = { + "transaction_id": uuid.uuid4().hex, + "result_code": ResultCodes.INVALID_INSTANCE_ID.value, + } + self.response_text = json_dump(body=body) + date = email.utils.formatdate( + timeval=None, + localtime=False, + usegmt=True, + ) + self.headers = { + "Connection": "keep-alive", + "Content-Type": "application/json", + "server": "envoy", + "Date": date, + "x-envoy-upstream-service-time": "5", + "Content-Length": str(object=len(self.response_text)), + "strict-transport-security": "max-age=31536000", + "x-aws-region": "us-east-2, us-west-2", + "x-content-type-options": "nosniff", + } + + @beartype class TargetStatusProcessingError(ValidatorError): """Exception raised when trying to delete a target which is processing.""" diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index f2ec64527..2b589ebd6 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -8,53 +8,130 @@ import requests from vws_auth_tools import authorization_header, rfc_1123_date +from mock_vws._constants import ResultCodes from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase _VWS_HOST = "https://vws.vuforia.com" _PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" +_PDF_SIGNATURE = b"%PDF" +_SVG_START = b"<" + + +def _make_vumark_request( + *, + vumark_vuforia_database: VuMarkVuforiaDatabase, + instance_id: str, + accept: str, +) -> requests.Response: + """Send a VuMark instance generation request and return the + response. + """ + request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" + content_type = "application/json" + content = json.dumps(obj={"instance_id": instance_id}).encode( + encoding="utf-8" + ) + date = rfc_1123_date() + authorization_string = authorization_header( + access_key=vumark_vuforia_database.server_access_key, + secret_key=vumark_vuforia_database.server_secret_key, + method=HTTPMethod.POST, + content=content, + content_type=content_type, + date=date, + request_path=request_path, + ) + + return requests.post( + url=_VWS_HOST + request_path, + headers={ + "Accept": accept, + "Authorization": authorization_string, + "Content-Length": str(object=len(content)), + "Content-Type": content_type, + "Date": date, + }, + data=content, + timeout=30, + ) @pytest.mark.usefixtures("verify_mock_vuforia") class TestGenerateInstance: """Tests for the VuMark instance generation endpoint.""" + @pytest.mark.parametrize( + argnames=("accept", "expected_content_type", "expected_signature"), + argvalues=[ + pytest.param("image/png", "image/png", _PNG_SIGNATURE, id="png"), + pytest.param( + "image/svg+xml", + "image/svg+xml", + _SVG_START, + id="svg", + ), + pytest.param( + "application/pdf", + "application/pdf", + _PDF_SIGNATURE, + id="pdf", + ), + ], + ) @staticmethod - def test_generate_instance_success( + def test_generate_instance_format( + accept: str, + expected_content_type: str, + expected_signature: bytes, vumark_vuforia_database: VuMarkVuforiaDatabase, ) -> None: - """A VuMark instance can be generated with valid template settings.""" - target_id = vumark_vuforia_database.target_id - request_path = f"/targets/{target_id}/instances" - content_type = "application/json" - generated_instance_id = uuid4().hex - body_dict = {"instance_id": generated_instance_id} - content = json.dumps(obj=body_dict).encode(encoding="utf-8") - date = rfc_1123_date() - authorization_string = authorization_header( - access_key=vumark_vuforia_database.server_access_key, - secret_key=vumark_vuforia_database.server_secret_key, - method=HTTPMethod.POST, - content=content, - content_type=content_type, - date=date, - request_path=request_path, + """A VuMark instance can be generated in the requested format.""" + response = _make_vumark_request( + vumark_vuforia_database=vumark_vuforia_database, + instance_id=uuid4().hex, + accept=accept, ) - response = requests.post( - url=_VWS_HOST + request_path, - headers={ - "Accept": "image/png", - "Authorization": authorization_string, - "Content-Length": str(object=len(content)), - "Content-Type": content_type, - "Date": date, - }, - data=content, - timeout=30, + assert response.status_code == HTTPStatus.OK + assert ( + response.headers["Content-Type"].split(sep=";")[0] + == expected_content_type ) + assert response.content.strip().startswith(expected_signature) + assert len(response.content) > len(expected_signature) - assert response.status_code == HTTPStatus.OK - content_type_value = response.headers["Content-Type"].split(sep=";")[0] - assert content_type_value == "image/png" - assert response.content.startswith(_PNG_SIGNATURE) - assert len(response.content) > len(_PNG_SIGNATURE) + @staticmethod + def test_invalid_accept_header( + vumark_vuforia_database: VuMarkVuforiaDatabase, + ) -> None: + """An unsupported Accept header returns an error.""" + response = _make_vumark_request( + vumark_vuforia_database=vumark_vuforia_database, + instance_id=uuid4().hex, + accept="text/plain", + ) + + assert response.status_code == HTTPStatus.BAD_REQUEST + response_json = response.json() + assert ( + response_json["result_code"] + == ResultCodes.INVALID_ACCEPT_HEADER.value + ) + + @staticmethod + def test_empty_instance_id( + vumark_vuforia_database: VuMarkVuforiaDatabase, + ) -> None: + """An empty instance_id returns InvalidInstanceId.""" + response = _make_vumark_request( + vumark_vuforia_database=vumark_vuforia_database, + instance_id="", + accept="image/png", + ) + + assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + response_json = response.json() + assert ( + response_json["result_code"] + == ResultCodes.INVALID_INSTANCE_ID.value + ) From 1a57511c5448cdedb10067276ede206aa070d7ee Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:05:51 +0000 Subject: [PATCH 2296/2467] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4c0fec1f8..d79fce6ad 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.18.1 +------------ + + 2026.02.18 ---------- From f6644a927d64e30eaef9e87bb53b0bac8ff2826c Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 23:25:15 +0000 Subject: [PATCH 2297/2467] Add VuMark to endpoint fixture for auth testing (#2960) * Add VuMark endpoint to parametrized fixture for auth testing Adds a vumark_generate_instance fixture to the endpoint parametrization, allowing all auth tests (missing header, malformed header, bad keys, etc.) to also cover the VuMark instance generation endpoint. Updates the Endpoint class to support endpoints with binary responses by making successful_headers_result_code optional. Updates test_date_header.py to handle VuMark's binary success response. Co-Authored-By: Claude Haiku 4.5 * Fix invalid JSON test failure for VuMark endpoint Real Vuforia returns result_code 'BadRequest' (not 'Fail') when invalid JSON is sent to the VuMark instance generation endpoint. Adds the BAD_REQUEST result code, a BadRequestError exception, and updates validate_json() to raise the correct error based on the request path. Updates the test to expect BadRequest for the /instances endpoint. Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Haiku 4.5 --- src/mock_vws/_constants.py | 1 + src/mock_vws/_services_validators/__init__.py | 2 +- .../_services_validators/exceptions.py | 40 ++++++++++++++++ .../_services_validators/json_validators.py | 11 ++++- tests/conftest.py | 1 + tests/mock_vws/fixtures/prepared_requests.py | 48 +++++++++++++++++++ tests/mock_vws/test_date_header.py | 12 +++++ tests/mock_vws/test_invalid_json.py | 7 ++- tests/mock_vws/utils/__init__.py | 2 +- 9 files changed, 119 insertions(+), 5 deletions(-) diff --git a/src/mock_vws/_constants.py b/src/mock_vws/_constants.py index 68bf9375f..52c88bb9b 100644 --- a/src/mock_vws/_constants.py +++ b/src/mock_vws/_constants.py @@ -62,6 +62,7 @@ class ResultCodes(Enum): TOO_MANY_REQUESTS = "TooManyRequests" INVALID_ACCEPT_HEADER = "InvalidAcceptHeader" INVALID_INSTANCE_ID = "InvalidInstanceId" + BAD_REQUEST = "BadRequest" @beartype diff --git a/src/mock_vws/_services_validators/__init__.py b/src/mock_vws/_services_validators/__init__.py index 44487b365..03a39bd1c 100644 --- a/src/mock_vws/_services_validators/__init__.py +++ b/src/mock_vws/_services_validators/__init__.py @@ -103,7 +103,7 @@ def run_services_validators( validate_date_format(request_headers=request_headers) validate_date_in_range(request_headers=request_headers) - validate_json(request_body=request_body) + validate_json(request_body=request_body, request_path=request_path) validate_keys( request_body=request_body, diff --git a/src/mock_vws/_services_validators/exceptions.py b/src/mock_vws/_services_validators/exceptions.py index f7cbe2439..a722f29ab 100644 --- a/src/mock_vws/_services_validators/exceptions.py +++ b/src/mock_vws/_services_validators/exceptions.py @@ -184,6 +184,46 @@ def __init__(self, *, status_code: HTTPStatus) -> None: } +@beartype +class BadRequestError(ValidatorError): + """Exception raised when Vuforia returns a response with a result code + 'BadRequest'. + """ + + def __init__(self) -> None: + """ + Attributes: + status_code: The status code to use in a response if this is + raised. + response_text: The response text to use in a response if this + is + raised. + """ + super().__init__() + self.status_code = HTTPStatus.BAD_REQUEST + body = { + "transaction_id": uuid.uuid4().hex, + "result_code": ResultCodes.BAD_REQUEST.value, + } + self.response_text = json_dump(body=body) + date = email.utils.formatdate( + timeval=None, + localtime=False, + usegmt=True, + ) + self.headers = { + "Connection": "keep-alive", + "Content-Type": "application/json", + "server": "envoy", + "Date": date, + "x-envoy-upstream-service-time": "5", + "Content-Length": str(object=len(self.response_text)), + "strict-transport-security": "max-age=31536000", + "x-aws-region": "us-east-2, us-west-2", + "x-content-type-options": "nosniff", + } + + @beartype class MetadataTooLargeError(ValidatorError): """Exception raised when Vuforia returns a response with a result code diff --git a/src/mock_vws/_services_validators/json_validators.py b/src/mock_vws/_services_validators/json_validators.py index 9d04eec29..4e0549cd0 100644 --- a/src/mock_vws/_services_validators/json_validators.py +++ b/src/mock_vws/_services_validators/json_validators.py @@ -8,6 +8,7 @@ from beartype import beartype from mock_vws._services_validators.exceptions import ( + BadRequestError, FailError, UnnecessaryRequestBodyError, ) @@ -43,14 +44,18 @@ def validate_body_given(*, request_body: bytes, request_method: str) -> None: @beartype -def validate_json(*, request_body: bytes) -> None: +def validate_json(*, request_body: bytes, request_path: str) -> None: """Validate that any given body is valid JSON. Args: request_body: The body of the request. + request_path: The path of the request. Raises: - FailError: The request body includes invalid JSON. + BadRequestError: The request body includes invalid JSON for the + VuMark instance generation endpoint. + FailError: The request body includes invalid JSON for other + endpoints. """ if not request_body: return @@ -59,4 +64,6 @@ def validate_json(*, request_body: bytes) -> None: json.loads(s=request_body.decode()) except JSONDecodeError as exc: _LOGGER.warning(msg="The request body is not valid JSON.") + if request_path.endswith("/instances"): + raise BadRequestError from exc raise FailError(status_code=HTTPStatus.BAD_REQUEST) from exc diff --git a/tests/conftest.py b/tests/conftest.py index 64ef1427f..76b970470 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -85,6 +85,7 @@ def target_id( "target_summary", "update_target", "query", + "vumark_generate_instance", ], ) def endpoint(request: pytest.FixtureRequest) -> Endpoint: diff --git a/tests/mock_vws/fixtures/prepared_requests.py b/tests/mock_vws/fixtures/prepared_requests.py index 6a1d93aa8..724ea8e27 100644 --- a/tests/mock_vws/fixtures/prepared_requests.py +++ b/tests/mock_vws/fixtures/prepared_requests.py @@ -5,6 +5,7 @@ import json from http import HTTPMethod, HTTPStatus from typing import Any +from uuid import uuid4 import pytest from urllib3.filepost import encode_multipart_formdata @@ -13,6 +14,7 @@ from mock_vws._constants import ResultCodes from mock_vws.database import VuforiaDatabase +from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase from tests.mock_vws.utils import Endpoint from tests.mock_vws.utils.retries import RETRY_ON_TOO_MANY_REQUESTS @@ -451,3 +453,49 @@ def query( access_key=access_key, secret_key=secret_key, ) + + +@pytest.fixture +def vumark_generate_instance( + vumark_vuforia_database: VuMarkVuforiaDatabase, +) -> Endpoint: + """Return details of the endpoint for generating a VuMark instance.""" + request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" + content_type = "application/json" + method = HTTPMethod.POST + content = json.dumps(obj={"instance_id": uuid4().hex}).encode( + encoding="utf-8" + ) + date = rfc_1123_date() + + access_key = vumark_vuforia_database.server_access_key + secret_key = vumark_vuforia_database.server_secret_key + authorization_string = authorization_header( + access_key=access_key, + secret_key=secret_key, + method=method, + content=content, + content_type=content_type, + date=date, + request_path=request_path, + ) + + headers = { + "Accept": "image/png", + "Authorization": authorization_string, + "Content-Length": str(object=len(content)), + "Content-Type": content_type, + "Date": date, + } + + return Endpoint( + successful_headers_status_code=HTTPStatus.OK, + successful_headers_result_code=None, + base_url=VWS_HOST, + path_url=request_path, + method=method, + headers=headers, + data=content, + access_key=access_key, + secret_key=secret_key, + ) diff --git a/tests/mock_vws/test_date_header.py b/tests/mock_vws/test_date_header.py index eea178ac3..d5e5f0b45 100644 --- a/tests/mock_vws/test_date_header.py +++ b/tests/mock_vws/test_date_header.py @@ -381,6 +381,12 @@ def test_date_in_range_after(endpoint: Endpoint) -> None: assert_query_success(response=response) return + if endpoint.successful_headers_result_code is None: + assert ( + response.status_code == endpoint.successful_headers_status_code + ) + return + assert_vws_response( response=response, status_code=endpoint.successful_headers_status_code, @@ -445,6 +451,12 @@ def test_date_in_range_before(endpoint: Endpoint) -> None: assert_query_success(response=response) return + if endpoint.successful_headers_result_code is None: + assert ( + response.status_code == endpoint.successful_headers_status_code + ) + return + assert_vws_response( response=response, status_code=endpoint.successful_headers_status_code, diff --git a/tests/mock_vws/test_invalid_json.py b/tests/mock_vws/test_invalid_json.py index 55d01ff6d..35b3253cd 100644 --- a/tests/mock_vws/test_invalid_json.py +++ b/tests/mock_vws/test_invalid_json.py @@ -75,10 +75,15 @@ def test_invalid_json(endpoint: Endpoint) -> None: assert_valid_date_header(response=response) if takes_json_data: + expected_result_code = ( + ResultCodes.BAD_REQUEST + if endpoint.path_url.endswith("/instances") + else ResultCodes.FAIL + ) assert_vws_failure( response=response, status_code=HTTPStatus.BAD_REQUEST, - result_code=ResultCodes.FAIL, + result_code=expected_result_code, ) return diff --git a/tests/mock_vws/utils/__init__.py b/tests/mock_vws/utils/__init__.py index c554e4571..d4bfa0014 100644 --- a/tests/mock_vws/utils/__init__.py +++ b/tests/mock_vws/utils/__init__.py @@ -47,7 +47,7 @@ class Endpoint: method: str headers: Mapping[str, str] data: bytes | str - successful_headers_result_code: ResultCodes + successful_headers_result_code: ResultCodes | None successful_headers_status_code: int access_key: str secret_key: str From f3079ae0ae8689fd43963bf372b772afeffd313f Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Wed, 18 Feb 2026 23:26:49 +0000 Subject: [PATCH 2298/2467] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d79fce6ad..6146edbec 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.18.2 +------------ + + 2026.02.18.1 ------------ From 7206757293d98fbc58921aca7670a436ffd1f07f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 20 Feb 2026 15:02:19 +0000 Subject: [PATCH 2299/2467] Rename Target and TargetDict to ImageTarget and ImageTargetDict (#2964) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rename Target and TargetDict classes to ImageTarget and ImageTargetDict Rename class Target → ImageTarget and class TargetDict → ImageTargetDict throughout the codebase. Updates all imports and usages in source code and tests. Co-Authored-By: Claude Haiku 4.5 * Update docs to reference ImageTarget instead of Target Co-Authored-By: Claude Haiku 4.5 --------- Co-authored-by: Claude Haiku 4.5 --- docs/source/mock-api-reference.rst | 2 +- src/mock_vws/_flask_server/target_manager.py | 4 ++-- src/mock_vws/_flask_server/vws.py | 4 ++-- .../mock_web_services_api.py | 4 ++-- src/mock_vws/database.py | 23 +++++++++++-------- src/mock_vws/target.py | 8 +++---- tests/mock_vws/fixtures/vuforia_backends.py | 4 ++-- tests/mock_vws/test_requests_mock_usage.py | 6 ++--- tests/mock_vws/test_target_validators.py | 4 ++-- 9 files changed, 31 insertions(+), 28 deletions(-) diff --git a/docs/source/mock-api-reference.rst b/docs/source/mock-api-reference.rst index ecb44f90b..798061cc4 100644 --- a/docs/source/mock-api-reference.rst +++ b/docs/source/mock-api-reference.rst @@ -24,7 +24,7 @@ API Reference :members: :undoc-members: -.. autoclass:: mock_vws.target.Target +.. autoclass:: mock_vws.target.ImageTarget Image matchers -------------- diff --git a/src/mock_vws/_flask_server/target_manager.py b/src/mock_vws/_flask_server/target_manager.py index e317549b1..1e6f6f8f1 100644 --- a/src/mock_vws/_flask_server/target_manager.py +++ b/src/mock_vws/_flask_server/target_manager.py @@ -14,7 +14,7 @@ from mock_vws.database import VuforiaDatabase from mock_vws.states import States -from mock_vws.target import Target +from mock_vws.target import ImageTarget from mock_vws.target_manager import TargetManager from mock_vws.target_raters import ( BrisqueTargetTrackingRater, @@ -202,7 +202,7 @@ def create_target(database_name: str) -> Response: settings = TargetManagerSettings.model_validate(obj={}) target_tracking_rater = settings.target_rater.to_target_rater() - target = Target( + target = ImageTarget( name=request_json["name"], width=request_json["width"], image_value=image_bytes, diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index 664571b6f..064b1348d 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -42,7 +42,7 @@ ImageMatcher, StructuralSimilarityMatcher, ) -from mock_vws.target import Target +from mock_vws.target import ImageTarget from mock_vws.target_raters import ( HardcodedTargetTrackingRater, ) @@ -192,7 +192,7 @@ def add_target() -> Response: # This rater is not used. target_tracking_rater = HardcodedTargetTrackingRater(rating=1) - new_target = Target( + new_target = ImageTarget( name=name, width=request_json["width"], image_value=base64.b64decode(s=request_json["image"]), diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index 320b776ef..8c0c770d6 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -37,7 +37,7 @@ ValidatorError, ) from mock_vws.image_matchers import ImageMatcher -from mock_vws.target import Target +from mock_vws.target import ImageTarget from mock_vws.target_manager import TargetManager from mock_vws.target_raters import TargetTrackingRater @@ -187,7 +187,7 @@ def add_target(self, request: PreparedRequest) -> _ResponseType: application_metadata = request_json.get("application_metadata") - new_target = Target( + new_target = ImageTarget( name=request_json["name"], width=request_json["width"], image_value=base64.b64decode(s=request_json["image"]), diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py index 2e28a9f61..e166281e4 100644 --- a/src/mock_vws/database.py +++ b/src/mock_vws/database.py @@ -9,7 +9,7 @@ from mock_vws._constants import TargetStatuses from mock_vws.states import States -from mock_vws.target import Target, TargetDict +from mock_vws.target import ImageTarget, ImageTargetDict @beartype @@ -22,7 +22,7 @@ class DatabaseDict(TypedDict): client_access_key: str client_secret_key: str state_name: str - targets: Iterable[TargetDict] + targets: Iterable[ImageTargetDict] @beartype @@ -61,7 +61,10 @@ class VuforiaDatabase: # ``frozen=True`` while still being able to keep the interface we want. # In particular, we might want to inspect the ``database`` object's targets # as they change via API requests. - targets: set[Target] = field(default_factory=set[Target], hash=False) + targets: set[ImageTarget] = field( + default_factory=set[ImageTarget], + hash=False, + ) state: States = States.WORKING request_quota: int = 100000 @@ -84,7 +87,7 @@ def to_dict(self) -> DatabaseDict: "targets": targets, } - def get_target(self, target_id: str) -> Target: + def get_target(self, target_id: str) -> ImageTarget: """Return a target from the database with the given ID.""" (target,) = ( target for target in self.targets if target.target_id == target_id @@ -102,18 +105,18 @@ def from_dict(cls, database_dict: DatabaseDict) -> Self: client_secret_key=database_dict["client_secret_key"], state=States[database_dict["state_name"]], targets={ - Target.from_dict(target_dict=target_dict) + ImageTarget.from_dict(target_dict=target_dict) for target_dict in database_dict["targets"] }, ) @property - def not_deleted_targets(self) -> set[Target]: + def not_deleted_targets(self) -> set[ImageTarget]: """All targets which have not been deleted.""" return {target for target in self.targets if not target.delete_date} @property - def active_targets(self) -> set[Target]: + def active_targets(self) -> set[ImageTarget]: """All active targets.""" return { target @@ -123,7 +126,7 @@ def active_targets(self) -> set[Target]: } @property - def inactive_targets(self) -> set[Target]: + def inactive_targets(self) -> set[ImageTarget]: """All inactive targets.""" return { target @@ -133,7 +136,7 @@ def inactive_targets(self) -> set[Target]: } @property - def failed_targets(self) -> set[Target]: + def failed_targets(self) -> set[ImageTarget]: """All failed targets.""" return { target @@ -142,7 +145,7 @@ def failed_targets(self) -> set[Target]: } @property - def processing_targets(self) -> set[Target]: + def processing_targets(self) -> set[ImageTarget]: """All processing targets.""" return { target diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py index afd8f20b1..a17e8c140 100644 --- a/src/mock_vws/target.py +++ b/src/mock_vws/target.py @@ -19,7 +19,7 @@ ) -class TargetDict(TypedDict): +class ImageTargetDict(TypedDict): """A dictionary type which represents a target.""" name: str @@ -50,7 +50,7 @@ def _time_now() -> datetime.datetime: @beartype(conf=BeartypeConf(is_pep484_tower=True)) @dataclass(frozen=True, eq=True) -class Target: +class ImageTarget: """ A Vuforia Target as managed in https://developer.vuforia.com/target-manager. @@ -145,7 +145,7 @@ def tracking_rating(self) -> int: return self._post_processing_target_rating @classmethod - def from_dict(cls, target_dict: TargetDict) -> Self: + def from_dict(cls, target_dict: ImageTargetDict) -> Self: """Load a target from a dictionary.""" timezone = ZoneInfo(key="GMT") name = target_dict["name"] @@ -187,7 +187,7 @@ def from_dict(cls, target_dict: TargetDict) -> Self: target_tracking_rater=target_tracking_rater, ) - def to_dict(self) -> TargetDict: + def to_dict(self) -> ImageTargetDict: """Dump a target to a dictionary which can be loaded as JSON.""" delete_date: str | None = None if self.delete_date: diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index c0e95b08c..231468ed6 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -21,7 +21,7 @@ from mock_vws._flask_server.vws import VWS_FLASK_APP from mock_vws.database import VuforiaDatabase from mock_vws.states import States -from mock_vws.target import Target +from mock_vws.target import ImageTarget from mock_vws.target_raters import HardcodedTargetTrackingRater from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase from tests.mock_vws.utils import make_image_file @@ -67,7 +67,7 @@ def _vumark_database( vumark_vuforia_database: VuMarkVuforiaDatabase, ) -> VuforiaDatabase: """Return a database with a target for VuMark instance generation.""" - vumark_target = Target( + vumark_target = ImageTarget( active_flag=True, application_metadata=None, image_value=make_image_file( diff --git a/tests/mock_vws/test_requests_mock_usage.py b/tests/mock_vws/test_requests_mock_usage.py index 4b9cf1f5c..81b297cd1 100644 --- a/tests/mock_vws/test_requests_mock_usage.py +++ b/tests/mock_vws/test_requests_mock_usage.py @@ -18,7 +18,7 @@ from mock_vws import MissingSchemeError, MockVWS from mock_vws.database import VuforiaDatabase from mock_vws.image_matchers import ExactMatcher, StructuralSimilarityMatcher -from mock_vws.target import Target +from mock_vws.target import ImageTarget from tests.mock_vws.utils import Endpoint from tests.mock_vws.utils.usage_test_helpers import ( processing_time_seconds, @@ -400,7 +400,7 @@ def test_to_dict(high_quality_image: io.BytesIO) -> None: # The dictionary is JSON dump-able assert json.dumps(obj=target_dict) - new_target = Target.from_dict(target_dict=target_dict) + new_target = ImageTarget.from_dict(target_dict=target_dict) assert new_target == target @staticmethod @@ -436,7 +436,7 @@ def test_to_dict_deleted(high_quality_image: io.BytesIO) -> None: # The dictionary is JSON dump-able assert json.dumps(obj=target_dict) - new_target = Target.from_dict(target_dict=target_dict) + new_target = ImageTarget.from_dict(target_dict=target_dict) assert new_target.delete_date == target.delete_date diff --git a/tests/mock_vws/test_target_validators.py b/tests/mock_vws/test_target_validators.py index 04b147422..4e49ce89c 100644 --- a/tests/mock_vws/test_target_validators.py +++ b/tests/mock_vws/test_target_validators.py @@ -10,14 +10,14 @@ validate_target_id_exists, ) from mock_vws.database import VuforiaDatabase -from mock_vws.target import Target +from mock_vws.target import ImageTarget from mock_vws.target_raters import HardcodedTargetTrackingRater from tests.mock_vws.utils import make_image_file def _database_with_target(*, target_id: str) -> VuforiaDatabase: """Create a database containing one target with the given ID.""" - target = Target( + target = ImageTarget( active_flag=True, application_metadata=None, image_value=make_image_file( From 6545b7f03b3dfe8454cfccf09629ff122cd87d86 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 20 Feb 2026 15:18:14 +0000 Subject: [PATCH 2300/2467] Rename DatabaseDict to CloudDatabaseDict (#2967) * Rename DatabaseDict to CloudDatabaseDict DatabaseDict represents a cloud database in the target manager. This rename clarifies that there are three database types: Device, Cloud, and VuMark. Co-Authored-By: Claude Haiku 4.5 * Rename VuforiaDatabase to CloudDatabase Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Haiku 4.5 --- README.rst | 4 +- docs/source/basic-example.rst | 4 +- docs/source/mock-api-reference.rst | 4 +- pyproject.toml | 2 +- src/mock_vws/_database_matchers.py | 10 ++-- src/mock_vws/_flask_server/target_manager.py | 6 +-- src/mock_vws/_flask_server/vwq.py | 6 +-- src/mock_vws/_flask_server/vws.py | 6 +-- src/mock_vws/_query_tools.py | 4 +- src/mock_vws/_query_validators/__init__.py | 4 +- .../_query_validators/auth_validators.py | 6 +-- .../project_state_validators.py | 4 +- .../_requests_mock_server/decorators.py | 4 +- src/mock_vws/_services_validators/__init__.py | 4 +- .../_services_validators/auth_validators.py | 6 +-- .../_services_validators/name_validators.py | 6 +-- .../project_state_validators.py | 4 +- .../_services_validators/target_validators.py | 4 +- src/mock_vws/database.py | 10 ++-- src/mock_vws/target_manager.py | 10 ++-- tests/conftest.py | 10 ++-- tests/mock_vws/fixtures/credentials.py | 28 +++++------ tests/mock_vws/fixtures/prepared_requests.py | 24 +++++----- tests/mock_vws/fixtures/vuforia_backends.py | 46 +++++++++---------- tests/mock_vws/test_authorization_header.py | 10 ++-- tests/mock_vws/test_database_summary.py | 6 +-- tests/mock_vws/test_docker.py | 4 +- tests/mock_vws/test_flask_app_usage.py | 38 +++++++-------- tests/mock_vws/test_query.py | 44 +++++++++--------- tests/mock_vws/test_requests_mock_usage.py | 44 +++++++++--------- tests/mock_vws/test_target_summary.py | 4 +- tests/mock_vws/test_target_validators.py | 12 ++--- tests/mock_vws/test_vumark_generation_api.py | 10 ++-- tests/mock_vws/utils/usage_test_helpers.py | 4 +- 34 files changed, 196 insertions(+), 196 deletions(-) diff --git a/README.rst b/README.rst index cd8434d30..bf2684105 100644 --- a/README.rst +++ b/README.rst @@ -26,10 +26,10 @@ This requires Python |minimum-python-version|\+. import requests from mock_vws import MockVWS - from mock_vws.database import VuforiaDatabase + from mock_vws.database import CloudDatabase with MockVWS() as mock: - database = VuforiaDatabase() + database = CloudDatabase() mock.add_database(database=database) # This will use the Vuforia mock. requests.get(url="https://vws.vuforia.com/summary", timeout=30) diff --git a/docs/source/basic-example.rst b/docs/source/basic-example.rst index 35c12e200..f3aec76c3 100644 --- a/docs/source/basic-example.rst +++ b/docs/source/basic-example.rst @@ -7,10 +7,10 @@ Using the mock redirects requests to Vuforia made with `requests`_ to an in-memo import requests from mock_vws import MockVWS - from mock_vws.database import VuforiaDatabase + from mock_vws.database import CloudDatabase with MockVWS() as mock: - database = VuforiaDatabase() + database = CloudDatabase() mock.add_database(database=database) # This will use the Vuforia mock. requests.get(url="https://vws.vuforia.com/summary", timeout=30) diff --git a/docs/source/mock-api-reference.rst b/docs/source/mock-api-reference.rst index 798061cc4..ee0215fe0 100644 --- a/docs/source/mock-api-reference.rst +++ b/docs/source/mock-api-reference.rst @@ -11,11 +11,11 @@ API Reference :members: :undoc-members: -.. Many parts of the VuforiaDatabase API are used for the Flask target +.. Many parts of the CloudDatabase API are used for the Flask target .. database app, but Python users are not expected to use them. .. Therefore, they are not documented. -.. autoclass:: mock_vws.database.VuforiaDatabase +.. autoclass:: mock_vws.database.CloudDatabase :members: :undoc-members: :exclude-members: to_dict, get_target, from_dict, not_deleted_targets, active_targets, inactive_targets, failed_targets, processing_targets diff --git a/pyproject.toml b/pyproject.toml index f4e49c096..466ed3297 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -450,7 +450,7 @@ ignore_names = [ # pydantic-settings "model_config", # Used in TYPE_CHECKING for type hints - "DatabaseDict", + "CloudDatabaseDict", "VuMarkDatabaseDict", ] # Duplicate some of .gitignore diff --git a/src/mock_vws/_database_matchers.py b/src/mock_vws/_database_matchers.py index 899cf71b2..0e6a8c76d 100644 --- a/src/mock_vws/_database_matchers.py +++ b/src/mock_vws/_database_matchers.py @@ -5,7 +5,7 @@ from beartype import beartype from vws_auth_tools import authorization_header -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase @beartype @@ -15,8 +15,8 @@ def get_database_matching_client_keys( request_body: bytes | None, request_method: str, request_path: str, - databases: Iterable[VuforiaDatabase], -) -> VuforiaDatabase: + databases: Iterable[CloudDatabase], +) -> CloudDatabase: """Return the first of the given databases which is being accessed by the given client request. @@ -64,8 +64,8 @@ def get_database_matching_server_keys( request_body: bytes | None, request_method: str, request_path: str, - databases: Iterable[VuforiaDatabase], -) -> VuforiaDatabase: + databases: Iterable[CloudDatabase], +) -> CloudDatabase: """Return the first of the given databases which is being accessed by the given server request. diff --git a/src/mock_vws/_flask_server/target_manager.py b/src/mock_vws/_flask_server/target_manager.py index 1e6f6f8f1..06948fd5c 100644 --- a/src/mock_vws/_flask_server/target_manager.py +++ b/src/mock_vws/_flask_server/target_manager.py @@ -12,7 +12,7 @@ from flask import Flask, Response, request from pydantic_settings import BaseSettings -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from mock_vws.states import States from mock_vws.target import ImageTarget from mock_vws.target_manager import TargetManager @@ -133,7 +133,7 @@ def create_database() -> Response: :status 201: The database has been successfully created. """ - random_database = VuforiaDatabase() + random_database = CloudDatabase() request_json = json.loads(s=request.data) server_access_key = request_json.get( "server_access_key", @@ -162,7 +162,7 @@ def create_database() -> Response: state = States[state_name] - database = VuforiaDatabase( + database = CloudDatabase( server_access_key=server_access_key, server_secret_key=server_secret_key, client_access_key=client_access_key, diff --git a/src/mock_vws/_flask_server/vwq.py b/src/mock_vws/_flask_server/vwq.py index 05192b56b..d9eb1fc43 100644 --- a/src/mock_vws/_flask_server/vwq.py +++ b/src/mock_vws/_flask_server/vwq.py @@ -21,7 +21,7 @@ from mock_vws._query_validators.exceptions import ( ValidatorError, ) -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from mock_vws.image_matchers import ( ExactMatcher, ImageMatcher, @@ -63,7 +63,7 @@ class VWQSettings(BaseSettings): @beartype -def get_all_databases() -> set[VuforiaDatabase]: +def get_all_databases() -> set[CloudDatabase]: """Get all database objects from the target manager back-end.""" settings = VWQSettings.model_validate(obj={}) response = requests.get( @@ -71,7 +71,7 @@ def get_all_databases() -> set[VuforiaDatabase]: timeout=30, ) return { - VuforiaDatabase.from_dict(database_dict=database_dict) + CloudDatabase.from_dict(database_dict=database_dict) for database_dict in response.json() } diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index 064b1348d..a6e516842 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -36,7 +36,7 @@ TargetStatusProcessingError, ValidatorError, ) -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from mock_vws.image_matchers import ( ExactMatcher, ImageMatcher, @@ -86,7 +86,7 @@ class VWSSettings(BaseSettings): @beartype -def get_all_databases() -> set[VuforiaDatabase]: +def get_all_databases() -> set[CloudDatabase]: """Get all database objects from the task manager back-end.""" settings = VWSSettings.model_validate(obj={}) timeout_seconds = 30 @@ -95,7 +95,7 @@ def get_all_databases() -> set[VuforiaDatabase]: timeout=timeout_seconds, ) return { - VuforiaDatabase.from_dict(database_dict=database_dict) + CloudDatabase.from_dict(database_dict=database_dict) for database_dict in response.json() } diff --git a/src/mock_vws/_query_tools.py b/src/mock_vws/_query_tools.py index b73f6c616..3c030844e 100644 --- a/src/mock_vws/_query_tools.py +++ b/src/mock_vws/_query_tools.py @@ -14,7 +14,7 @@ from mock_vws._constants import ResultCodes, TargetStatuses from mock_vws._database_matchers import get_database_matching_client_keys from mock_vws._mock_common import json_dump -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from mock_vws.image_matchers import ImageMatcher @@ -25,7 +25,7 @@ def get_query_match_response_text( request_body: bytes, request_method: str, request_path: str, - databases: Iterable[VuforiaDatabase], + databases: Iterable[CloudDatabase], query_match_checker: ImageMatcher, ) -> str: """ diff --git a/src/mock_vws/_query_validators/__init__.py b/src/mock_vws/_query_validators/__init__.py index 54e458e80..454767494 100644 --- a/src/mock_vws/_query_validators/__init__.py +++ b/src/mock_vws/_query_validators/__init__.py @@ -4,7 +4,7 @@ from beartype import beartype -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from .accept_header_validators import validate_accept_header from .auth_validators import ( @@ -45,7 +45,7 @@ def run_query_validators( request_headers: Mapping[str, str], request_body: bytes, request_method: str, - databases: Iterable[VuforiaDatabase], + databases: Iterable[CloudDatabase], ) -> None: """Run all validators. diff --git a/src/mock_vws/_query_validators/auth_validators.py b/src/mock_vws/_query_validators/auth_validators.py index a90273909..13553efa4 100644 --- a/src/mock_vws/_query_validators/auth_validators.py +++ b/src/mock_vws/_query_validators/auth_validators.py @@ -11,7 +11,7 @@ AuthHeaderMissingError, MalformedAuthHeaderError, ) -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase _LOGGER = logging.getLogger(name=__name__) @@ -63,7 +63,7 @@ def validate_auth_header_number_of_parts( def validate_client_key_exists( *, request_headers: Mapping[str, str], - databases: Iterable[VuforiaDatabase], + databases: Iterable[CloudDatabase], ) -> None: """Validate the authorization header includes a client key for a database. @@ -113,7 +113,7 @@ def validate_authorization( request_headers: Mapping[str, str], request_body: bytes, request_method: str, - databases: Iterable[VuforiaDatabase], + databases: Iterable[CloudDatabase], ) -> None: """Validate the authorization header given to the query endpoint. diff --git a/src/mock_vws/_query_validators/project_state_validators.py b/src/mock_vws/_query_validators/project_state_validators.py index 5a3517bde..5075b8560 100644 --- a/src/mock_vws/_query_validators/project_state_validators.py +++ b/src/mock_vws/_query_validators/project_state_validators.py @@ -7,7 +7,7 @@ from mock_vws._database_matchers import get_database_matching_client_keys from mock_vws._query_validators.exceptions import InactiveProjectError -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from mock_vws.states import States _LOGGER = logging.getLogger(name=__name__) @@ -19,7 +19,7 @@ def validate_project_state( request_headers: Mapping[str, str], request_body: bytes, request_method: str, - databases: Iterable[VuforiaDatabase], + databases: Iterable[CloudDatabase], ) -> None: """Validate the state of the project. diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index e8832b726..e13ed84dd 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -12,7 +12,7 @@ from requests import PreparedRequest from responses import RequestsMock -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from mock_vws.image_matchers import ( ImageMatcher, StructuralSimilarityMatcher, @@ -126,7 +126,7 @@ def __init__( query_match_checker=query_match_checker, ) - def add_database(self, database: VuforiaDatabase) -> None: + def add_database(self, database: CloudDatabase) -> None: """Add a cloud database. Args: diff --git a/src/mock_vws/_services_validators/__init__.py b/src/mock_vws/_services_validators/__init__.py index 03a39bd1c..e37c28d84 100644 --- a/src/mock_vws/_services_validators/__init__.py +++ b/src/mock_vws/_services_validators/__init__.py @@ -2,7 +2,7 @@ from collections.abc import Iterable, Mapping -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from .active_flag_validators import validate_active_flag from .auth_validators import ( @@ -55,7 +55,7 @@ def run_services_validators( request_headers: Mapping[str, str], request_body: bytes, request_method: str, - databases: Iterable[VuforiaDatabase], + databases: Iterable[CloudDatabase], ) -> None: """Run all validators. diff --git a/src/mock_vws/_services_validators/auth_validators.py b/src/mock_vws/_services_validators/auth_validators.py index f47164085..a5922b7ae 100644 --- a/src/mock_vws/_services_validators/auth_validators.py +++ b/src/mock_vws/_services_validators/auth_validators.py @@ -11,7 +11,7 @@ AuthenticationFailureError, FailError, ) -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase _LOGGER = logging.getLogger(name=__name__) @@ -36,7 +36,7 @@ def validate_auth_header_exists(*, request_headers: Mapping[str, str]) -> None: def validate_access_key_exists( *, request_headers: Mapping[str, str], - databases: Iterable[VuforiaDatabase], + databases: Iterable[CloudDatabase], ) -> None: """Validate the authorization header includes an access key for a database. @@ -92,7 +92,7 @@ def validate_authorization( request_headers: Mapping[str, str], request_body: bytes, request_method: str, - databases: Iterable[VuforiaDatabase], + databases: Iterable[CloudDatabase], ) -> None: """Validate the authorization header given to a VWS endpoint. diff --git a/src/mock_vws/_services_validators/name_validators.py b/src/mock_vws/_services_validators/name_validators.py index 37e511931..04db14721 100644 --- a/src/mock_vws/_services_validators/name_validators.py +++ b/src/mock_vws/_services_validators/name_validators.py @@ -12,7 +12,7 @@ FailError, TargetNameExistError, ) -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase _LOGGER = logging.getLogger(name=__name__) @@ -116,7 +116,7 @@ def validate_name_length(*, request_body: bytes) -> None: @beartype def validate_name_does_not_exist_new_target( *, - databases: Iterable[VuforiaDatabase], + databases: Iterable[CloudDatabase], request_body: bytes, request_headers: Mapping[str, str], request_method: str, @@ -176,7 +176,7 @@ def validate_name_does_not_exist_existing_target( request_body: bytes, request_method: str, request_path: str, - databases: Iterable[VuforiaDatabase], + databases: Iterable[CloudDatabase], ) -> None: """Validate that the name does not exist for any existing target apart from diff --git a/src/mock_vws/_services_validators/project_state_validators.py b/src/mock_vws/_services_validators/project_state_validators.py index 468e6188a..d4b263392 100644 --- a/src/mock_vws/_services_validators/project_state_validators.py +++ b/src/mock_vws/_services_validators/project_state_validators.py @@ -8,7 +8,7 @@ from mock_vws._database_matchers import get_database_matching_server_keys from mock_vws._services_validators.exceptions import ProjectInactiveError -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from mock_vws.states import States _LOGGER = logging.getLogger(name=__name__) @@ -21,7 +21,7 @@ def validate_project_state( request_headers: Mapping[str, str], request_body: bytes, request_method: str, - databases: Iterable[VuforiaDatabase], + databases: Iterable[CloudDatabase], ) -> None: """Validate the state of the project. diff --git a/src/mock_vws/_services_validators/target_validators.py b/src/mock_vws/_services_validators/target_validators.py index aedfa511e..ca5077ef2 100644 --- a/src/mock_vws/_services_validators/target_validators.py +++ b/src/mock_vws/_services_validators/target_validators.py @@ -7,7 +7,7 @@ from mock_vws._database_matchers import get_database_matching_server_keys from mock_vws._services_validators.exceptions import UnknownTargetError -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase _LOGGER = logging.getLogger(name=__name__) _TARGETS_WITH_INSTANCE_PATH_LENGTH = 4 @@ -20,7 +20,7 @@ def validate_target_id_exists( request_headers: Mapping[str, str], request_body: bytes, request_method: str, - databases: Iterable[VuforiaDatabase], + databases: Iterable[CloudDatabase], ) -> None: """Validate that if a target ID is given, it exists in the database matching the request. diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py index e166281e4..0fb6876bc 100644 --- a/src/mock_vws/database.py +++ b/src/mock_vws/database.py @@ -13,8 +13,8 @@ @beartype -class DatabaseDict(TypedDict): - """A dictionary type which represents a database.""" +class CloudDatabaseDict(TypedDict): + """A dictionary type which represents a cloud database.""" database_name: str server_access_key: str @@ -33,7 +33,7 @@ def _random_hex() -> str: @beartype @dataclass(eq=True, frozen=True) -class VuforiaDatabase: +class CloudDatabase: """Credentials for VWS APIs. Args: @@ -74,7 +74,7 @@ class VuforiaDatabase: total_recos: int = 0 target_quota: int = 1000 - def to_dict(self) -> DatabaseDict: + def to_dict(self) -> CloudDatabaseDict: """Dump a target to a dictionary which can be loaded as JSON.""" targets = [target.to_dict() for target in self.targets] return { @@ -95,7 +95,7 @@ def get_target(self, target_id: str) -> ImageTarget: return target @classmethod - def from_dict(cls, database_dict: DatabaseDict) -> Self: + def from_dict(cls, database_dict: CloudDatabaseDict) -> Self: """Load a database from a dictionary.""" return cls( database_name=database_dict["database_name"], diff --git a/src/mock_vws/target_manager.py b/src/mock_vws/target_manager.py index 8042f588e..620564ba6 100644 --- a/src/mock_vws/target_manager.py +++ b/src/mock_vws/target_manager.py @@ -4,7 +4,7 @@ from beartype import beartype -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase if TYPE_CHECKING: from collections.abc import Iterable @@ -20,9 +20,9 @@ class TargetManager: def __init__(self) -> None: """Create a target manager with no databases.""" - self._databases: Iterable[VuforiaDatabase] = set() + self._databases: Iterable[CloudDatabase] = set() - def remove_database(self, database: VuforiaDatabase) -> None: + def remove_database(self, database: CloudDatabase) -> None: """Remove a cloud database. Args: @@ -33,7 +33,7 @@ def remove_database(self, database: VuforiaDatabase) -> None: """ self._databases = {db for db in self._databases if db != database} - def add_database(self, database: VuforiaDatabase) -> None: + def add_database(self, database: CloudDatabase) -> None: """Add a cloud database. Args: @@ -82,6 +82,6 @@ def add_database(self, database: VuforiaDatabase) -> None: self._databases = {*self._databases, database} @property - def databases(self) -> set[VuforiaDatabase]: + def databases(self) -> set[CloudDatabase]: """All cloud databases.""" return set(self._databases) diff --git a/tests/conftest.py b/tests/conftest.py index 76b970470..0ce40f5e8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,7 @@ import pytest from vws import VWS, CloudRecoService -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from tests.mock_vws.utils import Endpoint pytest_plugins = [ @@ -19,7 +19,7 @@ @pytest.fixture(name="vws_client") -def fixture_vws_client(vuforia_database: VuforiaDatabase) -> VWS: +def fixture_vws_client(vuforia_database: CloudDatabase) -> VWS: """A VWS client for an active VWS database.""" return VWS( server_access_key=vuforia_database.server_access_key, @@ -28,7 +28,7 @@ def fixture_vws_client(vuforia_database: VuforiaDatabase) -> VWS: @pytest.fixture -def cloud_reco_client(vuforia_database: VuforiaDatabase) -> CloudRecoService: +def cloud_reco_client(vuforia_database: CloudDatabase) -> CloudRecoService: """A query client for an active VWS database.""" return CloudRecoService( client_access_key=vuforia_database.client_access_key, @@ -37,7 +37,7 @@ def cloud_reco_client(vuforia_database: VuforiaDatabase) -> CloudRecoService: @pytest.fixture(name="inactive_vws_client") -def fixture_inactive_vws_client(inactive_database: VuforiaDatabase) -> VWS: +def fixture_inactive_vws_client(inactive_database: CloudDatabase) -> VWS: """A client for an inactive VWS database.""" return VWS( server_access_key=inactive_database.server_access_key, @@ -47,7 +47,7 @@ def fixture_inactive_vws_client(inactive_database: VuforiaDatabase) -> VWS: @pytest.fixture def inactive_cloud_reco_client( - inactive_database: VuforiaDatabase, + inactive_database: CloudDatabase, ) -> CloudRecoService: """A query client for an inactive VWS database.""" return CloudRecoService( diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index bb7172a35..749b3fce9 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -6,11 +6,11 @@ import pytest from pydantic_settings import BaseSettings, SettingsConfigDict -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from mock_vws.states import States -class _VuforiaDatabaseSettings(BaseSettings): +class _CloudDatabaseSettings(BaseSettings): """Settings for a Vuforia database.""" target_manager_database_name: str @@ -26,7 +26,7 @@ class _VuforiaDatabaseSettings(BaseSettings): ) -class _InactiveVuforiaDatabaseSettings(_VuforiaDatabaseSettings): +class _InactiveCloudDatabaseSettings(_CloudDatabaseSettings): """Settings for an inactive Vuforia database.""" model_config = SettingsConfigDict( @@ -36,7 +36,7 @@ class _InactiveVuforiaDatabaseSettings(_VuforiaDatabaseSettings): ) -class _VuMarkVuforiaDatabaseSettings(BaseSettings): +class _VuMarkCloudDatabaseSettings(BaseSettings): """Settings for a VuMark Vuforia database.""" target_manager_database_name: str @@ -52,7 +52,7 @@ class _VuMarkVuforiaDatabaseSettings(BaseSettings): @dataclass(frozen=True) -class VuMarkVuforiaDatabase: +class VuMarkCloudDatabase: """Credentials for the VuMark generation API.""" target_manager_database_name: str = field(repr=False) @@ -62,10 +62,10 @@ class VuMarkVuforiaDatabase: @pytest.fixture -def vuforia_database() -> VuforiaDatabase: +def vuforia_database() -> CloudDatabase: """Return VWS credentials from environment variables.""" - settings = _VuforiaDatabaseSettings.model_validate(obj={}) - return VuforiaDatabase( + settings = _CloudDatabaseSettings.model_validate(obj={}) + return CloudDatabase( database_name=settings.target_manager_database_name, server_access_key=settings.server_access_key, server_secret_key=settings.server_secret_key, @@ -76,13 +76,13 @@ def vuforia_database() -> VuforiaDatabase: @pytest.fixture -def inactive_database() -> VuforiaDatabase: +def inactive_database() -> CloudDatabase: """ Return VWS credentials for an inactive project from environment variables. """ - settings = _InactiveVuforiaDatabaseSettings.model_validate(obj={}) - return VuforiaDatabase( + settings = _InactiveCloudDatabaseSettings.model_validate(obj={}) + return CloudDatabase( database_name=settings.target_manager_database_name, server_access_key=settings.server_access_key, server_secret_key=settings.server_secret_key, @@ -93,11 +93,11 @@ def inactive_database() -> VuforiaDatabase: @pytest.fixture -def vumark_vuforia_database() -> VuMarkVuforiaDatabase: +def vumark_vuforia_database() -> VuMarkCloudDatabase: """Return VuMark VWS credentials from environment variables.""" - settings = _VuMarkVuforiaDatabaseSettings.model_validate(obj={}) + settings = _VuMarkCloudDatabaseSettings.model_validate(obj={}) - return VuMarkVuforiaDatabase( + return VuMarkCloudDatabase( target_manager_database_name=settings.target_manager_database_name, server_access_key=settings.server_access_key, server_secret_key=settings.server_secret_key, diff --git a/tests/mock_vws/fixtures/prepared_requests.py b/tests/mock_vws/fixtures/prepared_requests.py index 724ea8e27..52dd0880f 100644 --- a/tests/mock_vws/fixtures/prepared_requests.py +++ b/tests/mock_vws/fixtures/prepared_requests.py @@ -13,8 +13,8 @@ from vws_auth_tools import authorization_header, rfc_1123_date from mock_vws._constants import ResultCodes -from mock_vws.database import VuforiaDatabase -from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase +from mock_vws.database import CloudDatabase +from tests.mock_vws.fixtures.credentials import VuMarkCloudDatabase from tests.mock_vws.utils import Endpoint from tests.mock_vws.utils.retries import RETRY_ON_TOO_MANY_REQUESTS @@ -37,7 +37,7 @@ def _wait_for_target_processed(vws_client: VWS, target_id: str) -> None: @pytest.fixture def add_target( - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, image_file_failed_state: io.BytesIO, ) -> Endpoint: """Return details of the endpoint for adding a target.""" @@ -91,7 +91,7 @@ def add_target( @pytest.fixture def delete_target( - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, target_id: str, vws_client: VWS, ) -> Endpoint: @@ -134,7 +134,7 @@ def delete_target( @pytest.fixture -def database_summary(vuforia_database: VuforiaDatabase) -> Endpoint: +def database_summary(vuforia_database: CloudDatabase) -> Endpoint: """ Return details of the endpoint for getting details about the database. @@ -178,7 +178,7 @@ def database_summary(vuforia_database: VuforiaDatabase) -> Endpoint: @pytest.fixture def get_duplicates( - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, target_id: str, vws_client: VWS, ) -> Endpoint: @@ -226,7 +226,7 @@ def get_duplicates( @pytest.fixture def get_target( - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, target_id: str, vws_client: VWS, ) -> Endpoint: @@ -270,7 +270,7 @@ def get_target( @pytest.fixture -def target_list(vuforia_database: VuforiaDatabase) -> Endpoint: +def target_list(vuforia_database: CloudDatabase) -> Endpoint: """Return details of the endpoint for getting a list of targets.""" date = rfc_1123_date() request_path = "/targets" @@ -311,7 +311,7 @@ def target_list(vuforia_database: VuforiaDatabase) -> Endpoint: @pytest.fixture def target_summary( - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, target_id: str, vws_client: VWS, ) -> Endpoint: @@ -359,7 +359,7 @@ def target_summary( @pytest.fixture def update_target( - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, target_id: str, vws_client: VWS, ) -> Endpoint: @@ -407,7 +407,7 @@ def update_target( @pytest.fixture def query( - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, high_quality_image: io.BytesIO, ) -> Endpoint: """ @@ -457,7 +457,7 @@ def query( @pytest.fixture def vumark_generate_instance( - vumark_vuforia_database: VuMarkVuforiaDatabase, + vumark_vuforia_database: VuMarkCloudDatabase, ) -> Endpoint: """Return details of the endpoint for generating a VuMark instance.""" request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 231468ed6..694af4b67 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -19,11 +19,11 @@ from mock_vws._flask_server.target_manager import TARGET_MANAGER_FLASK_APP from mock_vws._flask_server.vwq import CLOUDRECO_FLASK_APP from mock_vws._flask_server.vws import VWS_FLASK_APP -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from mock_vws.states import States from mock_vws.target import ImageTarget from mock_vws.target_raters import HardcodedTargetTrackingRater -from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase +from tests.mock_vws.fixtures.credentials import VuMarkCloudDatabase from tests.mock_vws.utils import make_image_file from tests.mock_vws.utils.retries import RETRY_ON_TOO_MANY_REQUESTS @@ -32,7 +32,7 @@ @RETRY_ON_TOO_MANY_REQUESTS -def _delete_all_targets(*, database_keys: VuforiaDatabase) -> None: +def _delete_all_targets(*, database_keys: CloudDatabase) -> None: """Delete all targets. Args: @@ -64,8 +64,8 @@ def _delete_all_targets(*, database_keys: VuforiaDatabase) -> None: @beartype def _vumark_database( *, - vumark_vuforia_database: VuMarkVuforiaDatabase, -) -> VuforiaDatabase: + vumark_vuforia_database: VuMarkCloudDatabase, +) -> CloudDatabase: """Return a database with a target for VuMark instance generation.""" vumark_target = ImageTarget( active_flag=True, @@ -82,7 +82,7 @@ def _vumark_database( target_tracking_rater=HardcodedTargetTrackingRater(rating=5), target_id=vumark_vuforia_database.target_id, ) - return VuforiaDatabase( + return CloudDatabase( database_name=vumark_vuforia_database.target_manager_database_name, server_access_key=vumark_vuforia_database.server_access_key, server_secret_key=vumark_vuforia_database.server_secret_key, @@ -93,9 +93,9 @@ def _vumark_database( @beartype def _enable_use_real_vuforia( *, - working_database: VuforiaDatabase, - inactive_database: VuforiaDatabase, - vumark_vuforia_database: VuMarkVuforiaDatabase, + working_database: CloudDatabase, + inactive_database: CloudDatabase, + vumark_vuforia_database: VuMarkCloudDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test against the real Vuforia.""" @@ -109,14 +109,14 @@ def _enable_use_real_vuforia( @beartype def _enable_use_mock_vuforia( *, - working_database: VuforiaDatabase, - inactive_database: VuforiaDatabase, - vumark_vuforia_database: VuMarkVuforiaDatabase, + working_database: CloudDatabase, + inactive_database: CloudDatabase, + vumark_vuforia_database: VuMarkCloudDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test against the in-memory mock Vuforia.""" assert monkeypatch - working_database = VuforiaDatabase( + working_database = CloudDatabase( database_name=working_database.database_name, server_access_key=working_database.server_access_key, server_secret_key=working_database.server_secret_key, @@ -124,7 +124,7 @@ def _enable_use_mock_vuforia( client_secret_key=working_database.client_secret_key, ) - inactive_database = VuforiaDatabase( + inactive_database = CloudDatabase( state=States.PROJECT_INACTIVE, database_name=inactive_database.database_name, server_access_key=inactive_database.server_access_key, @@ -146,9 +146,9 @@ def _enable_use_mock_vuforia( @beartype def _enable_use_docker_in_memory( *, - working_database: VuforiaDatabase, - inactive_database: VuforiaDatabase, - vumark_vuforia_database: VuMarkVuforiaDatabase, + working_database: CloudDatabase, + inactive_database: CloudDatabase, + vumark_vuforia_database: VuMarkCloudDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test against mock Vuforia created to be run in a container.""" @@ -286,9 +286,9 @@ def pytest_collection_modifyitems( ) def fixture_verify_mock_vuforia( request: pytest.FixtureRequest, - vuforia_database: VuforiaDatabase, - inactive_database: VuforiaDatabase, - vumark_vuforia_database: VuMarkVuforiaDatabase, + vuforia_database: CloudDatabase, + inactive_database: CloudDatabase, + vumark_vuforia_database: VuMarkCloudDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test functions which use this fixture are run multiple times. Once @@ -332,9 +332,9 @@ def fixture_verify_mock_vuforia( ) def mock_only_vuforia( request: pytest.FixtureRequest, - vuforia_database: VuforiaDatabase, - inactive_database: VuforiaDatabase, - vumark_vuforia_database: VuMarkVuforiaDatabase, + vuforia_database: CloudDatabase, + inactive_database: CloudDatabase, + vumark_vuforia_database: VuMarkCloudDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test functions which use this fixture are run multiple times. Once diff --git a/tests/mock_vws/test_authorization_header.py b/tests/mock_vws/test_authorization_header.py index c017c41ad..89ff5bc1d 100644 --- a/tests/mock_vws/test_authorization_header.py +++ b/tests/mock_vws/test_authorization_header.py @@ -13,7 +13,7 @@ from vws_auth_tools import rfc_1123_date from mock_vws._constants import ResultCodes -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from tests.mock_vws.utils import Endpoint from tests.mock_vws.utils.assertions import ( assert_valid_transaction_id, @@ -246,7 +246,7 @@ class TestBadKey: @staticmethod def test_bad_access_key_services( - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, ) -> None: """ If the server access key given does not match any database, a @@ -265,7 +265,7 @@ def test_bad_access_key_services( @staticmethod def test_bad_access_key_query( - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, high_quality_image: io.BytesIO, ) -> None: """ @@ -312,7 +312,7 @@ def test_bad_access_key_query( @staticmethod def test_bad_secret_key_services( - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, ) -> None: """ If the server secret key given is incorrect, an @@ -328,7 +328,7 @@ def test_bad_secret_key_services( @staticmethod def test_bad_secret_key_query( - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, high_quality_image: io.BytesIO, ) -> None: """ diff --git a/tests/mock_vws/test_database_summary.py b/tests/mock_vws/test_database_summary.py index e0220319f..c00deaf18 100644 --- a/tests/mock_vws/test_database_summary.py +++ b/tests/mock_vws/test_database_summary.py @@ -15,7 +15,7 @@ from vws.exceptions.vws_exceptions import FailError from mock_vws import MockVWS -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase LOGGER = logging.getLogger(name=__name__) LOGGER.setLevel(level=logging.DEBUG) @@ -94,7 +94,7 @@ class TestDatabaseSummary: @staticmethod def test_success( - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, vws_client: VWS, ) -> None: """It is possible to get a success response.""" @@ -239,7 +239,7 @@ def test_processing_images( image_file_success_state_low_rating: io.BytesIO, ) -> None: """The number of images in the processing state is returned.""" - database = VuforiaDatabase() + database = CloudDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py index dfac6cdba..7246044ae 100644 --- a/tests/mock_vws/test_docker.py +++ b/tests/mock_vws/test_docker.py @@ -19,7 +19,7 @@ from tenacity.wait import wait_fixed from vws import VWS, CloudRecoService -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase if TYPE_CHECKING: from docker.models.images import Image @@ -152,7 +152,7 @@ def test_build_and_run( rm=True, ) - database = VuforiaDatabase() + database = CloudDatabase() target_manager_container_name = "vws-mock-target-manager-" + random target_manager_internal_base_url = ( f"http://{target_manager_container_name}:5000" diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index af33fcde9..cacb3ca63 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -18,7 +18,7 @@ from mock_vws._flask_server.target_manager import TARGET_MANAGER_FLASK_APP from mock_vws._flask_server.vwq import CLOUDRECO_FLASK_APP from mock_vws._flask_server.vws import VWS_FLASK_APP -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from tests.mock_vws.utils.usage_test_helpers import ( processing_time_seconds, ) @@ -70,7 +70,7 @@ def test_default( image_file_failed_state: io.BytesIO, ) -> None: """By default, targets in the mock takes 2 seconds to be processed.""" - database = VuforiaDatabase() + database = CloudDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) @@ -93,7 +93,7 @@ def test_custom( name="PROCESSING_TIME_SECONDS", value=str(object=seconds), ) - database = VuforiaDatabase() + database = CloudDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) @@ -115,7 +115,7 @@ def test_duplicate_keys() -> None: It is not possible to have multiple databases with matching keys. """ - database = VuforiaDatabase( + database = CloudDatabase( server_access_key="1", server_secret_key="2", client_access_key="3", @@ -123,11 +123,11 @@ def test_duplicate_keys() -> None: database_name="5", ) - bad_server_access_key_db = VuforiaDatabase(server_access_key="1") - bad_server_secret_key_db = VuforiaDatabase(server_secret_key="2") - bad_client_access_key_db = VuforiaDatabase(client_access_key="3") - bad_client_secret_key_db = VuforiaDatabase(client_secret_key="4") - bad_database_name_db = VuforiaDatabase(database_name="5") + bad_server_access_key_db = CloudDatabase(server_access_key="1") + bad_server_secret_key_db = CloudDatabase(server_secret_key="2") + bad_client_access_key_db = CloudDatabase(client_access_key="3") + bad_client_secret_key_db = CloudDatabase(client_secret_key="4") + bad_database_name_db = CloudDatabase(database_name="5") server_access_key_conflict_error = ( "All server access keys must be unique. " @@ -238,7 +238,7 @@ def test_exact_match( """The exact matcher matches only exactly the same images.""" monkeypatch.setenv(name="QUERY_IMAGE_MATCHER", value="exact") - database = VuforiaDatabase() + database = CloudDatabase() vws_client = VWS( server_access_key=database.server_access_key, @@ -284,7 +284,7 @@ def test_structural_similarity_matcher( name="QUERY_IMAGE_MATCHER", value="structural_similarity", ) - database = VuforiaDatabase() + database = CloudDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, @@ -335,7 +335,7 @@ def test_exact_match( ) -> None: """The exact matcher matches only exactly the same images.""" monkeypatch.setenv(name="DUPLICATES_IMAGE_MATCHER", value="exact") - database = VuforiaDatabase() + database = CloudDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, @@ -387,7 +387,7 @@ def test_structural_similarity_matcher( name="DUPLICATES_IMAGE_MATCHER", value="structural_similarity", ) - database = VuforiaDatabase() + database = CloudDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, @@ -429,7 +429,7 @@ def test_default( high_quality_image: io.BytesIO, ) -> None: """By default, the BRISQUE target rater is used.""" - database = VuforiaDatabase() + database = CloudDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) @@ -480,7 +480,7 @@ def test_brisque( """It is possible to use the BRISQUE target rater.""" monkeypatch.setenv(name="TARGET_RATER", value="brisque") - database = VuforiaDatabase() + database = CloudDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) @@ -529,7 +529,7 @@ def test_perfect( ) -> None: """It is possible to use the perfect target rater.""" monkeypatch.setenv(name="TARGET_RATER", value="perfect") - database = VuforiaDatabase() + database = CloudDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) @@ -569,7 +569,7 @@ def test_random( """It is possible to use the random target rater.""" monkeypatch.setenv(name="TARGET_RATER", value="random") - database = VuforiaDatabase() + database = CloudDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) @@ -641,7 +641,7 @@ def _make_request() -> None: def test_default_no_delay(self) -> None: """By default, there is no response delay.""" - database = VuforiaDatabase() + database = CloudDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) @@ -659,7 +659,7 @@ def test_delay_is_applied( name="RESPONSE_DELAY_SECONDS", value=f"{self.DELAY_SECONDS}", ) - database = VuforiaDatabase() + database = CloudDatabase() databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index 716ba627f..32e7a6a56 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -38,7 +38,7 @@ from vws.response import Response from vws_auth_tools import authorization_header, rfc_1123_date -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from tests.mock_vws.utils import make_image_file from tests.mock_vws.utils.assertions import ( assert_query_success, @@ -90,7 +90,7 @@ def _query( *, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, body: dict[str, Any], ) -> Response: """Make a request to the endpoint to make an image recognition query. @@ -202,7 +202,7 @@ class TestContentType: def test_incorrect_no_boundary( *, high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, content_type: str, resp_status_code: int, resp_content_type: str | None, @@ -272,7 +272,7 @@ def test_incorrect_no_boundary( @staticmethod def test_incorrect_with_boundary( high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, ) -> None: """ If a Content-Type header which is not ``multipart/form-data`` is @@ -349,7 +349,7 @@ def test_incorrect_with_boundary( ) def test_no_boundary( high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, content_type: str, ) -> None: """ @@ -412,7 +412,7 @@ def test_no_boundary( @staticmethod def test_bogus_boundary( high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, ) -> None: """If a bogus boundary is given, a ``BAD_REQUEST`` is returned.""" image_content = high_quality_image.getvalue() @@ -473,7 +473,7 @@ def test_bogus_boundary( @staticmethod def test_extra_section( high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, ) -> None: """ If sections that are not the boundary section are given in the @@ -548,7 +548,7 @@ def test_no_results( @staticmethod def test_match_exact( high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, vws_client: VWS, ) -> None: """ @@ -728,7 +728,7 @@ class TestIncorrectFields: """Tests for incorrect and unexpected fields.""" @staticmethod - def test_missing_image(vuforia_database: VuforiaDatabase) -> None: + def test_missing_image(vuforia_database: CloudDatabase) -> None: """ If an image is not given, a ``BAD_REQUEST`` response is returned. @@ -748,7 +748,7 @@ def test_missing_image(vuforia_database: VuforiaDatabase) -> None: @staticmethod def test_extra_fields( high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, ) -> None: """ If extra fields are given, a ``BAD_REQUEST`` response is @@ -774,7 +774,7 @@ def test_extra_fields( @staticmethod def test_missing_image_and_extra_fields( - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, ) -> None: """If extra fields are given and no image field is given, a ``BAD_REQUEST`` response is returned. @@ -805,7 +805,7 @@ class TestMaxNumResults: @staticmethod def test_default( high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, vws_client: VWS, ) -> None: """The default ``max_num_results`` is 1.""" @@ -842,7 +842,7 @@ def test_default( @pytest.mark.parametrize(argnames="num_results", argvalues=[1, b"1", 50]) def test_valid_accepted( high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, num_results: int | bytes, ) -> None: """Numbers between 1 and 50 are valid inputs. @@ -935,7 +935,7 @@ def test_out_of_range( ) def test_invalid_type( high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, num_results: bytes, ) -> None: """An error is returned if ``max_num_results`` is given as @@ -998,7 +998,7 @@ class TestIncludeTargetData: def test_default( high_quality_image: io.BytesIO, vws_client: VWS, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, ) -> None: """The default ``include_target_data`` is 'top'.""" _add_and_wait_for_targets( @@ -1027,7 +1027,7 @@ def test_default( ) def test_top( high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, include_target_data: str, vws_client: VWS, ) -> None: @@ -1063,7 +1063,7 @@ def test_top( ) def test_none( high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, include_target_data: str, vws_client: VWS, ) -> None: @@ -1099,7 +1099,7 @@ def test_none( ) def test_all( high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, include_target_data: str, vws_client: VWS, ) -> None: @@ -1136,7 +1136,7 @@ def test_all( def test_invalid_value( *, high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, include_target_data: str | bool | int, ) -> None: """ @@ -1184,7 +1184,7 @@ class TestAcceptHeader: ) def test_valid( high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, extra_headers: dict[str, str], ) -> None: """An ``Accept`` header can be given iff its value is @@ -1239,7 +1239,7 @@ def test_valid( @staticmethod def test_invalid( high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, ) -> None: """ A NOT_ACCEPTABLE response is returned if an ``Accept`` header is @@ -1952,7 +1952,7 @@ class TestDateFormats: @pytest.mark.parametrize(argnames="include_tz", argvalues=[True, False]) def test_date_formats( high_quality_image: io.BytesIO, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, datetime_format: str, *, include_tz: bool, diff --git a/tests/mock_vws/test_requests_mock_usage.py b/tests/mock_vws/test_requests_mock_usage.py index 81b297cd1..cc79f716f 100644 --- a/tests/mock_vws/test_requests_mock_usage.py +++ b/tests/mock_vws/test_requests_mock_usage.py @@ -16,7 +16,7 @@ from vws_auth_tools import rfc_1123_date from mock_vws import MissingSchemeError, MockVWS -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from mock_vws.image_matchers import ExactMatcher, StructuralSimilarityMatcher from mock_vws.target import ImageTarget from tests.mock_vws.utils import Endpoint @@ -253,7 +253,7 @@ class TestProcessingTime: def test_default(self, image_file_failed_state: io.BytesIO) -> None: """By default, targets in the mock takes 2 seconds to be processed.""" - database = VuforiaDatabase() + database = CloudDatabase() with MockVWS() as mock: mock.add_database(database=database) time_taken = processing_time_seconds( @@ -266,7 +266,7 @@ def test_default(self, image_file_failed_state: io.BytesIO) -> None: def test_custom(self, image_file_failed_state: io.BytesIO) -> None: """It is possible to set a custom processing time.""" - database = VuforiaDatabase() + database = CloudDatabase() seconds = 5 with MockVWS(processing_time_seconds=seconds) as mock: mock.add_database(database=database) @@ -285,8 +285,8 @@ class TestDatabaseName: @staticmethod def test_default() -> None: """By default, the database has a random name.""" - database_details = VuforiaDatabase() - other_database_details = VuforiaDatabase() + database_details = CloudDatabase() + other_database_details = CloudDatabase() assert ( database_details.database_name != other_database_details.database_name @@ -295,7 +295,7 @@ def test_default() -> None: @staticmethod def test_custom_name() -> None: """It is possible to set a custom database name.""" - database_details = VuforiaDatabase(database_name="foo") + database_details = CloudDatabase(database_name="foo") assert database_details.database_name == "foo" @@ -376,7 +376,7 @@ def test_to_dict(high_quality_image: io.BytesIO) -> None: It is possible to dump a target to a dictionary and load it back. """ - database = VuforiaDatabase() + database = CloudDatabase() vws_client = VWS( server_access_key=database.server_access_key, @@ -410,7 +410,7 @@ def test_to_dict_deleted(high_quality_image: io.BytesIO) -> None: it back. """ - database = VuforiaDatabase() + database = CloudDatabase() vws_client = VWS( server_access_key=database.server_access_key, @@ -449,7 +449,7 @@ def test_to_dict(high_quality_image: io.BytesIO) -> None: It is possible to dump a database to a dictionary and load it back. """ - database = VuforiaDatabase() + database = CloudDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, @@ -470,7 +470,7 @@ def test_to_dict(high_quality_image: io.BytesIO) -> None: # The dictionary is JSON dump-able assert json.dumps(obj=database_dict) - new_database = VuforiaDatabase.from_dict(database_dict=database_dict) + new_database = CloudDatabase.from_dict(database_dict=database_dict) assert new_database == database @@ -512,7 +512,7 @@ def test_duplicate_keys() -> None: It is not possible to have multiple databases with matching keys. """ - database = VuforiaDatabase( + database = CloudDatabase( server_access_key="1", server_secret_key="2", client_access_key="3", @@ -520,11 +520,11 @@ def test_duplicate_keys() -> None: database_name="5", ) - bad_server_access_key_db = VuforiaDatabase(server_access_key="1") - bad_server_secret_key_db = VuforiaDatabase(server_secret_key="2") - bad_client_access_key_db = VuforiaDatabase(client_access_key="3") - bad_client_secret_key_db = VuforiaDatabase(client_secret_key="4") - bad_database_name_db = VuforiaDatabase(database_name="5") + bad_server_access_key_db = CloudDatabase(server_access_key="1") + bad_server_secret_key_db = CloudDatabase(server_secret_key="2") + bad_client_access_key_db = CloudDatabase(client_access_key="3") + bad_client_secret_key_db = CloudDatabase(client_secret_key="4") + bad_database_name_db = CloudDatabase(database_name="5") server_access_key_conflict_error = ( "All server access keys must be unique. " @@ -569,7 +569,7 @@ class TestQueryImageMatchers: @staticmethod def test_exact_match(high_quality_image: io.BytesIO) -> None: """The exact matcher matches only exactly the same images.""" - database = VuforiaDatabase() + database = CloudDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, @@ -605,7 +605,7 @@ def test_exact_match(high_quality_image: io.BytesIO) -> None: @staticmethod def test_custom_matcher(high_quality_image: io.BytesIO) -> None: """It is possible to use a custom matcher.""" - database = VuforiaDatabase() + database = CloudDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, @@ -644,7 +644,7 @@ def test_structural_similarity_matcher( different_high_quality_image: io.BytesIO, ) -> None: """The structural similarity matcher matches similar images.""" - database = VuforiaDatabase() + database = CloudDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, @@ -691,7 +691,7 @@ class TestDuplicatesImageMatchers: @staticmethod def test_exact_match(high_quality_image: io.BytesIO) -> None: """The exact matcher matches only exactly the same images.""" - database = VuforiaDatabase() + database = CloudDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, @@ -735,7 +735,7 @@ def test_exact_match(high_quality_image: io.BytesIO) -> None: @staticmethod def test_custom_matcher(high_quality_image: io.BytesIO) -> None: """It is possible to use a custom matcher.""" - database = VuforiaDatabase() + database = CloudDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, @@ -781,7 +781,7 @@ def test_structural_similarity_matcher( high_quality_image: io.BytesIO, ) -> None: """The structural similarity matcher matches similar images.""" - database = VuforiaDatabase() + database = CloudDatabase() vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, diff --git a/tests/mock_vws/test_target_summary.py b/tests/mock_vws/test_target_summary.py index 61fbd76ce..a5621d2cf 100644 --- a/tests/mock_vws/test_target_summary.py +++ b/tests/mock_vws/test_target_summary.py @@ -10,7 +10,7 @@ from vws.exceptions.vws_exceptions import UnknownTargetError from vws.reports import TargetStatuses -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase @pytest.mark.usefixtures("verify_mock_vuforia") @@ -21,7 +21,7 @@ class TestTargetSummary: @pytest.mark.parametrize(argnames="active_flag", argvalues=[True, False]) def test_target_summary( vws_client: VWS, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, image_file_failed_state: io.BytesIO, *, active_flag: bool, diff --git a/tests/mock_vws/test_target_validators.py b/tests/mock_vws/test_target_validators.py index 4e49ce89c..0fc74601c 100644 --- a/tests/mock_vws/test_target_validators.py +++ b/tests/mock_vws/test_target_validators.py @@ -9,13 +9,13 @@ from mock_vws._services_validators.target_validators import ( validate_target_id_exists, ) -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from mock_vws.target import ImageTarget from mock_vws.target_raters import HardcodedTargetTrackingRater from tests.mock_vws.utils import make_image_file -def _database_with_target(*, target_id: str) -> VuforiaDatabase: +def _database_with_target(*, target_id: str) -> CloudDatabase: """Create a database containing one target with the given ID.""" target = ImageTarget( active_flag=True, @@ -32,18 +32,18 @@ def _database_with_target(*, target_id: str) -> VuforiaDatabase: target_tracking_rater=HardcodedTargetTrackingRater(rating=5), width=1, ) - return VuforiaDatabase(targets={target}) + return CloudDatabase(targets={target}) def _always_match_database( *, - database: VuforiaDatabase, + database: CloudDatabase, request_headers: Mapping[str, str], request_body: bytes | None, request_method: str, request_path: str, - databases: Iterable[VuforiaDatabase], -) -> VuforiaDatabase: + databases: Iterable[CloudDatabase], +) -> CloudDatabase: """Return the given database regardless of request details.""" del request_headers del request_body diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 2b589ebd6..219c27440 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -9,7 +9,7 @@ from vws_auth_tools import authorization_header, rfc_1123_date from mock_vws._constants import ResultCodes -from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase +from tests.mock_vws.fixtures.credentials import VuMarkCloudDatabase _VWS_HOST = "https://vws.vuforia.com" _PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" @@ -19,7 +19,7 @@ def _make_vumark_request( *, - vumark_vuforia_database: VuMarkVuforiaDatabase, + vumark_vuforia_database: VuMarkCloudDatabase, instance_id: str, accept: str, ) -> requests.Response: @@ -83,7 +83,7 @@ def test_generate_instance_format( accept: str, expected_content_type: str, expected_signature: bytes, - vumark_vuforia_database: VuMarkVuforiaDatabase, + vumark_vuforia_database: VuMarkCloudDatabase, ) -> None: """A VuMark instance can be generated in the requested format.""" response = _make_vumark_request( @@ -102,7 +102,7 @@ def test_generate_instance_format( @staticmethod def test_invalid_accept_header( - vumark_vuforia_database: VuMarkVuforiaDatabase, + vumark_vuforia_database: VuMarkCloudDatabase, ) -> None: """An unsupported Accept header returns an error.""" response = _make_vumark_request( @@ -120,7 +120,7 @@ def test_invalid_accept_header( @staticmethod def test_empty_instance_id( - vumark_vuforia_database: VuMarkVuforiaDatabase, + vumark_vuforia_database: VuMarkCloudDatabase, ) -> None: """An empty instance_id returns InvalidInstanceId.""" response = _make_vumark_request( diff --git a/tests/mock_vws/utils/usage_test_helpers.py b/tests/mock_vws/utils/usage_test_helpers.py index 6c85dc0f6..32d9231e2 100644 --- a/tests/mock_vws/utils/usage_test_helpers.py +++ b/tests/mock_vws/utils/usage_test_helpers.py @@ -6,12 +6,12 @@ from vws import VWS from vws.reports import TargetStatuses -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase def processing_time_seconds( *, - vuforia_database: VuforiaDatabase, + vuforia_database: CloudDatabase, image: io.BytesIO, ) -> float: """Return the time taken to process a target in the database.""" From 1bb88c7d67700cbc2102997da548fb57523fb121 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 20 Feb 2026 15:51:36 +0000 Subject: [PATCH 2301/2467] Refresh secrets (#2966) * Refresh Vuforia secrets and simplify admin script Replace suspended license credentials with 100 fresh cloud database credentials. Simplify the secrets creation script to reuse existing VuMark and inactive database credentials from the existing secrets file instead of creating new ones each run. Co-Authored-By: Claude Opus 4.6 * Revert simplification of admin script to restore VuMark creation logic Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Opus 4.6 --- pyproject.toml | 2 +- secrets.tar.gpg | Bin 17558 -> 19046 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 466ed3297..c5a2f5685 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,7 +105,7 @@ optional-dependencies.dev = [ "vulture==2.14", "vws-python==2026.2.15", "vws-test-fixtures==2023.3.5", - "vws-web-tools==2026.2.17.1", + "vws-web-tools==2026.2.20", "yamlfix==1.19.1", "zizmor==1.22.0", ] diff --git a/secrets.tar.gpg b/secrets.tar.gpg index a21cf43fd538155a3612c7f1b41c19eb17508113..32c9b9c6f5e398b64161ca4cf9bd9aa193cca373 100644 GIT binary patch literal 19046 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 literal 17558 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 From 703a1e3a8bbd39d0848fb6ed8ed1a9a019f8db3e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 20 Feb 2026 17:41:33 +0000 Subject: [PATCH 2302/2467] Rename database functions to cloud_databases (#2969) * Refresh secrets (#2966) * Refresh Vuforia secrets and simplify admin script Replace suspended license credentials with 100 fresh cloud database credentials. Simplify the secrets creation script to reuse existing VuMark and inactive database credentials from the existing secrets file instead of creating new ones each run. Co-Authored-By: Claude Opus 4.6 * Revert simplification of admin script to restore VuMark creation logic Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Opus 4.6 * Fix docs endpoint reference after rename to create_cloud_database Update the autoflask directive in docker.rst to reference the renamed endpoint create_cloud_database instead of create_database. Co-Authored-By: Claude Haiku 4.5 --------- Co-authored-by: Claude Opus 4.6 --- docs/source/docker.rst | 2 +- src/mock_vws/_flask_server/target_manager.py | 16 +++++---- src/mock_vws/_flask_server/vwq.py | 4 +-- src/mock_vws/_flask_server/vws.py | 20 +++++------ .../mock_web_query_api.py | 4 +-- .../mock_web_services_api.py | 34 +++++++++---------- src/mock_vws/target_manager.py | 4 +-- 7 files changed, 43 insertions(+), 41 deletions(-) diff --git a/docs/source/docker.rst b/docs/source/docker.rst index e46c5d146..f77a811d6 100644 --- a/docs/source/docker.rst +++ b/docs/source/docker.rst @@ -54,7 +54,7 @@ To mimic this functionality, this mock provides a target manager container which To add a database, make a request to the following endpoint against the target manager container: .. autoflask:: mock_vws._flask_server.target_manager:TARGET_MANAGER_FLASK_APP - :endpoints: create_database + :endpoints: create_cloud_database For example, with the containers set up as in :ref:`creating-containers`, use ``curl``: diff --git a/src/mock_vws/_flask_server/target_manager.py b/src/mock_vws/_flask_server/target_manager.py index 06948fd5c..856738050 100644 --- a/src/mock_vws/_flask_server/target_manager.py +++ b/src/mock_vws/_flask_server/target_manager.py @@ -70,7 +70,7 @@ def delete_database(database_name: str) -> Response: try: (matching_database,) = { database - for database in TARGET_MANAGER.databases + for database in TARGET_MANAGER.cloud_databases if database_name == database.database_name } except ValueError: @@ -82,9 +82,11 @@ def delete_database(database_name: str) -> Response: @TARGET_MANAGER_FLASK_APP.route(rule="/databases", methods=[HTTPMethod.GET]) @beartype -def get_databases() -> Response: +def get_cloud_databases() -> Response: """Return a list of all databases.""" - databases = [database.to_dict() for database in TARGET_MANAGER.databases] + databases = [ + database.to_dict() for database in TARGET_MANAGER.cloud_databases + ] return Response( response=json.dumps(obj=databases), status=HTTPStatus.OK, @@ -93,7 +95,7 @@ def get_databases() -> Response: @TARGET_MANAGER_FLASK_APP.route(rule="/databases", methods=[HTTPMethod.POST]) @beartype -def create_database() -> Response: +def create_cloud_database() -> Response: """Create a new database. :reqheader Content-Type: application/json @@ -193,7 +195,7 @@ def create_target(database_name: str) -> Response: """Create a new target in a given database.""" (database,) = ( database - for database in TARGET_MANAGER.databases + for database in TARGET_MANAGER.cloud_databases if database.database_name == database_name ) request_json = json.loads(s=request.data) @@ -229,7 +231,7 @@ def delete_target(database_name: str, target_id: str) -> Response: """Delete a target.""" (database,) = ( database - for database in TARGET_MANAGER.databases + for database in TARGET_MANAGER.cloud_databases if database.database_name == database_name ) target = database.get_target(target_id=target_id) @@ -255,7 +257,7 @@ def update_target(database_name: str, target_id: str) -> Response: """Update a target.""" (database,) = ( database - for database in TARGET_MANAGER.databases + for database in TARGET_MANAGER.cloud_databases if database.database_name == database_name ) target = database.get_target(target_id=target_id) diff --git a/src/mock_vws/_flask_server/vwq.py b/src/mock_vws/_flask_server/vwq.py index d9eb1fc43..d8ef38350 100644 --- a/src/mock_vws/_flask_server/vwq.py +++ b/src/mock_vws/_flask_server/vwq.py @@ -63,7 +63,7 @@ class VWQSettings(BaseSettings): @beartype -def get_all_databases() -> set[CloudDatabase]: +def get_all_cloud_databases() -> set[CloudDatabase]: """Get all database objects from the target manager back-end.""" settings = VWQSettings.model_validate(obj={}) response = requests.get( @@ -132,7 +132,7 @@ def query() -> Response: settings = VWQSettings.model_validate(obj={}) query_match_checker = settings.query_image_matcher.to_image_matcher() - databases = get_all_databases() + databases = get_all_cloud_databases() request_body = request.stream.read() run_query_validators( request_headers=dict(request.headers), diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index a6e516842..d85d0701e 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -86,7 +86,7 @@ class VWSSettings(BaseSettings): @beartype -def get_all_databases() -> set[CloudDatabase]: +def get_all_cloud_databases() -> set[CloudDatabase]: """Get all database objects from the task manager back-end.""" settings = VWSSettings.model_validate(obj={}) timeout_seconds = 30 @@ -130,7 +130,7 @@ def set_terminate_wsgi_input() -> None: @beartype def validate_request() -> None: """Run validators on the request.""" - databases = get_all_databases() + databases = get_all_cloud_databases() run_services_validators( request_headers=dict(request.headers), request_body=request.data, @@ -172,7 +172,7 @@ def add_target() -> Response: https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#add """ settings = VWSSettings.model_validate(obj={}) - databases = get_all_databases() + databases = get_all_cloud_databases() database = get_database_matching_server_keys( request_headers=dict(request.headers), request_body=request.data, @@ -245,7 +245,7 @@ def get_target(target_id: str) -> Response: Fake implementation of https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#target-record """ - databases = get_all_databases() + databases = get_all_cloud_databases() database = get_database_matching_server_keys( request_headers=dict(request.headers), request_body=request.data, @@ -302,7 +302,7 @@ def delete_target(target_id: str) -> Response: https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#delete """ settings = VWSSettings.model_validate(obj={}) - databases = get_all_databases() + databases = get_all_cloud_databases() database = get_database_matching_server_keys( request_headers=dict(request.headers), request_body=request.data, @@ -402,7 +402,7 @@ def database_summary() -> Response: Fake implementation of https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#summary-report """ - databases = get_all_databases() + databases = get_all_cloud_databases() database = get_database_matching_server_keys( request_headers=dict(request.headers), request_body=request.data, @@ -457,7 +457,7 @@ def target_summary(target_id: str) -> Response: Fake implementation of https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#retrieve-report """ - databases = get_all_databases() + databases = get_all_cloud_databases() database = get_database_matching_server_keys( request_headers=dict(request.headers), request_body=request.data, @@ -511,7 +511,7 @@ def get_duplicates(target_id: str) -> Response: Fake implementation of https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#check """ - databases = get_all_databases() + databases = get_all_cloud_databases() settings = VWSSettings.model_validate(obj={}) database = get_database_matching_server_keys( request_headers=dict(request.headers), @@ -570,7 +570,7 @@ def target_list() -> Response: Fake implementation of https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#details-list """ - databases = get_all_databases() + databases = get_all_cloud_databases() database = get_database_matching_server_keys( request_headers=dict(request.headers), request_body=request.data, @@ -616,7 +616,7 @@ def update_target(target_id: str) -> Response: # We do not use ``request.get_json(force=True)`` because this only works # when the content type is given as ``application/json``. request_json = json.loads(s=request.data) - databases = get_all_databases() + databases = get_all_cloud_databases() database = get_database_matching_server_keys( request_headers=dict(request.headers), request_body=request.data, diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py index 8206a6d10..e2626a25a 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py @@ -122,7 +122,7 @@ def query(self, request: PreparedRequest) -> _ResponseType: request_headers=request.headers, request_body=_body_bytes(request=request), request_method=request.method or "", - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: return exc.status_code, exc.headers, exc.response_text @@ -132,7 +132,7 @@ def query(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, query_match_checker=self._query_match_checker, ) diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index 8c0c770d6..bbd782c24 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -164,7 +164,7 @@ def add_target(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: return exc.status_code, exc.headers, exc.response_text @@ -174,7 +174,7 @@ def add_target(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) request_json: dict[str, Any] = json.loads(s=request.body or b"") @@ -239,7 +239,7 @@ def delete_target(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: return exc.status_code, exc.headers, exc.response_text @@ -249,7 +249,7 @@ def delete_target(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) target_id = request.path_url.split(sep="/")[-1] @@ -314,7 +314,7 @@ def generate_vumark_instance( request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) accept = dict(request.headers).get("Accept", "") @@ -360,7 +360,7 @@ def database_summary(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: return exc.status_code, exc.headers, exc.response_text @@ -370,7 +370,7 @@ def database_summary(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) date = email.utils.formatdate( @@ -421,7 +421,7 @@ def target_list(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: return exc.status_code, exc.headers, exc.response_text @@ -431,7 +431,7 @@ def target_list(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) date = email.utils.formatdate( @@ -478,7 +478,7 @@ def get_target(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: return exc.status_code, exc.headers, exc.response_text @@ -488,7 +488,7 @@ def get_target(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) target_id = request.path_url.split(sep="/")[-1] target = database.get_target(target_id=target_id) @@ -544,7 +544,7 @@ def get_duplicates(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: return exc.status_code, exc.headers, exc.response_text @@ -554,7 +554,7 @@ def get_duplicates(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) target_id = request.path_url.split(sep="/")[-1] target = database.get_target(target_id=target_id) @@ -615,7 +615,7 @@ def update_target(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: return exc.status_code, exc.headers, exc.response_text @@ -625,7 +625,7 @@ def update_target(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) target_id = request.path_url.split(sep="/")[-1] @@ -728,7 +728,7 @@ def target_summary(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: return exc.status_code, exc.headers, exc.response_text @@ -738,7 +738,7 @@ def target_summary(self, request: PreparedRequest) -> _ResponseType: request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.databases, + databases=self._target_manager.cloud_databases, ) target_id = request.path_url.split(sep="/")[-1] target = database.get_target(target_id=target_id) diff --git a/src/mock_vws/target_manager.py b/src/mock_vws/target_manager.py index 620564ba6..c304cd40a 100644 --- a/src/mock_vws/target_manager.py +++ b/src/mock_vws/target_manager.py @@ -47,7 +47,7 @@ def add_database(self, database: CloudDatabase) -> None: "All {key_name}s must be unique. " 'There is already a database with the {key_name} "{value}".' ) - for existing_db in self.databases: + for existing_db in self.cloud_databases: for existing, new, key_name in ( ( existing_db.server_access_key, @@ -82,6 +82,6 @@ def add_database(self, database: CloudDatabase) -> None: self._databases = {*self._databases, database} @property - def databases(self) -> set[CloudDatabase]: + def cloud_databases(self) -> set[CloudDatabase]: """All cloud databases.""" return set(self._databases) From 5d2bb0c74ff11ae578a9b65bd4a4cf33a0198e33 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 20 Feb 2026 18:32:10 +0000 Subject: [PATCH 2303/2467] Rename database methods and endpoints to cloud_databases (#2970) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clarify that database-related methods, endpoints, and variables refer to cloud databases rather than generic databases. This lays the groundwork for supporting additional database types in the future. Changes: - Rename Flask endpoints from /databases to /cloud_databases - Rename TargetManager methods: add_database → add_cloud_database, remove_database → remove_cloud_database - Rename MockVWS.add_database → add_cloud_database - Update all callers, tests, documentation, and error messages accordingly Co-authored-by: Claude Haiku 4.5 --- README.rst | 2 +- docs/source/basic-example.rst | 2 +- docs/source/docker.rst | 4 +- src/mock_vws/_flask_server/target_manager.py | 68 +++++++++++-------- src/mock_vws/_flask_server/vwq.py | 2 +- src/mock_vws/_flask_server/vws.py | 8 +-- .../_requests_mock_server/decorators.py | 12 ++-- src/mock_vws/target_manager.py | 38 ++++++----- tests/mock_vws/fixtures/vuforia_backends.py | 8 +-- tests/mock_vws/test_database_summary.py | 2 +- tests/mock_vws/test_docker.py | 2 +- tests/mock_vws/test_flask_app_usage.py | 42 ++++++------ tests/mock_vws/test_requests_mock_usage.py | 36 +++++----- 13 files changed, 119 insertions(+), 107 deletions(-) diff --git a/README.rst b/README.rst index bf2684105..483954a59 100644 --- a/README.rst +++ b/README.rst @@ -30,7 +30,7 @@ This requires Python |minimum-python-version|\+. with MockVWS() as mock: database = CloudDatabase() - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) # This will use the Vuforia mock. requests.get(url="https://vws.vuforia.com/summary", timeout=30) diff --git a/docs/source/basic-example.rst b/docs/source/basic-example.rst index f3aec76c3..17a0f568c 100644 --- a/docs/source/basic-example.rst +++ b/docs/source/basic-example.rst @@ -11,7 +11,7 @@ Using the mock redirects requests to Vuforia made with `requests`_ to an in-memo with MockVWS() as mock: database = CloudDatabase() - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) # This will use the Vuforia mock. requests.get(url="https://vws.vuforia.com/summary", timeout=30) diff --git a/docs/source/docker.rst b/docs/source/docker.rst index f77a811d6..c33b669b7 100644 --- a/docs/source/docker.rst +++ b/docs/source/docker.rst @@ -63,7 +63,7 @@ For example, with the containers set up as in :ref:`creating-containers`, use `` $ curl --request POST \ --header "Content-Type: application/json" \ --data '{}' \ - '127.0.0.1:5005/databases' + '127.0.0.1:5005/cloud_databases' { "client_access_key": "2d61c1d17bb94694bee77c1f1f41e5d9", "client_secret_key": "b73f8170cf7d42728fa8ce66221ad147", @@ -80,7 +80,7 @@ Deleting a database To delete a database use the following endpoint: .. autoflask:: mock_vws._flask_server.target_manager:TARGET_MANAGER_FLASK_APP - :endpoints: delete_database + :endpoints: delete_cloud_database .. _Target Manager: https://developer.vuforia.com/target-manager diff --git a/src/mock_vws/_flask_server/target_manager.py b/src/mock_vws/_flask_server/target_manager.py index 856738050..045ed0a84 100644 --- a/src/mock_vws/_flask_server/target_manager.py +++ b/src/mock_vws/_flask_server/target_manager.py @@ -58,14 +58,14 @@ class TargetManagerSettings(BaseSettings): @TARGET_MANAGER_FLASK_APP.route( - rule="/databases/", + rule="/cloud_databases/", methods=[HTTPMethod.DELETE], ) @beartype -def delete_database(database_name: str) -> Response: - """Delete a database. +def delete_cloud_database(database_name: str) -> Response: + """Delete a cloud database. - :status 200: The database has been deleted. + :status 200: The cloud database has been deleted. """ try: (matching_database,) = { @@ -76,14 +76,16 @@ def delete_database(database_name: str) -> Response: except ValueError: return Response(response="", status=HTTPStatus.NOT_FOUND) - TARGET_MANAGER.remove_database(database=matching_database) + TARGET_MANAGER.remove_cloud_database(cloud_database=matching_database) return Response(response="", status=HTTPStatus.OK) -@TARGET_MANAGER_FLASK_APP.route(rule="/databases", methods=[HTTPMethod.GET]) +@TARGET_MANAGER_FLASK_APP.route( + rule="/cloud_databases", methods=[HTTPMethod.GET] +) @beartype def get_cloud_databases() -> Response: - """Return a list of all databases.""" + """Return a list of all cloud databases.""" databases = [ database.to_dict() for database in TARGET_MANAGER.cloud_databases ] @@ -93,47 +95,53 @@ def get_cloud_databases() -> Response: ) -@TARGET_MANAGER_FLASK_APP.route(rule="/databases", methods=[HTTPMethod.POST]) +@TARGET_MANAGER_FLASK_APP.route( + rule="/cloud_databases", methods=[HTTPMethod.POST] +) @beartype def create_cloud_database() -> Response: - """Create a new database. + """Create a new cloud database. :reqheader Content-Type: application/json :resheader Content-Type: application/json :reqjson string client_access_key: (Optional) The client access key for the - database. + cloud database. :reqjson string client_secret_key: (Optional) The client secret key for the - database. + cloud database. - :reqjson string database_name: (Optional) The name of the database. + :reqjson string database_name: (Optional) The name of the cloud database. :reqjson string server_access_key: (Optional) The server access key for the - database. + cloud database. :reqjson string server_secret_key: (Optional) The server secret key for the - database. + cloud database. - :reqjson string state_name: (Optional) The state of the database. This can - be "WORKING" or "PROJECT_INACTIVE". This defaults to "WORKING". + :reqjson string state_name: (Optional) The state of the cloud database. + This can be "WORKING" or "PROJECT_INACTIVE". This defaults to "WORKING". - :resjson string client_access_key: The client access key for the database. + :resjson string client_access_key: The client access key for the cloud + database. - :resjson string client_secret_key: The client secret key for the database. + :resjson string client_secret_key: The client secret key for the cloud + database. - :resjson string database_name: The database name. + :resjson string database_name: The cloud database name. - :resjson string server_access_key: The server access key for the database. + :resjson string server_access_key: The server access key for the cloud + database. - :resjson string server_secret_key: The server secret key for the database. + :resjson string server_secret_key: The server secret key for the cloud + database. - :resjson string state_name: The database state. This will be "WORKING" or - "PROJECT_INACTIVE". + :resjson string state_name: The cloud database state. This will be + "WORKING" or "PROJECT_INACTIVE". - :reqjsonarr targets: The targets in the database. + :reqjsonarr targets: The targets in the cloud database. - :status 201: The database has been successfully created. + :status 201: The cloud database has been successfully created. """ random_database = CloudDatabase() request_json = json.loads(s=request.data) @@ -173,7 +181,7 @@ def create_cloud_database() -> Response: state=state, ) try: - TARGET_MANAGER.add_database(database=database) + TARGET_MANAGER.add_cloud_database(cloud_database=database) except ValueError as exc: return Response( response=str(object=exc), @@ -187,12 +195,12 @@ def create_cloud_database() -> Response: @TARGET_MANAGER_FLASK_APP.route( - rule="/databases//targets", + rule="/cloud_databases//targets", methods=[HTTPMethod.POST], ) @beartype def create_target(database_name: str) -> Response: - """Create a new target in a given database.""" + """Create a new target in a given cloud database.""" (database,) = ( database for database in TARGET_MANAGER.cloud_databases @@ -223,7 +231,7 @@ def create_target(database_name: str) -> Response: @TARGET_MANAGER_FLASK_APP.route( - rule="/databases//targets/", + rule="/cloud_databases//targets/", methods={HTTPMethod.DELETE}, ) @beartype @@ -250,7 +258,7 @@ def delete_target(database_name: str, target_id: str) -> Response: @TARGET_MANAGER_FLASK_APP.route( - rule="/databases//targets/", + rule="/cloud_databases//targets/", methods=[HTTPMethod.PUT], ) def update_target(database_name: str, target_id: str) -> Response: diff --git a/src/mock_vws/_flask_server/vwq.py b/src/mock_vws/_flask_server/vwq.py index d8ef38350..a421b1b66 100644 --- a/src/mock_vws/_flask_server/vwq.py +++ b/src/mock_vws/_flask_server/vwq.py @@ -67,7 +67,7 @@ def get_all_cloud_databases() -> set[CloudDatabase]: """Get all database objects from the target manager back-end.""" settings = VWQSettings.model_validate(obj={}) response = requests.get( - url=f"{settings.target_manager_base_url}/databases", + url=f"{settings.target_manager_base_url}/cloud_databases", timeout=30, ) return { diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index d85d0701e..a11cc5a69 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -91,7 +91,7 @@ def get_all_cloud_databases() -> set[CloudDatabase]: settings = VWSSettings.model_validate(obj={}) timeout_seconds = 30 response = requests.get( - url=f"{settings.target_manager_base_url}/databases", + url=f"{settings.target_manager_base_url}/cloud_databases", timeout=timeout_seconds, ) return { @@ -202,7 +202,7 @@ def add_target() -> Response: target_tracking_rater=target_tracking_rater, ) - databases_url = f"{settings.target_manager_base_url}/databases" + databases_url = f"{settings.target_manager_base_url}/cloud_databases" timeout_seconds = 30 requests.post( url=f"{databases_url}/{database.database_name}/targets", @@ -318,7 +318,7 @@ def delete_target(target_id: str) -> Response: if target.status == TargetStatuses.PROCESSING.value: raise TargetStatusProcessingError - databases_url = f"{settings.target_manager_base_url}/databases" + databases_url = f"{settings.target_manager_base_url}/cloud_databases" requests.delete( url=f"{databases_url}/{database.database_name}/targets/{target_id}", timeout=30, @@ -669,7 +669,7 @@ def update_target(target_id: str) -> Response: update_values["image"] = image put_url = ( - f"{settings.target_manager_base_url}/databases/" + f"{settings.target_manager_base_url}/cloud_databases/" f"{database.database_name}/targets/{target_id}" ) requests.put(url=put_url, json=update_values, timeout=30) diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index e13ed84dd..60c698dc0 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -126,17 +126,19 @@ def __init__( query_match_checker=query_match_checker, ) - def add_database(self, database: CloudDatabase) -> None: + def add_cloud_database(self, cloud_database: CloudDatabase) -> None: """Add a cloud database. Args: - database: The database to add. + cloud_database: The cloud database to add. Raises: - ValueError: One of the given database keys matches a key for an - existing database. + ValueError: One of the given cloud database keys matches a key for + an existing cloud database. """ - self._target_manager.add_database(database=database) + self._target_manager.add_cloud_database( + cloud_database=cloud_database, + ) @staticmethod def _wrap_callback( diff --git a/src/mock_vws/target_manager.py b/src/mock_vws/target_manager.py index c304cd40a..f5900f9b2 100644 --- a/src/mock_vws/target_manager.py +++ b/src/mock_vws/target_manager.py @@ -19,59 +19,61 @@ class TargetManager: """ def __init__(self) -> None: - """Create a target manager with no databases.""" - self._databases: Iterable[CloudDatabase] = set() + """Create a target manager with no cloud databases.""" + self._cloud_databases: Iterable[CloudDatabase] = set() - def remove_database(self, database: CloudDatabase) -> None: + def remove_cloud_database(self, cloud_database: CloudDatabase) -> None: """Remove a cloud database. Args: - database: The database to add. + cloud_database: The cloud database to remove. Raises: - KeyError: The database is not in the target manager. + KeyError: The cloud database is not in the target manager. """ - self._databases = {db for db in self._databases if db != database} + self._cloud_databases = { + db for db in self._cloud_databases if db != cloud_database + } - def add_database(self, database: CloudDatabase) -> None: + def add_cloud_database(self, cloud_database: CloudDatabase) -> None: """Add a cloud database. Args: - database: The database to add. + cloud_database: The cloud database to add. Raises: - ValueError: One of the given database keys matches a key for an - existing database. + ValueError: One of the given cloud database keys matches a key for + an existing cloud database. """ message_fmt = ( "All {key_name}s must be unique. " - 'There is already a database with the {key_name} "{value}".' + 'There is already a cloud database with the {key_name} "{value}".' ) for existing_db in self.cloud_databases: for existing, new, key_name in ( ( existing_db.server_access_key, - database.server_access_key, + cloud_database.server_access_key, "server access key", ), ( existing_db.server_secret_key, - database.server_secret_key, + cloud_database.server_secret_key, "server secret key", ), ( existing_db.client_access_key, - database.client_access_key, + cloud_database.client_access_key, "client access key", ), ( existing_db.client_secret_key, - database.client_secret_key, + cloud_database.client_secret_key, "client secret key", ), ( existing_db.database_name, - database.database_name, + cloud_database.database_name, "name", ), ): @@ -79,9 +81,9 @@ def add_database(self, database: CloudDatabase) -> None: message = message_fmt.format(key_name=key_name, value=new) raise ValueError(message) - self._databases = {*self._databases, database} + self._cloud_databases = {*self._cloud_databases, cloud_database} @property def cloud_databases(self) -> set[CloudDatabase]: """All cloud databases.""" - return set(self._databases) + return set(self._cloud_databases) diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 694af4b67..7eae10e4e 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -137,9 +137,9 @@ def _enable_use_mock_vuforia( ) with MockVWS() as mock: - mock.add_database(database=working_database) - mock.add_database(database=inactive_database) - mock.add_database(database=vumark_database) + mock.add_cloud_database(cloud_database=working_database) + mock.add_cloud_database(cloud_database=inactive_database) + mock.add_cloud_database(cloud_database=vumark_database) yield @@ -196,7 +196,7 @@ def _enable_use_docker_in_memory( base_url=target_manager_base_url, ) - databases_url = target_manager_base_url + "/databases" + databases_url = target_manager_base_url + "/cloud_databases" databases = requests.get(url=databases_url, timeout=30).json() for database in databases: database_name = database["database_name"] diff --git a/tests/mock_vws/test_database_summary.py b/tests/mock_vws/test_database_summary.py index c00deaf18..892d3c71d 100644 --- a/tests/mock_vws/test_database_summary.py +++ b/tests/mock_vws/test_database_summary.py @@ -246,7 +246,7 @@ def test_processing_images( ) with MockVWS() as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) vws_client.add_target( name=uuid.uuid4().hex, width=1, diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py index 7246044ae..a0472e558 100644 --- a/tests/mock_vws/test_docker.py +++ b/tests/mock_vws/test_docker.py @@ -216,7 +216,7 @@ def test_build_and_run( ) response = requests.post( - url=f"{base_target_manager_url}/databases", + url=f"{base_target_manager_url}/cloud_databases", json=database.to_dict(), timeout=30, ) diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index cacb3ca63..49578e0b1 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -71,7 +71,7 @@ def test_default( ) -> None: """By default, targets in the mock takes 2 seconds to be processed.""" database = CloudDatabase() - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) time_taken = processing_time_seconds( @@ -94,7 +94,7 @@ def test_custom( value=str(object=seconds), ) database = CloudDatabase() - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) time_taken = processing_time_seconds( @@ -131,26 +131,26 @@ def test_duplicate_keys() -> None: server_access_key_conflict_error = ( "All server access keys must be unique. " - 'There is already a database with the server access key "1".' + 'There is already a cloud database with the server access key "1".' ) server_secret_key_conflict_error = ( "All server secret keys must be unique. " - 'There is already a database with the server secret key "2".' + 'There is already a cloud database with the server secret key "2".' ) client_access_key_conflict_error = ( "All client access keys must be unique. " - 'There is already a database with the client access key "3".' + 'There is already a cloud database with the client access key "3".' ) client_secret_key_conflict_error = ( "All client secret keys must be unique. " - 'There is already a database with the client secret key "4".' + 'There is already a cloud database with the client secret key "4".' ) database_name_conflict_error = ( "All names must be unique. " - 'There is already a database with the name "5".' + 'There is already a cloud database with the name "5".' ) - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) for bad_database, expected_message in ( @@ -172,7 +172,7 @@ def test_duplicate_keys() -> None: @staticmethod def test_give_no_details(high_quality_image: io.BytesIO) -> None: """It is possible to create a database without giving any data.""" - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" response = requests.post(url=databases_url, json={}, timeout=30) assert response.status_code == HTTPStatus.CREATED @@ -206,7 +206,7 @@ def test_not_found() -> None: does not exist. """ - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" delete_url = databases_url + "/" + "foobar" response = requests.delete(url=delete_url, json={}, timeout=30) assert response.status_code == HTTPStatus.NOT_FOUND @@ -214,7 +214,7 @@ def test_not_found() -> None: @staticmethod def test_delete_database() -> None: """It is possible to delete a database.""" - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" response = requests.post(url=databases_url, json={}, timeout=30) assert response.status_code == HTTPStatus.CREATED @@ -253,7 +253,7 @@ def test_exact_match( re_exported_image = io.BytesIO() pil_image.save(fp=re_exported_image, format="PNG") - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) target_id = vws_client.add_target( @@ -297,7 +297,7 @@ def test_structural_similarity_matcher( pil_image = Image.open(fp=high_quality_image) re_exported_image = io.BytesIO() pil_image.save(fp=re_exported_image, format="PNG") - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) assert re_exported_image.getvalue() != high_quality_image.getvalue() @@ -345,7 +345,7 @@ def test_exact_match( re_exported_image = io.BytesIO() pil_image.save(fp=re_exported_image, format="PNG") - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) target_id = vws_client.add_target( @@ -397,7 +397,7 @@ def test_structural_similarity_matcher( re_exported_image = io.BytesIO() pil_image.save(fp=re_exported_image, format="PNG") - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) target_id = vws_client.add_target( @@ -430,7 +430,7 @@ def test_default( ) -> None: """By default, the BRISQUE target rater is used.""" database = CloudDatabase() - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) vws_client = VWS( @@ -481,7 +481,7 @@ def test_brisque( monkeypatch.setenv(name="TARGET_RATER", value="brisque") database = CloudDatabase() - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) vws_client = VWS( @@ -530,7 +530,7 @@ def test_perfect( """It is possible to use the perfect target rater.""" monkeypatch.setenv(name="TARGET_RATER", value="perfect") database = CloudDatabase() - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) vws_client = VWS( @@ -570,7 +570,7 @@ def test_random( monkeypatch.setenv(name="TARGET_RATER", value="random") database = CloudDatabase() - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) vws_client = VWS( @@ -642,7 +642,7 @@ def _make_request() -> None: def test_default_no_delay(self) -> None: """By default, there is no response delay.""" database = CloudDatabase() - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) start = time.monotonic() @@ -660,7 +660,7 @@ def test_delay_is_applied( value=f"{self.DELAY_SECONDS}", ) database = CloudDatabase() - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" requests.post(url=databases_url, json=database.to_dict(), timeout=30) start = time.monotonic() diff --git a/tests/mock_vws/test_requests_mock_usage.py b/tests/mock_vws/test_requests_mock_usage.py index cc79f716f..37365bb49 100644 --- a/tests/mock_vws/test_requests_mock_usage.py +++ b/tests/mock_vws/test_requests_mock_usage.py @@ -255,7 +255,7 @@ def test_default(self, image_file_failed_state: io.BytesIO) -> None: """By default, targets in the mock takes 2 seconds to be processed.""" database = CloudDatabase() with MockVWS() as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) time_taken = processing_time_seconds( vuforia_database=database, image=image_file_failed_state, @@ -269,7 +269,7 @@ def test_custom(self, image_file_failed_state: io.BytesIO) -> None: database = CloudDatabase() seconds = 5 with MockVWS(processing_time_seconds=seconds) as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) time_taken = processing_time_seconds( vuforia_database=database, image=image_file_failed_state, @@ -384,7 +384,7 @@ def test_to_dict(high_quality_image: io.BytesIO) -> None: ) with MockVWS() as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) vws_client.add_target( name="example", width=1, @@ -418,7 +418,7 @@ def test_to_dict_deleted(high_quality_image: io.BytesIO) -> None: ) with MockVWS() as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) target_id = vws_client.add_target( name="example", width=1, @@ -457,7 +457,7 @@ def test_to_dict(high_quality_image: io.BytesIO) -> None: # We test a database with a target added. with MockVWS() as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) vws_client.add_target( name="example", width=1, @@ -528,27 +528,27 @@ def test_duplicate_keys() -> None: server_access_key_conflict_error = ( "All server access keys must be unique. " - 'There is already a database with the server access key "1".' + 'There is already a cloud database with the server access key "1".' ) server_secret_key_conflict_error = ( "All server secret keys must be unique. " - 'There is already a database with the server secret key "2".' + 'There is already a cloud database with the server secret key "2".' ) client_access_key_conflict_error = ( "All client access keys must be unique. " - 'There is already a database with the client access key "3".' + 'There is already a cloud database with the client access key "3".' ) client_secret_key_conflict_error = ( "All client secret keys must be unique. " - 'There is already a database with the client secret key "4".' + 'There is already a cloud database with the client secret key "4".' ) database_name_conflict_error = ( "All names must be unique. " - 'There is already a database with the name "5".' + 'There is already a cloud database with the name "5".' ) with MockVWS() as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) for bad_database, expected_message in ( (bad_server_access_key_db, server_access_key_conflict_error), (bad_server_secret_key_db, server_secret_key_conflict_error), @@ -560,7 +560,7 @@ def test_duplicate_keys() -> None: expected_exception=ValueError, match=expected_message + "$", ): - mock.add_database(database=bad_database) + mock.add_cloud_database(cloud_database=bad_database) class TestQueryImageMatchers: @@ -584,7 +584,7 @@ def test_exact_match(high_quality_image: io.BytesIO) -> None: pil_image.save(fp=re_exported_image, format="PNG") with MockVWS(query_match_checker=ExactMatcher()) as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) target_id = vws_client.add_target( name="example", width=1, @@ -620,7 +620,7 @@ def test_custom_matcher(high_quality_image: io.BytesIO) -> None: pil_image.save(fp=re_exported_image, format="PNG") with MockVWS(query_match_checker=_not_exact_matcher) as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) target_id = vws_client.add_target( name="example", width=1, @@ -661,7 +661,7 @@ def test_structural_similarity_matcher( with MockVWS( query_match_checker=StructuralSimilarityMatcher(), ) as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) target_id = vws_client.add_target( name="example", width=1, @@ -702,7 +702,7 @@ def test_exact_match(high_quality_image: io.BytesIO) -> None: pil_image.save(fp=re_exported_image, format="PNG") with MockVWS(duplicate_match_checker=ExactMatcher()) as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) target_id = vws_client.add_target( name="example_0", width=1, @@ -746,7 +746,7 @@ def test_custom_matcher(high_quality_image: io.BytesIO) -> None: pil_image.save(fp=re_exported_image, format="PNG") with MockVWS(duplicate_match_checker=_not_exact_matcher) as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) target_id = vws_client.add_target( name="example_0", width=1, @@ -794,7 +794,7 @@ def test_structural_similarity_matcher( with MockVWS( duplicate_match_checker=StructuralSimilarityMatcher(), ) as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) target_id = vws_client.add_target( name="example", width=1, From 2b6883b8f7de586c25fb3ee9f644a1b4169121b6 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 20 Feb 2026 22:12:07 +0000 Subject: [PATCH 2304/2467] Create VuMarkTarget type for semantically correct VuMark fixture (#2968) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create VuMarkTarget type for semantically correct VuMark fixture Previously, _vumark_database used ImageTarget for VuMark targets, which was semantically incorrect since VuMark targets don't have image data. Now VuMarkTarget is a proper type that reflects the differences: no image_value, no processing_time_seconds, always SUCCESS status, and hardcoded tracking_rating. Changes: - Add VuMarkTarget and VuMarkTargetDict to target.py - Add vumark_targets field to CloudDatabase (separate from image targets) - Update not_deleted_targets to include both types for validator lookups - Keep active_targets, inactive_targets, etc. as image-only for clarity - Add POST /databases/{name}/vumark_targets Flask endpoint - Update _vumark_database fixture to use VuMarkTarget - Update _enable_use_docker_in_memory to use new endpoint - Document VuMarkTarget in API reference Co-Authored-By: Claude Haiku 4.5 * Fix type issues with get_target returning image targets only The validator uses not_deleted_targets (which returns union type) so it can find both image and VuMark targets. But get_target() is only called for image targets in the Flask and requests mock servers, so return ImageTarget only. * Fix mypy type hint for not_deleted_targets union * Separate VuMark database from CloudDatabase and minimize VuMarkTarget Introduce VuMarkDatabase and VuMarkDatabaseDict as distinct types from CloudDatabase, since VuMark databases don't have client keys, state, or quota fields. Minimize VuMarkTarget/VuMarkTargetDict to only the fields actually used (target_id, name, delete_date). Update all validators, Flask endpoints, and request mock servers to handle the AnyDatabase union type. Split target manager endpoints by database type: /databases for cloud, /vumark_databases for VuMark. Add typed lookup helpers instead of isinstance guards in endpoint functions. Co-Authored-By: Claude Opus 4.6 * Fix Sphinx docs reference to renamed endpoint The create_database endpoint was renamed to create_cloud_database but the docs/source/docker.rst autoflask directive was not updated. Co-Authored-By: Claude Opus 4.6 * Split TargetManager.databases into typed cloud_databases and vumark_databases This eliminates 22 isinstance checks by storing each database type separately and narrowing all downstream signatures to the specific type they need (CloudDatabase for VWS/VWQ APIs and validators). Co-Authored-By: Claude Opus 4.6 * Revert unnecessary changes to cloud-specific functions Restore cloud functions to match main exactly - only VuMark additions remain. Co-Authored-By: Claude Opus 4.6 * Revert cosmetic changes to existing code Remove unnecessary docstring tweaks, comment rewraps, and refactors to CloudDatabase that are unrelated to VuMark support. Co-Authored-By: Claude Opus 4.6 * Fix VuMark auth by widening validators to accept both database types VuMark database credentials were not found during VWS authentication because validators only searched cloud databases. Widen all service validator signatures to accept AnyDatabase (CloudDatabase | VuMarkDatabase), pass both database types from the Flask and requests-mock servers, and add VuMarkDatabase to the Sphinx API docs. Co-Authored-By: Claude Opus 4.6 * Address bugbot review: scope validate_request to cloud-only databases - Skip validate_request for VuMark endpoint; it does its own validation with both database types (fixes 500 when VuMark creds hit cloud endpoints) - Fix misleading error message in add_cloud_database ("cloud database" → "database") - Deduplicate AnyDatabase alias: import from _database_matchers in target_manager Co-Authored-By: Claude Opus 4.6 * Update tests to match new database conflict error message Co-Authored-By: Claude Opus 4.6 * Fix coverage: remove dead VuMark branches, add duplicate key tests - Remove delete_date field from VuMarkTarget (VuMark targets can't be deleted) - Simplify VuMarkDatabase.not_deleted_targets (no filtering needed) - Remove NOT_FOUND branch from delete_vumark_database (internal API) - Add test_duplicate_vumark_keys to both Flask and requests-mock test suites Co-Authored-By: Claude Opus 4.6 * Fix VuMark duplicate keys test isolation in Flask tests Use distinct keys ("v1", "v2", "v3") for VuMark database tests to avoid conflicts with the TARGET_MANAGER singleton state from cloud database tests. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Haiku 4.5 --- docs/source/mock-api-reference.rst | 7 ++ src/mock_vws/_database_matchers.py | 10 +- src/mock_vws/_flask_server/target_manager.py | 102 +++++++++++++++++- src/mock_vws/_flask_server/vws.py | 42 +++++++- .../_requests_mock_server/decorators.py | 16 ++- .../mock_web_services_api.py | 11 +- src/mock_vws/_services_validators/__init__.py | 4 +- .../_services_validators/auth_validators.py | 10 +- .../_services_validators/name_validators.py | 10 +- .../project_state_validators.py | 10 +- .../_services_validators/target_validators.py | 8 +- src/mock_vws/database.py | 73 ++++++++++++- src/mock_vws/target.py | 39 +++++++ src/mock_vws/target_manager.py | 99 ++++++++++++++--- tests/mock_vws/fixtures/vuforia_backends.py | 63 +++++------ tests/mock_vws/test_flask_app_usage.py | 58 ++++++++-- tests/mock_vws/test_requests_mock_usage.py | 54 ++++++++-- 17 files changed, 529 insertions(+), 87 deletions(-) diff --git a/docs/source/mock-api-reference.rst b/docs/source/mock-api-reference.rst index ee0215fe0..338855d2c 100644 --- a/docs/source/mock-api-reference.rst +++ b/docs/source/mock-api-reference.rst @@ -20,12 +20,19 @@ API Reference :undoc-members: :exclude-members: to_dict, get_target, from_dict, not_deleted_targets, active_targets, inactive_targets, failed_targets, processing_targets +.. autoclass:: mock_vws.database.VuMarkDatabase + :members: + :undoc-members: + :exclude-members: to_dict, from_dict, not_deleted_targets + .. autoenum:: mock_vws.states.States :members: :undoc-members: .. autoclass:: mock_vws.target.ImageTarget +.. autoclass:: mock_vws.target.VuMarkTarget + Image matchers -------------- diff --git a/src/mock_vws/_database_matchers.py b/src/mock_vws/_database_matchers.py index 0e6a8c76d..dae0253a7 100644 --- a/src/mock_vws/_database_matchers.py +++ b/src/mock_vws/_database_matchers.py @@ -5,7 +5,9 @@ from beartype import beartype from vws_auth_tools import authorization_header -from mock_vws.database import CloudDatabase +from mock_vws.database import CloudDatabase, VuMarkDatabase + +AnyDatabase = CloudDatabase | VuMarkDatabase @beartype @@ -58,14 +60,14 @@ def get_database_matching_client_keys( @beartype -def get_database_matching_server_keys( +def get_database_matching_server_keys[DatabaseT: AnyDatabase]( *, request_headers: Mapping[str, str], request_body: bytes | None, request_method: str, request_path: str, - databases: Iterable[CloudDatabase], -) -> CloudDatabase: + databases: Iterable[DatabaseT], +) -> DatabaseT: """Return the first of the given databases which is being accessed by the given server request. diff --git a/src/mock_vws/_flask_server/target_manager.py b/src/mock_vws/_flask_server/target_manager.py index 045ed0a84..1a308e4dd 100644 --- a/src/mock_vws/_flask_server/target_manager.py +++ b/src/mock_vws/_flask_server/target_manager.py @@ -12,9 +12,9 @@ from flask import Flask, Response, request from pydantic_settings import BaseSettings -from mock_vws.database import CloudDatabase +from mock_vws.database import CloudDatabase, VuMarkDatabase from mock_vws.states import States -from mock_vws.target import ImageTarget +from mock_vws.target import ImageTarget, VuMarkTarget from mock_vws.target_manager import TargetManager from mock_vws.target_raters import ( BrisqueTargetTrackingRater, @@ -80,6 +80,25 @@ def delete_cloud_database(database_name: str) -> Response: return Response(response="", status=HTTPStatus.OK) +@TARGET_MANAGER_FLASK_APP.route( + rule="/vumark_databases/", + methods=[HTTPMethod.DELETE], +) +@beartype +def delete_vumark_database(database_name: str) -> Response: + """Delete a VuMark database. + + :status 200: The VuMark database has been deleted. + """ + (matching_database,) = { + database + for database in TARGET_MANAGER.vumark_databases + if database_name == database.database_name + } + TARGET_MANAGER.remove_vumark_database(vumark_database=matching_database) + return Response(response="", status=HTTPStatus.OK) + + @TARGET_MANAGER_FLASK_APP.route( rule="/cloud_databases", methods=[HTTPMethod.GET] ) @@ -95,6 +114,22 @@ def get_cloud_databases() -> Response: ) +@TARGET_MANAGER_FLASK_APP.route( + rule="/vumark_databases", + methods=[HTTPMethod.GET], +) +@beartype +def get_vumark_databases() -> Response: + """Return a list of all VuMark databases.""" + databases = [ + database.to_dict() for database in TARGET_MANAGER.vumark_databases + ] + return Response( + response=json.dumps(obj=databases), + status=HTTPStatus.OK, + ) + + @TARGET_MANAGER_FLASK_APP.route( rule="/cloud_databases", methods=[HTTPMethod.POST] ) @@ -194,6 +229,47 @@ def create_cloud_database() -> Response: ) +@TARGET_MANAGER_FLASK_APP.route( + rule="/vumark_databases", + methods=[HTTPMethod.POST], +) +@beartype +def create_vumark_database() -> Response: + """Create a new VuMark database. + + :status 201: The database has been successfully created. + """ + request_json = json.loads(s=request.data) + random_vumark_database = VuMarkDatabase() + database = VuMarkDatabase( + server_access_key=request_json.get( + "server_access_key", + random_vumark_database.server_access_key, + ), + server_secret_key=request_json.get( + "server_secret_key", + random_vumark_database.server_secret_key, + ), + database_name=request_json.get( + "database_name", + random_vumark_database.database_name, + ), + ) + + try: + TARGET_MANAGER.add_vumark_database(vumark_database=database) + except ValueError as exc: + return Response( + response=str(object=exc), + status=HTTPStatus.CONFLICT, + ) + + return Response( + response=json.dumps(obj=database.to_dict()), + status=HTTPStatus.CREATED, + ) + + @TARGET_MANAGER_FLASK_APP.route( rule="/cloud_databases//targets", methods=[HTTPMethod.POST], @@ -230,6 +306,28 @@ def create_target(database_name: str) -> Response: ) +@TARGET_MANAGER_FLASK_APP.route( + rule="/vumark_databases//vumark_targets", + methods=[HTTPMethod.POST], +) +@beartype +def create_vumark_target(database_name: str) -> Response: + """Create a new VuMark target in a given database.""" + (database,) = ( + database + for database in TARGET_MANAGER.vumark_databases + if database.database_name == database_name + ) + request_json = json.loads(s=request.data) + target = VuMarkTarget.from_dict(target_dict=request_json) + database.vumark_targets.add(target) + + return Response( + response=json.dumps(obj=target.to_dict()), + status=HTTPStatus.CREATED, + ) + + @TARGET_MANAGER_FLASK_APP.route( rule="/cloud_databases//targets/", methods={HTTPMethod.DELETE}, diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index a11cc5a69..1c98d1336 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -36,7 +36,7 @@ TargetStatusProcessingError, ValidatorError, ) -from mock_vws.database import CloudDatabase +from mock_vws.database import CloudDatabase, VuMarkDatabase from mock_vws.image_matchers import ( ExactMatcher, ImageMatcher, @@ -100,6 +100,21 @@ def get_all_cloud_databases() -> set[CloudDatabase]: } +@beartype +def get_all_vumark_databases() -> set[VuMarkDatabase]: + """Get all VuMark database objects from the task manager back-end.""" + settings = VWSSettings.model_validate(obj={}) + timeout_seconds = 30 + response = requests.get( + url=f"{settings.target_manager_base_url}/vumark_databases", + timeout=timeout_seconds, + ) + return { + VuMarkDatabase.from_dict(database_dict=database_dict) + for database_dict in response.json() + } + + @VWS_FLASK_APP.before_request def set_terminate_wsgi_input() -> None: """We set ``wsgi.input_terminated`` to ``True`` when going through @@ -129,14 +144,19 @@ def set_terminate_wsgi_input() -> None: @VWS_FLASK_APP.before_request @beartype def validate_request() -> None: - """Run validators on the request.""" - databases = get_all_cloud_databases() + """Run validators on the request. + + The VuMark endpoint does its own validation because it needs to + authenticate against both cloud and VuMark databases. + """ + if request.endpoint == "generate_vumark_instance": + return run_services_validators( request_headers=dict(request.headers), request_body=request.data, request_method=request.method, request_path=request.path, - databases=databases, + databases=get_all_cloud_databases(), ) @@ -357,6 +377,20 @@ def generate_vumark_instance(target_id: str) -> Response: Fake implementation of https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#generate-instance """ + cloud_databases = get_all_cloud_databases() + vumark_databases = get_all_vumark_databases() + all_databases: list[CloudDatabase | VuMarkDatabase] = [ + *cloud_databases, + *vumark_databases, + ] + run_services_validators( + request_headers=dict(request.headers), + request_body=request.data, + request_method=request.method, + request_path=request.path, + databases=all_databases, + ) + # ``target_id`` is validated by request validators. del target_id diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 60c698dc0..35535217b 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -12,7 +12,7 @@ from requests import PreparedRequest from responses import RequestsMock -from mock_vws.database import CloudDatabase +from mock_vws.database import CloudDatabase, VuMarkDatabase from mock_vws.image_matchers import ( ImageMatcher, StructuralSimilarityMatcher, @@ -140,6 +140,20 @@ def add_cloud_database(self, cloud_database: CloudDatabase) -> None: cloud_database=cloud_database, ) + def add_vumark_database(self, vumark_database: VuMarkDatabase) -> None: + """Add a VuMark database. + + Args: + vumark_database: The VuMark database to add. + + Raises: + ValueError: One of the given database keys matches a key for + an existing database. + """ + self._target_manager.add_vumark_database( + vumark_database=vumark_database, + ) + @staticmethod def _wrap_callback( callback: _Callback, diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index bbd782c24..5a5b7da96 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -12,7 +12,7 @@ import uuid from collections.abc import Callable, Iterable, Mapping from http import HTTPMethod, HTTPStatus -from typing import Any, ParamSpec, Protocol, runtime_checkable +from typing import TYPE_CHECKING, Any, ParamSpec, Protocol, runtime_checkable from zoneinfo import ZoneInfo from beartype import BeartypeConf, beartype @@ -41,6 +41,9 @@ from mock_vws.target_manager import TargetManager from mock_vws.target_raters import TargetTrackingRater +if TYPE_CHECKING: + from mock_vws.database import CloudDatabase, VuMarkDatabase + _TARGET_ID_PATTERN = "[A-Za-z0-9]+" @@ -309,12 +312,16 @@ def generate_vumark_instance( "application/pdf": VUMARK_PDF, } try: + all_databases: list[CloudDatabase | VuMarkDatabase] = [ + *self._target_manager.cloud_databases, + *self._target_manager.vumark_databases, + ] run_services_validators( request_headers=request.headers, request_body=_body_bytes(request=request), request_method=request.method or "", request_path=request.path_url, - databases=self._target_manager.cloud_databases, + databases=all_databases, ) accept = dict(request.headers).get("Accept", "") diff --git a/src/mock_vws/_services_validators/__init__.py b/src/mock_vws/_services_validators/__init__.py index e37c28d84..7a2e742a3 100644 --- a/src/mock_vws/_services_validators/__init__.py +++ b/src/mock_vws/_services_validators/__init__.py @@ -2,7 +2,7 @@ from collections.abc import Iterable, Mapping -from mock_vws.database import CloudDatabase +from mock_vws._database_matchers import AnyDatabase from .active_flag_validators import validate_active_flag from .auth_validators import ( @@ -55,7 +55,7 @@ def run_services_validators( request_headers: Mapping[str, str], request_body: bytes, request_method: str, - databases: Iterable[CloudDatabase], + databases: Iterable[AnyDatabase], ) -> None: """Run all validators. diff --git a/src/mock_vws/_services_validators/auth_validators.py b/src/mock_vws/_services_validators/auth_validators.py index a5922b7ae..1117b4636 100644 --- a/src/mock_vws/_services_validators/auth_validators.py +++ b/src/mock_vws/_services_validators/auth_validators.py @@ -6,12 +6,14 @@ from beartype import beartype -from mock_vws._database_matchers import get_database_matching_server_keys +from mock_vws._database_matchers import ( + AnyDatabase, + get_database_matching_server_keys, +) from mock_vws._services_validators.exceptions import ( AuthenticationFailureError, FailError, ) -from mock_vws.database import CloudDatabase _LOGGER = logging.getLogger(name=__name__) @@ -36,7 +38,7 @@ def validate_auth_header_exists(*, request_headers: Mapping[str, str]) -> None: def validate_access_key_exists( *, request_headers: Mapping[str, str], - databases: Iterable[CloudDatabase], + databases: Iterable[AnyDatabase], ) -> None: """Validate the authorization header includes an access key for a database. @@ -92,7 +94,7 @@ def validate_authorization( request_headers: Mapping[str, str], request_body: bytes, request_method: str, - databases: Iterable[CloudDatabase], + databases: Iterable[AnyDatabase], ) -> None: """Validate the authorization header given to a VWS endpoint. diff --git a/src/mock_vws/_services_validators/name_validators.py b/src/mock_vws/_services_validators/name_validators.py index 04db14721..abf1532c0 100644 --- a/src/mock_vws/_services_validators/name_validators.py +++ b/src/mock_vws/_services_validators/name_validators.py @@ -7,12 +7,14 @@ from beartype import beartype -from mock_vws._database_matchers import get_database_matching_server_keys +from mock_vws._database_matchers import ( + AnyDatabase, + get_database_matching_server_keys, +) from mock_vws._services_validators.exceptions import ( FailError, TargetNameExistError, ) -from mock_vws.database import CloudDatabase _LOGGER = logging.getLogger(name=__name__) @@ -116,7 +118,7 @@ def validate_name_length(*, request_body: bytes) -> None: @beartype def validate_name_does_not_exist_new_target( *, - databases: Iterable[CloudDatabase], + databases: Iterable[AnyDatabase], request_body: bytes, request_headers: Mapping[str, str], request_method: str, @@ -176,7 +178,7 @@ def validate_name_does_not_exist_existing_target( request_body: bytes, request_method: str, request_path: str, - databases: Iterable[CloudDatabase], + databases: Iterable[AnyDatabase], ) -> None: """Validate that the name does not exist for any existing target apart from diff --git a/src/mock_vws/_services_validators/project_state_validators.py b/src/mock_vws/_services_validators/project_state_validators.py index d4b263392..ac1fe97d2 100644 --- a/src/mock_vws/_services_validators/project_state_validators.py +++ b/src/mock_vws/_services_validators/project_state_validators.py @@ -6,7 +6,10 @@ from beartype import beartype -from mock_vws._database_matchers import get_database_matching_server_keys +from mock_vws._database_matchers import ( + AnyDatabase, + get_database_matching_server_keys, +) from mock_vws._services_validators.exceptions import ProjectInactiveError from mock_vws.database import CloudDatabase from mock_vws.states import States @@ -21,7 +24,7 @@ def validate_project_state( request_headers: Mapping[str, str], request_body: bytes, request_method: str, - databases: Iterable[CloudDatabase], + databases: Iterable[AnyDatabase], ) -> None: """Validate the state of the project. @@ -44,6 +47,9 @@ def validate_project_state( databases=databases, ) + if not isinstance(database, CloudDatabase): + return + if database.state != States.PROJECT_INACTIVE: return diff --git a/src/mock_vws/_services_validators/target_validators.py b/src/mock_vws/_services_validators/target_validators.py index ca5077ef2..58f1da0d7 100644 --- a/src/mock_vws/_services_validators/target_validators.py +++ b/src/mock_vws/_services_validators/target_validators.py @@ -5,9 +5,11 @@ from beartype import beartype -from mock_vws._database_matchers import get_database_matching_server_keys +from mock_vws._database_matchers import ( + AnyDatabase, + get_database_matching_server_keys, +) from mock_vws._services_validators.exceptions import UnknownTargetError -from mock_vws.database import CloudDatabase _LOGGER = logging.getLogger(name=__name__) _TARGETS_WITH_INSTANCE_PATH_LENGTH = 4 @@ -20,7 +22,7 @@ def validate_target_id_exists( request_headers: Mapping[str, str], request_body: bytes, request_method: str, - databases: Iterable[CloudDatabase], + databases: Iterable[AnyDatabase], ) -> None: """Validate that if a target ID is given, it exists in the database matching the request. diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py index 0fb6876bc..b030a9e4e 100644 --- a/src/mock_vws/database.py +++ b/src/mock_vws/database.py @@ -9,7 +9,12 @@ from mock_vws._constants import TargetStatuses from mock_vws.states import States -from mock_vws.target import ImageTarget, ImageTargetDict +from mock_vws.target import ( + ImageTarget, + ImageTargetDict, + VuMarkTarget, + VuMarkTargetDict, +) @beartype @@ -25,6 +30,16 @@ class CloudDatabaseDict(TypedDict): targets: Iterable[ImageTargetDict] +@beartype +class VuMarkDatabaseDict(TypedDict): + """A dictionary type which represents a VuMark database.""" + + database_name: str + server_access_key: str + server_secret_key: str + vumark_targets: Iterable[VuMarkTargetDict] + + @beartype def _random_hex() -> str: """Return a random hex value.""" @@ -152,3 +167,59 @@ def processing_targets(self) -> set[ImageTarget]: for target in self.not_deleted_targets if target.status == TargetStatuses.PROCESSING.value } + + +@beartype +@dataclass(eq=True, frozen=True) +class VuMarkDatabase: + """Credentials for the VuMark generation API. + + Args: + database_name: The name of a VWS target manager database name. Defaults + to a random string. + server_access_key: A VWS server access key. Defaults to a random + string. + server_secret_key: A VWS server secret key. Defaults to a random + string. + """ + + database_name: str = field(default_factory=_random_hex, repr=False) + server_access_key: str = field(default_factory=_random_hex, repr=False) + server_secret_key: str = field(default_factory=_random_hex, repr=False) + # We have ``vumark_targets`` as ``hash=False`` so that we can have the + # class as ``frozen=True`` while still being able to keep the interface + # we want. + vumark_targets: set[VuMarkTarget] = field( + default_factory=set[VuMarkTarget], + hash=False, + ) + + def to_dict(self) -> VuMarkDatabaseDict: + """Dump a VuMark database to a dictionary which can be loaded as + JSON. + """ + vumark_targets = [target.to_dict() for target in self.vumark_targets] + return { + "database_name": self.database_name, + "server_access_key": self.server_access_key, + "server_secret_key": self.server_secret_key, + "vumark_targets": vumark_targets, + } + + @classmethod + def from_dict(cls, database_dict: VuMarkDatabaseDict) -> Self: + """Load a VuMark database from a dictionary.""" + return cls( + database_name=database_dict["database_name"], + server_access_key=database_dict["server_access_key"], + server_secret_key=database_dict["server_secret_key"], + vumark_targets={ + VuMarkTarget.from_dict(target_dict=target_dict) + for target_dict in database_dict["vumark_targets"] + }, + ) + + @property + def not_deleted_targets(self) -> set[VuMarkTarget]: + """All VuMark targets.""" + return set(self.vumark_targets) diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py index a17e8c140..d381884d8 100644 --- a/src/mock_vws/target.py +++ b/src/mock_vws/target.py @@ -19,6 +19,13 @@ ) +class VuMarkTargetDict(TypedDict): + """A dictionary type which represents a VuMark target.""" + + target_id: str + name: str + + class ImageTargetDict(TypedDict): """A dictionary type which represents a target.""" @@ -208,3 +215,35 @@ def to_dict(self) -> ImageTargetDict: "upload_date": self.upload_date.isoformat(), "tracking_rating": self.tracking_rating, } + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +@dataclass(frozen=True, eq=True) +class VuMarkTarget: + """ + A VuMark target as managed in + https://developer.vuforia.com/target-manager. + + Unlike ImageTarget, VuMark targets do not require an image — they use a + VuMark template. + """ + + name: str + target_id: str = field(default_factory=_random_hex) + + @classmethod + def from_dict(cls, target_dict: VuMarkTargetDict) -> Self: + """Load a VuMark target from a dictionary.""" + return cls( + target_id=target_dict["target_id"], + name=target_dict["name"], + ) + + def to_dict(self) -> VuMarkTargetDict: + """Dump a VuMark target to a dictionary which can be loaded as + JSON. + """ + return { + "target_id": self.target_id, + "name": self.name, + } diff --git a/src/mock_vws/target_manager.py b/src/mock_vws/target_manager.py index f5900f9b2..14850df8e 100644 --- a/src/mock_vws/target_manager.py +++ b/src/mock_vws/target_manager.py @@ -4,10 +4,10 @@ from beartype import beartype -from mock_vws.database import CloudDatabase +from mock_vws.database import CloudDatabase, VuMarkDatabase if TYPE_CHECKING: - from collections.abc import Iterable + from mock_vws._database_matchers import AnyDatabase @beartype @@ -19,8 +19,19 @@ class TargetManager: """ def __init__(self) -> None: - """Create a target manager with no cloud databases.""" - self._cloud_databases: Iterable[CloudDatabase] = set() + """Create a target manager with no databases.""" + self._cloud_databases: set[CloudDatabase] = set() + self._vumark_databases: set[VuMarkDatabase] = set() + + @property + def cloud_databases(self) -> set[CloudDatabase]: + """All cloud databases.""" + return set(self._cloud_databases) + + @property + def vumark_databases(self) -> set[VuMarkDatabase]: + """All VuMark databases.""" + return set(self._vumark_databases) def remove_cloud_database(self, cloud_database: CloudDatabase) -> None: """Remove a cloud database. @@ -35,6 +46,16 @@ def remove_cloud_database(self, cloud_database: CloudDatabase) -> None: db for db in self._cloud_databases if db != cloud_database } + def remove_vumark_database(self, vumark_database: VuMarkDatabase) -> None: + """Remove a VuMark database. + + Args: + vumark_database: The VuMark database to remove. + """ + self._vumark_databases = { + db for db in self._vumark_databases if db != vumark_database + } + def add_cloud_database(self, cloud_database: CloudDatabase) -> None: """Add a cloud database. @@ -47,9 +68,13 @@ def add_cloud_database(self, cloud_database: CloudDatabase) -> None: """ message_fmt = ( "All {key_name}s must be unique. " - 'There is already a cloud database with the {key_name} "{value}".' + 'There is already a database with the {key_name} "{value}".' ) - for existing_db in self.cloud_databases: + all_databases: list[AnyDatabase] = [ + *self._cloud_databases, + *self._vumark_databases, + ] + for existing_db in all_databases: for existing, new, key_name in ( ( existing_db.server_access_key, @@ -62,18 +87,67 @@ def add_cloud_database(self, cloud_database: CloudDatabase) -> None: "server secret key", ), ( - existing_db.client_access_key, + existing_db.database_name, + cloud_database.database_name, + "name", + ), + ): + if existing == new: + message = message_fmt.format(key_name=key_name, value=new) + raise ValueError(message) + + for existing_cloud_db in self._cloud_databases: + for existing, new, key_name in ( + ( + existing_cloud_db.client_access_key, cloud_database.client_access_key, "client access key", ), ( - existing_db.client_secret_key, + existing_cloud_db.client_secret_key, cloud_database.client_secret_key, "client secret key", ), + ): + if existing == new: + message = message_fmt.format(key_name=key_name, value=new) + raise ValueError(message) + + self._cloud_databases = {*self._cloud_databases, cloud_database} + + def add_vumark_database(self, vumark_database: VuMarkDatabase) -> None: + """Add a VuMark database. + + Args: + vumark_database: The VuMark database to add. + + Raises: + ValueError: One of the given database keys matches a key for + an existing database. + """ + message_fmt = ( + "All {key_name}s must be unique. " + 'There is already a database with the {key_name} "{value}".' + ) + all_databases: list[AnyDatabase] = [ + *self._cloud_databases, + *self._vumark_databases, + ] + for existing_db in all_databases: + for existing, new, key_name in ( + ( + existing_db.server_access_key, + vumark_database.server_access_key, + "server access key", + ), + ( + existing_db.server_secret_key, + vumark_database.server_secret_key, + "server secret key", + ), ( existing_db.database_name, - cloud_database.database_name, + vumark_database.database_name, "name", ), ): @@ -81,9 +155,4 @@ def add_cloud_database(self, cloud_database: CloudDatabase) -> None: message = message_fmt.format(key_name=key_name, value=new) raise ValueError(message) - self._cloud_databases = {*self._cloud_databases, cloud_database} - - @property - def cloud_databases(self) -> set[CloudDatabase]: - """All cloud databases.""" - return set(self._cloud_databases) + self._vumark_databases = {*self._vumark_databases, vumark_database} diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 7eae10e4e..6f5ea90ba 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -19,12 +19,10 @@ from mock_vws._flask_server.target_manager import TARGET_MANAGER_FLASK_APP from mock_vws._flask_server.vwq import CLOUDRECO_FLASK_APP from mock_vws._flask_server.vws import VWS_FLASK_APP -from mock_vws.database import CloudDatabase +from mock_vws.database import CloudDatabase, VuMarkDatabase from mock_vws.states import States -from mock_vws.target import ImageTarget -from mock_vws.target_raters import HardcodedTargetTrackingRater +from mock_vws.target import VuMarkTarget from tests.mock_vws.fixtures.credentials import VuMarkCloudDatabase -from tests.mock_vws.utils import make_image_file from tests.mock_vws.utils.retries import RETRY_ON_TOO_MANY_REQUESTS LOGGER = logging.getLogger(name=__name__) @@ -65,28 +63,19 @@ def _delete_all_targets(*, database_keys: CloudDatabase) -> None: def _vumark_database( *, vumark_vuforia_database: VuMarkCloudDatabase, -) -> CloudDatabase: - """Return a database with a target for VuMark instance generation.""" - vumark_target = ImageTarget( - active_flag=True, - application_metadata=None, - image_value=make_image_file( - file_format="PNG", - color_space="RGB", - width=8, - height=8, - ).getvalue(), +) -> VuMarkDatabase: + """Return a database with a VuMark target for VuMark instance + generation. + """ + vumark_target = VuMarkTarget( name="mock-vumark-target", - processing_time_seconds=0, - width=1, - target_tracking_rater=HardcodedTargetTrackingRater(rating=5), target_id=vumark_vuforia_database.target_id, ) - return CloudDatabase( + return VuMarkDatabase( database_name=vumark_vuforia_database.target_manager_database_name, server_access_key=vumark_vuforia_database.server_access_key, server_secret_key=vumark_vuforia_database.server_secret_key, - targets={vumark_target}, + vumark_targets={vumark_target}, ) @@ -139,7 +128,7 @@ def _enable_use_mock_vuforia( with MockVWS() as mock: mock.add_cloud_database(cloud_database=working_database) mock.add_cloud_database(cloud_database=inactive_database) - mock.add_cloud_database(cloud_database=vumark_database) + mock.add_vumark_database(vumark_database=vumark_database) yield @@ -175,7 +164,7 @@ def _enable_use_docker_in_memory( vumark_database = _vumark_database( vumark_vuforia_database=vumark_vuforia_database, ) - (vumark_target,) = vumark_database.targets + (vumark_target,) = vumark_database.vumark_targets with responses.RequestsMock(assert_all_requests_are_fired=False) as mock: add_flask_app_to_mock( @@ -196,32 +185,44 @@ def _enable_use_docker_in_memory( base_url=target_manager_base_url, ) - databases_url = target_manager_base_url + "/cloud_databases" - databases = requests.get(url=databases_url, timeout=30).json() - for database in databases: - database_name = database["database_name"] + cloud_databases_url = target_manager_base_url + "/cloud_databases" + vumark_databases_url = target_manager_base_url + "/vumark_databases" + + for database in requests.get( + url=cloud_databases_url, timeout=30 + ).json(): + requests.delete( + url=cloud_databases_url + "/" + database["database_name"], + timeout=30, + ) + for database in requests.get( + url=vumark_databases_url, timeout=30 + ).json(): requests.delete( - url=databases_url + "/" + database_name, + url=vumark_databases_url + "/" + database["database_name"], timeout=30, ) requests.post( - url=databases_url, + url=cloud_databases_url, json=working_database.to_dict(), timeout=30, ) requests.post( - url=databases_url, + url=cloud_databases_url, json=inactive_database.to_dict(), timeout=30, ) requests.post( - url=databases_url, + url=vumark_databases_url, json=vumark_database.to_dict(), timeout=30, ) requests.post( - url=(f"{databases_url}/{vumark_database.database_name}/targets"), + url=( + f"{vumark_databases_url}" + f"/{vumark_database.database_name}/vumark_targets" + ), json=vumark_target.to_dict(), timeout=30, ) diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index 49578e0b1..5ce6ad1aa 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -18,7 +18,7 @@ from mock_vws._flask_server.target_manager import TARGET_MANAGER_FLASK_APP from mock_vws._flask_server.vwq import CLOUDRECO_FLASK_APP from mock_vws._flask_server.vws import VWS_FLASK_APP -from mock_vws.database import CloudDatabase +from mock_vws.database import CloudDatabase, VuMarkDatabase from tests.mock_vws.utils.usage_test_helpers import ( processing_time_seconds, ) @@ -131,23 +131,23 @@ def test_duplicate_keys() -> None: server_access_key_conflict_error = ( "All server access keys must be unique. " - 'There is already a cloud database with the server access key "1".' + 'There is already a database with the server access key "1".' ) server_secret_key_conflict_error = ( "All server secret keys must be unique. " - 'There is already a cloud database with the server secret key "2".' + 'There is already a database with the server secret key "2".' ) client_access_key_conflict_error = ( "All client access keys must be unique. " - 'There is already a cloud database with the client access key "3".' + 'There is already a database with the client access key "3".' ) client_secret_key_conflict_error = ( "All client secret keys must be unique. " - 'There is already a cloud database with the client secret key "4".' + 'There is already a database with the client secret key "4".' ) database_name_conflict_error = ( "All names must be unique. " - 'There is already a cloud database with the name "5".' + 'There is already a database with the name "5".' ) databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" @@ -169,6 +169,52 @@ def test_duplicate_keys() -> None: assert response.status_code == HTTPStatus.CONFLICT assert response.text == expected_message + @staticmethod + def test_duplicate_vumark_keys() -> None: + """ + It is not possible to have multiple databases with matching + keys, including VuMark databases. + """ + database = VuMarkDatabase( + server_access_key="v1", + server_secret_key="v2", + database_name="v3", + ) + + bad_server_access_key_db = VuMarkDatabase(server_access_key="v1") + bad_server_secret_key_db = VuMarkDatabase(server_secret_key="v2") + bad_database_name_db = VuMarkDatabase(database_name="v3") + + server_access_key_conflict_error = ( + "All server access keys must be unique. " + 'There is already a database with the server access key "v1".' + ) + server_secret_key_conflict_error = ( + "All server secret keys must be unique. " + 'There is already a database with the server secret key "v2".' + ) + database_name_conflict_error = ( + "All names must be unique. " + 'There is already a database with the name "v3".' + ) + + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/vumark_databases" + requests.post(url=databases_url, json=database.to_dict(), timeout=30) + + for bad_database, expected_message in ( + (bad_server_access_key_db, server_access_key_conflict_error), + (bad_server_secret_key_db, server_secret_key_conflict_error), + (bad_database_name_db, database_name_conflict_error), + ): + response = requests.post( + url=databases_url, + json=bad_database.to_dict(), + timeout=30, + ) + + assert response.status_code == HTTPStatus.CONFLICT + assert response.text == expected_message + @staticmethod def test_give_no_details(high_quality_image: io.BytesIO) -> None: """It is possible to create a database without giving any data.""" diff --git a/tests/mock_vws/test_requests_mock_usage.py b/tests/mock_vws/test_requests_mock_usage.py index 37365bb49..cc27c8bf7 100644 --- a/tests/mock_vws/test_requests_mock_usage.py +++ b/tests/mock_vws/test_requests_mock_usage.py @@ -16,7 +16,7 @@ from vws_auth_tools import rfc_1123_date from mock_vws import MissingSchemeError, MockVWS -from mock_vws.database import CloudDatabase +from mock_vws.database import CloudDatabase, VuMarkDatabase from mock_vws.image_matchers import ExactMatcher, StructuralSimilarityMatcher from mock_vws.target import ImageTarget from tests.mock_vws.utils import Endpoint @@ -528,23 +528,23 @@ def test_duplicate_keys() -> None: server_access_key_conflict_error = ( "All server access keys must be unique. " - 'There is already a cloud database with the server access key "1".' + 'There is already a database with the server access key "1".' ) server_secret_key_conflict_error = ( "All server secret keys must be unique. " - 'There is already a cloud database with the server secret key "2".' + 'There is already a database with the server secret key "2".' ) client_access_key_conflict_error = ( "All client access keys must be unique. " - 'There is already a cloud database with the client access key "3".' + 'There is already a database with the client access key "3".' ) client_secret_key_conflict_error = ( "All client secret keys must be unique. " - 'There is already a cloud database with the client secret key "4".' + 'There is already a database with the client secret key "4".' ) database_name_conflict_error = ( "All names must be unique. " - 'There is already a cloud database with the name "5".' + 'There is already a database with the name "5".' ) with MockVWS() as mock: @@ -562,6 +562,48 @@ def test_duplicate_keys() -> None: ): mock.add_cloud_database(cloud_database=bad_database) + @staticmethod + def test_duplicate_vumark_keys() -> None: + """ + It is not possible to have multiple databases with matching + keys, including VuMark databases. + """ + database = VuMarkDatabase( + server_access_key="1", + server_secret_key="2", + database_name="3", + ) + + bad_server_access_key_db = VuMarkDatabase(server_access_key="1") + bad_server_secret_key_db = VuMarkDatabase(server_secret_key="2") + bad_database_name_db = VuMarkDatabase(database_name="3") + + server_access_key_conflict_error = ( + "All server access keys must be unique. " + 'There is already a database with the server access key "1".' + ) + server_secret_key_conflict_error = ( + "All server secret keys must be unique. " + 'There is already a database with the server secret key "2".' + ) + database_name_conflict_error = ( + "All names must be unique. " + 'There is already a database with the name "3".' + ) + + with MockVWS() as mock: + mock.add_vumark_database(vumark_database=database) + for bad_database, expected_message in ( + (bad_server_access_key_db, server_access_key_conflict_error), + (bad_server_secret_key_db, server_secret_key_conflict_error), + (bad_database_name_db, database_name_conflict_error), + ): + with pytest.raises( + expected_exception=ValueError, + match=expected_message + "$", + ): + mock.add_vumark_database(vumark_database=bad_database) + class TestQueryImageMatchers: """Tests for query image matchers.""" From b6e659075f0445fe04e44974b078a9d07a9113b2 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 00:15:20 +0000 Subject: [PATCH 2305/2467] Add database and target type support with VuMark validation (#2963) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add database type and target type support with VuMark validation (#2962) Add DatabaseType enum (CLOUD_RECO, VUMARK) to distinguish database types and TargetType enum (IMAGE, VUMARK_TEMPLATE) for target classification. Implement InvalidTargetTypeError in VuMark generation endpoints to validate that VuMark instance generation only works on VUMARK-type databases. Update database and target serialization to include type information, and allow pre-population of VuMark targets in VuMark-type databases. Co-Authored-By: Claude Haiku 4.5 * Document DatabaseType and TargetType in API reference; clarify Target class Add autoenum entries for DatabaseType and TargetType to the API reference docs. Add a docstring note to Target clarifying that some attributes are primarily meaningful for image targets rather than VuMark template targets. Add vulture whitelist entries for the new TypedDict field and enum value. Co-Authored-By: Claude Haiku 4.5 * [pre-commit.ci lite] apply automatic fixes * Refactor Target into ImageTarget and VuMarkTarget classes - Rename Target → ImageTarget and TargetDict → ImageTargetDict - Add VuMarkTarget dataclass for VuMark template targets (name, active_flag, processing_time_seconds, target_id, dates; status always succeeds after processing) - Remove TargetType enum (target_type.py deleted); class type is now the discriminator - VuforiaDatabase.targets holds set[ImageTarget | VuMarkTarget] - Image-only operations (duplicates, query matching, width/reco fields) guarded with isinstance(target, ImageTarget) checks - Update docs API reference and CHANGELOG Co-Authored-By: Claude Sonnet 4.6 * Add VuMark to spelling dictionary Co-Authored-By: Claude Sonnet 4.6 * Fix InvalidTargetType response status code to 422 Real Vuforia returns 422 Unprocessable Entity (not 403 Forbidden) when attempting VuMark generation on a non-VuMark database. Co-Authored-By: Claude Sonnet 4.6 * Fix mypy errors and stale 'databases' attribute reference - Change new_target type annotations from ImageTarget | VuMarkTarget to ImageTarget in delete_target and update_target, since CloudDatabase.targets is set[ImageTarget] - Remove dead else branches in update_target (target is always ImageTarget in cloud databases) - Remove unused type: ignore[assignment] comments - Fix generate_vumark_instance to use all_databases instead of stale self._target_manager.databases attribute - Remove unused VuMarkTarget import from mock_web_services_api Co-Authored-By: Claude Sonnet 4.6 * Remove unnecessary isinstance checks for ImageTarget Since CloudDatabase.targets is set[ImageTarget], targets are always ImageTarget — isinstance checks are redundant and pyright flags them. Remove all unnecessary isinstance(target, ImageTarget) guards and their dead else branches. Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Haiku 4.5 Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- CHANGELOG.rst | 4 ++ docs/source/mock-api-reference.rst | 4 ++ pyproject.toml | 1 + spelling_private_dict.txt | 1 + src/mock_vws/_constants.py | 1 + src/mock_vws/_flask_server/target_manager.py | 28 ++++---- src/mock_vws/_flask_server/vws.py | 32 ++++++++-- .../mock_web_services_api.py | 64 ++++++++++++------- .../_services_validators/exceptions.py | 40 ++++++++++++ src/mock_vws/database.py | 19 ++++-- src/mock_vws/database_type.py | 13 ++++ src/mock_vws/target.py | 5 +- tests/mock_vws/test_requests_mock_usage.py | 2 + tests/mock_vws/test_vumark_generation_api.py | 63 ++++++++++++++++-- 14 files changed, 221 insertions(+), 56 deletions(-) create mode 100644 src/mock_vws/database_type.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6146edbec..86542874d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +- Add ``VuMarkTarget`` class for VuMark template targets, alongside the renamed ``ImageTarget`` class (previously ``Target``). + ``ImageTarget`` is for image-based targets and ``VuMarkTarget`` is for VuMark template targets. + Both can be stored in a ``VuforiaDatabase``. + 2026.02.18.2 ------------ diff --git a/docs/source/mock-api-reference.rst b/docs/source/mock-api-reference.rst index 338855d2c..1b2ea255a 100644 --- a/docs/source/mock-api-reference.rst +++ b/docs/source/mock-api-reference.rst @@ -29,6 +29,10 @@ API Reference :members: :undoc-members: +.. autoenum:: mock_vws.database_type.DatabaseType + :members: + :undoc-members: + .. autoclass:: mock_vws.target.ImageTarget .. autoclass:: mock_vws.target.VuMarkTarget diff --git a/pyproject.toml b/pyproject.toml index c5a2f5685..df251f8d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -452,6 +452,7 @@ ignore_names = [ # Used in TYPE_CHECKING for type hints "CloudDatabaseDict", "VuMarkDatabaseDict", + "VuMarkTargetDict", ] # Duplicate some of .gitignore exclude = [ ".venv" ] diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index 365309073..0b053e3bb 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -3,6 +3,7 @@ MPixel MiB MissingSchema Ubuntu +VuMark admin another's api diff --git a/src/mock_vws/_constants.py b/src/mock_vws/_constants.py index 52c88bb9b..c6077c62a 100644 --- a/src/mock_vws/_constants.py +++ b/src/mock_vws/_constants.py @@ -63,6 +63,7 @@ class ResultCodes(Enum): INVALID_ACCEPT_HEADER = "InvalidAcceptHeader" INVALID_INSTANCE_ID = "InvalidInstanceId" BAD_REQUEST = "BadRequest" + INVALID_TARGET_TYPE = "InvalidTargetType" @beartype diff --git a/src/mock_vws/_flask_server/target_manager.py b/src/mock_vws/_flask_server/target_manager.py index 1a308e4dd..3c5b6fd51 100644 --- a/src/mock_vws/_flask_server/target_manager.py +++ b/src/mock_vws/_flask_server/target_manager.py @@ -13,6 +13,7 @@ from pydantic_settings import BaseSettings from mock_vws.database import CloudDatabase, VuMarkDatabase +from mock_vws.database_type import DatabaseType from mock_vws.states import States from mock_vws.target import ImageTarget, VuMarkTarget from mock_vws.target_manager import TargetManager @@ -204,8 +205,13 @@ def create_cloud_database() -> Response: "state_name", random_database.state.name, ) + database_type_name = request_json.get( + "database_type_name", + random_database.database_type.name, + ) state = States[state_name] + database_type = DatabaseType[database_type_name] database = CloudDatabase( server_access_key=server_access_key, @@ -214,6 +220,7 @@ def create_cloud_database() -> Response: client_secret_key=client_secret_key, database_name=database_name, state=state, + database_type=database_type, ) try: TARGET_MANAGER.add_cloud_database(cloud_database=database) @@ -283,11 +290,10 @@ def create_target(database_name: str) -> Response: if database.database_name == database_name ) request_json = json.loads(s=request.data) - image_base64 = request_json["image_base64"] - image_bytes = base64.b64decode(s=image_base64) settings = TargetManagerSettings.model_validate(obj={}) - target_tracking_rater = settings.target_rater.to_target_rater() + image_bytes = base64.b64decode(s=request_json["image_base64"]) + target_tracking_rater = settings.target_rater.to_target_rater() target = ImageTarget( name=request_json["name"], width=request_json["width"], @@ -343,7 +349,7 @@ def delete_target(database_name: str, target_id: str) -> Response: target = database.get_target(target_id=target_id) now = datetime.datetime.now(tz=target.upload_date.tzinfo) # See https://github.com/facebook/pyrefly/issues/1897 - new_target = copy.replace( + new_target: ImageTarget = copy.replace( target, # pyrefly: ignore[bad-argument-type] delete_date=now, ) @@ -369,24 +375,22 @@ def update_target(database_name: str, target_id: str) -> Response: target = database.get_target(target_id=target_id) request_json = json.loads(s=request.data) - width = request_json.get("width", target.width) name = request_json.get("name", target.name) active_flag = request_json.get("active_flag", target.active_flag) + + gmt = ZoneInfo(key="GMT") + last_modified_date = datetime.datetime.now(tz=gmt) + + width = request_json.get("width", target.width) application_metadata = request_json.get( "application_metadata", target.application_metadata, ) - image_value = target.image_value - request_json = json.loads(s=request.data) if "image" in request_json: image_value = base64.b64decode(s=request_json["image"]) - - gmt = ZoneInfo(key="GMT") - last_modified_date = datetime.datetime.now(tz=gmt) - # See https://github.com/facebook/pyrefly/issues/1897 - new_target = copy.replace( + new_target: ImageTarget = copy.replace( target, # pyrefly: ignore[bad-argument-type] name=name, width=width, diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index 1c98d1336..fc9bb8a84 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -32,6 +32,7 @@ FailError, InvalidAcceptHeaderError, InvalidInstanceIdError, + InvalidTargetTypeError, TargetStatusNotSuccessError, TargetStatusProcessingError, ValidatorError, @@ -278,13 +279,16 @@ def get_target(target_id: str) -> Response: target for target in database.targets if target.target_id == target_id ) + width = target.width + tracking_rating = target.tracking_rating + reco_rating = target.reco_rating target_record = { "target_id": target.target_id, "active_flag": target.active_flag, "name": target.name, - "width": target.width, - "tracking_rating": target.tracking_rating, - "reco_rating": target.reco_rating, + "width": width, + "tracking_rating": tracking_rating, + "reco_rating": reco_rating, } date = email.utils.formatdate(timeval=None, localtime=False, usegmt=True) @@ -394,6 +398,16 @@ def generate_vumark_instance(target_id: str) -> Response: # ``target_id`` is validated by request validators. del target_id + database = get_database_matching_server_keys( + request_headers=dict(request.headers), + request_body=request.data, + request_method=request.method, + request_path=request.path, + databases=all_databases, + ) + if not isinstance(database, VuMarkDatabase): + raise InvalidTargetTypeError + accept = request.headers.get(key="Accept", default="") valid_accept_types: dict[str, bytes] = { "image/png": VUMARK_PNG, @@ -503,6 +517,10 @@ def target_summary(target_id: str) -> Response: (target,) = ( target for target in database.targets if target.target_id == target_id ) + tracking_rating = target.tracking_rating + total_recos = target.total_recos + current_month_recos = target.current_month_recos + previous_month_recos = target.previous_month_recos body = { "status": target.status, "transaction_id": uuid.uuid4().hex, @@ -511,10 +529,10 @@ def target_summary(target_id: str) -> Response: "target_name": target.name, "upload_date": target.upload_date.strftime(format="%Y-%m-%d"), "active_flag": target.active_flag, - "tracking_rating": target.tracking_rating, - "total_recos": target.total_recos, - "current_month_recos": target.current_month_recos, - "previous_month_recos": target.previous_month_recos, + "tracking_rating": tracking_rating, + "total_recos": total_recos, + "current_month_recos": current_month_recos, + "previous_month_recos": previous_month_recos, } date = email.utils.formatdate(timeval=None, localtime=False, usegmt=True) headers = { diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index 5a5b7da96..deac884bf 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -32,17 +32,19 @@ FailError, InvalidAcceptHeaderError, InvalidInstanceIdError, + InvalidTargetTypeError, TargetStatusNotSuccessError, TargetStatusProcessingError, ValidatorError, ) +from mock_vws.database import VuMarkDatabase from mock_vws.image_matchers import ImageMatcher from mock_vws.target import ImageTarget from mock_vws.target_manager import TargetManager from mock_vws.target_raters import TargetTrackingRater if TYPE_CHECKING: - from mock_vws.database import CloudDatabase, VuMarkDatabase + from mock_vws.database import CloudDatabase _TARGET_ID_PATTERN = "[A-Za-z0-9]+" @@ -268,7 +270,7 @@ def delete_target(self, request: PreparedRequest) -> _ResponseType: now = datetime.datetime.now(tz=target.upload_date.tzinfo) # See https://github.com/facebook/pyrefly/issues/1897 - new_target = copy.replace( + new_target: ImageTarget = copy.replace( target, # pyrefly: ignore[bad-argument-type] delete_date=now, ) @@ -324,6 +326,16 @@ def generate_vumark_instance( databases=all_databases, ) + database = get_database_matching_server_keys( + request_headers=request.headers, + request_body=_body_bytes(request=request), + request_method=request.method or "", + request_path=request.path_url, + databases=all_databases, + ) + if not isinstance(database, VuMarkDatabase): + raise InvalidTargetTypeError + accept = dict(request.headers).get("Accept", "") if accept not in valid_accept_types: raise InvalidAcceptHeaderError @@ -500,13 +512,16 @@ def get_target(self, request: PreparedRequest) -> _ResponseType: target_id = request.path_url.split(sep="/")[-1] target = database.get_target(target_id=target_id) + width = target.width + tracking_rating = target.tracking_rating + reco_rating = target.reco_rating target_record = { "target_id": target.target_id, "active_flag": target.active_flag, "name": target.name, - "width": target.width, - "tracking_rating": target.tracking_rating, - "reco_rating": target.reco_rating, + "width": width, + "tracking_rating": tracking_rating, + "reco_rating": reco_rating, } date = email.utils.formatdate( timeval=None, @@ -653,17 +668,8 @@ def update_target(self, request: PreparedRequest) -> _ResponseType: ) request_json: dict[str, Any] = json.loads(s=request.body or b"") - width = request_json.get("width", target.width) name = request_json.get("name", target.name) active_flag = request_json.get("active_flag", target.active_flag) - application_metadata = request_json.get( - "application_metadata", - target.application_metadata, - ) - - image_value = target.image_value - if "image" in request_json: - image_value = base64.b64decode(s=request_json["image"]) if "active_flag" in request_json and active_flag is None: fail_exception = FailError(status_code=HTTPStatus.BAD_REQUEST) @@ -673,6 +679,19 @@ def update_target(self, request: PreparedRequest) -> _ResponseType: fail_exception.response_text, ) + gmt = ZoneInfo(key="GMT") + last_modified_date = datetime.datetime.now(tz=gmt) + + width = request_json.get("width", target.width) + application_metadata = request_json.get( + "application_metadata", + target.application_metadata, + ) + + image_value = target.image_value + if "image" in request_json: + image_value = base64.b64decode(s=request_json["image"]) + if ( "application_metadata" in request_json and application_metadata is None @@ -684,11 +703,8 @@ def update_target(self, request: PreparedRequest) -> _ResponseType: fail_exception.response_text, ) - gmt = ZoneInfo(key="GMT") - last_modified_date = datetime.datetime.now(tz=gmt) - # See https://github.com/facebook/pyrefly/issues/1897 - new_target = copy.replace( + new_target: ImageTarget = copy.replace( target, # pyrefly: ignore[bad-argument-type] name=name, width=width, @@ -755,6 +771,10 @@ def target_summary(self, request: PreparedRequest) -> _ResponseType: localtime=False, usegmt=True, ) + tracking_rating = target.tracking_rating + total_recos = target.total_recos + current_month_recos = target.current_month_recos + previous_month_recos = target.previous_month_recos body = { "status": target.status, "transaction_id": uuid.uuid4().hex, @@ -763,10 +783,10 @@ def target_summary(self, request: PreparedRequest) -> _ResponseType: "target_name": target.name, "upload_date": target.upload_date.strftime(format="%Y-%m-%d"), "active_flag": target.active_flag, - "tracking_rating": target.tracking_rating, - "total_recos": target.total_recos, - "current_month_recos": target.current_month_recos, - "previous_month_recos": target.previous_month_recos, + "tracking_rating": tracking_rating, + "total_recos": total_recos, + "current_month_recos": current_month_recos, + "previous_month_recos": previous_month_recos, } body_json = json_dump(body=body) headers = { diff --git a/src/mock_vws/_services_validators/exceptions.py b/src/mock_vws/_services_validators/exceptions.py index a722f29ab..da058422d 100644 --- a/src/mock_vws/_services_validators/exceptions.py +++ b/src/mock_vws/_services_validators/exceptions.py @@ -646,6 +646,46 @@ def __init__(self) -> None: } +@beartype +class InvalidTargetTypeError(ValidatorError): + """Exception raised when the target type is not valid for the + operation. + """ + + def __init__(self) -> None: + """ + Attributes: + status_code: The status code to use in a response if this is + raised. + response_text: The response text to use in a response if this + is + raised. + """ + super().__init__() + self.status_code = HTTPStatus.UNPROCESSABLE_ENTITY + body = { + "transaction_id": uuid.uuid4().hex, + "result_code": ResultCodes.INVALID_TARGET_TYPE.value, + } + self.response_text = json_dump(body=body) + date = email.utils.formatdate( + timeval=None, + localtime=False, + usegmt=True, + ) + self.headers = { + "Connection": "keep-alive", + "Content-Type": "application/json", + "server": "envoy", + "Date": date, + "x-envoy-upstream-service-time": "5", + "Content-Length": str(object=len(self.response_text)), + "strict-transport-security": "max-age=31536000", + "x-aws-region": "us-east-2, us-west-2", + "x-content-type-options": "nosniff", + } + + @beartype class TargetStatusProcessingError(ValidatorError): """Exception raised when trying to delete a target which is processing.""" diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py index b030a9e4e..d4e2389a8 100644 --- a/src/mock_vws/database.py +++ b/src/mock_vws/database.py @@ -8,6 +8,7 @@ from beartype import beartype from mock_vws._constants import TargetStatuses +from mock_vws.database_type import DatabaseType from mock_vws.states import States from mock_vws.target import ( ImageTarget, @@ -27,6 +28,7 @@ class CloudDatabaseDict(TypedDict): client_access_key: str client_secret_key: str state_name: str + database_type_name: str targets: Iterable[ImageTargetDict] @@ -81,6 +83,7 @@ class CloudDatabase: hash=False, ) state: States = States.WORKING + database_type: DatabaseType = DatabaseType.CLOUD_RECO request_quota: int = 100000 reco_threshold: int = 1000 @@ -91,7 +94,9 @@ class CloudDatabase: def to_dict(self) -> CloudDatabaseDict: """Dump a target to a dictionary which can be loaded as JSON.""" - targets = [target.to_dict() for target in self.targets] + targets: list[ImageTargetDict] = [ + target.to_dict() for target in self.targets + ] return { "database_name": self.database_name, "server_access_key": self.server_access_key, @@ -99,6 +104,7 @@ def to_dict(self) -> CloudDatabaseDict: "client_access_key": self.client_access_key, "client_secret_key": self.client_secret_key, "state_name": self.state.name, + "database_type_name": self.database_type.name, "targets": targets, } @@ -112,6 +118,11 @@ def get_target(self, target_id: str) -> ImageTarget: @classmethod def from_dict(cls, database_dict: CloudDatabaseDict) -> Self: """Load a database from a dictionary.""" + targets: set[ImageTarget] = { + ImageTarget.from_dict(target_dict=target_dict) + for target_dict in database_dict["targets"] + } + return cls( database_name=database_dict["database_name"], server_access_key=database_dict["server_access_key"], @@ -119,10 +130,8 @@ def from_dict(cls, database_dict: CloudDatabaseDict) -> Self: client_access_key=database_dict["client_access_key"], client_secret_key=database_dict["client_secret_key"], state=States[database_dict["state_name"]], - targets={ - ImageTarget.from_dict(target_dict=target_dict) - for target_dict in database_dict["targets"] - }, + database_type=DatabaseType[database_dict["database_type_name"]], + targets=targets, ) @property diff --git a/src/mock_vws/database_type.py b/src/mock_vws/database_type.py new file mode 100644 index 000000000..bd72733d4 --- /dev/null +++ b/src/mock_vws/database_type.py @@ -0,0 +1,13 @@ +"""Vuforia database types.""" + +from enum import StrEnum, auto, unique + +from beartype import beartype + + +@beartype +@unique +class DatabaseType(StrEnum): + """Constants representing various database types.""" + + CLOUD_RECO = auto() diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py index d381884d8..50e9ec034 100644 --- a/src/mock_vws/target.py +++ b/src/mock_vws/target.py @@ -27,7 +27,7 @@ class VuMarkTargetDict(TypedDict): class ImageTargetDict(TypedDict): - """A dictionary type which represents a target.""" + """A dictionary type which represents an image target.""" name: str width: float @@ -58,8 +58,7 @@ def _time_now() -> datetime.datetime: @beartype(conf=BeartypeConf(is_pep484_tower=True)) @dataclass(frozen=True, eq=True) class ImageTarget: - """ - A Vuforia Target as managed in + """A Vuforia image target as managed in https://developer.vuforia.com/target-manager. """ diff --git a/tests/mock_vws/test_requests_mock_usage.py b/tests/mock_vws/test_requests_mock_usage.py index cc27c8bf7..8fa5d91de 100644 --- a/tests/mock_vws/test_requests_mock_usage.py +++ b/tests/mock_vws/test_requests_mock_usage.py @@ -395,6 +395,7 @@ def test_to_dict(high_quality_image: io.BytesIO) -> None: assert len(database.targets) == 1 target = next(iter(database.targets)) + assert isinstance(target, ImageTarget) target_dict = target.to_dict() # The dictionary is JSON dump-able @@ -431,6 +432,7 @@ def test_to_dict_deleted(high_quality_image: io.BytesIO) -> None: assert len(database.targets) == 1 target = next(iter(database.targets)) + assert isinstance(target, ImageTarget) target_dict = target.to_dict() # The dictionary is JSON dump-able diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 219c27440..2258988a6 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -6,10 +6,13 @@ import pytest import requests +from vws import VWS from vws_auth_tools import authorization_header, rfc_1123_date from mock_vws._constants import ResultCodes +from mock_vws.database import CloudDatabase from tests.mock_vws.fixtures.credentials import VuMarkCloudDatabase +from tests.mock_vws.utils import make_image_file _VWS_HOST = "https://vws.vuforia.com" _PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" @@ -19,22 +22,24 @@ def _make_vumark_request( *, - vumark_vuforia_database: VuMarkCloudDatabase, + server_access_key: str, + server_secret_key: str, + target_id: str, instance_id: str, accept: str, ) -> requests.Response: """Send a VuMark instance generation request and return the response. """ - request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" + request_path = f"/targets/{target_id}/instances" content_type = "application/json" content = json.dumps(obj={"instance_id": instance_id}).encode( encoding="utf-8" ) date = rfc_1123_date() authorization_string = authorization_header( - access_key=vumark_vuforia_database.server_access_key, - secret_key=vumark_vuforia_database.server_secret_key, + access_key=server_access_key, + secret_key=server_secret_key, method=HTTPMethod.POST, content=content, content_type=content_type, @@ -87,7 +92,9 @@ def test_generate_instance_format( ) -> None: """A VuMark instance can be generated in the requested format.""" response = _make_vumark_request( - vumark_vuforia_database=vumark_vuforia_database, + server_access_key=vumark_vuforia_database.server_access_key, + server_secret_key=vumark_vuforia_database.server_secret_key, + target_id=vumark_vuforia_database.target_id, instance_id=uuid4().hex, accept=accept, ) @@ -106,7 +113,9 @@ def test_invalid_accept_header( ) -> None: """An unsupported Accept header returns an error.""" response = _make_vumark_request( - vumark_vuforia_database=vumark_vuforia_database, + server_access_key=vumark_vuforia_database.server_access_key, + server_secret_key=vumark_vuforia_database.server_secret_key, + target_id=vumark_vuforia_database.target_id, instance_id=uuid4().hex, accept="text/plain", ) @@ -124,7 +133,9 @@ def test_empty_instance_id( ) -> None: """An empty instance_id returns InvalidInstanceId.""" response = _make_vumark_request( - vumark_vuforia_database=vumark_vuforia_database, + server_access_key=vumark_vuforia_database.server_access_key, + server_secret_key=vumark_vuforia_database.server_secret_key, + target_id=vumark_vuforia_database.target_id, instance_id="", accept="image/png", ) @@ -135,3 +146,41 @@ def test_empty_instance_id( response_json["result_code"] == ResultCodes.INVALID_INSTANCE_ID.value ) + + @staticmethod + def test_non_vumark_database( + vuforia_database: CloudDatabase, + ) -> None: + """Generating a VuMark instance for a target in a non-VuMark + database returns InvalidTargetType. + """ + vws_client = VWS( + server_access_key=vuforia_database.server_access_key, + server_secret_key=vuforia_database.server_secret_key, + ) + image = make_image_file( + file_format="PNG", + color_space="RGB", + width=8, + height=8, + ) + target_id = vws_client.add_target( + name="test", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + response = _make_vumark_request( + server_access_key=vuforia_database.server_access_key, + server_secret_key=vuforia_database.server_secret_key, + target_id=target_id, + instance_id=uuid4().hex, + accept="image/png", + ) + assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + response_json = response.json() + assert ( + response_json["result_code"] + == ResultCodes.INVALID_TARGET_TYPE.value + ) From f03ba49c69c18f7ffff841e81f1fd05ad73b21fe Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sat, 21 Feb 2026 00:16:29 +0000 Subject: [PATCH 2306/2467] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 86542874d..517e311d7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.21 +---------- + + - Add ``VuMarkTarget`` class for VuMark template targets, alongside the renamed ``ImageTarget`` class (previously ``Target``). ``ImageTarget`` is for image-based targets and ``VuMarkTarget`` is for VuMark template targets. Both can be stored in a ``VuforiaDatabase``. From 795b2a96b945465f95d890d758149c7cb503d36d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 00:40:14 +0000 Subject: [PATCH 2307/2467] Fix test isolation in Flask app database tests (#2971) * Fix test isolation in Flask app database tests Reset TARGET_MANAGER state between tests by cleaning up cloud and VuMark databases in the autouse fixture. This fixes the leaky test issue that required workaround keys ("v1", "v2", "v3" instead of "1", "2", "3") in test_duplicate_vumark_keys. Co-Authored-By: Claude Haiku 4.5 * Fix type annotation in test cleanup --------- Co-authored-by: Claude Haiku 4.5 --- tests/mock_vws/test_flask_app_usage.py | 28 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index 5ce6ad1aa..4cef8424a 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -15,7 +15,10 @@ from requests_mock_flask import add_flask_app_to_mock from vws import VWS, CloudRecoService -from mock_vws._flask_server.target_manager import TARGET_MANAGER_FLASK_APP +from mock_vws._flask_server.target_manager import ( + TARGET_MANAGER, + TARGET_MANAGER_FLASK_APP, +) from mock_vws._flask_server.vwq import CLOUDRECO_FLASK_APP from mock_vws._flask_server.vws import VWS_FLASK_APP from mock_vws.database import CloudDatabase, VuMarkDatabase @@ -57,6 +60,11 @@ def _(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]: yield + for cloud_database in TARGET_MANAGER.cloud_databases: + TARGET_MANAGER.remove_cloud_database(cloud_database=cloud_database) + for vumark_database in TARGET_MANAGER.vumark_databases: + TARGET_MANAGER.remove_vumark_database(vumark_database=vumark_database) + class TestProcessingTime: """Tests for the time taken to process targets in the mock.""" @@ -176,26 +184,26 @@ def test_duplicate_vumark_keys() -> None: keys, including VuMark databases. """ database = VuMarkDatabase( - server_access_key="v1", - server_secret_key="v2", - database_name="v3", + server_access_key="1", + server_secret_key="2", + database_name="3", ) - bad_server_access_key_db = VuMarkDatabase(server_access_key="v1") - bad_server_secret_key_db = VuMarkDatabase(server_secret_key="v2") - bad_database_name_db = VuMarkDatabase(database_name="v3") + bad_server_access_key_db = VuMarkDatabase(server_access_key="1") + bad_server_secret_key_db = VuMarkDatabase(server_secret_key="2") + bad_database_name_db = VuMarkDatabase(database_name="3") server_access_key_conflict_error = ( "All server access keys must be unique. " - 'There is already a database with the server access key "v1".' + 'There is already a database with the server access key "1".' ) server_secret_key_conflict_error = ( "All server secret keys must be unique. " - 'There is already a database with the server secret key "v2".' + 'There is already a database with the server secret key "2".' ) database_name_conflict_error = ( "All names must be unique. " - 'There is already a database with the name "v3".' + 'There is already a database with the name "3".' ) databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/vumark_databases" From 4e91959bdef94f99e9199c97a9f904b4ac2c17d2 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 00:42:30 +0000 Subject: [PATCH 2308/2467] Add error handling for missing VuMark database deletion (#2972) The delete_vumark_database endpoint was missing the try/except ValueError error handling present in delete_cloud_database. When no matching VuMark database exists, the set unpacking raises an unhandled ValueError, resulting in a 500 error instead of a 404 response. Added proper error handling and corresponding tests. Co-authored-by: Claude Haiku 4.5 --- src/mock_vws/_flask_server/target_manager.py | 14 ++++++---- tests/mock_vws/test_flask_app_usage.py | 27 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/mock_vws/_flask_server/target_manager.py b/src/mock_vws/_flask_server/target_manager.py index 3c5b6fd51..0b7f3469b 100644 --- a/src/mock_vws/_flask_server/target_manager.py +++ b/src/mock_vws/_flask_server/target_manager.py @@ -91,11 +91,15 @@ def delete_vumark_database(database_name: str) -> Response: :status 200: The VuMark database has been deleted. """ - (matching_database,) = { - database - for database in TARGET_MANAGER.vumark_databases - if database_name == database.database_name - } + try: + (matching_database,) = { + database + for database in TARGET_MANAGER.vumark_databases + if database_name == database.database_name + } + except ValueError: + return Response(response="", status=HTTPStatus.NOT_FOUND) + TARGET_MANAGER.remove_vumark_database(vumark_database=matching_database) return Response(response="", status=HTTPStatus.OK) diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index 4cef8424a..59c0d73f5 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -280,6 +280,33 @@ def test_delete_database() -> None: response = requests.delete(url=delete_url, json={}, timeout=30) assert response.status_code == HTTPStatus.NOT_FOUND + @staticmethod + def test_vumark_not_found() -> None: + """ + A 404 error is returned when trying to delete a VuMark database + which + does not exist. + """ + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/vumark_databases" + delete_url = databases_url + "/" + "foobar" + response = requests.delete(url=delete_url, json={}, timeout=30) + assert response.status_code == HTTPStatus.NOT_FOUND + + @staticmethod + def test_delete_vumark_database() -> None: + """It is possible to delete a VuMark database.""" + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/vumark_databases" + response = requests.post(url=databases_url, json={}, timeout=30) + assert response.status_code == HTTPStatus.CREATED + + data = json.loads(s=response.text) + delete_url = databases_url + "/" + data["database_name"] + response = requests.delete(url=delete_url, json={}, timeout=30) + assert response.status_code == HTTPStatus.OK + + response = requests.delete(url=delete_url, json={}, timeout=30) + assert response.status_code == HTTPStatus.NOT_FOUND + class TestQueryImageMatchers: """Tests for query image matchers.""" From c92f60bfd908d83972a8d3b615a49b5d24fc6ca8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 08:29:04 +0000 Subject: [PATCH 2309/2467] Clarify cloud vs VuMark database tests with split classes and updated names (#2975) Split TestAddDatabase into TestAddCloudDatabase and TestAddVuMarkDatabase, and TestDeleteDatabase into TestDeleteCloudDatabase and TestDeleteVuMarkDatabase. Updated docstrings and method names to explicitly reference "cloud database" or "VuMark database" for clarity. Co-authored-by: Claude Haiku 4.5 --- tests/mock_vws/test_flask_app_usage.py | 94 ++++++++++++++------------ 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index 59c0d73f5..23fa647fe 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -114,13 +114,14 @@ def test_custom( assert expected - self.LEEWAY < time_taken < expected + self.LEEWAY -class TestAddDatabase: - """Tests for adding databases to the mock.""" +class TestAddCloudDatabase: + """Tests for adding cloud databases to the mock.""" @staticmethod def test_duplicate_keys() -> None: """ - It is not possible to have multiple databases with matching + It is not possible to have multiple cloud databases with + matching keys. """ database = CloudDatabase( @@ -178,10 +179,43 @@ def test_duplicate_keys() -> None: assert response.text == expected_message @staticmethod - def test_duplicate_vumark_keys() -> None: + def test_give_no_details(high_quality_image: io.BytesIO) -> None: + """It is possible to create a cloud database without giving any + data. + """ + databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" + response = requests.post(url=databases_url, json={}, timeout=30) + assert response.status_code == HTTPStatus.CREATED + + data = json.loads(s=response.text) + + assert data["targets"] == [] + assert data["state_name"] == "WORKING" + assert "database_name" in data + + vws_client = VWS( + server_access_key=data["server_access_key"], + server_secret_key=data["server_secret_key"], + ) + + cloud_reco_client = CloudRecoService( + client_access_key=data["client_access_key"], + client_secret_key=data["client_secret_key"], + ) + + assert not vws_client.list_targets() + assert not cloud_reco_client.query(image=high_quality_image) + + +class TestAddVuMarkDatabase: + """Tests for adding VuMark databases to the mock.""" + + @staticmethod + def test_duplicate_keys() -> None: """ - It is not possible to have multiple databases with matching - keys, including VuMark databases. + It is not possible to have multiple VuMark databases with + matching + keys. """ database = VuMarkDatabase( server_access_key="1", @@ -223,42 +257,15 @@ def test_duplicate_vumark_keys() -> None: assert response.status_code == HTTPStatus.CONFLICT assert response.text == expected_message - @staticmethod - def test_give_no_details(high_quality_image: io.BytesIO) -> None: - """It is possible to create a database without giving any data.""" - databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" - response = requests.post(url=databases_url, json={}, timeout=30) - assert response.status_code == HTTPStatus.CREATED - data = json.loads(s=response.text) - - assert data["targets"] == [] - assert data["state_name"] == "WORKING" - assert "database_name" in data - - vws_client = VWS( - server_access_key=data["server_access_key"], - server_secret_key=data["server_secret_key"], - ) - - cloud_reco_client = CloudRecoService( - client_access_key=data["client_access_key"], - client_secret_key=data["client_secret_key"], - ) - - assert not vws_client.list_targets() - assert not cloud_reco_client.query(image=high_quality_image) - - -class TestDeleteDatabase: - """Tests for deleting databases from the mock.""" +class TestDeleteCloudDatabase: + """Tests for deleting cloud databases from the mock.""" @staticmethod def test_not_found() -> None: """ - A 404 error is returned when trying to delete a database which - does not - exist. + A 404 error is returned when trying to delete a cloud database + which does not exist. """ databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" delete_url = databases_url + "/" + "foobar" @@ -266,8 +273,8 @@ def test_not_found() -> None: assert response.status_code == HTTPStatus.NOT_FOUND @staticmethod - def test_delete_database() -> None: - """It is possible to delete a database.""" + def test_delete_cloud_database() -> None: + """It is possible to delete a cloud database.""" databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/cloud_databases" response = requests.post(url=databases_url, json={}, timeout=30) assert response.status_code == HTTPStatus.CREATED @@ -280,12 +287,15 @@ def test_delete_database() -> None: response = requests.delete(url=delete_url, json={}, timeout=30) assert response.status_code == HTTPStatus.NOT_FOUND + +class TestDeleteVuMarkDatabase: + """Tests for deleting VuMark databases from the mock.""" + @staticmethod - def test_vumark_not_found() -> None: + def test_not_found() -> None: """ A 404 error is returned when trying to delete a VuMark database - which - does not exist. + which does not exist. """ databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/vumark_databases" delete_url = databases_url + "/" + "foobar" From 1a1a9e2561ac1efdc9e31afcf29aefa8f62ef7e9 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 08:54:07 +0000 Subject: [PATCH 2310/2467] Refactor request handling to use library-agnostic RequestData dataclass (#2976) Introduce RequestData as a frozen dataclass to replace PreparedRequest dependency in handler layer. Convert PreparedRequest to RequestData at the responses adapter boundary, removing type-checking conflicts and centralizing request normalization. This decouples core business logic from the requests library and enables future adapter support (respx, httpx, etc). - Add RequestData dataclass with method, path, headers, body fields - Update all handler signatures to accept RequestData instead of PreparedRequest - Move body normalization (None/str/bytes conversion) to adapter boundary - Remove duplicate _body_bytes() helpers from both API handler modules - Update decorators.py to convert at boundary in _wrap_callback Fixes #2974 Co-authored-by: Claude Sonnet 4.6 --- src/mock_vws/_mock_common.py | 19 +- .../_requests_mock_server/decorators.py | 24 ++- .../mock_web_query_api.py | 31 +--- .../mock_web_services_api.py | 164 ++++++++---------- 4 files changed, 120 insertions(+), 118 deletions(-) diff --git a/src/mock_vws/_mock_common.py b/src/mock_vws/_mock_common.py index 0ebbd379b..5a7069562 100644 --- a/src/mock_vws/_mock_common.py +++ b/src/mock_vws/_mock_common.py @@ -1,13 +1,30 @@ """Common utilities for creating mock routes.""" import json -from collections.abc import Iterable +from collections.abc import Iterable, Mapping from dataclasses import dataclass from typing import Any from beartype import beartype +@dataclass(frozen=True) +class RequestData: + """A library-agnostic representation of an HTTP request. + + Args: + method: The HTTP method of the request. + path: The path of the request. + headers: The headers sent with the request. + body: The body of the request. + """ + + method: str + path: str + headers: Mapping[str, str] + body: bytes + + @dataclass(frozen=True) class Route: """A representation of a VWS route. diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 35535217b..04a961989 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -12,6 +12,7 @@ from requests import PreparedRequest from responses import RequestsMock +from mock_vws._mock_common import RequestData from mock_vws.database import CloudDatabase, VuMarkDatabase from mock_vws.image_matchers import ( ImageMatcher, @@ -27,7 +28,8 @@ from .mock_web_services_api import MockVuforiaWebServicesAPI _ResponseType = tuple[int, Mapping[str, str], str | bytes] -_Callback = Callable[[PreparedRequest], _ResponseType] +_MockCallback = Callable[[RequestData], _ResponseType] +_ResponsesCallback = Callable[[PreparedRequest], _ResponseType] _STRUCTURAL_SIMILARITY_MATCHER = StructuralSimilarityMatcher() _BRISQUE_TRACKING_RATER = BrisqueTargetTrackingRater() @@ -156,10 +158,10 @@ def add_vumark_database(self, vumark_database: VuMarkDatabase) -> None: @staticmethod def _wrap_callback( - callback: _Callback, + callback: _MockCallback, delay_seconds: float, sleep_fn: Callable[[float], None], - ) -> _Callback: + ) -> _ResponsesCallback: """Wrap a callback to add a response delay.""" def wrapped( @@ -186,7 +188,21 @@ def wrapped( sleep_fn(effective) raise requests.exceptions.Timeout - result = callback(request) + raw_body = request.body + if raw_body is None: + body_bytes = b"" + elif isinstance(raw_body, str): + body_bytes = raw_body.encode(encoding="utf-8") + else: + body_bytes = raw_body + + request_data = RequestData( + method=request.method or "", + path=request.path_url, + headers=dict(request.headers), + body=body_bytes, + ) + result = callback(request_data) sleep_fn(delay_seconds) return result diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py index e2626a25a..6f8f519d3 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py @@ -10,9 +10,8 @@ from typing import ParamSpec, Protocol, runtime_checkable from beartype import beartype -from requests.models import PreparedRequest -from mock_vws._mock_common import Route +from mock_vws._mock_common import RequestData, Route from mock_vws._query_tools import ( get_query_match_response_text, ) @@ -78,21 +77,9 @@ def decorator( return decorator -@beartype -def _body_bytes(request: PreparedRequest) -> bytes: - """Return the body of a request as bytes.""" - if request.body is None or isinstance(request.body, str): - return b"" - - return request.body - - @beartype class MockVuforiaWebQueryAPI: - """A fake implementation of the Vuforia Web Query API. - - This implementation is tied to the implementation of ``responses``. - """ + """A fake implementation of the Vuforia Web Query API.""" def __init__( self, @@ -114,14 +101,14 @@ def __init__( self._query_match_checker = query_match_checker @route(path_pattern="/v1/query", http_methods={HTTPMethod.POST}) - def query(self, request: PreparedRequest) -> _ResponseType: + def query(self, request: RequestData) -> _ResponseType: """Perform an image recognition query.""" try: run_query_validators( - request_path=request.path_url, + request_path=request.path, request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", + request_body=request.body, + request_method=request.method, databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: @@ -129,9 +116,9 @@ def query(self, request: PreparedRequest) -> _ResponseType: response_text = get_query_match_response_text( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, query_match_checker=self._query_match_checker, ) diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index deac884bf..1b0662cea 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -16,7 +16,6 @@ from zoneinfo import ZoneInfo from beartype import BeartypeConf, beartype -from requests.models import PreparedRequest from mock_vws._constants import ( VUMARK_PDF, @@ -26,7 +25,7 @@ TargetStatuses, ) from mock_vws._database_matchers import get_database_matching_server_keys -from mock_vws._mock_common import Route, json_dump +from mock_vws._mock_common import RequestData, Route, json_dump from mock_vws._services_validators import run_services_validators from mock_vws._services_validators.exceptions import ( FailError, @@ -105,24 +104,9 @@ def decorator( return decorator -@beartype -def _body_bytes(request: PreparedRequest) -> bytes: - """Return the body of a request as bytes.""" - if request.body is None: - return b"" - - if isinstance(request.body, str): - return request.body.encode(encoding="utf-8") - - return request.body - - @beartype(conf=BeartypeConf(is_pep484_tower=True)) class MockVuforiaWebServicesAPI: - """A fake implementation of the Vuforia Web Services API. - - This implementation is tied to the implementation of ``responses``. - """ + """A fake implementation of the Vuforia Web Services API.""" def __init__( self, @@ -157,7 +141,7 @@ def __init__( path_pattern="/targets", http_methods={HTTPMethod.POST}, ) - def add_target(self, request: PreparedRequest) -> _ResponseType: + def add_target(self, request: RequestData) -> _ResponseType: """Add a target. Fake implementation of @@ -166,9 +150,9 @@ def add_target(self, request: PreparedRequest) -> _ResponseType: try: run_services_validators( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: @@ -176,13 +160,13 @@ def add_target(self, request: PreparedRequest) -> _ResponseType: database = get_database_matching_server_keys( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) - request_json: dict[str, Any] = json.loads(s=request.body or b"") + request_json: dict[str, Any] = json.loads(s=request.body) given_active_flag = request_json.get("active_flag") active_flag = { None: True, @@ -232,7 +216,7 @@ def add_target(self, request: PreparedRequest) -> _ResponseType: path_pattern=f"/targets/{_TARGET_ID_PATTERN}", http_methods={HTTPMethod.DELETE}, ) - def delete_target(self, request: PreparedRequest) -> _ResponseType: + def delete_target(self, request: RequestData) -> _ResponseType: """Delete a target. Fake implementation of @@ -241,9 +225,9 @@ def delete_target(self, request: PreparedRequest) -> _ResponseType: try: run_services_validators( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: @@ -251,13 +235,13 @@ def delete_target(self, request: PreparedRequest) -> _ResponseType: database = get_database_matching_server_keys( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) - target_id = request.path_url.split(sep="/")[-1] + target_id = request.path.split(sep="/")[-1] target = database.get_target(target_id=target_id) if target.status == TargetStatuses.PROCESSING.value: @@ -304,9 +288,7 @@ def delete_target(self, request: PreparedRequest) -> _ResponseType: path_pattern=f"/targets/{_TARGET_ID_PATTERN}/instances", http_methods={HTTPMethod.POST}, ) - def generate_vumark_instance( - self, request: PreparedRequest - ) -> _ResponseType: + def generate_vumark_instance(self, request: RequestData) -> _ResponseType: """Generate a VuMark instance.""" valid_accept_types: dict[str, bytes] = { "image/png": VUMARK_PNG, @@ -320,17 +302,17 @@ def generate_vumark_instance( ] run_services_validators( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=all_databases, ) database = get_database_matching_server_keys( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=all_databases, ) if not isinstance(database, VuMarkDatabase): @@ -340,7 +322,7 @@ def generate_vumark_instance( if accept not in valid_accept_types: raise InvalidAcceptHeaderError - request_json = json.loads(s=_body_bytes(request=request)) + request_json = json.loads(s=request.body) instance_id = request_json.get("instance_id", "") if not instance_id: raise InvalidInstanceIdError @@ -367,7 +349,7 @@ def generate_vumark_instance( return HTTPStatus.OK, headers, response_body @route(path_pattern="/summary", http_methods={HTTPMethod.GET}) - def database_summary(self, request: PreparedRequest) -> _ResponseType: + def database_summary(self, request: RequestData) -> _ResponseType: """Get a database summary report. Fake implementation of @@ -376,9 +358,9 @@ def database_summary(self, request: PreparedRequest) -> _ResponseType: try: run_services_validators( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: @@ -386,9 +368,9 @@ def database_summary(self, request: PreparedRequest) -> _ResponseType: database = get_database_matching_server_keys( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) @@ -428,7 +410,7 @@ def database_summary(self, request: PreparedRequest) -> _ResponseType: return HTTPStatus.OK, headers, body_json @route(path_pattern="/targets", http_methods={HTTPMethod.GET}) - def target_list(self, request: PreparedRequest) -> _ResponseType: + def target_list(self, request: RequestData) -> _ResponseType: """Get a list of all targets. Fake implementation of @@ -437,9 +419,9 @@ def target_list(self, request: PreparedRequest) -> _ResponseType: try: run_services_validators( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: @@ -447,9 +429,9 @@ def target_list(self, request: PreparedRequest) -> _ResponseType: database = get_database_matching_server_keys( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) @@ -485,7 +467,7 @@ def target_list(self, request: PreparedRequest) -> _ResponseType: path_pattern=f"/targets/{_TARGET_ID_PATTERN}", http_methods={HTTPMethod.GET}, ) - def get_target(self, request: PreparedRequest) -> _ResponseType: + def get_target(self, request: RequestData) -> _ResponseType: """Get details of a target. Fake implementation of @@ -494,9 +476,9 @@ def get_target(self, request: PreparedRequest) -> _ResponseType: try: run_services_validators( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: @@ -504,12 +486,12 @@ def get_target(self, request: PreparedRequest) -> _ResponseType: database = get_database_matching_server_keys( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) - target_id = request.path_url.split(sep="/")[-1] + target_id = request.path.split(sep="/")[-1] target = database.get_target(target_id=target_id) width = target.width @@ -553,7 +535,7 @@ def get_target(self, request: PreparedRequest) -> _ResponseType: path_pattern=f"/duplicates/{_TARGET_ID_PATTERN}", http_methods={HTTPMethod.GET}, ) - def get_duplicates(self, request: PreparedRequest) -> _ResponseType: + def get_duplicates(self, request: RequestData) -> _ResponseType: """Get targets which may be considered duplicates of a given target. @@ -563,9 +545,9 @@ def get_duplicates(self, request: PreparedRequest) -> _ResponseType: try: run_services_validators( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: @@ -573,12 +555,12 @@ def get_duplicates(self, request: PreparedRequest) -> _ResponseType: database = get_database_matching_server_keys( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) - target_id = request.path_url.split(sep="/")[-1] + target_id = request.path.split(sep="/")[-1] target = database.get_target(target_id=target_id) other_targets = database.targets - {target} @@ -625,7 +607,7 @@ def get_duplicates(self, request: PreparedRequest) -> _ResponseType: path_pattern=f"/targets/{_TARGET_ID_PATTERN}", http_methods={HTTPMethod.PUT}, ) - def update_target(self, request: PreparedRequest) -> _ResponseType: + def update_target(self, request: RequestData) -> _ResponseType: """Update a target. Fake implementation of @@ -634,9 +616,9 @@ def update_target(self, request: PreparedRequest) -> _ResponseType: try: run_services_validators( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: @@ -644,13 +626,13 @@ def update_target(self, request: PreparedRequest) -> _ResponseType: database = get_database_matching_server_keys( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) - target_id = request.path_url.split(sep="/")[-1] + target_id = request.path.split(sep="/")[-1] target = database.get_target(target_id=target_id) date = email.utils.formatdate( @@ -667,7 +649,7 @@ def update_target(self, request: PreparedRequest) -> _ResponseType: exception.response_text, ) - request_json: dict[str, Any] = json.loads(s=request.body or b"") + request_json: dict[str, Any] = json.loads(s=request.body) name = request_json.get("name", target.name) active_flag = request_json.get("active_flag", target.active_flag) @@ -739,7 +721,7 @@ def update_target(self, request: PreparedRequest) -> _ResponseType: path_pattern=f"/summary/{_TARGET_ID_PATTERN}", http_methods={HTTPMethod.GET}, ) - def target_summary(self, request: PreparedRequest) -> _ResponseType: + def target_summary(self, request: RequestData) -> _ResponseType: """Get a summary report for a target. Fake implementation of @@ -748,9 +730,9 @@ def target_summary(self, request: PreparedRequest) -> _ResponseType: try: run_services_validators( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) except ValidatorError as exc: @@ -758,12 +740,12 @@ def target_summary(self, request: PreparedRequest) -> _ResponseType: database = get_database_matching_server_keys( request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, + request_body=request.body, + request_method=request.method, + request_path=request.path, databases=self._target_manager.cloud_databases, ) - target_id = request.path_url.split(sep="/")[-1] + target_id = request.path.split(sep="/")[-1] target = database.get_target(target_id=target_id) date = email.utils.formatdate( From c7ae974fc40d8f535ee359be66bf42d5a92577a9 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 10:00:39 +0000 Subject: [PATCH 2311/2467] Enforce keyword-only arguments (#2977) Co-authored-by: Claude Opus 4.6 --- src/mock_vws/_flask_server/target_manager.py | 1 + src/mock_vws/_flask_server/vwq.py | 2 ++ src/mock_vws/_flask_server/vws.py | 6 ++++++ src/mock_vws/_mock_common.py | 2 ++ src/mock_vws/_query_validators/content_type_validators.py | 1 + src/mock_vws/_query_validators/date_validators.py | 1 + src/mock_vws/_query_validators/image_validators.py | 1 + .../_query_validators/include_target_data_validators.py | 1 + src/mock_vws/_query_validators/project_state_validators.py | 1 + src/mock_vws/_requests_mock_server/decorators.py | 1 + src/mock_vws/_requests_mock_server/mock_web_query_api.py | 1 + src/mock_vws/_requests_mock_server/mock_web_services_api.py | 1 + src/mock_vws/_services_validators/__init__.py | 4 ++++ src/mock_vws/_services_validators/key_validators.py | 1 + 14 files changed, 24 insertions(+) diff --git a/src/mock_vws/_flask_server/target_manager.py b/src/mock_vws/_flask_server/target_manager.py index 0b7f3469b..8d7ba28f0 100644 --- a/src/mock_vws/_flask_server/target_manager.py +++ b/src/mock_vws/_flask_server/target_manager.py @@ -369,6 +369,7 @@ def delete_target(database_name: str, target_id: str) -> Response: rule="/cloud_databases//targets/", methods=[HTTPMethod.PUT], ) +@beartype def update_target(database_name: str, target_id: str) -> Response: """Update a target.""" (database,) = ( diff --git a/src/mock_vws/_flask_server/vwq.py b/src/mock_vws/_flask_server/vwq.py index a421b1b66..4fb1e75c1 100644 --- a/src/mock_vws/_flask_server/vwq.py +++ b/src/mock_vws/_flask_server/vwq.py @@ -113,6 +113,7 @@ def add_response_delay(response: Response) -> Response: @CLOUDRECO_FLASK_APP.errorhandler(code_or_exception=ValidatorError) +@beartype def handle_exceptions(exc: ValidatorError) -> Response: """Return the error response associated with the given exception.""" response = Response( @@ -127,6 +128,7 @@ def handle_exceptions(exc: ValidatorError) -> Response: @CLOUDRECO_FLASK_APP.route(rule="/v1/query", methods=[HTTPMethod.POST]) +@beartype def query() -> Response: """Perform an image recognition query.""" settings = VWQSettings.model_validate(obj={}) diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index fc9bb8a84..ab1c1cb9b 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -117,6 +117,7 @@ def get_all_vumark_databases() -> set[VuMarkDatabase]: @VWS_FLASK_APP.before_request +@beartype def set_terminate_wsgi_input() -> None: """We set ``wsgi.input_terminated`` to ``True`` when going through ``requests`` in our tests, so that requests have the given ``Content- @@ -171,6 +172,7 @@ def add_response_delay(response: Response) -> Response: @VWS_FLASK_APP.errorhandler(code_or_exception=ValidatorError) +@beartype def handle_exceptions(exc: ValidatorError) -> Response: """Return the error response associated with the given exception.""" response = Response( @@ -319,6 +321,7 @@ def get_target(target_id: str) -> Response: rule="/targets/", methods=[HTTPMethod.DELETE], ) +@beartype def delete_target(target_id: str) -> Response: """Delete a target. @@ -499,6 +502,7 @@ def database_summary() -> Response: rule="/summary/", methods=[HTTPMethod.GET], ) +@beartype def target_summary(target_id: str) -> Response: """Get a summary report for a target. @@ -616,6 +620,7 @@ def get_duplicates(target_id: str) -> Response: @VWS_FLASK_APP.route(rule="/targets", methods=[HTTPMethod.GET]) +@beartype def target_list() -> Response: """Get a list of all targets. @@ -658,6 +663,7 @@ def target_list() -> Response: @VWS_FLASK_APP.route( rule="/targets/", methods=[HTTPMethod.PUT] ) +@beartype def update_target(target_id: str) -> Response: """Update a target. diff --git a/src/mock_vws/_mock_common.py b/src/mock_vws/_mock_common.py index 5a7069562..5b0c81a62 100644 --- a/src/mock_vws/_mock_common.py +++ b/src/mock_vws/_mock_common.py @@ -8,6 +8,7 @@ from beartype import beartype +@beartype @dataclass(frozen=True) class RequestData: """A library-agnostic representation of an HTTP request. @@ -25,6 +26,7 @@ class RequestData: body: bytes +@beartype @dataclass(frozen=True) class Route: """A representation of a VWS route. diff --git a/src/mock_vws/_query_validators/content_type_validators.py b/src/mock_vws/_query_validators/content_type_validators.py index 3e7dc4792..586978ccc 100644 --- a/src/mock_vws/_query_validators/content_type_validators.py +++ b/src/mock_vws/_query_validators/content_type_validators.py @@ -18,6 +18,7 @@ @beartype def validate_content_type_header( + *, request_headers: Mapping[str, str], request_body: bytes, ) -> None: diff --git a/src/mock_vws/_query_validators/date_validators.py b/src/mock_vws/_query_validators/date_validators.py index 4151b0d31..116aff3eb 100644 --- a/src/mock_vws/_query_validators/date_validators.py +++ b/src/mock_vws/_query_validators/date_validators.py @@ -34,6 +34,7 @@ def validate_date_header_given(*, request_headers: Mapping[str, str]) -> None: raise DateHeaderNotGivenError +@beartype def _accepted_date_formats() -> set[str]: """Return all known accepted date formats. diff --git a/src/mock_vws/_query_validators/image_validators.py b/src/mock_vws/_query_validators/image_validators.py index e2f8f498f..d08617ec2 100644 --- a/src/mock_vws/_query_validators/image_validators.py +++ b/src/mock_vws/_query_validators/image_validators.py @@ -171,6 +171,7 @@ def validate_image_format( @beartype def validate_image_is_image( + *, request_headers: Mapping[str, str], request_body: bytes, ) -> None: diff --git a/src/mock_vws/_query_validators/include_target_data_validators.py b/src/mock_vws/_query_validators/include_target_data_validators.py index b3719aad8..b3696c658 100644 --- a/src/mock_vws/_query_validators/include_target_data_validators.py +++ b/src/mock_vws/_query_validators/include_target_data_validators.py @@ -15,6 +15,7 @@ @beartype def validate_include_target_data( + *, request_headers: Mapping[str, str], request_body: bytes, ) -> None: diff --git a/src/mock_vws/_query_validators/project_state_validators.py b/src/mock_vws/_query_validators/project_state_validators.py index 5075b8560..7767499b2 100644 --- a/src/mock_vws/_query_validators/project_state_validators.py +++ b/src/mock_vws/_query_validators/project_state_validators.py @@ -15,6 +15,7 @@ @beartype def validate_project_state( + *, request_path: str, request_headers: Mapping[str, str], request_body: bytes, diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 04a961989..7cea0e976 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -35,6 +35,7 @@ _BRISQUE_TRACKING_RATER = BrisqueTargetTrackingRater() +@beartype class MissingSchemeError(Exception): """Raised when a URL is missing a schema.""" diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py index 6f8f519d3..b7d7aad57 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py @@ -41,6 +41,7 @@ def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _ResponseType: @beartype def route( + *, path_pattern: str, http_methods: Iterable[str], ) -> Callable[[_RouteMethod[_P]], _RouteMethod[_P]]: diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index 1b0662cea..671539cdb 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -67,6 +67,7 @@ def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _ResponseType: @beartype def route( + *, path_pattern: str, http_methods: Iterable[HTTPMethod], ) -> Callable[[_RouteMethod[_P]], _RouteMethod[_P]]: diff --git a/src/mock_vws/_services_validators/__init__.py b/src/mock_vws/_services_validators/__init__.py index 7a2e742a3..026d46800 100644 --- a/src/mock_vws/_services_validators/__init__.py +++ b/src/mock_vws/_services_validators/__init__.py @@ -2,6 +2,8 @@ from collections.abc import Iterable, Mapping +from beartype import beartype + from mock_vws._database_matchers import AnyDatabase from .active_flag_validators import validate_active_flag @@ -50,7 +52,9 @@ from .width_validators import validate_width +@beartype def run_services_validators( + *, request_path: str, request_headers: Mapping[str, str], request_body: bytes, diff --git a/src/mock_vws/_services_validators/key_validators.py b/src/mock_vws/_services_validators/key_validators.py index cf4d32fa4..379daaddd 100644 --- a/src/mock_vws/_services_validators/key_validators.py +++ b/src/mock_vws/_services_validators/key_validators.py @@ -14,6 +14,7 @@ _LOGGER = logging.getLogger(name=__name__) +@beartype @dataclass class _Route: """A representation of a VWS route. From c4c432de1a19f04172a1a8d5f07c0522921d1889 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 10:03:11 +0000 Subject: [PATCH 2312/2467] Add missing @beartype decorators (#2978) Co-authored-by: Claude Opus 4.6 From b86f040f4999bb4f5d497580864c02597498abef Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 10:22:18 +0000 Subject: [PATCH 2313/2467] Bump vws-python to 2026.2.21 (#2982) * Bump vws-python to 2026.02.21 Co-Authored-By: Claude Opus 4.6 * Add content parameter to Response calls for vws-python 2026.2.21 Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- pyproject.toml | 2 +- tests/mock_vws/test_query.py | 9 +++++++++ tests/mock_vws/utils/__init__.py | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index df251f8d2..9d5df154d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,7 +103,7 @@ optional-dependencies.dev = [ "types-requests==2.32.4.20260107", "urllib3==2.6.3", "vulture==2.14", - "vws-python==2026.2.15", + "vws-python==2026.2.21", "vws-test-fixtures==2023.3.5", "vws-web-tools==2026.2.20", "yamlfix==1.19.1", diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index 32e7a6a56..f6760b13d 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -143,6 +143,7 @@ def _query( headers=dict(requests_response.headers), request_body=requests_response.request.body, tell_position=requests_response.raw.tell(), + content=requests_response.content, ) handle_server_errors(response=vws_response) return vws_response @@ -250,6 +251,7 @@ def test_incorrect_no_boundary( headers=dict(requests_response.headers), request_body=requests_response.request.body, tell_position=requests_response.raw.tell(), + content=requests_response.content, ) if resp_status_code != HTTPStatus.INTERNAL_SERVER_ERROR: @@ -326,6 +328,7 @@ def test_incorrect_with_boundary( headers=dict(requests_response.headers), request_body=requests_response.request.body, tell_position=requests_response.raw.tell(), + content=requests_response.content, ) handle_server_errors(response=vws_response) assert not requests_response.text @@ -397,6 +400,7 @@ def test_no_boundary( headers=dict(requests_response.headers), request_body=requests_response.request.body, tell_position=requests_response.raw.tell(), + content=requests_response.content, ) expected_text = "RESTEASY007550: Unable to get boundary for multipart" assert requests_response.text == expected_text @@ -456,6 +460,7 @@ def test_bogus_boundary( headers=dict(requests_response.headers), request_body=requests_response.request.body, tell_position=requests_response.raw.tell(), + content=requests_response.content, ) handle_server_errors(response=vws_response) @@ -521,6 +526,7 @@ def test_extra_section( headers=dict(requests_response.headers), request_body=requests_response.request.body, tell_position=requests_response.raw.tell(), + content=requests_response.content, ) handle_server_errors(response=vws_response) assert_query_success(response=vws_response) @@ -1230,6 +1236,7 @@ def test_valid( headers=dict(requests_response.headers), request_body=requests_response.request.body, tell_position=requests_response.raw.tell(), + content=requests_response.content, ) handle_server_errors(response=vws_response) assert_query_success(response=vws_response) @@ -1288,6 +1295,7 @@ def test_invalid( headers=dict(requests_response.headers), request_body=requests_response.request.body, tell_position=requests_response.raw.tell(), + content=requests_response.content, ) handle_server_errors(response=vws_response) @@ -2008,6 +2016,7 @@ def test_date_formats( headers=dict(requests_response.headers), request_body=requests_response.request.body, tell_position=requests_response.raw.tell(), + content=requests_response.content, ) handle_server_errors(response=vws_response) assert_query_success(response=vws_response) diff --git a/tests/mock_vws/utils/__init__.py b/tests/mock_vws/utils/__init__.py index d4bfa0014..de18b5bdb 100644 --- a/tests/mock_vws/utils/__init__.py +++ b/tests/mock_vws/utils/__init__.py @@ -71,6 +71,7 @@ def send(self) -> Response: headers=dict(requests_response.headers), request_body=requests_response.request.body, tell_position=requests_response.raw.tell(), + content=requests_response.content, ) @property From e930c59d0ac72d4637bec075a96816daca1bbb23 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 10:33:27 +0000 Subject: [PATCH 2314/2467] Use native pytest TOML configuration (#2979) * Use native pytest TOML configuration Remove `ini_options.` prefix from pytest configuration keys under `[tool.pytest]`, using the native TOML configuration format supported since pytest 9.0. Co-Authored-By: Claude Opus 4.6 * Quote pytest-retry options as strings The pytest-retry plugin registers its options as string type via `getini`, so native TOML integers/booleans cause a TypeError. Quote the values to maintain compatibility. Co-Authored-By: Claude Opus 4.6 * Keep cumulative_timing as native bool This option expects a bool, not a string. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- pyproject.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9d5df154d..db6c4e83d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -329,18 +329,18 @@ keep_full_version = true max_supported_python = "3.13" [tool.pytest] -ini_options.xfail_strict = true -ini_options.log_cli = true -ini_options.addopts = [ +xfail_strict = true +log_cli = true +addopts = [ "--strict-markers", ] -ini_options.markers = [ +markers = [ "requires_docker_build", ] # Options for pytest-retry. -ini_options.retries = 10 -ini_options.retry_delay = 10 -ini_options.cumulative_timing = false +retries = "10" +retry_delay = "10" +cumulative_timing = false [tool.coverage] run.branch = true From 4dbfbf4ca254e039950a9eaa9c6eece4a55ffe4d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 11:13:14 +0000 Subject: [PATCH 2315/2467] Use VuMarkService in VuMark generation API tests (#2984) * Use VuMarkService in VuMark generation API tests * Add beartype to VuMark test helper functions --- tests/mock_vws/test_vumark_generation_api.py | 110 ++++++++++++++----- 1 file changed, 83 insertions(+), 27 deletions(-) diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 2258988a6..e529bd479 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -6,7 +6,13 @@ import pytest import requests -from vws import VWS +from beartype import beartype +from vws import VWS, VuMarkService +from vws.exceptions.vws_exceptions import ( + InvalidInstanceIdError, + InvalidTargetTypeError, +) +from vws.vumark_accept import VuMarkAccept from vws_auth_tools import authorization_header, rfc_1123_date from mock_vws._constants import ResultCodes @@ -20,6 +26,20 @@ _SVG_START = b"<" +@beartype +def _make_vumark_service( + *, + server_access_key: str, + server_secret_key: str, +) -> VuMarkService: + """Return a VuMark service client.""" + return VuMarkService( + server_access_key=server_access_key, + server_secret_key=server_secret_key, + ) + + +@beartype def _make_vumark_request( *, server_access_key: str, @@ -66,18 +86,16 @@ class TestGenerateInstance: """Tests for the VuMark instance generation endpoint.""" @pytest.mark.parametrize( - argnames=("accept", "expected_content_type", "expected_signature"), + argnames=("accept", "expected_signature"), argvalues=[ - pytest.param("image/png", "image/png", _PNG_SIGNATURE, id="png"), + pytest.param(VuMarkAccept.PNG, _PNG_SIGNATURE, id="png"), pytest.param( - "image/svg+xml", - "image/svg+xml", + VuMarkAccept.SVG, _SVG_START, id="svg", ), pytest.param( - "application/pdf", - "application/pdf", + VuMarkAccept.PDF, _PDF_SIGNATURE, id="pdf", ), @@ -85,12 +103,39 @@ class TestGenerateInstance: ) @staticmethod def test_generate_instance_format( - accept: str, - expected_content_type: str, + accept: VuMarkAccept, expected_signature: bytes, vumark_vuforia_database: VuMarkCloudDatabase, ) -> None: """A VuMark instance can be generated in the requested format.""" + vumark_client = _make_vumark_service( + server_access_key=vumark_vuforia_database.server_access_key, + server_secret_key=vumark_vuforia_database.server_secret_key, + ) + vumark_bytes = vumark_client.generate_vumark_instance( + target_id=vumark_vuforia_database.target_id, + instance_id=uuid4().hex, + accept=accept, + ) + + assert vumark_bytes.strip().startswith(expected_signature) + assert len(vumark_bytes) > len(expected_signature) + + @pytest.mark.parametrize( + argnames=("accept", "expected_content_type"), + argvalues=[ + pytest.param("image/png", "image/png", id="png"), + pytest.param("image/svg+xml", "image/svg+xml", id="svg"), + pytest.param("application/pdf", "application/pdf", id="pdf"), + ], + ) + @staticmethod + def test_generate_instance_content_type_header( + accept: str, + expected_content_type: str, + vumark_vuforia_database: VuMarkCloudDatabase, + ) -> None: + """VuMark image responses include the expected content type.""" response = _make_vumark_request( server_access_key=vumark_vuforia_database.server_access_key, server_secret_key=vumark_vuforia_database.server_secret_key, @@ -104,8 +149,6 @@ def test_generate_instance_format( response.headers["Content-Type"].split(sep=";")[0] == expected_content_type ) - assert response.content.strip().startswith(expected_signature) - assert len(response.content) > len(expected_signature) @staticmethod def test_invalid_accept_header( @@ -132,16 +175,21 @@ def test_empty_instance_id( vumark_vuforia_database: VuMarkCloudDatabase, ) -> None: """An empty instance_id returns InvalidInstanceId.""" - response = _make_vumark_request( + vumark_client = _make_vumark_service( server_access_key=vumark_vuforia_database.server_access_key, server_secret_key=vumark_vuforia_database.server_secret_key, - target_id=vumark_vuforia_database.target_id, - instance_id="", - accept="image/png", ) + with pytest.raises(expected_exception=InvalidInstanceIdError) as exc: + vumark_client.generate_vumark_instance( + target_id=vumark_vuforia_database.target_id, + instance_id="", + accept=VuMarkAccept.PNG, + ) - assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY - response_json = response.json() + assert ( + exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + ) + response_json = json.loads(s=exc.value.response.text) assert ( response_json["result_code"] == ResultCodes.INVALID_INSTANCE_ID.value @@ -154,9 +202,15 @@ def test_non_vumark_database( """Generating a VuMark instance for a target in a non-VuMark database returns InvalidTargetType. """ + server_access_key = vuforia_database.server_access_key + server_secret_key = vuforia_database.server_secret_key vws_client = VWS( - server_access_key=vuforia_database.server_access_key, - server_secret_key=vuforia_database.server_secret_key, + server_access_key=server_access_key, + server_secret_key=server_secret_key, + ) + vumark_client = _make_vumark_service( + server_access_key=server_access_key, + server_secret_key=server_secret_key, ) image = make_image_file( file_format="PNG", @@ -171,15 +225,17 @@ def test_non_vumark_database( active_flag=True, application_metadata=None, ) - response = _make_vumark_request( - server_access_key=vuforia_database.server_access_key, - server_secret_key=vuforia_database.server_secret_key, - target_id=target_id, - instance_id=uuid4().hex, - accept="image/png", + with pytest.raises(expected_exception=InvalidTargetTypeError) as exc: + vumark_client.generate_vumark_instance( + target_id=target_id, + instance_id=uuid4().hex, + accept=VuMarkAccept.PNG, + ) + + assert ( + exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY ) - assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY - response_json = response.json() + response_json = json.loads(s=exc.value.response.text) assert ( response_json["result_code"] == ResultCodes.INVALID_TARGET_TYPE.value From 88b2a8eb9a2e88256a0bb8963bda432b0904a120 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 17:06:59 +0000 Subject: [PATCH 2316/2467] Add clarifying comment and test for unknown target in VuMark generation (#2985) - Add comment in test_invalid_given_id explaining the scope of the shared check - Add test_unknown_target to verify UnknownTarget response for VuMark generation API Co-authored-by: Claude Haiku 4.5 --- tests/mock_vws/test_invalid_given_id.py | 3 +++ tests/mock_vws/test_vumark_generation_api.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/tests/mock_vws/test_invalid_given_id.py b/tests/mock_vws/test_invalid_given_id.py index 067867f75..9db6c8cac 100644 --- a/tests/mock_vws/test_invalid_given_id.py +++ b/tests/mock_vws/test_invalid_given_id.py @@ -34,6 +34,9 @@ def test_not_real_id( target ID of a target which does not exist. """ + # This shared check only covers endpoints that end in target_id, + # such as /targets/{target_id}. Endpoints with trailing segments + # are covered by endpoint-specific tests. if not endpoint.path_url.endswith(target_id): return diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index e529bd479..8705713ce 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -195,6 +195,23 @@ def test_empty_instance_id( == ResultCodes.INVALID_INSTANCE_ID.value ) + @staticmethod + def test_unknown_target( + vumark_vuforia_database: VuMarkCloudDatabase, + ) -> None: + """An unknown target_id returns UnknownTarget.""" + response = _make_vumark_request( + server_access_key=vumark_vuforia_database.server_access_key, + server_secret_key=vumark_vuforia_database.server_secret_key, + target_id=uuid4().hex, + instance_id=uuid4().hex, + accept=VuMarkAccept.PNG, + ) + + assert response.status_code == HTTPStatus.NOT_FOUND + response_json = response.json() + assert response_json["result_code"] == ResultCodes.UNKNOWN_TARGET.value + @staticmethod def test_non_vumark_database( vuforia_database: CloudDatabase, From cc510cd26866537334d8be1cf9c4608453fd5668 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 00:08:52 +0000 Subject: [PATCH 2317/2467] Use keyword-only parameters in tests (#2986) Enforce keyword-only arguments in test functions that accept multiple parameters, improving call-site clarity. --- ci/test_custom_linters.py | 1 + tests/mock_vws/test_add_target.py | 26 +++++++++++++++ tests/mock_vws/test_authorization_header.py | 2 ++ tests/mock_vws/test_database_summary.py | 10 +++++- tests/mock_vws/test_delete_target.py | 4 +-- tests/mock_vws/test_docker.py | 1 + tests/mock_vws/test_flask_app_usage.py | 9 ++++++ tests/mock_vws/test_get_duplicates.py | 5 +++ tests/mock_vws/test_get_target.py | 4 +++ tests/mock_vws/test_invalid_given_id.py | 1 + tests/mock_vws/test_query.py | 34 ++++++++++++++++++-- tests/mock_vws/test_requests_mock_usage.py | 1 + tests/mock_vws/test_target_list.py | 2 ++ tests/mock_vws/test_target_summary.py | 4 ++- tests/mock_vws/test_update_target.py | 31 +++++++++++++++--- tests/mock_vws/test_vumark_generation_api.py | 2 ++ 16 files changed, 126 insertions(+), 11 deletions(-) diff --git a/ci/test_custom_linters.py b/ci/test_custom_linters.py index c25260b46..c02fcacc6 100644 --- a/ci/test_custom_linters.py +++ b/ci/test_custom_linters.py @@ -83,6 +83,7 @@ def test_ci_patterns_valid(request: pytest.FixtureRequest) -> None: def test_tests_collected_once( + *, capsys: pytest.CaptureFixture[str], request: pytest.FixtureRequest, ) -> None: diff --git a/tests/mock_vws/test_add_target.py b/tests/mock_vws/test_add_target.py index d732dc94f..1703004ee 100644 --- a/tests/mock_vws/test_add_target.py +++ b/tests/mock_vws/test_add_target.py @@ -103,6 +103,7 @@ class TestContentTypes: ], ) def test_content_types( + *, vws_client: VWS, image_file_failed_state: io.BytesIO, content_type: str, @@ -129,6 +130,7 @@ def test_content_types( @staticmethod def test_empty_content_type( + *, vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: @@ -174,6 +176,7 @@ class TestMissingData: argvalues=["name", "width", "image"], ) def test_missing_data( + *, vws_client: VWS, image_file_failed_state: io.BytesIO, data_to_remove: str, @@ -212,6 +215,7 @@ class TestWidth: ids=["Negative", "Wrong Type", "None", "Zero"], ) def test_width_invalid( + *, vws_client: VWS, image_file_failed_state: io.BytesIO, width: int | str | None, @@ -239,6 +243,7 @@ def test_width_invalid( @staticmethod def test_width_valid( + *, vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: @@ -273,6 +278,7 @@ class TestTargetName: ids=["Short name", "Max char value", "Long name"], ) def test_name_valid( + *, name: str, image_file_failed_state: io.BytesIO, vws_client: VWS, @@ -310,6 +316,7 @@ def test_name_valid( ], ) def test_name_invalid( + *, name: str | int | None, image_file_failed_state: io.BytesIO, status_code: int, @@ -349,6 +356,7 @@ def test_name_invalid( @staticmethod def test_existing_target_name( + *, image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: @@ -378,6 +386,7 @@ def test_existing_target_name( @staticmethod def test_deleted_existing_target_name( + *, image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: @@ -411,6 +420,7 @@ class TestImage: @staticmethod def test_image_valid( + *, vws_client: VWS, image_files_failed_state: io.BytesIO, ) -> None: @@ -428,6 +438,7 @@ def test_image_valid( @staticmethod def test_bad_image_format_or_color_space( + *, bad_image_file: io.BytesIO, vws_client: VWS, ) -> None: @@ -454,6 +465,7 @@ def test_bad_image_format_or_color_space( @staticmethod def test_corrupted( + *, corrupted_image_file: io.BytesIO, vws_client: VWS, ) -> None: @@ -543,6 +555,7 @@ def test_image_file_size_too_large(vws_client: VWS) -> None: @staticmethod def test_not_base64_encoded_processable( + *, vws_client: VWS, not_base64_encoded_processable: str, ) -> None: @@ -570,6 +583,7 @@ def test_not_base64_encoded_processable( @staticmethod def test_not_base64_encoded_not_processable( + *, vws_client: VWS, not_base64_encoded_not_processable: str, ) -> None: @@ -622,6 +636,7 @@ def test_not_image(vws_client: VWS) -> None: argvalues=[1, None], ) def test_invalid_type( + *, invalid_type_image: int | None, vws_client: VWS, ) -> None: @@ -681,6 +696,7 @@ def test_valid( @staticmethod def test_invalid( + *, image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: @@ -717,6 +733,7 @@ def test_invalid( @staticmethod def test_not_set( + *, vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: @@ -740,6 +757,7 @@ def test_not_set( @staticmethod def test_set_to_none( + *, vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: @@ -773,6 +791,7 @@ class TestUnexpectedData: @staticmethod def test_invalid_extra_data( + *, vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: @@ -816,6 +835,7 @@ class TestApplicationMetadata: ids=["Short", "Max length"], ) def test_base64_encoded( + *, image_file_failed_state: io.BytesIO, metadata: bytes, vws_client: VWS, @@ -835,6 +855,7 @@ def test_base64_encoded( @staticmethod def test_null( + *, vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: @@ -860,6 +881,7 @@ def test_null( @staticmethod def test_invalid_type( + *, vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: @@ -890,6 +912,7 @@ def test_invalid_type( @staticmethod def test_not_base64_encoded_processable( + *, high_quality_image: io.BytesIO, not_base64_encoded_processable: str, vws_client: VWS, @@ -909,6 +932,7 @@ def test_not_base64_encoded_processable( @staticmethod def test_not_base64_encoded_not_processable( + *, high_quality_image: io.BytesIO, not_base64_encoded_not_processable: str, vws_client: VWS, @@ -935,6 +959,7 @@ def test_not_base64_encoded_not_processable( @staticmethod def test_metadata_too_large( + *, image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: @@ -970,6 +995,7 @@ class TestInactiveProject: @staticmethod def test_inactive_project( + *, image_file_failed_state: io.BytesIO, inactive_vws_client: VWS, ) -> None: diff --git a/tests/mock_vws/test_authorization_header.py b/tests/mock_vws/test_authorization_header.py index 89ff5bc1d..3c18e914c 100644 --- a/tests/mock_vws/test_authorization_header.py +++ b/tests/mock_vws/test_authorization_header.py @@ -265,6 +265,7 @@ def test_bad_access_key_services( @staticmethod def test_bad_access_key_query( + *, vuforia_database: CloudDatabase, high_quality_image: io.BytesIO, ) -> None: @@ -328,6 +329,7 @@ def test_bad_secret_key_services( @staticmethod def test_bad_secret_key_query( + *, vuforia_database: CloudDatabase, high_quality_image: io.BytesIO, ) -> None: diff --git a/tests/mock_vws/test_database_summary.py b/tests/mock_vws/test_database_summary.py index 892d3c71d..86448394b 100644 --- a/tests/mock_vws/test_database_summary.py +++ b/tests/mock_vws/test_database_summary.py @@ -94,6 +94,7 @@ class TestDatabaseSummary: @staticmethod def test_success( + *, vuforia_database: CloudDatabase, vws_client: VWS, ) -> None: @@ -110,7 +111,7 @@ def test_success( ) @staticmethod - def test_active_images(vws_client: VWS, target_id: str) -> None: + def test_active_images(*, vws_client: VWS, target_id: str) -> None: """The number of images in the active state is returned.""" vws_client.wait_for_target_processed(target_id=target_id) @@ -124,6 +125,7 @@ def test_active_images(vws_client: VWS, target_id: str) -> None: @staticmethod def test_failed_images( + *, image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: @@ -148,6 +150,7 @@ def test_failed_images( @staticmethod def test_inactive_images( + *, vws_client: VWS, image_file_success_state_low_rating: io.BytesIO, ) -> None: @@ -176,6 +179,7 @@ def test_inactive_images( @staticmethod def test_inactive_failed( + *, image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: @@ -200,6 +204,7 @@ def test_inactive_failed( @staticmethod def test_deleted( + *, image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: @@ -289,6 +294,7 @@ class TestRecos: @staticmethod def test_query_request( + *, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, vws_client: VWS, @@ -344,6 +350,7 @@ def test_target_request(vws_client: VWS) -> None: @staticmethod def test_bad_target_request( + *, high_quality_image: io.BytesIO, vws_client: VWS, ) -> None: @@ -372,6 +379,7 @@ def test_bad_target_request( @staticmethod def test_query_request( + *, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, vws_client: VWS, diff --git a/tests/mock_vws/test_delete_target.py b/tests/mock_vws/test_delete_target.py index 9c3f68d1e..26befe849 100644 --- a/tests/mock_vws/test_delete_target.py +++ b/tests/mock_vws/test_delete_target.py @@ -19,7 +19,7 @@ class TestDelete: """Tests for deleting targets.""" @staticmethod - def test_no_wait(target_id: str, vws_client: VWS) -> None: + def test_no_wait(*, target_id: str, vws_client: VWS) -> None: """When attempting to delete a target immediately after creating it, a `FORBIDDEN` response is returned. @@ -41,7 +41,7 @@ def test_no_wait(target_id: str, vws_client: VWS) -> None: ) @staticmethod - def test_processed(target_id: str, vws_client: VWS) -> None: + def test_processed(*, target_id: str, vws_client: VWS) -> None: """When a target has finished processing, it can be deleted.""" vws_client.wait_for_target_processed(target_id=target_id) vws_client.delete_target(target_id=target_id) diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py index a0472e558..02f1fd873 100644 --- a/tests/mock_vws/test_docker.py +++ b/tests/mock_vws/test_docker.py @@ -88,6 +88,7 @@ def fixture_custom_bridge_network() -> Iterator[Network]: @pytest.mark.requires_docker_build def test_build_and_run( + *, high_quality_image: io.BytesIO, custom_bridge_network: Network, request: pytest.FixtureRequest, diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index 23fa647fe..bef5b44ec 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -92,6 +92,7 @@ def test_default( def test_custom( self, + *, image_file_failed_state: io.BytesIO, monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -323,6 +324,7 @@ class TestQueryImageMatchers: @staticmethod def test_exact_match( + *, high_quality_image: io.BytesIO, monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -366,6 +368,7 @@ def test_exact_match( @staticmethod def test_structural_similarity_matcher( + *, high_quality_image: io.BytesIO, different_high_quality_image: io.BytesIO, monkeypatch: pytest.MonkeyPatch, @@ -421,6 +424,7 @@ class TestDuplicatesImageMatchers: @staticmethod def test_exact_match( + *, high_quality_image: io.BytesIO, monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -470,6 +474,7 @@ def test_exact_match( @staticmethod def test_structural_similarity_matcher( + *, high_quality_image: io.BytesIO, monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -516,6 +521,7 @@ class TestTargetRaters: @staticmethod def test_default( + *, image_file_success_state_low_rating: io.BytesIO, high_quality_image: io.BytesIO, ) -> None: @@ -564,6 +570,7 @@ def test_default( @staticmethod def test_brisque( + *, monkeypatch: pytest.MonkeyPatch, image_file_success_state_low_rating: io.BytesIO, high_quality_image: io.BytesIO, @@ -615,6 +622,7 @@ def test_brisque( @staticmethod def test_perfect( + *, monkeypatch: pytest.MonkeyPatch, high_quality_image: io.BytesIO, ) -> None: @@ -654,6 +662,7 @@ def test_perfect( @staticmethod def test_random( + *, monkeypatch: pytest.MonkeyPatch, high_quality_image: io.BytesIO, ) -> None: diff --git a/tests/mock_vws/test_get_duplicates.py b/tests/mock_vws/test_get_duplicates.py index 5634e8737..0c33383df 100644 --- a/tests/mock_vws/test_get_duplicates.py +++ b/tests/mock_vws/test_get_duplicates.py @@ -17,6 +17,7 @@ class TestDuplicates: @staticmethod def test_duplicates( + *, high_quality_image: io.BytesIO, image_file_success_state_low_rating: io.BytesIO, vws_client: VWS, @@ -61,6 +62,7 @@ def test_duplicates( @staticmethod def test_duplicates_not_same( + *, high_quality_image: io.BytesIO, vws_client: VWS, ) -> None: @@ -100,6 +102,7 @@ def test_duplicates_not_same( @staticmethod def test_status( + *, image_file_failed_state: io.BytesIO, vws_client: VWS, ) -> None: @@ -141,6 +144,7 @@ class TestActiveFlag: @staticmethod def test_active_flag( + *, high_quality_image: io.BytesIO, vws_client: VWS, ) -> None: @@ -194,6 +198,7 @@ class TestProcessing: @staticmethod def test_processing( + *, high_quality_image: io.BytesIO, vws_client: VWS, ) -> None: diff --git a/tests/mock_vws/test_get_target.py b/tests/mock_vws/test_get_target.py index e0d22f634..325517864 100644 --- a/tests/mock_vws/test_get_target.py +++ b/tests/mock_vws/test_get_target.py @@ -18,6 +18,7 @@ class TestGetRecord: @staticmethod def test_get_vws_target( + *, vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: @@ -53,6 +54,7 @@ def test_get_vws_target( @staticmethod def test_fail_status( + *, vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: @@ -77,6 +79,7 @@ def test_fail_status( @staticmethod def test_success_status( + *, image_file_success_state_low_rating: io.BytesIO, vws_client: VWS, ) -> None: @@ -139,6 +142,7 @@ class TestTargetTrackingRating: @staticmethod def test_target_quality( + *, vws_client: VWS, high_quality_image: io.BytesIO, image_file_success_state_low_rating: io.BytesIO, diff --git a/tests/mock_vws/test_invalid_given_id.py b/tests/mock_vws/test_invalid_given_id.py index 9db6c8cac..1d3b49e8c 100644 --- a/tests/mock_vws/test_invalid_given_id.py +++ b/tests/mock_vws/test_invalid_given_id.py @@ -25,6 +25,7 @@ class TestInvalidGivenID: @staticmethod def test_not_real_id( + *, vws_client: VWS, endpoint: Endpoint, target_id: str, diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index f6760b13d..0a1102643 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -273,6 +273,7 @@ def test_incorrect_no_boundary( @staticmethod def test_incorrect_with_boundary( + *, high_quality_image: io.BytesIO, vuforia_database: CloudDatabase, ) -> None: @@ -351,6 +352,7 @@ def test_incorrect_with_boundary( ], ) def test_no_boundary( + *, high_quality_image: io.BytesIO, vuforia_database: CloudDatabase, content_type: str, @@ -415,6 +417,7 @@ def test_no_boundary( @staticmethod def test_bogus_boundary( + *, high_quality_image: io.BytesIO, vuforia_database: CloudDatabase, ) -> None: @@ -477,6 +480,7 @@ def test_bogus_boundary( @staticmethod def test_extra_section( + *, high_quality_image: io.BytesIO, vuforia_database: CloudDatabase, ) -> None: @@ -540,6 +544,7 @@ class TestSuccess: @staticmethod def test_no_results( + *, high_quality_image: io.BytesIO, cloud_reco_client: CloudRecoService, ) -> None: @@ -553,6 +558,7 @@ def test_no_results( @staticmethod def test_match_exact( + *, high_quality_image: io.BytesIO, vuforia_database: CloudDatabase, vws_client: VWS, @@ -603,6 +609,7 @@ def test_match_exact( @staticmethod def test_low_quality_image( + *, image_file_success_state_low_rating: io.BytesIO, cloud_reco_client: CloudRecoService, vws_client: VWS, @@ -631,6 +638,7 @@ def test_low_quality_image( @staticmethod def test_match_similar( + *, high_quality_image: io.BytesIO, different_high_quality_image: io.BytesIO, vws_client: VWS, @@ -681,6 +689,7 @@ def test_match_similar( @staticmethod def test_not_base64_encoded_processable( + *, high_quality_image: io.BytesIO, vws_client: VWS, not_base64_encoded_processable: str, @@ -753,6 +762,7 @@ def test_missing_image(vuforia_database: CloudDatabase) -> None: @staticmethod def test_extra_fields( + *, high_quality_image: io.BytesIO, vuforia_database: CloudDatabase, ) -> None: @@ -810,6 +820,7 @@ class TestMaxNumResults: @staticmethod def test_default( + *, high_quality_image: io.BytesIO, vuforia_database: CloudDatabase, vws_client: VWS, @@ -847,6 +858,7 @@ def test_default( @staticmethod @pytest.mark.parametrize(argnames="num_results", argvalues=[1, b"1", 50]) def test_valid_accepted( + *, high_quality_image: io.BytesIO, vuforia_database: CloudDatabase, num_results: int | bytes, @@ -877,6 +889,7 @@ def test_valid_accepted( @staticmethod def test_valid_works( + *, high_quality_image: io.BytesIO, vws_client: VWS, cloud_reco_client: CloudRecoService, @@ -899,6 +912,7 @@ def test_valid_works( @staticmethod @pytest.mark.parametrize(argnames="num_results", argvalues=[-1, 0, 51]) def test_out_of_range( + *, high_quality_image: io.BytesIO, num_results: int, cloud_reco_client: CloudRecoService, @@ -940,6 +954,7 @@ def test_out_of_range( argvalues=[b"0.1", b"1.1", b"a", b"2147483648"], ) def test_invalid_type( + *, high_quality_image: io.BytesIO, vuforia_database: CloudDatabase, num_results: bytes, @@ -1002,6 +1017,7 @@ class TestIncludeTargetData: @staticmethod def test_default( + *, high_quality_image: io.BytesIO, vws_client: VWS, vuforia_database: CloudDatabase, @@ -1032,6 +1048,7 @@ def test_default( argvalues=["top", "TOP"], ) def test_top( + *, high_quality_image: io.BytesIO, vuforia_database: CloudDatabase, include_target_data: str, @@ -1068,6 +1085,7 @@ def test_top( argvalues=["none", "NONE"], ) def test_none( + *, high_quality_image: io.BytesIO, vuforia_database: CloudDatabase, include_target_data: str, @@ -1104,6 +1122,7 @@ def test_none( argvalues=["all", "ALL"], ) def test_all( + *, high_quality_image: io.BytesIO, vuforia_database: CloudDatabase, include_target_data: str, @@ -1189,6 +1208,7 @@ class TestAcceptHeader: ], ) def test_valid( + *, high_quality_image: io.BytesIO, vuforia_database: CloudDatabase, extra_headers: dict[str, str], @@ -1245,6 +1265,7 @@ def test_valid( @staticmethod def test_invalid( + *, high_quality_image: io.BytesIO, vuforia_database: CloudDatabase, ) -> None: @@ -1315,6 +1336,7 @@ class TestActiveFlag: @staticmethod def test_inactive( + *, high_quality_image: io.BytesIO, vws_client: VWS, cloud_reco_client: CloudRecoService, @@ -1339,6 +1361,7 @@ class TestBadImage: @staticmethod def test_corrupted( + *, corrupted_image_file: io.BytesIO, cloud_reco_client: CloudRecoService, ) -> None: @@ -1667,6 +1690,7 @@ class TestImageFormats: @staticmethod @pytest.mark.parametrize(argnames="file_format", argvalues=["png", "jpeg"]) def test_supported( + *, high_quality_image: io.BytesIO, file_format: str, cloud_reco_client: CloudRecoService, @@ -1683,6 +1707,7 @@ def test_supported( @staticmethod def test_unsupported( + *, high_quality_image: io.BytesIO, cloud_reco_client: CloudRecoService, ) -> None: @@ -1729,10 +1754,10 @@ class TestProcessing: @staticmethod @pytest.mark.parametrize(argnames="active_flag", argvalues=[True, False]) def test_processing( + *, high_quality_image: io.BytesIO, vws_client: VWS, cloud_reco_client: CloudRecoService, - *, active_flag: bool, ) -> None: """ @@ -1773,6 +1798,7 @@ class TestUpdate: @staticmethod def test_updated_target( + *, high_quality_image: io.BytesIO, different_high_quality_image: io.BytesIO, vws_client: VWS, @@ -1850,6 +1876,7 @@ class TestDeleted: @staticmethod def test_deleted_active( + *, high_quality_image: io.BytesIO, vws_client: VWS, cloud_reco_client: CloudRecoService, @@ -1886,6 +1913,7 @@ def test_deleted_active( @staticmethod def test_deleted_inactive( + *, high_quality_image: io.BytesIO, vws_client: VWS, cloud_reco_client: CloudRecoService, @@ -1914,6 +1942,7 @@ class TestTargetStatusFailed: @staticmethod def test_status_failed( + *, image_file_failed_state: io.BytesIO, vws_client: VWS, cloud_reco_client: CloudRecoService, @@ -1959,10 +1988,10 @@ class TestDateFormats: ) @pytest.mark.parametrize(argnames="include_tz", argvalues=[True, False]) def test_date_formats( + *, high_quality_image: io.BytesIO, vuforia_database: CloudDatabase, datetime_format: str, - *, include_tz: bool, ) -> None: """Test various date formats which are known to be accepted. @@ -2030,6 +2059,7 @@ class TestInactiveProject: @staticmethod def test_inactive_project( + *, high_quality_image: io.BytesIO, inactive_cloud_reco_client: CloudRecoService, ) -> None: diff --git a/tests/mock_vws/test_requests_mock_usage.py b/tests/mock_vws/test_requests_mock_usage.py index 8fa5d91de..87820d6ef 100644 --- a/tests/mock_vws/test_requests_mock_usage.py +++ b/tests/mock_vws/test_requests_mock_usage.py @@ -684,6 +684,7 @@ def test_custom_matcher(high_quality_image: io.BytesIO) -> None: @staticmethod def test_structural_similarity_matcher( + *, high_quality_image: io.BytesIO, different_high_quality_image: io.BytesIO, ) -> None: diff --git a/tests/mock_vws/test_target_list.py b/tests/mock_vws/test_target_list.py index b83db3dfd..180435ccd 100644 --- a/tests/mock_vws/test_target_list.py +++ b/tests/mock_vws/test_target_list.py @@ -10,6 +10,7 @@ class TestTargetList: @staticmethod def test_includes_targets( + *, vws_client: VWS, target_id: str, ) -> None: @@ -18,6 +19,7 @@ def test_includes_targets( @staticmethod def test_deleted( + *, vws_client: VWS, target_id: str, ) -> None: diff --git a/tests/mock_vws/test_target_summary.py b/tests/mock_vws/test_target_summary.py index a5621d2cf..6f1b7b0d7 100644 --- a/tests/mock_vws/test_target_summary.py +++ b/tests/mock_vws/test_target_summary.py @@ -20,10 +20,10 @@ class TestTargetSummary: @staticmethod @pytest.mark.parametrize(argnames="active_flag", argvalues=[True, False]) def test_target_summary( + *, vws_client: VWS, vuforia_database: CloudDatabase, image_file_failed_state: io.BytesIO, - *, active_flag: bool, ) -> None: """A target summary is returned.""" @@ -70,6 +70,7 @@ def test_target_summary( ], ) def test_after_processing( + *, vws_client: VWS, request: pytest.FixtureRequest, image_fixture_name: str, @@ -121,6 +122,7 @@ class TestRecognitionCounts: @staticmethod def test_recognition( + *, vws_client: VWS, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, diff --git a/tests/mock_vws/test_update_target.py b/tests/mock_vws/test_update_target.py index 1dddaffcc..20245ba1a 100644 --- a/tests/mock_vws/test_update_target.py +++ b/tests/mock_vws/test_update_target.py @@ -77,6 +77,7 @@ class TestUpdate: ids=["Documented Content-Type", "Undocumented Content-Type"], ) def test_content_types( + *, vws_client: VWS, image_file_failed_state: io.BytesIO, content_type: str, @@ -113,6 +114,7 @@ def test_content_types( @staticmethod def test_empty_content_type( + *, vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: @@ -147,6 +149,7 @@ def test_empty_content_type( @staticmethod def test_no_fields_given( + *, vws_client: VWS, target_id: str, ) -> None: @@ -185,6 +188,7 @@ class TestUnexpectedData: @staticmethod def test_invalid_extra_data( + *, vws_client: VWS, target_id: str, ) -> None: @@ -219,6 +223,7 @@ class TestWidth: ids=["Negative", "Wrong Type", "None", "Zero"], ) def test_width_invalid( + *, vws_client: VWS, width: int | str | None, target_id: str, @@ -246,7 +251,7 @@ def test_width_invalid( assert target_details.target_record.width == original_width @staticmethod - def test_width_valid(vws_client: VWS, target_id: str) -> None: + def test_width_valid(*, vws_client: VWS, target_id: str) -> None: """Positive numbers are valid widths.""" vws_client.wait_for_target_processed(target_id=target_id) @@ -270,9 +275,9 @@ class TestActiveFlag: argvalues=[True, False], ) def test_active_flag( + *, vws_client: VWS, image_file_success_state_low_rating: io.BytesIO, - *, initial_active_flag: bool, desired_active_flag: bool, ) -> None: @@ -300,6 +305,7 @@ def test_active_flag( argvalues=["string", None], ) def test_invalid( + *, vws_client: VWS, target_id: str, desired_active_flag: str | None, @@ -338,6 +344,7 @@ class TestApplicationMetadata: ids=["Short", "Max length"], ) def test_base64_encoded( + *, target_id: str, metadata: bytes, vws_client: VWS, @@ -355,6 +362,7 @@ def test_base64_encoded( @staticmethod @pytest.mark.parametrize(argnames="invalid_metadata", argvalues=[1, None]) def test_invalid_type( + *, vws_client: VWS, target_id: str, invalid_metadata: int | None, @@ -377,6 +385,7 @@ def test_invalid_type( @staticmethod def test_not_base64_encoded_processable( + *, vws_client: VWS, target_id: str, not_base64_encoded_processable: str, @@ -395,6 +404,7 @@ def test_not_base64_encoded_processable( @staticmethod def test_not_base64_encoded_not_processable( + *, vws_client: VWS, target_id: str, not_base64_encoded_not_processable: str, @@ -419,7 +429,7 @@ def test_not_base64_encoded_not_processable( ) @staticmethod - def test_metadata_too_large(vws_client: VWS, target_id: str) -> None: + def test_metadata_too_large(*, vws_client: VWS, target_id: str) -> None: """ A base64 encoded string of greater than 1024 * 1024 bytes is too large @@ -465,6 +475,7 @@ class TestTargetName: ids=["Short name", "Max char value", "Long name"], ) def test_name_valid( + *, name: str, target_id: str, vws_client: VWS, @@ -512,6 +523,7 @@ def test_name_valid( ], ) def test_name_invalid( + *, name: str | int | None, target_id: str, vws_client: VWS, @@ -536,6 +548,7 @@ def test_name_invalid( @staticmethod def test_existing_target_name( + *, image_file_success_state_low_rating: io.BytesIO, vws_client: VWS, ) -> None: @@ -576,6 +589,7 @@ def test_existing_target_name( @staticmethod def test_same_name_given( + *, image_file_success_state_low_rating: io.BytesIO, vws_client: VWS, ) -> None: @@ -606,6 +620,7 @@ class TestImage: @staticmethod def test_image_valid( + *, image_files_failed_state: io.BytesIO, target_id: str, vws_client: VWS, @@ -623,6 +638,7 @@ def test_image_valid( @staticmethod def test_bad_image_format_or_color_space( + *, bad_image_file: io.BytesIO, target_id: str, vws_client: VWS, @@ -643,6 +659,7 @@ def test_bad_image_format_or_color_space( @staticmethod def test_corrupted( + *, vws_client: VWS, corrupted_image_file: io.BytesIO, target_id: str, @@ -662,7 +679,7 @@ def test_corrupted( ) @staticmethod - def test_image_too_large(target_id: str, vws_client: VWS) -> None: + def test_image_too_large(*, target_id: str, vws_client: VWS) -> None: """ An `ImageTooLargeError` result is returned if the image is above a @@ -723,6 +740,7 @@ def test_image_too_large(target_id: str, vws_client: VWS) -> None: @staticmethod def test_not_base64_encoded_processable( + *, vws_client: VWS, target_id: str, not_base64_encoded_processable: str, @@ -751,6 +769,7 @@ def test_not_base64_encoded_processable( @staticmethod def test_not_base64_encoded_not_processable( + *, vws_client: VWS, target_id: str, not_base64_encoded_not_processable: str, @@ -777,7 +796,7 @@ def test_not_base64_encoded_not_processable( ) @staticmethod - def test_not_image(target_id: str, vws_client: VWS) -> None: + def test_not_image(*, target_id: str, vws_client: VWS) -> None: """ If the given image is not an image file then a `BadImageError` result @@ -803,6 +822,7 @@ def test_not_image(target_id: str, vws_client: VWS) -> None: argvalues=[1, None], ) def test_invalid_type( + *, invalid_type_image: int | None, target_id: str, vws_client: VWS, @@ -825,6 +845,7 @@ def test_invalid_type( @staticmethod def test_rating_can_change( + *, image_file_success_state_low_rating: io.BytesIO, high_quality_image: io.BytesIO, vws_client: VWS, diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 8705713ce..d78ab76d1 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -103,6 +103,7 @@ class TestGenerateInstance: ) @staticmethod def test_generate_instance_format( + *, accept: VuMarkAccept, expected_signature: bytes, vumark_vuforia_database: VuMarkCloudDatabase, @@ -131,6 +132,7 @@ def test_generate_instance_format( ) @staticmethod def test_generate_instance_content_type_header( + *, accept: str, expected_content_type: str, vumark_vuforia_database: VuMarkCloudDatabase, From 5922dbaf231660dda76c22c7b29507993cace5cb Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 01:03:38 +0000 Subject: [PATCH 2318/2467] Enforce keyword-only arguments in test helper (#2987) Add `*` to `_wait_for_target_processed` to require keyword-only arguments, preventing accidental positional usage. Co-authored-by: Cursor --- tests/mock_vws/fixtures/prepared_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mock_vws/fixtures/prepared_requests.py b/tests/mock_vws/fixtures/prepared_requests.py index 52dd0880f..b800ed39b 100644 --- a/tests/mock_vws/fixtures/prepared_requests.py +++ b/tests/mock_vws/fixtures/prepared_requests.py @@ -23,7 +23,7 @@ @RETRY_ON_TOO_MANY_REQUESTS -def _wait_for_target_processed(vws_client: VWS, target_id: str) -> None: +def _wait_for_target_processed(*, vws_client: VWS, target_id: str) -> None: """Wait for a target to be processed. We retry here because pytest-retry does not retry on exceptions From d7f76740a851f3f8688edc61be3922dd0b433de5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 01:07:17 +0000 Subject: [PATCH 2319/2467] Add @beartype to test helper functions (#2988) Add runtime type checking to non-fixture test helpers that were missing it: processing_time_seconds, make_image_file, Endpoint.send, assert_vwq_failure, _wait_for_target_processed, _delete_all_targets. Co-authored-by: Cursor --- tests/mock_vws/fixtures/prepared_requests.py | 2 ++ tests/mock_vws/fixtures/vuforia_backends.py | 1 + tests/mock_vws/utils/__init__.py | 3 +++ tests/mock_vws/utils/assertions.py | 1 + tests/mock_vws/utils/usage_test_helpers.py | 2 ++ 5 files changed, 9 insertions(+) diff --git a/tests/mock_vws/fixtures/prepared_requests.py b/tests/mock_vws/fixtures/prepared_requests.py index b800ed39b..4bd4ec33e 100644 --- a/tests/mock_vws/fixtures/prepared_requests.py +++ b/tests/mock_vws/fixtures/prepared_requests.py @@ -8,6 +8,7 @@ from uuid import uuid4 import pytest +from beartype import beartype from urllib3.filepost import encode_multipart_formdata from vws import VWS from vws_auth_tools import authorization_header, rfc_1123_date @@ -22,6 +23,7 @@ VWQ_HOST = "https://cloudreco.vuforia.com" +@beartype @RETRY_ON_TOO_MANY_REQUESTS def _wait_for_target_processed(*, vws_client: VWS, target_id: str) -> None: """Wait for a target to be processed. diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 6f5ea90ba..d975546ac 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -29,6 +29,7 @@ LOGGER.setLevel(level=logging.DEBUG) +@beartype @RETRY_ON_TOO_MANY_REQUESTS def _delete_all_targets(*, database_keys: CloudDatabase) -> None: """Delete all targets. diff --git a/tests/mock_vws/utils/__init__.py b/tests/mock_vws/utils/__init__.py index de18b5bdb..c5decad57 100644 --- a/tests/mock_vws/utils/__init__.py +++ b/tests/mock_vws/utils/__init__.py @@ -8,6 +8,7 @@ from urllib.parse import urljoin import requests +from beartype import beartype from PIL import Image from requests.structures import CaseInsensitiveDict from vws.response import Response @@ -52,6 +53,7 @@ class Endpoint: access_key: str secret_key: str + @beartype def send(self) -> Response: """Send the request.""" request = requests.Request( @@ -81,6 +83,7 @@ def auth_header_content_type(self) -> str: return full_content_type.split(sep=";")[0] +@beartype def make_image_file( file_format: str, color_space: Literal["RGB", "CMYK"], diff --git a/tests/mock_vws/utils/assertions.py b/tests/mock_vws/utils/assertions.py index e026fed02..f0d132329 100644 --- a/tests/mock_vws/utils/assertions.py +++ b/tests/mock_vws/utils/assertions.py @@ -219,6 +219,7 @@ def assert_query_success(*, response: Response) -> None: ) +@beartype def assert_vwq_failure( *, response: Response, diff --git a/tests/mock_vws/utils/usage_test_helpers.py b/tests/mock_vws/utils/usage_test_helpers.py index 32d9231e2..708b1b168 100644 --- a/tests/mock_vws/utils/usage_test_helpers.py +++ b/tests/mock_vws/utils/usage_test_helpers.py @@ -3,12 +3,14 @@ import datetime import io +from beartype import beartype from vws import VWS from vws.reports import TargetStatuses from mock_vws.database import CloudDatabase +@beartype def processing_time_seconds( *, vuforia_database: CloudDatabase, From bcf35234e148034529a7c673b90f877717223558 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 02:37:37 +0000 Subject: [PATCH 2320/2467] Enforce keyword-only arguments in `make_image_file` (#2989) Co-authored-by: Cursor --- tests/mock_vws/utils/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/mock_vws/utils/__init__.py b/tests/mock_vws/utils/__init__.py index c5decad57..08764a520 100644 --- a/tests/mock_vws/utils/__init__.py +++ b/tests/mock_vws/utils/__init__.py @@ -85,6 +85,7 @@ def auth_header_content_type(self) -> str: @beartype def make_image_file( + *, file_format: str, color_space: Literal["RGB", "CMYK"], width: int, From 5d5701b78a9fd6d93f970cc826d103b411253ab2 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 09:56:28 +0000 Subject: [PATCH 2321/2467] Fix PytestAssertRewriteWarning for credentials fixture module (#2992) Move `tests.mock_vws.fixtures.credentials` first in `pytest_plugins` so pytest registers it for assertion rewriting before other fixture modules import it as a side effect. Co-authored-by: Claude Sonnet 4.6 --- tests/conftest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0ce40f5e8..957422d2c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,9 +11,12 @@ from mock_vws.database import CloudDatabase from tests.mock_vws.utils import Endpoint +# `credentials` must be listed before modules that import from it. +# If listed later, those imports happen before pytest can register it for +# assertion rewriting, causing a PytestAssertRewriteWarning. pytest_plugins = [ - "tests.mock_vws.fixtures.prepared_requests", "tests.mock_vws.fixtures.credentials", + "tests.mock_vws.fixtures.prepared_requests", "tests.mock_vws.fixtures.vuforia_backends", ] From 2d1cfa69592882c1d9c6f4664e47573aa2d08a3b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 09:59:06 +0000 Subject: [PATCH 2322/2467] Add respx support for mocking httpx requests (#2973) * Add respx support for mocking httpx requests Implement MockVWSForHttpx context manager using respx to intercept httpx requests to Vuforia APIs. Reuses existing handler logic by converting httpx.Request to requests.PreparedRequest. Includes 13 tests covering real_http parameter, response delays, custom URLs, and database management. Adds httpx and respx as core dependencies. Co-Authored-By: Claude Haiku 4.5 * Fix mypy type issues in respx implementation * Fix mypy error codes for type ignore comments * Fix ruff linting issue with dict comprehension Add noqa comment to suppress C416 ruff error while keeping dict comprehension to satisfy pyrefly type checking requirements. Co-Authored-By: Claude Haiku 4.5 * Use RequestData instead of PreparedRequest in respx adapter Convert httpx.Request directly to RequestData, removing the PreparedRequest intermediate. This eliminates the requests library dependency from the respx module and removes all type suppression comments (type: ignore, noqa). Co-Authored-By: Claude Opus 4.6 * Fix lint and type issues in respx mock docs * Add documentation for MockVWSForHttpx Document the new httpx/respx mock backend in README, index, getting-started, mock-api-reference, and a new httpx-example.rst file. Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Haiku 4.5 --- README.rst | 24 ++ docs/source/getting-started.rst | 8 + docs/source/httpx-example.rst | 22 ++ docs/source/index.rst | 5 + docs/source/mock-api-reference.rst | 4 + pyproject.toml | 2 + spelling_private_dict.txt | 2 + src/mock_vws/__init__.py | 2 + src/mock_vws/_respx_mock_server/__init__.py | 1 + src/mock_vws/_respx_mock_server/decorators.py | 275 ++++++++++++++ tests/mock_vws/test_respx_mock_usage.py | 350 ++++++++++++++++++ 11 files changed, 695 insertions(+) create mode 100644 docs/source/httpx-example.rst create mode 100644 src/mock_vws/_respx_mock_server/__init__.py create mode 100644 src/mock_vws/_respx_mock_server/decorators.py create mode 100644 tests/mock_vws/test_respx_mock_usage.py diff --git a/README.rst b/README.rst index 483954a59..0226ac3d5 100644 --- a/README.rst +++ b/README.rst @@ -38,6 +38,30 @@ By default, an exception will be raised if any requests to unmocked addresses ar .. _requests: https://pypi.org/project/requests/ +Mocking calls made to Vuforia with Python ``httpx`` +---------------------------------------------------- + +Using the mock redirects requests to Vuforia made with `httpx`_ to an in-memory implementation. + +.. code-block:: python + + """Make a request to the Vuforia Web Services API mock.""" + + import httpx + + from mock_vws import MockVWSForHttpx + from mock_vws.database import CloudDatabase + + with MockVWSForHttpx() as mock: + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + # This will use the Vuforia mock. + httpx.get(url="https://vws.vuforia.com/summary", timeout=30) + +By default, an exception will be raised if any requests to unmocked addresses are made. + +.. _httpx: https://pypi.org/project/httpx/ + Using Docker to mock calls to Vuforia from any language ------------------------------------------------------- diff --git a/docs/source/getting-started.rst b/docs/source/getting-started.rst index f55e3d324..120f0138b 100644 --- a/docs/source/getting-started.rst +++ b/docs/source/getting-started.rst @@ -1,4 +1,12 @@ Getting started --------------- +Mocking calls made to Vuforia with Python ``requests`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + .. include:: basic-example.rst + +Mocking calls made to Vuforia with Python ``httpx`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: httpx-example.rst diff --git a/docs/source/httpx-example.rst b/docs/source/httpx-example.rst new file mode 100644 index 000000000..2d12eb4d7 --- /dev/null +++ b/docs/source/httpx-example.rst @@ -0,0 +1,22 @@ +Using the mock redirects requests to Vuforia made with `httpx`_ to an in-memory implementation. + +.. code-block:: python + + """Make a request to the Vuforia Web Services API mock.""" + + import httpx + + from mock_vws import MockVWSForHttpx + from mock_vws.database import CloudDatabase + + with MockVWSForHttpx() as mock: + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + # This will use the Vuforia mock. + httpx.get(url="https://vws.vuforia.com/summary", timeout=30) + +By default, an exception will be raised if any requests to unmocked addresses are made. + +See :ref:`mock-api-reference` for details of what can be changed and how. + +.. _httpx: https://pypi.org/project/httpx/ diff --git a/docs/source/index.rst b/docs/source/index.rst index 6f39583a2..22c386d5d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -12,6 +12,11 @@ This requires Python |minimum-python-version|\+. .. include:: basic-example.rst +Mocking calls made to Vuforia with Python ``httpx`` +---------------------------------------------------- + +.. include:: httpx-example.rst + Using Docker to mock calls to Vuforia from any language ------------------------------------------------------- diff --git a/docs/source/mock-api-reference.rst b/docs/source/mock-api-reference.rst index 1b2ea255a..f44d65f1d 100644 --- a/docs/source/mock-api-reference.rst +++ b/docs/source/mock-api-reference.rst @@ -7,6 +7,10 @@ API Reference :members: :undoc-members: +.. autoclass:: mock_vws.MockVWSForHttpx + :members: + :undoc-members: + .. autoclass:: mock_vws.MissingSchemeError :members: :undoc-members: diff --git a/pyproject.toml b/pyproject.toml index db6c4e83d..5fb0b1a4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,12 +36,14 @@ dynamic = [ dependencies = [ "beartype>=0.22.9", "flask>=3.0.3", + "httpx>=0.27.0", "numpy>=1.26.4", "pillow>=11.0.0", "piq>=0.8.0", "pydantic-settings>=2.6.1", "requests>=2.32.3", "responses>=0.25.3", + "respx>=0.21.0", "torch>=2.5.1", "torchmetrics>=1.5.1", "torchvision>=0.20.1", diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index 0b053e3bb..b1ad02e08 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -45,6 +45,7 @@ hmac html http https +httpx iff io issuecomment @@ -98,6 +99,7 @@ reqjsonarr resheader resjson resjsonarr +respx rfc rgb str diff --git a/src/mock_vws/__init__.py b/src/mock_vws/__init__.py index d6d5e053a..42d6d5264 100644 --- a/src/mock_vws/__init__.py +++ b/src/mock_vws/__init__.py @@ -4,8 +4,10 @@ MissingSchemeError, MockVWS, ) +from mock_vws._respx_mock_server.decorators import MockVWSForHttpx __all__ = [ "MissingSchemeError", "MockVWS", + "MockVWSForHttpx", ] diff --git a/src/mock_vws/_respx_mock_server/__init__.py b/src/mock_vws/_respx_mock_server/__init__.py new file mode 100644 index 000000000..a5ffb7cab --- /dev/null +++ b/src/mock_vws/_respx_mock_server/__init__.py @@ -0,0 +1 @@ +"""A fake implementation of Vuforia Web Services for use with respx.""" diff --git a/src/mock_vws/_respx_mock_server/decorators.py b/src/mock_vws/_respx_mock_server/decorators.py new file mode 100644 index 000000000..3b4b58560 --- /dev/null +++ b/src/mock_vws/_respx_mock_server/decorators.py @@ -0,0 +1,275 @@ +"""Decorators for using the mock with httpx via respx.""" + +import re +import time +from collections.abc import Callable, Mapping +from contextlib import ContextDecorator +from typing import Literal, Self +from urllib.parse import urljoin, urlparse + +import httpx +import respx +from beartype import BeartypeConf, beartype + +from mock_vws._mock_common import RequestData +from mock_vws._requests_mock_server.decorators import MissingSchemeError +from mock_vws._requests_mock_server.mock_web_query_api import ( + MockVuforiaWebQueryAPI, +) +from mock_vws._requests_mock_server.mock_web_services_api import ( + MockVuforiaWebServicesAPI, +) +from mock_vws.database import CloudDatabase, VuMarkDatabase +from mock_vws.image_matchers import ( + ImageMatcher, + StructuralSimilarityMatcher, +) +from mock_vws.target_manager import TargetManager +from mock_vws.target_raters import ( + BrisqueTargetTrackingRater, + TargetTrackingRater, +) + +_ResponseType = tuple[int, Mapping[str, str], str | bytes] + +_STRUCTURAL_SIMILARITY_MATCHER = StructuralSimilarityMatcher() +_BRISQUE_TRACKING_RATER = BrisqueTargetTrackingRater() + + +def _to_request_data(request: httpx.Request) -> RequestData: + """Convert an httpx.Request to a RequestData. + + Args: + request: The httpx request to convert. + + Returns: + A RequestData with method, path, headers, and body set. + """ + return RequestData( + method=request.method, + path=request.url.raw_path.decode(encoding="ascii"), + headers=request.headers, + body=request.content, + ) + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +class MockVWSForHttpx(ContextDecorator): + """Route httpx requests to Vuforia's Web Service APIs to fakes of those + APIs. + """ + + def __init__( + self, + *, + base_vws_url: str = "https://vws.vuforia.com", + base_vwq_url: str = "https://cloudreco.vuforia.com", + duplicate_match_checker: ImageMatcher = _STRUCTURAL_SIMILARITY_MATCHER, + query_match_checker: ImageMatcher = _STRUCTURAL_SIMILARITY_MATCHER, + processing_time_seconds: float = 2.0, + target_tracking_rater: TargetTrackingRater = _BRISQUE_TRACKING_RATER, + real_http: bool = False, + response_delay_seconds: float = 0.0, + sleep_fn: Callable[[float], None] = time.sleep, + ) -> None: + """Route httpx requests to Vuforia's Web Service APIs to fakes of + those APIs. + + Args: + real_http: Whether or not to forward requests to the real + server if they are not handled by the mock. + processing_time_seconds: The number of seconds to process each + image for. + In the real Vuforia Web Services, this is not deterministic. + base_vwq_url: The base URL for the VWQ API. + base_vws_url: The base URL for the VWS API. + query_match_checker: A callable which takes two image values and + returns whether they will match in a query request. + duplicate_match_checker: A callable which takes two image values + and returns whether they are duplicates. + target_tracking_rater: A callable for rating targets for tracking. + response_delay_seconds: The number of seconds to delay each + response by. This can be used to test timeout handling. + sleep_fn: The function to use for sleeping during response + delays. Defaults to ``time.sleep``. Inject a custom + function to control virtual time in tests without + monkey-patching. + + Raises: + MissingSchemeError: There is no scheme in a given URL. + """ + super().__init__() + self._real_http = real_http + self._response_delay_seconds = response_delay_seconds + self._sleep_fn = sleep_fn + self._router: respx.MockRouter + self._target_manager = TargetManager() + + self._base_vws_url = base_vws_url + self._base_vwq_url = base_vwq_url + for url in (base_vwq_url, base_vws_url): + parse_result = urlparse(url=url) + if not parse_result.scheme: + raise MissingSchemeError(url=url) + + self._mock_vws_api = MockVuforiaWebServicesAPI( + target_manager=self._target_manager, + processing_time_seconds=float(processing_time_seconds), + duplicate_match_checker=duplicate_match_checker, + target_tracking_rater=target_tracking_rater, + ) + + self._mock_vwq_api = MockVuforiaWebQueryAPI( + target_manager=self._target_manager, + query_match_checker=query_match_checker, + ) + + def add_cloud_database(self, cloud_database: CloudDatabase) -> None: + """Add a cloud database. + + Args: + cloud_database: The cloud database to add. + + Raises: + ValueError: One of the given cloud database keys matches a key for + an existing cloud database. + """ + self._target_manager.add_cloud_database( + cloud_database=cloud_database, + ) + + def add_vumark_database(self, vumark_database: VuMarkDatabase) -> None: + """Add a VuMark database. + + Args: + vumark_database: The VuMark database to add. + + Raises: + ValueError: One of the given database keys matches a key for + an existing database. + """ + self._target_manager.add_vumark_database( + vumark_database=vumark_database, + ) + + def _make_callback( + self, + handler: Callable[[RequestData], _ResponseType], + ) -> Callable[[httpx.Request], httpx.Response]: + """Create a respx-compatible callback from a handler. + + Args: + handler: A handler that takes a RequestData and returns a + response tuple. + + Returns: + A callback that takes an httpx.Request and returns an + httpx.Response. + """ + delay_seconds = self._response_delay_seconds + sleep_fn = self._sleep_fn + + def callback(request: httpx.Request) -> httpx.Response: + """Handle an httpx request by converting it and calling the + handler. + + Args: + request: The httpx request to handle. + + Returns: + An httpx.Response built from the handler's return value. + + Raises: + Exception: A timeout error is raised when the response + delay exceeds the read timeout. + """ + request_data = _to_request_data(request=request) + timeout_info: dict[str, float | None] = request.extensions.get( + "timeout", {} + ) + read_timeout = timeout_info.get("read") + if read_timeout is not None and delay_seconds > read_timeout: + sleep_fn(read_timeout) + raise httpx.ReadTimeout( + message="Response delay exceeded read timeout", + request=request, + ) + status_code, headers, body = handler(request_data) + sleep_fn(delay_seconds) + if isinstance(body, str): + body = body.encode() + return httpx.Response( + status_code=status_code, + headers=headers, + content=body, + ) + + return callback + + @staticmethod + def _block_unmatched(request: httpx.Request) -> httpx.Response: + """Raise ConnectError for unmatched requests when real_http=False. + + Args: + request: The unmatched httpx request. + + Raises: + Exception: A connection error is always raised to block + unmatched requests. + """ + raise httpx.ConnectError( + message="Connection refused by mock", + request=request, + ) + + def __enter__(self) -> Self: + """Start an instance of a Vuforia mock. + + Returns: + ``self``. + """ + router = respx.MockRouter( + assert_all_called=False, + assert_all_mocked=False, + ) + + for api, base_url in ( + (self._mock_vws_api, self._base_vws_url), + (self._mock_vwq_api, self._base_vwq_url), + ): + for route in api.routes: + url_pattern = urljoin( + base=base_url, + url=f"{route.path_pattern}$", + ) + compiled_url_pattern = re.compile(pattern=url_pattern) + + for http_method in route.http_methods: + original_callback = getattr(api, route.route_name) + router.route( + method=http_method, + url=compiled_url_pattern, + ).mock( + side_effect=self._make_callback( + handler=original_callback, + ), + ) + + if self._real_http: + router.route().pass_through() + else: + router.route().mock(side_effect=self._block_unmatched) + + router.start() + self._router = router + return self + + def __exit__(self, *exc: object) -> Literal[False]: + """Stop the Vuforia mock. + + Returns: + False + """ + del exc + self._router.stop() + return False diff --git a/tests/mock_vws/test_respx_mock_usage.py b/tests/mock_vws/test_respx_mock_usage.py new file mode 100644 index 000000000..1c408e4fe --- /dev/null +++ b/tests/mock_vws/test_respx_mock_usage.py @@ -0,0 +1,350 @@ +"""Tests for the usage of the mock for ``httpx`` via ``respx``.""" + +import socket + +import httpx +import pytest +from vws_auth_tools import rfc_1123_date + +from mock_vws import MissingSchemeError, MockVWSForHttpx +from mock_vws.database import CloudDatabase, VuMarkDatabase + + +def _request_unmocked_address() -> None: + """Make a request using ``httpx`` to an unmocked, free local address. + + Raises: + Exception: A connection error is expected, as there is nothing + to connect to. + """ + sock = socket.socket() + sock.bind(("", 0)) + port = sock.getsockname()[1] + sock.close() + httpx.get(url=f"http://localhost:{port}", timeout=30) + + +def _request_mocked_address() -> None: + """Make a request using ``httpx`` to a mocked Vuforia endpoint.""" + httpx.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": rfc_1123_date(), + "Authorization": "bad_auth_token", + }, + timeout=30, + ) + + +class TestRealHTTP: + """Tests for making requests to mocked and unmocked addresses.""" + + @staticmethod + def test_default() -> None: + """By default, the mock stops any requests made with ``httpx`` to + non-Vuforia addresses, but not to mocked Vuforia endpoints. + """ + with MockVWSForHttpx(): + with pytest.raises(expected_exception=httpx.ConnectError): + _request_unmocked_address() + + # No exception is raised when making a request to a mocked + # endpoint. + _request_mocked_address() + + # The mocking stops when the context manager stops. + with pytest.raises(expected_exception=httpx.ConnectError): + _request_unmocked_address() + + @staticmethod + def test_real_http() -> None: + """When the ``real_http`` parameter is ``True``, requests to + unmocked + addresses are not stopped. + """ + with ( + MockVWSForHttpx(real_http=True), + pytest.raises(expected_exception=httpx.ConnectError), + ): + _request_unmocked_address() + + +class TestResponseDelay: + """Tests for the response delay feature.""" + + @staticmethod + def test_default_no_delay() -> None: + """By default, there is no response delay.""" + with MockVWSForHttpx(): + response = httpx.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": rfc_1123_date(), + "Authorization": "bad_auth_token", + }, + timeout=0.5, + ) + assert response.status_code is not None + + @staticmethod + def test_delay_causes_timeout() -> None: + """When ``response_delay_seconds`` is set higher than the client + timeout, a ``ReadTimeout`` exception is raised. + """ + with ( + MockVWSForHttpx(response_delay_seconds=0.5), + pytest.raises(expected_exception=httpx.ReadTimeout), + ): + httpx.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": rfc_1123_date(), + "Authorization": "bad_auth_token", + }, + timeout=0.1, + ) + + @staticmethod + def test_delay_allows_completion() -> None: + """When ``response_delay_seconds`` is set lower than the client + timeout, the request completes successfully. + """ + with MockVWSForHttpx(response_delay_seconds=0.1): + response = httpx.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": rfc_1123_date(), + "Authorization": "bad_auth_token", + }, + timeout=2.0, + ) + assert response.status_code is not None + + @staticmethod + def test_custom_sleep_fn_called_on_delay() -> None: + """When a custom ``sleep_fn`` is provided, it is called instead of + ``time.sleep`` for the non-timeout delay path. + """ + calls: list[float] = [] + with MockVWSForHttpx( + response_delay_seconds=5.0, + sleep_fn=calls.append, + ): + httpx.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": rfc_1123_date(), + "Authorization": "bad_auth_token", + }, + timeout=30, + ) + assert calls == [5.0] + + @staticmethod + def test_custom_sleep_fn_called_on_timeout() -> None: + """When a custom ``sleep_fn`` is provided, it is called with the + effective timeout when the delay exceeds it. + """ + calls: list[float] = [] + with ( + MockVWSForHttpx( + response_delay_seconds=5.0, + sleep_fn=calls.append, + ), + pytest.raises(expected_exception=httpx.ReadTimeout), + ): + httpx.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": rfc_1123_date(), + "Authorization": "bad_auth_token", + }, + timeout=1.0, + ) + assert calls == [1.0] + + +class TestCustomBaseURLs: + """Tests for using custom base URLs.""" + + @staticmethod + def test_custom_base_vws_url() -> None: + """It is possible to use a custom base VWS URL.""" + with MockVWSForHttpx( + base_vws_url="https://vuforia.vws.example.com", + real_http=False, + ): + with pytest.raises(expected_exception=httpx.ConnectError): + httpx.get(url="https://vws.vuforia.com/summary", timeout=30) + + httpx.get( + url="https://vuforia.vws.example.com/summary", + timeout=30, + ) + httpx.post( + url="https://cloudreco.vuforia.com/v1/query", + timeout=30, + ) + + @staticmethod + def test_custom_base_vwq_url() -> None: + """It is possible to use a custom base cloud recognition URL.""" + with MockVWSForHttpx( + base_vwq_url="https://vuforia.vwq.example.com", + real_http=False, + ): + with pytest.raises(expected_exception=httpx.ConnectError): + httpx.post( + url="https://cloudreco.vuforia.com/v1/query", + timeout=30, + ) + + httpx.post( + url="https://vuforia.vwq.example.com/v1/query", + timeout=30, + ) + httpx.get( + url="https://vws.vuforia.com/summary", + timeout=30, + ) + + @staticmethod + def test_no_scheme() -> None: + """An error is raised if a URL is given with no scheme.""" + with pytest.raises(expected_exception=MissingSchemeError) as vws_exc: + MockVWSForHttpx(base_vws_url="vuforia.vws.example.com") + + expected = ( + 'Invalid URL "vuforia.vws.example.com": No scheme supplied. ' + 'Perhaps you meant "https://vuforia.vws.example.com".' + ) + assert str(object=vws_exc.value) == expected + with pytest.raises(expected_exception=MissingSchemeError) as vwq_exc: + MockVWSForHttpx(base_vwq_url="vuforia.vwq.example.com") + expected = ( + 'Invalid URL "vuforia.vwq.example.com": No scheme supplied. ' + 'Perhaps you meant "https://vuforia.vwq.example.com".' + ) + assert str(object=vwq_exc.value) == expected + + +class TestAddDatabase: + """Tests for adding databases to the mock.""" + + @staticmethod + def test_duplicate_keys() -> None: + """It is not possible to have multiple databases with matching + keys. + """ + database = CloudDatabase( + server_access_key="1", + server_secret_key="2", + client_access_key="3", + client_secret_key="4", + database_name="5", + ) + + bad_server_access_key_db = CloudDatabase(server_access_key="1") + bad_server_secret_key_db = CloudDatabase(server_secret_key="2") + bad_client_access_key_db = CloudDatabase(client_access_key="3") + bad_client_secret_key_db = CloudDatabase(client_secret_key="4") + bad_database_name_db = CloudDatabase(database_name="5") + + server_access_key_conflict_error = ( + "All server access keys must be unique. " + 'There is already a database with the server access key "1".' + ) + server_secret_key_conflict_error = ( + "All server secret keys must be unique. " + 'There is already a database with the server secret key "2".' + ) + client_access_key_conflict_error = ( + "All client access keys must be unique. " + 'There is already a database with the client access key "3".' + ) + client_secret_key_conflict_error = ( + "All client secret keys must be unique. " + 'There is already a database with the client secret key "4".' + ) + database_name_conflict_error = ( + "All names must be unique. " + 'There is already a database with the name "5".' + ) + + with MockVWSForHttpx() as mock: + mock.add_cloud_database(cloud_database=database) + for bad_database, expected_message in ( + (bad_server_access_key_db, server_access_key_conflict_error), + (bad_server_secret_key_db, server_secret_key_conflict_error), + (bad_client_access_key_db, client_access_key_conflict_error), + (bad_client_secret_key_db, client_secret_key_conflict_error), + (bad_database_name_db, database_name_conflict_error), + ): + with pytest.raises( + expected_exception=ValueError, + match=expected_message + "$", + ): + mock.add_cloud_database(cloud_database=bad_database) + + @staticmethod + def test_duplicate_vumark_keys() -> None: + """It is not possible to have multiple databases with matching + keys, + including VuMark databases. + """ + database = VuMarkDatabase( + server_access_key="1", + server_secret_key="2", + database_name="3", + ) + + bad_server_access_key_db = VuMarkDatabase(server_access_key="1") + bad_server_secret_key_db = VuMarkDatabase(server_secret_key="2") + bad_database_name_db = VuMarkDatabase(database_name="3") + + server_access_key_conflict_error = ( + "All server access keys must be unique. " + 'There is already a database with the server access key "1".' + ) + server_secret_key_conflict_error = ( + "All server secret keys must be unique. " + 'There is already a database with the server secret key "2".' + ) + database_name_conflict_error = ( + "All names must be unique. " + 'There is already a database with the name "3".' + ) + + with MockVWSForHttpx() as mock: + mock.add_vumark_database(vumark_database=database) + for bad_database, expected_message in ( + (bad_server_access_key_db, server_access_key_conflict_error), + (bad_server_secret_key_db, server_secret_key_conflict_error), + (bad_database_name_db, database_name_conflict_error), + ): + with pytest.raises( + expected_exception=ValueError, + match=expected_message + "$", + ): + mock.add_vumark_database(vumark_database=bad_database) + + +class TestVWSEndpoints: + """Tests that VWS endpoints are accessible via httpx.""" + + @staticmethod + def test_database_summary() -> None: + """The database summary endpoint is accessible via httpx.""" + database = CloudDatabase() + with MockVWSForHttpx() as mock: + mock.add_cloud_database(cloud_database=database) + response = httpx.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": rfc_1123_date(), + "Authorization": "bad_auth_token", + }, + timeout=30, + ) + # We just verify we get a response (auth will fail but endpoint works) + assert response.status_code is not None From 37c28117275a59c121d6e59feda0c0ae70dd5fa9 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 10:02:38 +0000 Subject: [PATCH 2323/2467] Validate target status for VuMark instance generation (#2991) * Validate target status for VuMark instance generation (#2981) Return TargetStatusNotSuccessError when a VuMark instance generation request targets a VuMark target that is not yet in the success state. Co-authored-by: Cursor * Add tests for VuMark target status validation and serialization Test that generating a VuMark instance for a still-processing target returns TargetStatusNotSuccessError, and that VuMarkTarget/VuMarkDatabase round-trip through to_dict/from_dict with the new timing fields. Co-authored-by: Cursor * Add Flask app test for VuMark target status validation TestVuMarkTargetStatus.test_processing_target_returns_forbidden exercises the Flask server code path (vws.py generate_vumark_instance) with a processing VuMark target, covering the branch that raises TargetStatusNotSuccessError. MockVWS uses the requests-mock server, so this branch was previously uncovered. Co-authored-by: Cursor --------- Co-authored-by: Cursor --- src/mock_vws/_flask_server/vws.py | 7 +- .../mock_web_services_api.py | 5 + src/mock_vws/database.py | 9 ++ src/mock_vws/target.py | 38 +++++++ tests/mock_vws/test_flask_app_usage.py | 82 +++++++++++++- tests/mock_vws/test_requests_mock_usage.py | 37 ++++++- tests/mock_vws/test_vumark_generation_api.py | 101 +++++++++++++++++- 7 files changed, 273 insertions(+), 6 deletions(-) diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index ab1c1cb9b..57942cb20 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -398,9 +398,6 @@ def generate_vumark_instance(target_id: str) -> Response: databases=all_databases, ) - # ``target_id`` is validated by request validators. - del target_id - database = get_database_matching_server_keys( request_headers=dict(request.headers), request_body=request.data, @@ -411,6 +408,10 @@ def generate_vumark_instance(target_id: str) -> Response: if not isinstance(database, VuMarkDatabase): raise InvalidTargetTypeError + target = database.get_vumark_target(target_id=target_id) + if target.status != TargetStatuses.SUCCESS.value: + raise TargetStatusNotSuccessError + accept = request.headers.get(key="Accept", default="") valid_accept_types: dict[str, bytes] = { "image/png": VUMARK_PNG, diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index 671539cdb..50fc7aa95 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -319,6 +319,11 @@ def generate_vumark_instance(self, request: RequestData) -> _ResponseType: if not isinstance(database, VuMarkDatabase): raise InvalidTargetTypeError + target_id = request.path.split(sep="/")[-2] + target = database.get_vumark_target(target_id=target_id) + if target.status != TargetStatuses.SUCCESS.value: + raise TargetStatusNotSuccessError + accept = dict(request.headers).get("Accept", "") if accept not in valid_accept_types: raise InvalidAcceptHeaderError diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py index d4e2389a8..61ec48377 100644 --- a/src/mock_vws/database.py +++ b/src/mock_vws/database.py @@ -203,6 +203,15 @@ class VuMarkDatabase: hash=False, ) + def get_vumark_target(self, target_id: str) -> VuMarkTarget: + """Return a VuMark target from the database with the given ID.""" + (target,) = ( + target + for target in self.vumark_targets + if target.target_id == target_id + ) + return target + def to_dict(self) -> VuMarkDatabaseDict: """Dump a VuMark database to a dictionary which can be loaded as JSON. diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py index 50e9ec034..2e3760e4b 100644 --- a/src/mock_vws/target.py +++ b/src/mock_vws/target.py @@ -24,6 +24,9 @@ class VuMarkTargetDict(TypedDict): target_id: str name: str + processing_time_seconds: float + last_modified_date: str + upload_date: str class ImageTargetDict(TypedDict): @@ -228,14 +231,46 @@ class VuMarkTarget: """ name: str + processing_time_seconds: float = 0.0 target_id: str = field(default_factory=_random_hex) + last_modified_date: datetime.datetime = field(default_factory=_time_now) + upload_date: datetime.datetime = field(default_factory=_time_now) + + @property + def status(self) -> str: + """Return the status of the target. + + VuMark targets always succeed after processing. + """ + processing_time = datetime.timedelta( + seconds=float(self.processing_time_seconds), + ) + + timezone = self.upload_date.tzinfo + now = datetime.datetime.now(tz=timezone) + time_since_change = now - self.last_modified_date + + if time_since_change <= processing_time: + return TargetStatuses.PROCESSING.value + + return TargetStatuses.SUCCESS.value @classmethod def from_dict(cls, target_dict: VuMarkTargetDict) -> Self: """Load a VuMark target from a dictionary.""" + timezone = ZoneInfo(key="GMT") + last_modified_date = datetime.datetime.fromisoformat( + target_dict["last_modified_date"], + ).replace(tzinfo=timezone) + upload_date = datetime.datetime.fromisoformat( + target_dict["upload_date"], + ).replace(tzinfo=timezone) return cls( target_id=target_dict["target_id"], name=target_dict["name"], + processing_time_seconds=target_dict["processing_time_seconds"], + last_modified_date=last_modified_date, + upload_date=upload_date, ) def to_dict(self) -> VuMarkTargetDict: @@ -245,4 +280,7 @@ def to_dict(self) -> VuMarkTargetDict: return { "target_id": self.target_id, "name": self.name, + "processing_time_seconds": float(self.processing_time_seconds), + "last_modified_date": self.last_modified_date.isoformat(), + "upload_date": self.upload_date.isoformat(), } diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index bef5b44ec..6b7f7eee2 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -6,7 +6,7 @@ import time import uuid from collections.abc import Iterator -from http import HTTPStatus +from http import HTTPMethod, HTTPStatus import pytest import requests @@ -14,7 +14,9 @@ from PIL import Image from requests_mock_flask import add_flask_app_to_mock from vws import VWS, CloudRecoService +from vws_auth_tools import authorization_header, rfc_1123_date +from mock_vws._constants import ResultCodes from mock_vws._flask_server.target_manager import ( TARGET_MANAGER, TARGET_MANAGER_FLASK_APP, @@ -22,6 +24,7 @@ from mock_vws._flask_server.vwq import CLOUDRECO_FLASK_APP from mock_vws._flask_server.vws import VWS_FLASK_APP from mock_vws.database import CloudDatabase, VuMarkDatabase +from mock_vws.target import VuMarkTarget from tests.mock_vws.utils.usage_test_helpers import ( processing_time_seconds, ) @@ -709,6 +712,83 @@ def test_random( assert lowest_rating != highest_rating +class TestVuMarkTargetStatus: + """Tests for VuMark instance generation when target status is + validated (Flask app code path). + """ + + @staticmethod + def test_processing_target_returns_forbidden() -> None: + """A VuMark target still processing returns 403 when generating + an instance via the Flask app. + """ + vumark_target = VuMarkTarget( + name="processing-target", + processing_time_seconds=9999, + ) + vumark_database = VuMarkDatabase( + vumark_targets=set(), + ) + + vumark_databases_url = ( + _EXAMPLE_URL_FOR_TARGET_MANAGER + "/vumark_databases" + ) + response = requests.post( + url=vumark_databases_url, + json=vumark_database.to_dict(), + timeout=30, + ) + assert response.status_code == HTTPStatus.CREATED + database_data = json.loads(s=response.text) + + vumark_targets_url = ( + f"{vumark_databases_url}" + f"/{database_data['database_name']}/vumark_targets" + ) + response = requests.post( + url=vumark_targets_url, + json=vumark_target.to_dict(), + timeout=30, + ) + assert response.status_code == HTTPStatus.CREATED + + request_path = f"/targets/{vumark_target.target_id}/instances" + content_type = "application/json" + content = json.dumps( + obj={"instance_id": uuid.uuid4().hex}, + ).encode(encoding="utf-8") + date = rfc_1123_date() + authorization_string = authorization_header( + access_key=vumark_database.server_access_key, + secret_key=vumark_database.server_secret_key, + method=HTTPMethod.POST, + content=content, + content_type=content_type, + date=date, + request_path=request_path, + ) + + response = requests.post( + url="https://vws.vuforia.com" + request_path, + headers={ + "Accept": "image/png", + "Authorization": authorization_string, + "Content-Length": str(object=len(content)), + "Content-Type": content_type, + "Date": date, + }, + data=content, + timeout=30, + ) + + assert response.status_code == HTTPStatus.FORBIDDEN + response_json = response.json() + assert ( + response_json["result_code"] + == ResultCodes.TARGET_STATUS_NOT_SUCCESS.value + ) + + class TestResponseDelay: """Tests for the response delay feature. diff --git a/tests/mock_vws/test_requests_mock_usage.py b/tests/mock_vws/test_requests_mock_usage.py index 87820d6ef..50d714e90 100644 --- a/tests/mock_vws/test_requests_mock_usage.py +++ b/tests/mock_vws/test_requests_mock_usage.py @@ -18,7 +18,7 @@ from mock_vws import MissingSchemeError, MockVWS from mock_vws.database import CloudDatabase, VuMarkDatabase from mock_vws.image_matchers import ExactMatcher, StructuralSimilarityMatcher -from mock_vws.target import ImageTarget +from mock_vws.target import ImageTarget, VuMarkTarget from tests.mock_vws.utils import Endpoint from tests.mock_vws.utils.usage_test_helpers import ( processing_time_seconds, @@ -441,6 +441,22 @@ def test_to_dict_deleted(high_quality_image: io.BytesIO) -> None: new_target = ImageTarget.from_dict(target_dict=target_dict) assert new_target.delete_date == target.delete_date + @staticmethod + def test_vumark_target_to_dict() -> None: + """It is possible to dump a VuMark target to a dictionary and + load it back. + """ + vumark_target = VuMarkTarget( + name="example-vumark", + processing_time_seconds=5.0, + ) + target_dict = vumark_target.to_dict() + + assert json.dumps(obj=target_dict) + + new_target = VuMarkTarget.from_dict(target_dict=target_dict) + assert new_target == vumark_target + class TestDatabaseToDict: """Tests for dumping a database to a dictionary.""" @@ -475,6 +491,25 @@ def test_to_dict(high_quality_image: io.BytesIO) -> None: new_database = CloudDatabase.from_dict(database_dict=database_dict) assert new_database == database + @staticmethod + def test_vumark_database_to_dict() -> None: + """It is possible to dump a VuMark database to a dictionary and + load it back. + """ + vumark_target = VuMarkTarget( + name="example-vumark", + processing_time_seconds=3.0, + ) + database = VuMarkDatabase( + vumark_targets={vumark_target}, + ) + + database_dict = database.to_dict() + assert json.dumps(obj=database_dict) + + new_database = VuMarkDatabase.from_dict(database_dict=database_dict) + assert new_database == database + class TestDateHeader: """Tests for the date header in responses from mock routes.""" diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index d78ab76d1..22e11b6a3 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -11,12 +11,15 @@ from vws.exceptions.vws_exceptions import ( InvalidInstanceIdError, InvalidTargetTypeError, + TargetStatusNotSuccessError, ) from vws.vumark_accept import VuMarkAccept from vws_auth_tools import authorization_header, rfc_1123_date +from mock_vws import MockVWS from mock_vws._constants import ResultCodes -from mock_vws.database import CloudDatabase +from mock_vws.database import CloudDatabase, VuMarkDatabase +from mock_vws.target import VuMarkTarget from tests.mock_vws.fixtures.credentials import VuMarkCloudDatabase from tests.mock_vws.utils import make_image_file @@ -259,3 +262,99 @@ def test_non_vumark_database( response_json["result_code"] == ResultCodes.INVALID_TARGET_TYPE.value ) + + +class TestTargetStatusNotSuccess: + """Tests for VuMark generation when the target is not in success + state. + """ + + @staticmethod + def test_processing_target() -> None: + """A VuMark target still processing returns + TargetStatusNotSuccess. + """ + vumark_target = VuMarkTarget( + name="processing-target", + processing_time_seconds=9999, + ) + vumark_database = VuMarkDatabase( + vumark_targets={vumark_target}, + ) + vumark_client = _make_vumark_service( + server_access_key=vumark_database.server_access_key, + server_secret_key=vumark_database.server_secret_key, + ) + + with MockVWS() as mock: + mock.add_vumark_database(vumark_database=vumark_database) + with pytest.raises( + expected_exception=TargetStatusNotSuccessError, + ) as exc: + vumark_client.generate_vumark_instance( + target_id=vumark_target.target_id, + instance_id=uuid4().hex, + accept=VuMarkAccept.PNG, + ) + + assert exc.value.response.status_code == HTTPStatus.FORBIDDEN + response_json = json.loads(s=exc.value.response.text) + assert ( + response_json["result_code"] + == ResultCodes.TARGET_STATUS_NOT_SUCCESS.value + ) + + @staticmethod + def test_processing_target_raw_response() -> None: + """The raw HTTP response for a processing target has the expected + status code and result code. + """ + vumark_target = VuMarkTarget( + name="processing-target", + processing_time_seconds=9999, + ) + vumark_database = VuMarkDatabase( + vumark_targets={vumark_target}, + ) + + with MockVWS() as mock: + mock.add_vumark_database(vumark_database=vumark_database) + response = _make_vumark_request( + server_access_key=vumark_database.server_access_key, + server_secret_key=vumark_database.server_secret_key, + target_id=vumark_target.target_id, + instance_id=uuid4().hex, + accept="image/png", + ) + + assert response.status_code == HTTPStatus.FORBIDDEN + response_json = response.json() + assert ( + response_json["result_code"] + == ResultCodes.TARGET_STATUS_NOT_SUCCESS.value + ) + + @staticmethod + def test_successful_target() -> None: + """A VuMark target that has finished processing succeeds.""" + vumark_target = VuMarkTarget( + name="ready-target", + processing_time_seconds=0, + ) + vumark_database = VuMarkDatabase( + vumark_targets={vumark_target}, + ) + vumark_client = _make_vumark_service( + server_access_key=vumark_database.server_access_key, + server_secret_key=vumark_database.server_secret_key, + ) + + with MockVWS() as mock: + mock.add_vumark_database(vumark_database=vumark_database) + vumark_bytes = vumark_client.generate_vumark_instance( + target_id=vumark_target.target_id, + instance_id=uuid4().hex, + accept=VuMarkAccept.PNG, + ) + + assert vumark_bytes.strip().startswith(_PNG_SIGNATURE) From 606c81d78a17c5ac5b44dc50bd27b0443f5413b1 Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sun, 22 Feb 2026 10:03:58 +0000 Subject: [PATCH 2324/2467] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 517e311d7..bae41cee7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.22 +---------- + + 2026.02.21 ---------- From eaea57aeb0ad63c6a5cc8569c39d9b4cbebed4f4 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 11:55:13 +0000 Subject: [PATCH 2325/2467] Fix urljoin bug with base URL path prefixes (#2994) * Fix urljoin bug with base URL path prefixes Replace urljoin with string concatenation in URL pattern construction to preserve path prefixes in base URLs. This fixes the issue where MockVWS(base_vws_url="http://localhost/prefix") would incorrectly register handlers at http://localhost/targets instead of http://localhost/prefix/targets. Add tests for path prefix handling in both requests_mock and respx implementations. Co-Authored-By: Claude Haiku 4.5 * Fix bytes branch never reached in respx callback Normalize httpx header keys to title case in _to_request_data so that validators can find headers like Authorization and Content-Type, which httpx stores as lowercase. This enables properly-authenticated requests through the respx mock. Add test_vumark_bytes_response to exercise the bytes response path in the respx callback, which is only reachable via the vumark endpoint with valid authentication. Co-Authored-By: Claude Sonnet 4.6 * Fix pylint spelling: vumark -> VuMark in docstring Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Haiku 4.5 --- .../_requests_mock_server/decorators.py | 7 +- src/mock_vws/_respx_mock_server/decorators.py | 9 +- tests/mock_vws/test_requests_mock_usage.py | 44 ++++++++++ tests/mock_vws/test_respx_mock_usage.py | 83 ++++++++++++++++++- 4 files changed, 131 insertions(+), 12 deletions(-) diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 7cea0e976..007cc2fce 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -5,7 +5,7 @@ from collections.abc import Callable, Mapping from contextlib import ContextDecorator from typing import Any, Literal, Self -from urllib.parse import urljoin, urlparse +from urllib.parse import urlparse import requests from beartype import BeartypeConf, beartype @@ -222,10 +222,7 @@ def __enter__(self) -> Self: (self._mock_vwq_api, self._base_vwq_url), ): for route in api.routes: - url_pattern = urljoin( - base=base_url, - url=f"{route.path_pattern}$", - ) + url_pattern = base_url.rstrip("/") + route.path_pattern + "$" compiled_url_pattern = re.compile(pattern=url_pattern) for http_method in route.http_methods: diff --git a/src/mock_vws/_respx_mock_server/decorators.py b/src/mock_vws/_respx_mock_server/decorators.py index 3b4b58560..7ef03ccd4 100644 --- a/src/mock_vws/_respx_mock_server/decorators.py +++ b/src/mock_vws/_respx_mock_server/decorators.py @@ -5,7 +5,7 @@ from collections.abc import Callable, Mapping from contextlib import ContextDecorator from typing import Literal, Self -from urllib.parse import urljoin, urlparse +from urllib.parse import urlparse import httpx import respx @@ -48,7 +48,7 @@ def _to_request_data(request: httpx.Request) -> RequestData: return RequestData( method=request.method, path=request.url.raw_path.decode(encoding="ascii"), - headers=request.headers, + headers={k.title(): v for k, v in request.headers.items()}, body=request.content, ) @@ -238,10 +238,7 @@ def __enter__(self) -> Self: (self._mock_vwq_api, self._base_vwq_url), ): for route in api.routes: - url_pattern = urljoin( - base=base_url, - url=f"{route.path_pattern}$", - ) + url_pattern = base_url.rstrip("/") + route.path_pattern + "$" compiled_url_pattern = re.compile(pattern=url_pattern) for http_method in route.http_methods: diff --git a/tests/mock_vws/test_requests_mock_usage.py b/tests/mock_vws/test_requests_mock_usage.py index 50d714e90..79c3d9288 100644 --- a/tests/mock_vws/test_requests_mock_usage.py +++ b/tests/mock_vws/test_requests_mock_usage.py @@ -347,6 +347,50 @@ def test_custom_base_vwq_url() -> None: timeout=30, ) + @staticmethod + def test_custom_base_vws_url_with_path_prefix() -> None: + """A custom base VWS URL with a path prefix intercepts at the + prefix. + """ + with MockVWS( + base_vws_url="https://vuforia.vws.example.com/prefix", + real_http=False, + ): + with pytest.raises( + expected_exception=requests.exceptions.ConnectionError + ): + requests.get( + url="https://vuforia.vws.example.com/summary", + timeout=30, + ) + + requests.get( + url="https://vuforia.vws.example.com/prefix/summary", + timeout=30, + ) + + @staticmethod + def test_custom_base_vwq_url_with_path_prefix() -> None: + """A custom base VWQ URL with a path prefix intercepts at the + prefix. + """ + with MockVWS( + base_vwq_url="https://vuforia.vwq.example.com/prefix", + real_http=False, + ): + with pytest.raises( + expected_exception=requests.exceptions.ConnectionError + ): + requests.post( + url="https://vuforia.vwq.example.com/v1/query", + timeout=30, + ) + + requests.post( + url="https://vuforia.vwq.example.com/prefix/v1/query", + timeout=30, + ) + @staticmethod def test_no_scheme() -> None: """An error if raised if a URL is given with no scheme.""" diff --git a/tests/mock_vws/test_respx_mock_usage.py b/tests/mock_vws/test_respx_mock_usage.py index 1c408e4fe..34e15285d 100644 --- a/tests/mock_vws/test_respx_mock_usage.py +++ b/tests/mock_vws/test_respx_mock_usage.py @@ -1,13 +1,17 @@ """Tests for the usage of the mock for ``httpx`` via ``respx``.""" +import json import socket +import uuid +from http import HTTPMethod, HTTPStatus import httpx import pytest -from vws_auth_tools import rfc_1123_date +from vws_auth_tools import authorization_header, rfc_1123_date from mock_vws import MissingSchemeError, MockVWSForHttpx from mock_vws.database import CloudDatabase, VuMarkDatabase +from mock_vws.target import VuMarkTarget def _request_unmocked_address() -> None: @@ -208,6 +212,46 @@ def test_custom_base_vwq_url() -> None: timeout=30, ) + @staticmethod + def test_custom_base_vws_url_with_path_prefix() -> None: + """A custom base VWS URL with a path prefix intercepts at the + prefix. + """ + with MockVWSForHttpx( + base_vws_url="https://vuforia.vws.example.com/prefix", + real_http=False, + ): + with pytest.raises(expected_exception=httpx.ConnectError): + httpx.get( + url="https://vuforia.vws.example.com/summary", + timeout=30, + ) + + httpx.get( + url="https://vuforia.vws.example.com/prefix/summary", + timeout=30, + ) + + @staticmethod + def test_custom_base_vwq_url_with_path_prefix() -> None: + """A custom base VWQ URL with a path prefix intercepts at the + prefix. + """ + with MockVWSForHttpx( + base_vwq_url="https://vuforia.vwq.example.com/prefix", + real_http=False, + ): + with pytest.raises(expected_exception=httpx.ConnectError): + httpx.post( + url="https://vuforia.vwq.example.com/v1/query", + timeout=30, + ) + + httpx.post( + url="https://vuforia.vwq.example.com/prefix/v1/query", + timeout=30, + ) + @staticmethod def test_no_scheme() -> None: """An error is raised if a URL is given with no scheme.""" @@ -348,3 +392,40 @@ def test_database_summary() -> None: ) # We just verify we get a response (auth will fail but endpoint works) assert response.status_code is not None + + @staticmethod + def test_vumark_bytes_response() -> None: + """The VuMark endpoint returns bytes content via httpx.""" + vumark_target = VuMarkTarget(name="test-target") + database = VuMarkDatabase(vumark_targets={vumark_target}) + target_id = vumark_target.target_id + request_path = f"/targets/{target_id}/instances" + content_type = "application/json" + content = json.dumps(obj={"instance_id": uuid.uuid4().hex}).encode( + encoding="utf-8" + ) + date = rfc_1123_date() + auth = authorization_header( + access_key=database.server_access_key, + secret_key=database.server_secret_key, + method=HTTPMethod.POST, + content=content, + content_type=content_type, + date=date, + request_path=request_path, + ) + with MockVWSForHttpx() as mock: + mock.add_vumark_database(vumark_database=database) + response = httpx.post( + url="https://vws.vuforia.com" + request_path, + headers={ + "Accept": "image/png", + "Authorization": auth, + "Content-Length": str(object=len(content)), + "Content-Type": content_type, + "Date": date, + }, + content=content, + timeout=30, + ) + assert response.status_code == HTTPStatus.OK From 937ea33210f3ccbe03d21fca1a0f6479d38cdb80 Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sun, 22 Feb 2026 11:56:10 +0000 Subject: [PATCH 2326/2467] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bae41cee7..2c4a04208 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.22.1 +------------ + + 2026.02.22 ---------- From 21d7e7044c593b87f3f08ce8dfa435c4415f3254 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 13:17:13 +0000 Subject: [PATCH 2327/2467] Fix path prefix stripping for base URL with path prefix (#2996) When MockVWS or MockVWSForHttpx is configured with a base_vws_url containing a path prefix (e.g. https://example.com/prefix), strip the prefix from the request path before passing it to validators and route handlers. Previously, validators performing path-length checks (e.g. validate_target_id_exists, validate_keys, validate_name_*) received the full prefixed path and incorrectly parsed it, causing spurious errors such as UnknownTarget for endpoints that take no target ID. Fixes #2995. Co-authored-by: Claude Sonnet 4.6 --- .../_requests_mock_server/decorators.py | 9 ++++- src/mock_vws/_respx_mock_server/decorators.py | 21 ++++++++-- tests/mock_vws/test_requests_mock_usage.py | 39 ++++++++++++++++++- tests/mock_vws/test_respx_mock_usage.py | 36 +++++++++++++++++ 4 files changed, 100 insertions(+), 5 deletions(-) diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 007cc2fce..291a80d6c 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -162,6 +162,7 @@ def _wrap_callback( callback: _MockCallback, delay_seconds: float, sleep_fn: Callable[[float], None], + base_path: str, ) -> _ResponsesCallback: """Wrap a callback to add a response delay.""" @@ -197,9 +198,13 @@ def wrapped( else: body_bytes = raw_body + path = request.path_url + if base_path and path.startswith(base_path): + path = path[len(base_path) :] + request_data = RequestData( method=request.method or "", - path=request.path_url, + path=path, headers=dict(request.headers), body=body_bytes, ) @@ -221,6 +226,7 @@ def __enter__(self) -> Self: (self._mock_vws_api, self._base_vws_url), (self._mock_vwq_api, self._base_vwq_url), ): + base_path = urlparse(url=base_url).path.rstrip("/") for route in api.routes: url_pattern = base_url.rstrip("/") + route.path_pattern + "$" compiled_url_pattern = re.compile(pattern=url_pattern) @@ -234,6 +240,7 @@ def __enter__(self) -> Self: callback=original_callback, delay_seconds=self._response_delay_seconds, sleep_fn=self._sleep_fn, + base_path=base_path, ), content_type=None, ) diff --git a/src/mock_vws/_respx_mock_server/decorators.py b/src/mock_vws/_respx_mock_server/decorators.py index 7ef03ccd4..b9c91e078 100644 --- a/src/mock_vws/_respx_mock_server/decorators.py +++ b/src/mock_vws/_respx_mock_server/decorators.py @@ -36,18 +36,26 @@ _BRISQUE_TRACKING_RATER = BrisqueTargetTrackingRater() -def _to_request_data(request: httpx.Request) -> RequestData: +def _to_request_data( + request: httpx.Request, + *, + base_path: str, +) -> RequestData: """Convert an httpx.Request to a RequestData. Args: request: The httpx request to convert. + base_path: The base path prefix to strip from the request path. Returns: A RequestData with method, path, headers, and body set. """ + path = request.url.raw_path.decode(encoding="ascii") + if base_path and path.startswith(base_path): + path = path[len(base_path) :] return RequestData( method=request.method, - path=request.url.raw_path.decode(encoding="ascii"), + path=path, headers={k.title(): v for k, v in request.headers.items()}, body=request.content, ) @@ -155,12 +163,14 @@ def add_vumark_database(self, vumark_database: VuMarkDatabase) -> None: def _make_callback( self, handler: Callable[[RequestData], _ResponseType], + base_path: str, ) -> Callable[[httpx.Request], httpx.Response]: """Create a respx-compatible callback from a handler. Args: handler: A handler that takes a RequestData and returns a response tuple. + base_path: The base path prefix to strip from the request path. Returns: A callback that takes an httpx.Request and returns an @@ -183,7 +193,10 @@ def callback(request: httpx.Request) -> httpx.Response: Exception: A timeout error is raised when the response delay exceeds the read timeout. """ - request_data = _to_request_data(request=request) + request_data = _to_request_data( + request=request, + base_path=base_path, + ) timeout_info: dict[str, float | None] = request.extensions.get( "timeout", {} ) @@ -237,6 +250,7 @@ def __enter__(self) -> Self: (self._mock_vws_api, self._base_vws_url), (self._mock_vwq_api, self._base_vwq_url), ): + base_path = urlparse(url=base_url).path.rstrip("/") for route in api.routes: url_pattern = base_url.rstrip("/") + route.path_pattern + "$" compiled_url_pattern = re.compile(pattern=url_pattern) @@ -249,6 +263,7 @@ def __enter__(self) -> Self: ).mock( side_effect=self._make_callback( handler=original_callback, + base_path=base_path, ), ) diff --git a/tests/mock_vws/test_requests_mock_usage.py b/tests/mock_vws/test_requests_mock_usage.py index 79c3d9288..60f07ed3a 100644 --- a/tests/mock_vws/test_requests_mock_usage.py +++ b/tests/mock_vws/test_requests_mock_usage.py @@ -5,6 +5,7 @@ import io import json import socket +from http import HTTPStatus from urllib.parse import urlparse import pytest @@ -13,7 +14,7 @@ from freezegun import freeze_time from PIL import Image from vws import VWS, CloudRecoService -from vws_auth_tools import rfc_1123_date +from vws_auth_tools import authorization_header, rfc_1123_date from mock_vws import MissingSchemeError, MockVWS from mock_vws.database import CloudDatabase, VuMarkDatabase @@ -391,6 +392,42 @@ def test_custom_base_vwq_url_with_path_prefix() -> None: timeout=30, ) + @staticmethod + def test_vws_operations_work_with_path_prefix() -> None: + """VWS API operations work correctly with a base URL path + prefix. + """ + database = CloudDatabase() + base_vws_url = "https://vuforia.vws.example.com/prefix" + + with MockVWS(base_vws_url=base_vws_url) as mock: + mock.add_cloud_database(cloud_database=database) + + request_path = "/targets" + date = rfc_1123_date() + auth = authorization_header( + access_key=database.server_access_key, + secret_key=database.server_secret_key, + method="GET", + content=b"", + content_type="", + date=date, + request_path=request_path, + ) + response = requests.get( + url=base_vws_url + request_path, + headers={ + "Authorization": auth, + "Date": date, + }, + timeout=30, + ) + + assert response.status_code == HTTPStatus.OK + response_json = response.json() + assert response_json["result_code"] == "Success" + assert response_json["results"] == [] + @staticmethod def test_no_scheme() -> None: """An error if raised if a URL is given with no scheme.""" diff --git a/tests/mock_vws/test_respx_mock_usage.py b/tests/mock_vws/test_respx_mock_usage.py index 34e15285d..6d66a189c 100644 --- a/tests/mock_vws/test_respx_mock_usage.py +++ b/tests/mock_vws/test_respx_mock_usage.py @@ -252,6 +252,42 @@ def test_custom_base_vwq_url_with_path_prefix() -> None: timeout=30, ) + @staticmethod + def test_vws_operations_work_with_path_prefix() -> None: + """VWS API operations work correctly with a base URL path + prefix. + """ + database = CloudDatabase() + base_vws_url = "https://vuforia.vws.example.com/prefix" + + with MockVWSForHttpx(base_vws_url=base_vws_url) as mock: + mock.add_cloud_database(cloud_database=database) + + request_path = "/targets" + date = rfc_1123_date() + auth = authorization_header( + access_key=database.server_access_key, + secret_key=database.server_secret_key, + method="GET", + content=b"", + content_type="", + date=date, + request_path=request_path, + ) + response = httpx.get( + url=base_vws_url + request_path, + headers={ + "Authorization": auth, + "Date": date, + }, + timeout=30, + ) + + assert response.status_code == HTTPStatus.OK + response_json = response.json() + assert response_json["result_code"] == "Success" + assert response_json["results"] == [] + @staticmethod def test_no_scheme() -> None: """An error is raised if a URL is given with no scheme.""" From 1fda71496b7d3bdf788d1dcfaa4e10254bca4651 Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sun, 22 Feb 2026 13:18:04 +0000 Subject: [PATCH 2328/2467] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2c4a04208..8d57f2531 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.22.2 +------------ + + 2026.02.22.1 ------------ From aa5741a218bc7109e1aba4aaf81a06dc8f116553 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 21:24:27 +0000 Subject: [PATCH 2329/2467] Bump vws-web-tools to 2026.2.22 (#2997) * Bump vws-web-tools to 2026.2.22 and refactor secrets file creation Use get_database_details from vws-web-tools to fetch inactive database credentials instead of loading from an existing secrets file. This simplifies the setup by reducing env vars from 6 to 1 (INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME). Removes python-dotenv dependency and simplifies admin/create_secrets_files.py. Co-Authored-By: Claude Haiku 4.5 * Simplify _fetch_inactive_database_details to match existing helper pattern Take a driver parameter and let TimeoutException propagate, consistent with _create_and_get_database_details and the other helper functions. Co-Authored-By: Claude Haiku 4.5 --------- Co-authored-by: Claude Haiku 4.5 --- admin/create_secrets_files.py | 47 ++++++++++++++++++++++------------- docs/source/contributing.rst | 2 +- pyproject.toml | 4 +-- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 13d2310d0..b7dd42d24 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -11,7 +11,6 @@ from typing import TYPE_CHECKING import vws_web_tools -from dotenv import load_dotenv from selenium.common.exceptions import TimeoutException if TYPE_CHECKING: @@ -123,6 +122,25 @@ def _create_and_get_vumark_target_id( ) +def _fetch_inactive_database_details( + driver: "WebDriver", + email_address: str, + password: str, + database_name: str, +) -> "DatabaseDict": + """Fetch details for an existing inactive database.""" + vws_web_tools.log_in( + driver=driver, + email_address=email_address, + password=password, + ) + vws_web_tools.wait_for_logged_in(driver=driver) + return vws_web_tools.get_database_details( + driver=driver, + database_name=database_name, + ) + + def _create_vuforia_resource_names() -> tuple[str, str, str, str]: """Create names for Vuforia resources.""" time = datetime.datetime.now(tz=datetime.UTC).strftime( @@ -141,23 +159,18 @@ def main() -> None: email_address = os.environ["VWS_EMAIL_ADDRESS"] password = os.environ["VWS_PASSWORD"] new_secrets_dir = Path(os.environ["NEW_SECRETS_DIR"]).expanduser() - existing_secrets_file = Path( - os.environ["EXISTING_SECRETS_FILE"] - ).expanduser() - if not existing_secrets_file.exists(): - msg = f"Existing secrets file does not exist: {existing_secrets_file}" - raise FileNotFoundError(msg) - load_dotenv(dotenv_path=existing_secrets_file) - inactive_database_details: DatabaseDict = { - "database_name": os.environ[ - "INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME" - ], - "server_access_key": os.environ["INACTIVE_VUFORIA_SERVER_ACCESS_KEY"], - "server_secret_key": os.environ["INACTIVE_VUFORIA_SERVER_SECRET_KEY"], - "client_access_key": os.environ["INACTIVE_VUFORIA_CLIENT_ACCESS_KEY"], - "client_secret_key": os.environ["INACTIVE_VUFORIA_CLIENT_SECRET_KEY"], - } + inactive_database_name = os.environ[ + "INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME" + ] new_secrets_dir.mkdir(exist_ok=True) + inactive_driver = vws_web_tools.create_chrome_driver() + inactive_database_details = _fetch_inactive_database_details( + driver=inactive_driver, + email_address=email_address, + password=password, + database_name=inactive_database_name, + ) + inactive_driver.quit() num_databases = 100 required_files = [ diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 09e1f4ca9..27d34e67c 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -102,7 +102,7 @@ To create databases without using the browser, use :file:`admin/create_secrets_f $ export VWS_EMAIL_ADDRESS=... $ export VWS_PASSWORD=... $ export NEW_SECRETS_DIR=... - $ export EXISTING_SECRETS_FILE=/existing/file/with/inactive/db/creds + $ export INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME=... # You may have to run this a few times, but it is idempotent. $ python admin/create_secrets_files.py # Each generated file gets its own Cloud database credentials and shares diff --git a/pyproject.toml b/pyproject.toml index 5fb0b1a4f..f6d0d6f80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,6 @@ optional-dependencies.dev = [ "pytest==9.0.2", "pytest-retry==1.7.0", "pytest-xdist==3.8.0", - "python-dotenv==1.2.1", "pyyaml==6.0.3", "requests-mock-flask==2026.2.16", "ruff==0.15.1", @@ -107,7 +106,7 @@ optional-dependencies.dev = [ "vulture==2.14", "vws-python==2026.2.21", "vws-test-fixtures==2023.3.5", - "vws-web-tools==2026.2.20", + "vws-web-tools==2026.2.22", "yamlfix==1.19.1", "zizmor==1.22.0", ] @@ -453,6 +452,7 @@ ignore_names = [ "model_config", # Used in TYPE_CHECKING for type hints "CloudDatabaseDict", + "DatabaseDict", "VuMarkDatabaseDict", "VuMarkTargetDict", ] From 59e100ddabbcbe4807ac83168704de6837f58d1a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 22:00:22 +0000 Subject: [PATCH 2330/2467] Use context manager for PIL Image.open to ensure proper resource cleanup Image.open() was used without explicit close or context manager. When the source is a file stream (e.g. multipart upload), this can hold file handles until garbage collection. Prefer 'with Image.open(...) as img:' to ensure files are closed promptly. Fixes resource leak in: - image_matchers.py (StructuralSimilarityMatcher) - _query_validators/image_validators.py (validate_image_format, validate_image_dimensions, validate_image_is_image) - _services_validators/image_validators.py (validate_image_integrity, validate_image_format, validate_image_color_space, validate_image_is_image) - target.py (ImageTarget._post_processing_status) - target_raters.py (_get_brisque_target_tracking_rating) Co-authored-by: Cursor --- .../_query_validators/image_validators.py | 20 ++++++------- .../_services_validators/image_validators.py | 30 +++++++++---------- src/mock_vws/image_matchers.py | 16 +++++----- src/mock_vws/target.py | 7 ++--- src/mock_vws/target_raters.py | 16 +++++----- 5 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/mock_vws/_query_validators/image_validators.py b/src/mock_vws/_query_validators/image_validators.py index d08617ec2..8c0494c7e 100644 --- a/src/mock_vws/_query_validators/image_validators.py +++ b/src/mock_vws/_query_validators/image_validators.py @@ -130,11 +130,11 @@ def validate_image_dimensions( image_part = files["image"] image_value = image_part.stream.read() image_file = io.BytesIO(initial_bytes=image_value) - pil_image = Image.open(fp=image_file) - max_width = 30000 - max_height = 30000 - if pil_image.height <= max_height and pil_image.width <= max_width: - return + with Image.open(fp=image_file) as pil_image: + max_width = 30000 + max_height = 30000 + if pil_image.height <= max_height and pil_image.width <= max_width: + return _LOGGER.warning(msg="The image dimensions are too large.") raise BadImageError @@ -160,10 +160,9 @@ def validate_image_format( request_body=request_body, ) image_part = files["image"] - pil_image = Image.open(fp=image_part.stream) - - if pil_image.format in {"PNG", "JPEG"}: - return + with Image.open(fp=image_part.stream) as pil_image: + if pil_image.format in {"PNG", "JPEG"}: + return _LOGGER.warning(msg="The image format is not PNG or JPEG.") raise BadImageError @@ -191,7 +190,8 @@ def validate_image_is_image( image_file = files["image"].stream try: - Image.open(fp=image_file) + with Image.open(fp=image_file) as _: + pass except OSError as exc: _LOGGER.warning(msg="The image is not an image file.") raise BadImageError from exc diff --git a/src/mock_vws/_services_validators/image_validators.py b/src/mock_vws/_services_validators/image_validators.py index c5744efff..e5413b7f8 100644 --- a/src/mock_vws/_services_validators/image_validators.py +++ b/src/mock_vws/_services_validators/image_validators.py @@ -40,13 +40,12 @@ def validate_image_integrity(*, request_body: bytes) -> None: decoded = decode_base64(encoded_data=image) image_file = io.BytesIO(initial_bytes=decoded) - pil_image = Image.open(fp=image_file) - - try: - pil_image.verify() - except SyntaxError as exc: - _LOGGER.warning(msg="The image is not a valid image file.") - raise BadImageError from exc + with Image.open(fp=image_file) as pil_image: + try: + pil_image.verify() + except SyntaxError as exc: + _LOGGER.warning(msg="The image is not a valid image file.") + raise BadImageError from exc @beartype @@ -70,10 +69,9 @@ def validate_image_format(*, request_body: bytes) -> None: decoded = decode_base64(encoded_data=image) image_file = io.BytesIO(initial_bytes=decoded) - pil_image = Image.open(fp=image_file) - - if pil_image.format in {"PNG", "JPEG"}: - return + with Image.open(fp=image_file) as pil_image: + if pil_image.format in {"PNG", "JPEG"}: + return _LOGGER.warning(msg="The image is not a PNG or JPEG.") raise BadImageError @@ -101,10 +99,9 @@ def validate_image_color_space(*, request_body: bytes) -> None: decoded = decode_base64(encoded_data=image) image_file = io.BytesIO(initial_bytes=decoded) - pil_image = Image.open(fp=image_file) - - if pil_image.mode in {"L", "RGB"}: - return + with Image.open(fp=image_file) as pil_image: + if pil_image.mode in {"L", "RGB"}: + return _LOGGER.warning( msg="The image is not in the RGB or greyscale color space.", @@ -165,7 +162,8 @@ def validate_image_is_image(*, request_body: bytes) -> None: image_file = io.BytesIO(initial_bytes=decoded) try: - Image.open(fp=image_file) + with Image.open(fp=image_file) as _: + pass except OSError as exc: raise BadImageError from exc diff --git a/src/mock_vws/image_matchers.py b/src/mock_vws/image_matchers.py index 2aa954794..686957ad2 100644 --- a/src/mock_vws/image_matchers.py +++ b/src/mock_vws/image_matchers.py @@ -69,14 +69,16 @@ def __call__( second_image_content: Another image's content. """ first_image_file = io.BytesIO(initial_bytes=first_image_content) - first_image = Image.open(fp=first_image_file) second_image_file = io.BytesIO(initial_bytes=second_image_content) - second_image = Image.open(fp=second_image_file) - # Images must be the same size, and they must be larger than the - # default SSIM window size of 11x11. - target_size = (256, 256) - first_image_resized = first_image.resize(size=target_size) - second_image_resized = second_image.resize(size=target_size) + with ( + Image.open(fp=first_image_file) as first_image, + Image.open(fp=second_image_file) as second_image, + ): + # Images must be the same size, and they must be larger than the + # default SSIM window size of 11x11. + target_size = (256, 256) + first_image_resized = first_image.resize(size=target_size) + second_image_resized = second_image.resize(size=target_size) first_image_np = np.array(object=first_image_resized, dtype=np.float32) first_image_tensor = torch.tensor(data=first_image_np).float() / 255 diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py index 2e3760e4b..0c567a799 100644 --- a/src/mock_vws/target.py +++ b/src/mock_vws/target.py @@ -92,10 +92,9 @@ def _post_processing_status(self) -> TargetStatuses: suitable the target is for detection. """ image_file = io.BytesIO(initial_bytes=self.image_value) - image = Image.open(fp=image_file) - image_stat = ImageStat.Stat(image_or_list=image) - - average_std_dev = statistics.mean(data=image_stat.stddev) + with Image.open(fp=image_file) as image: + image_stat = ImageStat.Stat(image_or_list=image) + average_std_dev = statistics.mean(data=image_stat.stddev) success_threshold = 5 diff --git a/src/mock_vws/target_raters.py b/src/mock_vws/target_raters.py index ad75099fb..3358ca483 100644 --- a/src/mock_vws/target_raters.py +++ b/src/mock_vws/target_raters.py @@ -26,14 +26,14 @@ def _get_brisque_target_tracking_rating(*, image_content: bytes) -> int: image_content: A target's image's content. """ image_file = io.BytesIO(initial_bytes=image_content) - image = Image.open(fp=image_file) - image_np = np.array(object=image, dtype=np.float32) - image_tensor = torch.tensor(data=image_np).float() / 255 - image_tensor = image_tensor.view( - image.size[1], - image.size[0], - len(image.getbands()), - ) + with Image.open(fp=image_file) as image: + image_np = np.array(object=image, dtype=np.float32) + image_tensor = torch.tensor(data=image_np).float() / 255 + image_tensor = image_tensor.view( + image.size[1], + image.size[0], + len(image.getbands()), + ) image_tensor = image_tensor.permute(2, 0, 1).unsqueeze(dim=0) try: brisque_score = brisque(x=image_tensor, data_range=255) From 3df0281d4e2cb9b24a6c0612d5230721e661e865 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 22:39:41 +0000 Subject: [PATCH 2331/2467] Make MockVWS intercept both requests and httpx (#2998) * Make MockVWS intercept both requests and httpx MockVWS now starts both responses (for requests) and respx (for httpx) mocks simultaneously, eliminating the need for a separate MockVWSForHttpx class. Removes MockVWSForHttpx entirely. Updates all tests and docs to reflect this change. Co-Authored-By: Claude Haiku 4.5 * Fix pylint C0413 and C0402 in respx decorators Move TYPE_CHECKING guard to after all imports to fix wrong-import-position. Replace "MockRouter" in docstrings with "respx router" to fix spelling warning. Co-Authored-By: Claude Sonnet 4.6 * Suppress pyrefly false positive with inline ignore comments pyrefly's dual search path ("." and "src") causes the same class to be seen under two module paths, triggering a spurious bad-argument-type error when passing API objects to start_respx_router. Co-Authored-By: Claude Sonnet 4.6 * Replace concrete type imports with Protocol to fix pyrefly false positive Define _APIHandler Protocol in _respx_mock_server/decorators.py so it no longer imports concrete classes from _requests_mock_server/. This removes the cross-module dependency that caused pyrefly to see the same class under two module paths (mock_vws.* vs src.mock_vws.*) and report a spurious bad-argument-type error. Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Haiku 4.5 --- CHANGELOG.rst | 3 + README.rst | 22 +- docs/source/basic-example.rst | 3 +- docs/source/getting-started.rst | 7 +- docs/source/httpx-example.rst | 12 +- docs/source/index.rst | 7 +- docs/source/mock-api-reference.rst | 4 - src/mock_vws/__init__.py | 8 +- src/mock_vws/_mock_common.py | 23 ++ .../_requests_mock_server/decorators.py | 50 ++- src/mock_vws/_respx_mock_server/decorators.py | 350 ++++++------------ tests/mock_vws/test_requests_mock_usage.py | 48 +++ tests/mock_vws/test_respx_mock_usage.py | 155 +------- 13 files changed, 258 insertions(+), 434 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8d57f2531..e03655b46 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,9 @@ Changelog Next ---- +- ``MockVWS`` now intercepts both ``requests`` (via ``responses``) and ``httpx`` (via ``respx``) simultaneously. + ``MockVWSForHttpx`` has been removed — ``MockVWS`` handles both HTTP libraries. + 2026.02.22.2 ------------ diff --git a/README.rst b/README.rst index 0226ac3d5..81e350071 100644 --- a/README.rst +++ b/README.rst @@ -8,10 +8,10 @@ VWS Mock Mock for the Vuforia Web Services (VWS) API and the Vuforia Web Query API. -Mocking calls made to Vuforia with Python ``requests`` ------------------------------------------------------- +Mocking calls made to Vuforia +------------------------------ -Using the mock redirects requests to Vuforia made with `requests`_ to an in-memory implementation. +``MockVWS`` intercepts requests made with `requests`_ or `httpx`_. .. code-block:: shell @@ -34,25 +34,18 @@ This requires Python |minimum-python-version|\+. # This will use the Vuforia mock. requests.get(url="https://vws.vuforia.com/summary", timeout=30) -By default, an exception will be raised if any requests to unmocked addresses are made. - -.. _requests: https://pypi.org/project/requests/ - -Mocking calls made to Vuforia with Python ``httpx`` ----------------------------------------------------- - -Using the mock redirects requests to Vuforia made with `httpx`_ to an in-memory implementation. +``MockVWS`` also intercepts `httpx`_ requests: .. code-block:: python - """Make a request to the Vuforia Web Services API mock.""" + """Make a request to the Vuforia Web Services API mock using httpx.""" import httpx - from mock_vws import MockVWSForHttpx + from mock_vws import MockVWS from mock_vws.database import CloudDatabase - with MockVWSForHttpx() as mock: + with MockVWS() as mock: database = CloudDatabase() mock.add_cloud_database(cloud_database=database) # This will use the Vuforia mock. @@ -60,6 +53,7 @@ Using the mock redirects requests to Vuforia made with `httpx`_ to an in-memory By default, an exception will be raised if any requests to unmocked addresses are made. +.. _requests: https://pypi.org/project/requests/ .. _httpx: https://pypi.org/project/httpx/ Using Docker to mock calls to Vuforia from any language diff --git a/docs/source/basic-example.rst b/docs/source/basic-example.rst index 17a0f568c..c6829a3b4 100644 --- a/docs/source/basic-example.rst +++ b/docs/source/basic-example.rst @@ -1,4 +1,4 @@ -Using the mock redirects requests to Vuforia made with `requests`_ to an in-memory implementation. +``MockVWS`` intercepts requests to Vuforia made with `requests`_ or `httpx`_. .. code-block:: python @@ -20,3 +20,4 @@ By default, an exception will be raised if any requests to unmocked addresses ar See :ref:`mock-api-reference` for details of what can be changed and how. .. _requests: https://pypi.org/project/requests/ +.. _httpx: https://pypi.org/project/httpx/ diff --git a/docs/source/getting-started.rst b/docs/source/getting-started.rst index 120f0138b..17c1bce89 100644 --- a/docs/source/getting-started.rst +++ b/docs/source/getting-started.rst @@ -1,12 +1,9 @@ Getting started --------------- -Mocking calls made to Vuforia with Python ``requests`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Mocking calls made to Vuforia +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. include:: basic-example.rst -Mocking calls made to Vuforia with Python ``httpx`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - .. include:: httpx-example.rst diff --git a/docs/source/httpx-example.rst b/docs/source/httpx-example.rst index 2d12eb4d7..0a74e9c82 100644 --- a/docs/source/httpx-example.rst +++ b/docs/source/httpx-example.rst @@ -1,22 +1,18 @@ -Using the mock redirects requests to Vuforia made with `httpx`_ to an in-memory implementation. +``MockVWS`` also intercepts requests made with `httpx`_. .. code-block:: python - """Make a request to the Vuforia Web Services API mock.""" + """Make a request to the Vuforia Web Services API mock using httpx.""" import httpx - from mock_vws import MockVWSForHttpx + from mock_vws import MockVWS from mock_vws.database import CloudDatabase - with MockVWSForHttpx() as mock: + with MockVWS() as mock: database = CloudDatabase() mock.add_cloud_database(cloud_database=database) # This will use the Vuforia mock. httpx.get(url="https://vws.vuforia.com/summary", timeout=30) -By default, an exception will be raised if any requests to unmocked addresses are made. - -See :ref:`mock-api-reference` for details of what can be changed and how. - .. _httpx: https://pypi.org/project/httpx/ diff --git a/docs/source/index.rst b/docs/source/index.rst index 22c386d5d..04e81ebfe 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,8 +1,8 @@ |project| ========= -Mocking calls made to Vuforia with Python ``requests`` ------------------------------------------------------- +Mocking calls made to Vuforia +------------------------------ .. code-block:: console @@ -12,9 +12,6 @@ This requires Python |minimum-python-version|\+. .. include:: basic-example.rst -Mocking calls made to Vuforia with Python ``httpx`` ----------------------------------------------------- - .. include:: httpx-example.rst Using Docker to mock calls to Vuforia from any language diff --git a/docs/source/mock-api-reference.rst b/docs/source/mock-api-reference.rst index f44d65f1d..1b2ea255a 100644 --- a/docs/source/mock-api-reference.rst +++ b/docs/source/mock-api-reference.rst @@ -7,10 +7,6 @@ API Reference :members: :undoc-members: -.. autoclass:: mock_vws.MockVWSForHttpx - :members: - :undoc-members: - .. autoclass:: mock_vws.MissingSchemeError :members: :undoc-members: diff --git a/src/mock_vws/__init__.py b/src/mock_vws/__init__.py index 42d6d5264..86151570d 100644 --- a/src/mock_vws/__init__.py +++ b/src/mock_vws/__init__.py @@ -1,13 +1,9 @@ """Tools for using a fake implementation of Vuforia.""" -from mock_vws._requests_mock_server.decorators import ( - MissingSchemeError, - MockVWS, -) -from mock_vws._respx_mock_server.decorators import MockVWSForHttpx +from mock_vws._mock_common import MissingSchemeError +from mock_vws._requests_mock_server.decorators import MockVWS __all__ = [ "MissingSchemeError", "MockVWS", - "MockVWSForHttpx", ] diff --git a/src/mock_vws/_mock_common.py b/src/mock_vws/_mock_common.py index 5b0c81a62..15c3776ae 100644 --- a/src/mock_vws/_mock_common.py +++ b/src/mock_vws/_mock_common.py @@ -8,6 +8,29 @@ from beartype import beartype +@beartype +class MissingSchemeError(Exception): + """Raised when a URL is missing a schema.""" + + def __init__(self, url: str) -> None: + """ + Args: + url: The URL which is missing a scheme. + """ + super().__init__() + self.url = url + + def __str__(self) -> str: + """ + Give a string representation of this error with a + suggestion. + """ + return ( + f'Invalid URL "{self.url}": No scheme supplied. ' + f'Perhaps you meant "https://{self.url}".' + ) + + @beartype @dataclass(frozen=True) class RequestData: diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 291a80d6c..26a0b7967 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -4,7 +4,7 @@ import time from collections.abc import Callable, Mapping from contextlib import ContextDecorator -from typing import Any, Literal, Self +from typing import TYPE_CHECKING, Any, Literal, Self from urllib.parse import urlparse import requests @@ -12,7 +12,8 @@ from requests import PreparedRequest from responses import RequestsMock -from mock_vws._mock_common import RequestData +from mock_vws._mock_common import MissingSchemeError, RequestData +from mock_vws._respx_mock_server.decorators import start_respx_router from mock_vws.database import CloudDatabase, VuMarkDatabase from mock_vws.image_matchers import ( ImageMatcher, @@ -27,6 +28,9 @@ from .mock_web_query_api import MockVuforiaWebQueryAPI from .mock_web_services_api import MockVuforiaWebServicesAPI +if TYPE_CHECKING: + import respx + _ResponseType = tuple[int, Mapping[str, str], str | bytes] _MockCallback = Callable[[RequestData], _ResponseType] _ResponsesCallback = Callable[[PreparedRequest], _ResponseType] @@ -35,32 +39,12 @@ _BRISQUE_TRACKING_RATER = BrisqueTargetTrackingRater() -@beartype -class MissingSchemeError(Exception): - """Raised when a URL is missing a schema.""" - - def __init__(self, url: str) -> None: - """ - Args: - url: The URL which is missing a scheme. - """ - super().__init__() - self.url = url - - def __str__(self) -> str: - """ - Give a string representation of this error with a - suggestion. - """ - return ( - f'Invalid URL "{self.url}": No scheme supplied. ' - f'Perhaps you meant "https://{self.url}".' - ) - - @beartype(conf=BeartypeConf(is_pep484_tower=True)) class MockVWS(ContextDecorator): - """Route requests to Vuforia's Web Service APIs to fakes of those APIs.""" + """Route requests to Vuforia's Web Service APIs to fakes of those APIs. + + Works with both ``requests`` and ``httpx``. + """ def __init__( self, @@ -78,6 +62,8 @@ def __init__( """Route requests to Vuforia's Web Service APIs to fakes of those APIs. + Works with both ``requests`` and ``httpx``. + Args: real_http: Whether or not to forward requests to the real server if they are not handled by the mock. @@ -108,6 +94,7 @@ def __init__( self._response_delay_seconds = response_delay_seconds self._sleep_fn = sleep_fn self._mock: RequestsMock + self._router: respx.MockRouter self._target_manager = TargetManager() self._base_vws_url = base_vws_url @@ -252,6 +239,16 @@ def __enter__(self) -> Self: self._mock = mock self._mock.start() + self._router = start_respx_router( + mock_vws_api=self._mock_vws_api, + mock_vwq_api=self._mock_vwq_api, + base_vws_url=self._base_vws_url, + base_vwq_url=self._base_vwq_url, + response_delay_seconds=self._response_delay_seconds, + sleep_fn=self._sleep_fn, + real_http=self._real_http, + ) + return self def __exit__(self, *exc: object) -> Literal[False]: @@ -265,4 +262,5 @@ def __exit__(self, *exc: object) -> Literal[False]: del exc self._mock.stop() + self._router.stop() return False diff --git a/src/mock_vws/_respx_mock_server/decorators.py b/src/mock_vws/_respx_mock_server/decorators.py index b9c91e078..090695d0a 100644 --- a/src/mock_vws/_respx_mock_server/decorators.py +++ b/src/mock_vws/_respx_mock_server/decorators.py @@ -1,39 +1,22 @@ -"""Decorators for using the mock with httpx via respx.""" +"""Helpers for mocking Vuforia with httpx via respx.""" import re -import time from collections.abc import Callable, Mapping -from contextlib import ContextDecorator -from typing import Literal, Self +from typing import Protocol from urllib.parse import urlparse import httpx import respx -from beartype import BeartypeConf, beartype - -from mock_vws._mock_common import RequestData -from mock_vws._requests_mock_server.decorators import MissingSchemeError -from mock_vws._requests_mock_server.mock_web_query_api import ( - MockVuforiaWebQueryAPI, -) -from mock_vws._requests_mock_server.mock_web_services_api import ( - MockVuforiaWebServicesAPI, -) -from mock_vws.database import CloudDatabase, VuMarkDatabase -from mock_vws.image_matchers import ( - ImageMatcher, - StructuralSimilarityMatcher, -) -from mock_vws.target_manager import TargetManager -from mock_vws.target_raters import ( - BrisqueTargetTrackingRater, - TargetTrackingRater, -) + +from mock_vws._mock_common import RequestData, Route _ResponseType = tuple[int, Mapping[str, str], str | bytes] -_STRUCTURAL_SIMILARITY_MATCHER = StructuralSimilarityMatcher() -_BRISQUE_TRACKING_RATER = BrisqueTargetTrackingRater() + +class _APIHandler(Protocol): + """An API handler with mock routes.""" + + routes: set[Route] def _to_request_data( @@ -61,227 +44,140 @@ def _to_request_data( ) -@beartype(conf=BeartypeConf(is_pep484_tower=True)) -class MockVWSForHttpx(ContextDecorator): - """Route httpx requests to Vuforia's Web Service APIs to fakes of those - APIs. - """ - - def __init__( - self, - *, - base_vws_url: str = "https://vws.vuforia.com", - base_vwq_url: str = "https://cloudreco.vuforia.com", - duplicate_match_checker: ImageMatcher = _STRUCTURAL_SIMILARITY_MATCHER, - query_match_checker: ImageMatcher = _STRUCTURAL_SIMILARITY_MATCHER, - processing_time_seconds: float = 2.0, - target_tracking_rater: TargetTrackingRater = _BRISQUE_TRACKING_RATER, - real_http: bool = False, - response_delay_seconds: float = 0.0, - sleep_fn: Callable[[float], None] = time.sleep, - ) -> None: - """Route httpx requests to Vuforia's Web Service APIs to fakes of - those APIs. +def _block_unmatched(request: httpx.Request) -> httpx.Response: + """Raise ConnectError for unmatched requests when real_http=False. - Args: - real_http: Whether or not to forward requests to the real - server if they are not handled by the mock. - processing_time_seconds: The number of seconds to process each - image for. - In the real Vuforia Web Services, this is not deterministic. - base_vwq_url: The base URL for the VWQ API. - base_vws_url: The base URL for the VWS API. - query_match_checker: A callable which takes two image values and - returns whether they will match in a query request. - duplicate_match_checker: A callable which takes two image values - and returns whether they are duplicates. - target_tracking_rater: A callable for rating targets for tracking. - response_delay_seconds: The number of seconds to delay each - response by. This can be used to test timeout handling. - sleep_fn: The function to use for sleeping during response - delays. Defaults to ``time.sleep``. Inject a custom - function to control virtual time in tests without - monkey-patching. + Args: + request: The unmatched httpx request. - Raises: - MissingSchemeError: There is no scheme in a given URL. - """ - super().__init__() - self._real_http = real_http - self._response_delay_seconds = response_delay_seconds - self._sleep_fn = sleep_fn - self._router: respx.MockRouter - self._target_manager = TargetManager() - - self._base_vws_url = base_vws_url - self._base_vwq_url = base_vwq_url - for url in (base_vwq_url, base_vws_url): - parse_result = urlparse(url=url) - if not parse_result.scheme: - raise MissingSchemeError(url=url) - - self._mock_vws_api = MockVuforiaWebServicesAPI( - target_manager=self._target_manager, - processing_time_seconds=float(processing_time_seconds), - duplicate_match_checker=duplicate_match_checker, - target_tracking_rater=target_tracking_rater, - ) + Raises: + Exception: A connection error is always raised to block + unmatched requests. + """ + raise httpx.ConnectError( + message="Connection refused by mock", + request=request, + ) - self._mock_vwq_api = MockVuforiaWebQueryAPI( - target_manager=self._target_manager, - query_match_checker=query_match_checker, - ) - def add_cloud_database(self, cloud_database: CloudDatabase) -> None: - """Add a cloud database. +def _make_respx_callback( + *, + handler: Callable[[RequestData], _ResponseType], + base_path: str, + delay_seconds: float, + sleep_fn: Callable[[float], None], +) -> Callable[[httpx.Request], httpx.Response]: + """Create a respx-compatible callback from a handler. - Args: - cloud_database: The cloud database to add. + Args: + handler: A handler that takes a RequestData and returns a + response tuple. + base_path: The base path prefix to strip from the request path. + delay_seconds: The number of seconds to delay the response by. + sleep_fn: The function to use for sleeping during delays. - Raises: - ValueError: One of the given cloud database keys matches a key for - an existing cloud database. - """ - self._target_manager.add_cloud_database( - cloud_database=cloud_database, - ) + Returns: + A callback that takes an httpx.Request and returns an + httpx.Response. + """ - def add_vumark_database(self, vumark_database: VuMarkDatabase) -> None: - """Add a VuMark database. + def callback(request: httpx.Request) -> httpx.Response: + """Handle an httpx request by converting it and calling the + handler. Args: - vumark_database: The VuMark database to add. + request: The httpx request to handle. + + Returns: + An httpx.Response built from the handler's return value. Raises: - ValueError: One of the given database keys matches a key for - an existing database. + Exception: A timeout error is raised when the response + delay exceeds the read timeout. """ - self._target_manager.add_vumark_database( - vumark_database=vumark_database, + request_data = _to_request_data( + request=request, + base_path=base_path, ) - - def _make_callback( - self, - handler: Callable[[RequestData], _ResponseType], - base_path: str, - ) -> Callable[[httpx.Request], httpx.Response]: - """Create a respx-compatible callback from a handler. - - Args: - handler: A handler that takes a RequestData and returns a - response tuple. - base_path: The base path prefix to strip from the request path. - - Returns: - A callback that takes an httpx.Request and returns an - httpx.Response. - """ - delay_seconds = self._response_delay_seconds - sleep_fn = self._sleep_fn - - def callback(request: httpx.Request) -> httpx.Response: - """Handle an httpx request by converting it and calling the - handler. - - Args: - request: The httpx request to handle. - - Returns: - An httpx.Response built from the handler's return value. - - Raises: - Exception: A timeout error is raised when the response - delay exceeds the read timeout. - """ - request_data = _to_request_data( + timeout_info: dict[str, float | None] = request.extensions.get( + "timeout", {} + ) + read_timeout = timeout_info.get("read") + if read_timeout is not None and delay_seconds > read_timeout: + sleep_fn(read_timeout) + raise httpx.ReadTimeout( + message="Response delay exceeded read timeout", request=request, - base_path=base_path, - ) - timeout_info: dict[str, float | None] = request.extensions.get( - "timeout", {} - ) - read_timeout = timeout_info.get("read") - if read_timeout is not None and delay_seconds > read_timeout: - sleep_fn(read_timeout) - raise httpx.ReadTimeout( - message="Response delay exceeded read timeout", - request=request, - ) - status_code, headers, body = handler(request_data) - sleep_fn(delay_seconds) - if isinstance(body, str): - body = body.encode() - return httpx.Response( - status_code=status_code, - headers=headers, - content=body, ) + status_code, headers, body = handler(request_data) + sleep_fn(delay_seconds) + if isinstance(body, str): + body = body.encode() + return httpx.Response( + status_code=status_code, + headers=headers, + content=body, + ) - return callback + return callback - @staticmethod - def _block_unmatched(request: httpx.Request) -> httpx.Response: - """Raise ConnectError for unmatched requests when real_http=False. - Args: - request: The unmatched httpx request. +def start_respx_router( + *, + mock_vws_api: _APIHandler, + mock_vwq_api: _APIHandler, + base_vws_url: str, + base_vwq_url: str, + response_delay_seconds: float, + sleep_fn: Callable[[float], None], + real_http: bool, +) -> respx.MockRouter: + """Configure and start a respx router with Vuforia routes. - Raises: - Exception: A connection error is always raised to block - unmatched requests. - """ - raise httpx.ConnectError( - message="Connection refused by mock", - request=request, - ) + Args: + mock_vws_api: The VWS API handler. + mock_vwq_api: The VWQ API handler. + base_vws_url: The base URL for the VWS API. + base_vwq_url: The base URL for the VWQ API. + response_delay_seconds: The number of seconds to delay responses. + sleep_fn: The function to use for sleeping during delays. + real_http: Whether to pass through unmatched requests. - def __enter__(self) -> Self: - """Start an instance of a Vuforia mock. + Returns: + A started respx router. + """ + router = respx.MockRouter( + assert_all_called=False, + assert_all_mocked=False, + ) - Returns: - ``self``. - """ - router = respx.MockRouter( - assert_all_called=False, - assert_all_mocked=False, - ) + for api, base_url in ( + (mock_vws_api, base_vws_url), + (mock_vwq_api, base_vwq_url), + ): + base_path = urlparse(url=base_url).path.rstrip("/") + for route in api.routes: + url_pattern = base_url.rstrip("/") + route.path_pattern + "$" + compiled_url_pattern = re.compile(pattern=url_pattern) + + for http_method in route.http_methods: + original_callback = getattr(api, route.route_name) + router.route( + method=http_method, + url=compiled_url_pattern, + ).mock( + side_effect=_make_respx_callback( + handler=original_callback, + base_path=base_path, + delay_seconds=response_delay_seconds, + sleep_fn=sleep_fn, + ), + ) - for api, base_url in ( - (self._mock_vws_api, self._base_vws_url), - (self._mock_vwq_api, self._base_vwq_url), - ): - base_path = urlparse(url=base_url).path.rstrip("/") - for route in api.routes: - url_pattern = base_url.rstrip("/") + route.path_pattern + "$" - compiled_url_pattern = re.compile(pattern=url_pattern) - - for http_method in route.http_methods: - original_callback = getattr(api, route.route_name) - router.route( - method=http_method, - url=compiled_url_pattern, - ).mock( - side_effect=self._make_callback( - handler=original_callback, - base_path=base_path, - ), - ) - - if self._real_http: - router.route().pass_through() - else: - router.route().mock(side_effect=self._block_unmatched) - - router.start() - self._router = router - return self - - def __exit__(self, *exc: object) -> Literal[False]: - """Stop the Vuforia mock. + if real_http: + router.route().pass_through() + else: + router.route().mock(side_effect=_block_unmatched) - Returns: - False - """ - del exc - self._router.stop() - return False + router.start() + return router diff --git a/tests/mock_vws/test_requests_mock_usage.py b/tests/mock_vws/test_requests_mock_usage.py index 60f07ed3a..eaa82a494 100644 --- a/tests/mock_vws/test_requests_mock_usage.py +++ b/tests/mock_vws/test_requests_mock_usage.py @@ -8,6 +8,7 @@ from http import HTTPStatus from urllib.parse import urlparse +import httpx import pytest import requests from beartype import beartype @@ -1004,3 +1005,50 @@ def test_text(endpoint: Endpoint) -> None: ) response = new_endpoint.send() assert response.status_code == endpoint.successful_headers_status_code + + +class TestHttpxAlsoIntercepted: + """Tests that MockVWS also intercepts httpx requests.""" + + @staticmethod + def test_httpx_vuforia_endpoint_intercepted() -> None: + """``MockVWS`` intercepts ``httpx`` requests to Vuforia + endpoints. + """ + with MockVWS(): + response = httpx.get( + url="https://vws.vuforia.com/summary", + headers={ + "Date": rfc_1123_date(), + "Authorization": "bad_auth_token", + }, + timeout=30, + ) + assert response.status_code is not None + + @staticmethod + def test_httpx_unmocked_address_blocked() -> None: + """``MockVWS`` blocks ``httpx`` requests to non-Vuforia + addresses. + """ + sock = socket.socket() + sock.bind(("", 0)) + port = sock.getsockname()[1] + sock.close() + with MockVWS(), pytest.raises(expected_exception=httpx.ConnectError): + httpx.get(url=f"http://localhost:{port}", timeout=30) + + @staticmethod + def test_httpx_real_http() -> None: + """When ``real_http=True``, ``httpx`` requests to non-Vuforia + addresses are not blocked. + """ + sock = socket.socket() + sock.bind(("", 0)) + port = sock.getsockname()[1] + sock.close() + with ( + MockVWS(real_http=True), + pytest.raises(expected_exception=httpx.ConnectError), + ): + httpx.get(url=f"http://localhost:{port}", timeout=30) diff --git a/tests/mock_vws/test_respx_mock_usage.py b/tests/mock_vws/test_respx_mock_usage.py index 6d66a189c..3a5294225 100644 --- a/tests/mock_vws/test_respx_mock_usage.py +++ b/tests/mock_vws/test_respx_mock_usage.py @@ -1,4 +1,4 @@ -"""Tests for the usage of the mock for ``httpx`` via ``respx``.""" +"""Tests for ``MockVWS`` intercepting ``httpx`` requests.""" import json import socket @@ -9,7 +9,7 @@ import pytest from vws_auth_tools import authorization_header, rfc_1123_date -from mock_vws import MissingSchemeError, MockVWSForHttpx +from mock_vws import MockVWS from mock_vws.database import CloudDatabase, VuMarkDatabase from mock_vws.target import VuMarkTarget @@ -48,7 +48,7 @@ def test_default() -> None: """By default, the mock stops any requests made with ``httpx`` to non-Vuforia addresses, but not to mocked Vuforia endpoints. """ - with MockVWSForHttpx(): + with MockVWS(): with pytest.raises(expected_exception=httpx.ConnectError): _request_unmocked_address() @@ -63,11 +63,10 @@ def test_default() -> None: @staticmethod def test_real_http() -> None: """When the ``real_http`` parameter is ``True``, requests to - unmocked - addresses are not stopped. + unmocked addresses are not stopped. """ with ( - MockVWSForHttpx(real_http=True), + MockVWS(real_http=True), pytest.raises(expected_exception=httpx.ConnectError), ): _request_unmocked_address() @@ -79,7 +78,7 @@ class TestResponseDelay: @staticmethod def test_default_no_delay() -> None: """By default, there is no response delay.""" - with MockVWSForHttpx(): + with MockVWS(): response = httpx.get( url="https://vws.vuforia.com/summary", headers={ @@ -96,7 +95,7 @@ def test_delay_causes_timeout() -> None: timeout, a ``ReadTimeout`` exception is raised. """ with ( - MockVWSForHttpx(response_delay_seconds=0.5), + MockVWS(response_delay_seconds=0.5), pytest.raises(expected_exception=httpx.ReadTimeout), ): httpx.get( @@ -113,7 +112,7 @@ def test_delay_allows_completion() -> None: """When ``response_delay_seconds`` is set lower than the client timeout, the request completes successfully. """ - with MockVWSForHttpx(response_delay_seconds=0.1): + with MockVWS(response_delay_seconds=0.1): response = httpx.get( url="https://vws.vuforia.com/summary", headers={ @@ -130,7 +129,7 @@ def test_custom_sleep_fn_called_on_delay() -> None: ``time.sleep`` for the non-timeout delay path. """ calls: list[float] = [] - with MockVWSForHttpx( + with MockVWS( response_delay_seconds=5.0, sleep_fn=calls.append, ): @@ -151,7 +150,7 @@ def test_custom_sleep_fn_called_on_timeout() -> None: """ calls: list[float] = [] with ( - MockVWSForHttpx( + MockVWS( response_delay_seconds=5.0, sleep_fn=calls.append, ), @@ -174,7 +173,7 @@ class TestCustomBaseURLs: @staticmethod def test_custom_base_vws_url() -> None: """It is possible to use a custom base VWS URL.""" - with MockVWSForHttpx( + with MockVWS( base_vws_url="https://vuforia.vws.example.com", real_http=False, ): @@ -193,7 +192,7 @@ def test_custom_base_vws_url() -> None: @staticmethod def test_custom_base_vwq_url() -> None: """It is possible to use a custom base cloud recognition URL.""" - with MockVWSForHttpx( + with MockVWS( base_vwq_url="https://vuforia.vwq.example.com", real_http=False, ): @@ -217,7 +216,7 @@ def test_custom_base_vws_url_with_path_prefix() -> None: """A custom base VWS URL with a path prefix intercepts at the prefix. """ - with MockVWSForHttpx( + with MockVWS( base_vws_url="https://vuforia.vws.example.com/prefix", real_http=False, ): @@ -237,7 +236,7 @@ def test_custom_base_vwq_url_with_path_prefix() -> None: """A custom base VWQ URL with a path prefix intercepts at the prefix. """ - with MockVWSForHttpx( + with MockVWS( base_vwq_url="https://vuforia.vwq.example.com/prefix", real_http=False, ): @@ -260,7 +259,7 @@ def test_vws_operations_work_with_path_prefix() -> None: database = CloudDatabase() base_vws_url = "https://vuforia.vws.example.com/prefix" - with MockVWSForHttpx(base_vws_url=base_vws_url) as mock: + with MockVWS(base_vws_url=base_vws_url) as mock: mock.add_cloud_database(cloud_database=database) request_path = "/targets" @@ -288,126 +287,6 @@ def test_vws_operations_work_with_path_prefix() -> None: assert response_json["result_code"] == "Success" assert response_json["results"] == [] - @staticmethod - def test_no_scheme() -> None: - """An error is raised if a URL is given with no scheme.""" - with pytest.raises(expected_exception=MissingSchemeError) as vws_exc: - MockVWSForHttpx(base_vws_url="vuforia.vws.example.com") - - expected = ( - 'Invalid URL "vuforia.vws.example.com": No scheme supplied. ' - 'Perhaps you meant "https://vuforia.vws.example.com".' - ) - assert str(object=vws_exc.value) == expected - with pytest.raises(expected_exception=MissingSchemeError) as vwq_exc: - MockVWSForHttpx(base_vwq_url="vuforia.vwq.example.com") - expected = ( - 'Invalid URL "vuforia.vwq.example.com": No scheme supplied. ' - 'Perhaps you meant "https://vuforia.vwq.example.com".' - ) - assert str(object=vwq_exc.value) == expected - - -class TestAddDatabase: - """Tests for adding databases to the mock.""" - - @staticmethod - def test_duplicate_keys() -> None: - """It is not possible to have multiple databases with matching - keys. - """ - database = CloudDatabase( - server_access_key="1", - server_secret_key="2", - client_access_key="3", - client_secret_key="4", - database_name="5", - ) - - bad_server_access_key_db = CloudDatabase(server_access_key="1") - bad_server_secret_key_db = CloudDatabase(server_secret_key="2") - bad_client_access_key_db = CloudDatabase(client_access_key="3") - bad_client_secret_key_db = CloudDatabase(client_secret_key="4") - bad_database_name_db = CloudDatabase(database_name="5") - - server_access_key_conflict_error = ( - "All server access keys must be unique. " - 'There is already a database with the server access key "1".' - ) - server_secret_key_conflict_error = ( - "All server secret keys must be unique. " - 'There is already a database with the server secret key "2".' - ) - client_access_key_conflict_error = ( - "All client access keys must be unique. " - 'There is already a database with the client access key "3".' - ) - client_secret_key_conflict_error = ( - "All client secret keys must be unique. " - 'There is already a database with the client secret key "4".' - ) - database_name_conflict_error = ( - "All names must be unique. " - 'There is already a database with the name "5".' - ) - - with MockVWSForHttpx() as mock: - mock.add_cloud_database(cloud_database=database) - for bad_database, expected_message in ( - (bad_server_access_key_db, server_access_key_conflict_error), - (bad_server_secret_key_db, server_secret_key_conflict_error), - (bad_client_access_key_db, client_access_key_conflict_error), - (bad_client_secret_key_db, client_secret_key_conflict_error), - (bad_database_name_db, database_name_conflict_error), - ): - with pytest.raises( - expected_exception=ValueError, - match=expected_message + "$", - ): - mock.add_cloud_database(cloud_database=bad_database) - - @staticmethod - def test_duplicate_vumark_keys() -> None: - """It is not possible to have multiple databases with matching - keys, - including VuMark databases. - """ - database = VuMarkDatabase( - server_access_key="1", - server_secret_key="2", - database_name="3", - ) - - bad_server_access_key_db = VuMarkDatabase(server_access_key="1") - bad_server_secret_key_db = VuMarkDatabase(server_secret_key="2") - bad_database_name_db = VuMarkDatabase(database_name="3") - - server_access_key_conflict_error = ( - "All server access keys must be unique. " - 'There is already a database with the server access key "1".' - ) - server_secret_key_conflict_error = ( - "All server secret keys must be unique. " - 'There is already a database with the server secret key "2".' - ) - database_name_conflict_error = ( - "All names must be unique. " - 'There is already a database with the name "3".' - ) - - with MockVWSForHttpx() as mock: - mock.add_vumark_database(vumark_database=database) - for bad_database, expected_message in ( - (bad_server_access_key_db, server_access_key_conflict_error), - (bad_server_secret_key_db, server_secret_key_conflict_error), - (bad_database_name_db, database_name_conflict_error), - ): - with pytest.raises( - expected_exception=ValueError, - match=expected_message + "$", - ): - mock.add_vumark_database(vumark_database=bad_database) - class TestVWSEndpoints: """Tests that VWS endpoints are accessible via httpx.""" @@ -416,7 +295,7 @@ class TestVWSEndpoints: def test_database_summary() -> None: """The database summary endpoint is accessible via httpx.""" database = CloudDatabase() - with MockVWSForHttpx() as mock: + with MockVWS() as mock: mock.add_cloud_database(cloud_database=database) response = httpx.get( url="https://vws.vuforia.com/summary", @@ -450,7 +329,7 @@ def test_vumark_bytes_response() -> None: date=date, request_path=request_path, ) - with MockVWSForHttpx() as mock: + with MockVWS() as mock: mock.add_vumark_database(vumark_database=database) response = httpx.post( url="https://vws.vuforia.com" + request_path, From 9f82cd7460859d636038274a7373af4df3854268 Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sun, 22 Feb 2026 22:40:39 +0000 Subject: [PATCH 2332/2467] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e03655b46..9387cdc62 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.22.3 +------------ + + - ``MockVWS`` now intercepts both ``requests`` (via ``responses``) and ``httpx`` (via ``respx``) simultaneously. ``MockVWSForHttpx`` has been removed — ``MockVWS`` handles both HTTP libraries. From 3d30fcc9dbe81b8498180a2ceace05ac14139501 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 22:46:20 +0000 Subject: [PATCH 2333/2467] Consolidate timeout handling and add step logging (#2999) Merge three separate try/except blocks into one catch for TimeoutException, and add logging for each step of the database setup process for better visibility into which step times out. Co-authored-by: Claude Haiku 4.5 --- admin/create_secrets_files.py | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index b7dd42d24..0db93946c 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -193,6 +193,7 @@ def main() -> None: ) = _create_vuforia_resource_names() try: + sys.stdout.write("Creating database details\n") database_details = _create_and_get_database_details( driver=driver, email_address=email_address, @@ -200,37 +201,19 @@ def main() -> None: license_name=license_name, database_name=database_name, ) - except TimeoutException: - sys.stderr.write( - "Timed out waiting for database setup/details after retries\n" - ) - driver.quit() - driver = None - continue - - try: + sys.stdout.write("Creating VuMark database details\n") vumark_details = _create_and_get_vumark_details( driver=driver, vumark_database_name=vumark_database_name, ) - except TimeoutException: - sys.stderr.write( - "Timed out waiting for VuMark setup/details after retries\n" - ) - driver.quit() - driver = None - continue - - try: + sys.stdout.write("Creating VuMark target\n") vumark_target_id = _create_and_get_vumark_target_id( driver=driver, vumark_database_name=vumark_database_name, vumark_template_name=vumark_template_name, ) except TimeoutException: - sys.stderr.write( - "Timed out waiting for VuMark template upload after retries\n" - ) + sys.stderr.write("Timed out during database setup\n") driver.quit() driver = None continue From 1d2cb599a0b23e5f87b725434d46eaae286f39c0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 23:04:42 +0000 Subject: [PATCH 2334/2467] Create inactive databases by deleting their licenses (#3001) * Create inactive databases by deleting their licenses Refactor create_secrets_files.py to automatically create each inactive database and delete its license, eliminating the need for the INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME environment variable. Each secrets file now gets its own freshly-created inactive database, removing the requirement to pre-provision a single reusable inactive database. * Create inactive database once and reuse across all secrets files Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Sonnet 4.6 --- admin/create_secrets_files.py | 29 +++++++++++++++++++++-------- docs/source/contributing.rst | 5 +---- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 0db93946c..0da21a7ae 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -122,23 +122,34 @@ def _create_and_get_vumark_target_id( ) -def _fetch_inactive_database_details( +def _create_and_get_inactive_database_details( driver: "WebDriver", email_address: str, password: str, + license_name: str, database_name: str, ) -> "DatabaseDict": - """Fetch details for an existing inactive database.""" + """Create a cloud database, get its details, then delete the license to + make it inactive. + """ vws_web_tools.log_in( driver=driver, email_address=email_address, password=password, ) vws_web_tools.wait_for_logged_in(driver=driver) - return vws_web_tools.get_database_details( + vws_web_tools.create_license(driver=driver, license_name=license_name) + vws_web_tools.create_cloud_database( + driver=driver, + database_name=database_name, + license_name=license_name, + ) + database_details = vws_web_tools.get_database_details( driver=driver, database_name=database_name, ) + vws_web_tools.delete_license(driver=driver, license_name=license_name) + return database_details def _create_vuforia_resource_names() -> tuple[str, str, str, str]: @@ -159,16 +170,18 @@ def main() -> None: email_address = os.environ["VWS_EMAIL_ADDRESS"] password = os.environ["VWS_PASSWORD"] new_secrets_dir = Path(os.environ["NEW_SECRETS_DIR"]).expanduser() - inactive_database_name = os.environ[ - "INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME" - ] new_secrets_dir.mkdir(exist_ok=True) + + time = datetime.datetime.now(tz=datetime.UTC).strftime( + format="%Y-%m-%d-%H-%M-%S", + ) inactive_driver = vws_web_tools.create_chrome_driver() - inactive_database_details = _fetch_inactive_database_details( + inactive_database_details = _create_and_get_inactive_database_details( driver=inactive_driver, email_address=email_address, password=password, - database_name=inactive_database_name, + license_name=f"my-inactive-license-{time}", + database_name=f"my-inactive-database-{time}", ) inactive_driver.quit() diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 27d34e67c..a902f2af8 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -86,11 +86,9 @@ To find the environment variables to set in the :file:`vuforia_secrets.env` file Two Cloud databases are necessary in order to run all the Cloud Target tests. One of those must be an inactive project. -To create an inactive project, delete the license key associated with a database. +The script creates the inactive project automatically by deleting its license. VuMark tests require one VuMark database. -When creating multiple credentials files, the same inactive database and the -same VuMark database can be reused across all files. Targets sometimes get stuck at the "Processing" stage meaning that they cannot be deleted. When this happens, create a new target database to use for testing. @@ -102,7 +100,6 @@ To create databases without using the browser, use :file:`admin/create_secrets_f $ export VWS_EMAIL_ADDRESS=... $ export VWS_PASSWORD=... $ export NEW_SECRETS_DIR=... - $ export INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME=... # You may have to run this a few times, but it is idempotent. $ python admin/create_secrets_files.py # Each generated file gets its own Cloud database credentials and shares From 22973e813bb674b5fc9ceddedf315c2dc2a7c20b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 23:49:20 +0000 Subject: [PATCH 2335/2467] Refactor variable names to clarify cloud databases (#3004) Rename variables and function names in admin/create_secrets_files.py to make it explicit which databases are cloud databases versus VuMark databases. Changes include: - _create_and_get_database_details -> _create_and_get_cloud_database_details - database_name/license_name -> cloud_database_name/cloud_license_name - database_details -> cloud_database_details - Generated resource names use "cloud" prefix for clarity This addresses the first item in issue #3002 to improve code clarity. Co-authored-by: Claude Sonnet 4.6 --- admin/create_secrets_files.py | 72 +++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 0da21a7ae..c4d3b12e3 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -23,12 +23,12 @@ ) -def _create_and_get_database_details( +def _create_and_get_cloud_database_details( driver: "WebDriver", email_address: str, password: str, - license_name: str, - database_name: str, + cloud_license_name: str, + cloud_database_name: str, ) -> "DatabaseDict": """Create a cloud database and get its details. @@ -40,17 +40,19 @@ def _create_and_get_database_details( password=password, ) vws_web_tools.wait_for_logged_in(driver=driver) - vws_web_tools.create_license(driver=driver, license_name=license_name) + vws_web_tools.create_license( + driver=driver, license_name=cloud_license_name + ) vws_web_tools.create_cloud_database( driver=driver, - database_name=database_name, - license_name=license_name, + database_name=cloud_database_name, + license_name=cloud_license_name, ) return vws_web_tools.get_database_details( driver=driver, - database_name=database_name, + database_name=cloud_database_name, ) @@ -74,7 +76,7 @@ def _create_and_get_vumark_details( def _generate_secrets_file_content( - database_details: "DatabaseDict", + cloud_database_details: "DatabaseDict", vumark_details: "VuMarkDatabaseDict", inactive_database_details: "DatabaseDict", vumark_target_id: str, @@ -82,11 +84,11 @@ def _generate_secrets_file_content( """Generate the content of a secrets file.""" return textwrap.dedent( text=f"""\ - VUFORIA_TARGET_MANAGER_DATABASE_NAME={database_details["database_name"]} - VUFORIA_SERVER_ACCESS_KEY={database_details["server_access_key"]} - VUFORIA_SERVER_SECRET_KEY={database_details["server_secret_key"]} - VUFORIA_CLIENT_ACCESS_KEY={database_details["client_access_key"]} - VUFORIA_CLIENT_SECRET_KEY={database_details["client_secret_key"]} + VUFORIA_TARGET_MANAGER_DATABASE_NAME={cloud_database_details["database_name"]} + VUFORIA_SERVER_ACCESS_KEY={cloud_database_details["server_access_key"]} + VUFORIA_SERVER_SECRET_KEY={cloud_database_details["server_secret_key"]} + VUFORIA_CLIENT_ACCESS_KEY={cloud_database_details["client_access_key"]} + VUFORIA_CLIENT_SECRET_KEY={cloud_database_details["client_secret_key"]} INACTIVE_VUFORIA_TARGET_MANAGER_DATABASE_NAME={inactive_database_details["database_name"]} INACTIVE_VUFORIA_SERVER_ACCESS_KEY={inactive_database_details["server_access_key"]} @@ -126,8 +128,8 @@ def _create_and_get_inactive_database_details( driver: "WebDriver", email_address: str, password: str, - license_name: str, - database_name: str, + cloud_license_name: str, + cloud_database_name: str, ) -> "DatabaseDict": """Create a cloud database, get its details, then delete the license to make it inactive. @@ -138,18 +140,22 @@ def _create_and_get_inactive_database_details( password=password, ) vws_web_tools.wait_for_logged_in(driver=driver) - vws_web_tools.create_license(driver=driver, license_name=license_name) + vws_web_tools.create_license( + driver=driver, license_name=cloud_license_name + ) vws_web_tools.create_cloud_database( driver=driver, - database_name=database_name, - license_name=license_name, + database_name=cloud_database_name, + license_name=cloud_license_name, ) - database_details = vws_web_tools.get_database_details( + cloud_database_details = vws_web_tools.get_database_details( driver=driver, - database_name=database_name, + database_name=cloud_database_name, + ) + vws_web_tools.delete_license( + driver=driver, license_name=cloud_license_name ) - vws_web_tools.delete_license(driver=driver, license_name=license_name) - return database_details + return cloud_database_details def _create_vuforia_resource_names() -> tuple[str, str, str, str]: @@ -158,8 +164,8 @@ def _create_vuforia_resource_names() -> tuple[str, str, str, str]: format="%Y-%m-%d-%H-%M-%S", ) return ( - f"my-license-{time}", - f"my-database-{time}", + f"my-cloud-license-{time}", + f"my-cloud-database-{time}", f"my-vumark-database-{time}", f"my-vumark-template-{time}", ) @@ -180,8 +186,8 @@ def main() -> None: driver=inactive_driver, email_address=email_address, password=password, - license_name=f"my-inactive-license-{time}", - database_name=f"my-inactive-database-{time}", + cloud_license_name=f"my-inactive-cloud-license-{time}", + cloud_database_name=f"my-inactive-cloud-database-{time}", ) inactive_driver.quit() @@ -199,20 +205,20 @@ def main() -> None: file = files_to_create[-1] sys.stdout.write(f"Creating database {file.name}\n") ( - license_name, - database_name, + cloud_license_name, + cloud_database_name, vumark_database_name, vumark_template_name, ) = _create_vuforia_resource_names() try: - sys.stdout.write("Creating database details\n") - database_details = _create_and_get_database_details( + sys.stdout.write("Creating cloud database details\n") + cloud_database_details = _create_and_get_cloud_database_details( driver=driver, email_address=email_address, password=password, - license_name=license_name, - database_name=database_name, + cloud_license_name=cloud_license_name, + cloud_database_name=cloud_database_name, ) sys.stdout.write("Creating VuMark database details\n") vumark_details = _create_and_get_vumark_details( @@ -235,7 +241,7 @@ def main() -> None: driver = None file_contents = _generate_secrets_file_content( - database_details=database_details, + cloud_database_details=cloud_database_details, vumark_details=vumark_details, inactive_database_details=inactive_database_details, vumark_target_id=vumark_target_id, From 4e76f60c6ffe27bddcb30cc1b18d6d7e8904e8c7 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 23:50:31 +0000 Subject: [PATCH 2336/2467] Use context manager for PIL Image.open to ensure proper resource cleanup (#3000) Image.open() was used without explicit close or context manager. When the source is a file stream (e.g. multipart upload), this can hold file handles until garbage collection. Prefer 'with Image.open(...) as img:' to ensure files are closed promptly. Fixes resource leak in: - image_matchers.py (StructuralSimilarityMatcher) - _query_validators/image_validators.py (validate_image_format, validate_image_dimensions, validate_image_is_image) - _services_validators/image_validators.py (validate_image_integrity, validate_image_format, validate_image_color_space, validate_image_is_image) - target.py (ImageTarget._post_processing_status) - target_raters.py (_get_brisque_target_tracking_rating) Co-authored-by: Cursor --- .../_query_validators/image_validators.py | 20 ++++++------- .../_services_validators/image_validators.py | 30 +++++++++---------- src/mock_vws/image_matchers.py | 16 +++++----- src/mock_vws/target.py | 7 ++--- src/mock_vws/target_raters.py | 16 +++++----- 5 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/mock_vws/_query_validators/image_validators.py b/src/mock_vws/_query_validators/image_validators.py index d08617ec2..8c0494c7e 100644 --- a/src/mock_vws/_query_validators/image_validators.py +++ b/src/mock_vws/_query_validators/image_validators.py @@ -130,11 +130,11 @@ def validate_image_dimensions( image_part = files["image"] image_value = image_part.stream.read() image_file = io.BytesIO(initial_bytes=image_value) - pil_image = Image.open(fp=image_file) - max_width = 30000 - max_height = 30000 - if pil_image.height <= max_height and pil_image.width <= max_width: - return + with Image.open(fp=image_file) as pil_image: + max_width = 30000 + max_height = 30000 + if pil_image.height <= max_height and pil_image.width <= max_width: + return _LOGGER.warning(msg="The image dimensions are too large.") raise BadImageError @@ -160,10 +160,9 @@ def validate_image_format( request_body=request_body, ) image_part = files["image"] - pil_image = Image.open(fp=image_part.stream) - - if pil_image.format in {"PNG", "JPEG"}: - return + with Image.open(fp=image_part.stream) as pil_image: + if pil_image.format in {"PNG", "JPEG"}: + return _LOGGER.warning(msg="The image format is not PNG or JPEG.") raise BadImageError @@ -191,7 +190,8 @@ def validate_image_is_image( image_file = files["image"].stream try: - Image.open(fp=image_file) + with Image.open(fp=image_file) as _: + pass except OSError as exc: _LOGGER.warning(msg="The image is not an image file.") raise BadImageError from exc diff --git a/src/mock_vws/_services_validators/image_validators.py b/src/mock_vws/_services_validators/image_validators.py index c5744efff..e5413b7f8 100644 --- a/src/mock_vws/_services_validators/image_validators.py +++ b/src/mock_vws/_services_validators/image_validators.py @@ -40,13 +40,12 @@ def validate_image_integrity(*, request_body: bytes) -> None: decoded = decode_base64(encoded_data=image) image_file = io.BytesIO(initial_bytes=decoded) - pil_image = Image.open(fp=image_file) - - try: - pil_image.verify() - except SyntaxError as exc: - _LOGGER.warning(msg="The image is not a valid image file.") - raise BadImageError from exc + with Image.open(fp=image_file) as pil_image: + try: + pil_image.verify() + except SyntaxError as exc: + _LOGGER.warning(msg="The image is not a valid image file.") + raise BadImageError from exc @beartype @@ -70,10 +69,9 @@ def validate_image_format(*, request_body: bytes) -> None: decoded = decode_base64(encoded_data=image) image_file = io.BytesIO(initial_bytes=decoded) - pil_image = Image.open(fp=image_file) - - if pil_image.format in {"PNG", "JPEG"}: - return + with Image.open(fp=image_file) as pil_image: + if pil_image.format in {"PNG", "JPEG"}: + return _LOGGER.warning(msg="The image is not a PNG or JPEG.") raise BadImageError @@ -101,10 +99,9 @@ def validate_image_color_space(*, request_body: bytes) -> None: decoded = decode_base64(encoded_data=image) image_file = io.BytesIO(initial_bytes=decoded) - pil_image = Image.open(fp=image_file) - - if pil_image.mode in {"L", "RGB"}: - return + with Image.open(fp=image_file) as pil_image: + if pil_image.mode in {"L", "RGB"}: + return _LOGGER.warning( msg="The image is not in the RGB or greyscale color space.", @@ -165,7 +162,8 @@ def validate_image_is_image(*, request_body: bytes) -> None: image_file = io.BytesIO(initial_bytes=decoded) try: - Image.open(fp=image_file) + with Image.open(fp=image_file) as _: + pass except OSError as exc: raise BadImageError from exc diff --git a/src/mock_vws/image_matchers.py b/src/mock_vws/image_matchers.py index 2aa954794..686957ad2 100644 --- a/src/mock_vws/image_matchers.py +++ b/src/mock_vws/image_matchers.py @@ -69,14 +69,16 @@ def __call__( second_image_content: Another image's content. """ first_image_file = io.BytesIO(initial_bytes=first_image_content) - first_image = Image.open(fp=first_image_file) second_image_file = io.BytesIO(initial_bytes=second_image_content) - second_image = Image.open(fp=second_image_file) - # Images must be the same size, and they must be larger than the - # default SSIM window size of 11x11. - target_size = (256, 256) - first_image_resized = first_image.resize(size=target_size) - second_image_resized = second_image.resize(size=target_size) + with ( + Image.open(fp=first_image_file) as first_image, + Image.open(fp=second_image_file) as second_image, + ): + # Images must be the same size, and they must be larger than the + # default SSIM window size of 11x11. + target_size = (256, 256) + first_image_resized = first_image.resize(size=target_size) + second_image_resized = second_image.resize(size=target_size) first_image_np = np.array(object=first_image_resized, dtype=np.float32) first_image_tensor = torch.tensor(data=first_image_np).float() / 255 diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py index 2e3760e4b..0c567a799 100644 --- a/src/mock_vws/target.py +++ b/src/mock_vws/target.py @@ -92,10 +92,9 @@ def _post_processing_status(self) -> TargetStatuses: suitable the target is for detection. """ image_file = io.BytesIO(initial_bytes=self.image_value) - image = Image.open(fp=image_file) - image_stat = ImageStat.Stat(image_or_list=image) - - average_std_dev = statistics.mean(data=image_stat.stddev) + with Image.open(fp=image_file) as image: + image_stat = ImageStat.Stat(image_or_list=image) + average_std_dev = statistics.mean(data=image_stat.stddev) success_threshold = 5 diff --git a/src/mock_vws/target_raters.py b/src/mock_vws/target_raters.py index ad75099fb..3358ca483 100644 --- a/src/mock_vws/target_raters.py +++ b/src/mock_vws/target_raters.py @@ -26,14 +26,14 @@ def _get_brisque_target_tracking_rating(*, image_content: bytes) -> int: image_content: A target's image's content. """ image_file = io.BytesIO(initial_bytes=image_content) - image = Image.open(fp=image_file) - image_np = np.array(object=image, dtype=np.float32) - image_tensor = torch.tensor(data=image_np).float() / 255 - image_tensor = image_tensor.view( - image.size[1], - image.size[0], - len(image.getbands()), - ) + with Image.open(fp=image_file) as image: + image_np = np.array(object=image, dtype=np.float32) + image_tensor = torch.tensor(data=image_np).float() / 255 + image_tensor = image_tensor.view( + image.size[1], + image.size[0], + len(image.getbands()), + ) image_tensor = image_tensor.permute(2, 0, 1).unsqueeze(dim=0) try: brisque_score = brisque(x=image_tensor, data_range=255) From abf9c900964e72ccd8b30d49e9af59fa9e9e06f3 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 23:52:47 +0000 Subject: [PATCH 2337/2467] Rename inactive_database fixture to inactive_cloud_database (#3005) Update the fixture name throughout the codebase for consistency with naming conventions. Co-authored-by: Claude Haiku 4.5 --- tests/conftest.py | 12 ++++---- tests/mock_vws/fixtures/credentials.py | 2 +- tests/mock_vws/fixtures/vuforia_backends.py | 32 ++++++++++----------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 957422d2c..a2b736172 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -40,22 +40,22 @@ def cloud_reco_client(vuforia_database: CloudDatabase) -> CloudRecoService: @pytest.fixture(name="inactive_vws_client") -def fixture_inactive_vws_client(inactive_database: CloudDatabase) -> VWS: +def fixture_inactive_vws_client(inactive_cloud_database: CloudDatabase) -> VWS: """A client for an inactive VWS database.""" return VWS( - server_access_key=inactive_database.server_access_key, - server_secret_key=inactive_database.server_secret_key, + server_access_key=inactive_cloud_database.server_access_key, + server_secret_key=inactive_cloud_database.server_secret_key, ) @pytest.fixture def inactive_cloud_reco_client( - inactive_database: CloudDatabase, + inactive_cloud_database: CloudDatabase, ) -> CloudRecoService: """A query client for an inactive VWS database.""" return CloudRecoService( - client_access_key=inactive_database.client_access_key, - client_secret_key=inactive_database.client_secret_key, + client_access_key=inactive_cloud_database.client_access_key, + client_secret_key=inactive_cloud_database.client_secret_key, ) diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index 749b3fce9..6a8b9d7b8 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -76,7 +76,7 @@ def vuforia_database() -> CloudDatabase: @pytest.fixture -def inactive_database() -> CloudDatabase: +def inactive_cloud_database() -> CloudDatabase: """ Return VWS credentials for an inactive project from environment variables. diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index d975546ac..572281cd7 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -84,13 +84,13 @@ def _vumark_database( def _enable_use_real_vuforia( *, working_database: CloudDatabase, - inactive_database: CloudDatabase, + inactive_cloud_database: CloudDatabase, vumark_vuforia_database: VuMarkCloudDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test against the real Vuforia.""" assert monkeypatch - assert inactive_database + assert inactive_cloud_database assert vumark_vuforia_database _delete_all_targets(database_keys=working_database) yield @@ -100,7 +100,7 @@ def _enable_use_real_vuforia( def _enable_use_mock_vuforia( *, working_database: CloudDatabase, - inactive_database: CloudDatabase, + inactive_cloud_database: CloudDatabase, vumark_vuforia_database: VuMarkCloudDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: @@ -114,13 +114,13 @@ def _enable_use_mock_vuforia( client_secret_key=working_database.client_secret_key, ) - inactive_database = CloudDatabase( + inactive_cloud_database = CloudDatabase( state=States.PROJECT_INACTIVE, - database_name=inactive_database.database_name, - server_access_key=inactive_database.server_access_key, - server_secret_key=inactive_database.server_secret_key, - client_access_key=inactive_database.client_access_key, - client_secret_key=inactive_database.client_secret_key, + database_name=inactive_cloud_database.database_name, + server_access_key=inactive_cloud_database.server_access_key, + server_secret_key=inactive_cloud_database.server_secret_key, + client_access_key=inactive_cloud_database.client_access_key, + client_secret_key=inactive_cloud_database.client_secret_key, ) vumark_database = _vumark_database( vumark_vuforia_database=vumark_vuforia_database, @@ -128,7 +128,7 @@ def _enable_use_mock_vuforia( with MockVWS() as mock: mock.add_cloud_database(cloud_database=working_database) - mock.add_cloud_database(cloud_database=inactive_database) + mock.add_cloud_database(cloud_database=inactive_cloud_database) mock.add_vumark_database(vumark_database=vumark_database) yield @@ -137,7 +137,7 @@ def _enable_use_mock_vuforia( def _enable_use_docker_in_memory( *, working_database: CloudDatabase, - inactive_database: CloudDatabase, + inactive_cloud_database: CloudDatabase, vumark_vuforia_database: VuMarkCloudDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: @@ -211,7 +211,7 @@ def _enable_use_docker_in_memory( ) requests.post( url=cloud_databases_url, - json=inactive_database.to_dict(), + json=inactive_cloud_database.to_dict(), timeout=30, ) requests.post( @@ -289,7 +289,7 @@ def pytest_collection_modifyitems( def fixture_verify_mock_vuforia( request: pytest.FixtureRequest, vuforia_database: CloudDatabase, - inactive_database: CloudDatabase, + inactive_cloud_database: CloudDatabase, vumark_vuforia_database: VuMarkCloudDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: @@ -317,7 +317,7 @@ def fixture_verify_mock_vuforia( yield from enable_function( working_database=vuforia_database, - inactive_database=inactive_database, + inactive_cloud_database=inactive_cloud_database, vumark_vuforia_database=vumark_vuforia_database, monkeypatch=monkeypatch, ) @@ -335,7 +335,7 @@ def fixture_verify_mock_vuforia( def mock_only_vuforia( request: pytest.FixtureRequest, vuforia_database: CloudDatabase, - inactive_database: CloudDatabase, + inactive_cloud_database: CloudDatabase, vumark_vuforia_database: VuMarkCloudDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: @@ -363,7 +363,7 @@ def mock_only_vuforia( yield from enable_function( working_database=vuforia_database, - inactive_database=inactive_database, + inactive_cloud_database=inactive_cloud_database, vumark_vuforia_database=vumark_vuforia_database, monkeypatch=monkeypatch, ) From 2d36488e65b88d215632d5f5367edca372b28d41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 05:04:44 +0000 Subject: [PATCH 2338/2467] Bump pylint[spelling] from 4.0.4 to 4.0.5 Bumps [pylint[spelling]](https://github.com/pylint-dev/pylint) from 4.0.4 to 4.0.5. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v4.0.4...v4.0.5) --- updated-dependencies: - dependency-name: pylint[spelling] dependency-version: 4.0.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f6d0d6f80..0ca317f7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "prek==0.3.3", "pydocstringformatter==0.7.5", "pydocstyle==6.3", - "pylint[spelling]==4.0.4", + "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.16.1", "pyrefly==0.53.0", From be236b05fcfb0309dbcd5224f2cc144b58efcc3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 05:05:28 +0000 Subject: [PATCH 2339/2467] Bump ruff from 0.15.1 to 0.15.2 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.1 to 0.15.2. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.1...0.15.2) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f6d0d6f80..82d3ce474 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ "pytest-xdist==3.8.0", "pyyaml==6.0.3", "requests-mock-flask==2026.2.16", - "ruff==0.15.1", + "ruff==0.15.2", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 14e481e78673ccb737375615efea2026f7c3a750 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 05:17:12 +0000 Subject: [PATCH 2340/2467] Bump ty from 0.0.17 to 0.0.18 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.17 to 0.0.18. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.17...0.0.18) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.18 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0ca317f7f..4afafb04c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.4", - "ty==0.0.17", + "ty==0.0.18", "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", From 74d386d777f5e2dd34e187b07d37390e529f0c23 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 23 Feb 2026 08:22:54 +0000 Subject: [PATCH 2341/2467] Add shared inactive VuMark database to secrets files (#3012) Create a single inactive VuMark database (one per run, shared across all 100 secrets files) by mimicking the pattern used for inactive cloud databases: create a license, create the VuMark database, capture its credentials, then delete the license. This provides test fixtures with inactive VuMark database credentials alongside the existing inactive cloud database credentials. Co-authored-by: Claude Sonnet 4.6 --- admin/create_secrets_files.py | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index c4d3b12e3..739094098 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -79,6 +79,7 @@ def _generate_secrets_file_content( cloud_database_details: "DatabaseDict", vumark_details: "VuMarkDatabaseDict", inactive_database_details: "DatabaseDict", + inactive_vumark_details: "VuMarkDatabaseDict", vumark_target_id: str, ) -> str: """Generate the content of a secrets file.""" @@ -100,6 +101,10 @@ def _generate_secrets_file_content( VUMARK_VUFORIA_TARGET_ID={vumark_target_id} VUMARK_VUFORIA_SERVER_ACCESS_KEY={vumark_details["server_access_key"]} VUMARK_VUFORIA_SERVER_SECRET_KEY={vumark_details["server_secret_key"]} + + INACTIVE_VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME={inactive_vumark_details["database_name"]} + INACTIVE_VUMARK_VUFORIA_SERVER_ACCESS_KEY={inactive_vumark_details["server_access_key"]} + INACTIVE_VUMARK_VUFORIA_SERVER_SECRET_KEY={inactive_vumark_details["server_secret_key"]} """, ) @@ -158,6 +163,40 @@ def _create_and_get_inactive_database_details( return cloud_database_details +def _create_and_get_inactive_vumark_details( + driver: "WebDriver", + email_address: str, + password: str, + vumark_license_name: str, + vumark_database_name: str, +) -> "VuMarkDatabaseDict": + """Create a VuMark database, get its details, then delete the license + to + make it inactive. + """ + vws_web_tools.log_in( + driver=driver, + email_address=email_address, + password=password, + ) + vws_web_tools.wait_for_logged_in(driver=driver) + vws_web_tools.create_license( + driver=driver, license_name=vumark_license_name + ) + vws_web_tools.create_vumark_database( + driver=driver, + database_name=vumark_database_name, + ) + vumark_database_details = vws_web_tools.get_vumark_database_details( + driver=driver, + database_name=vumark_database_name, + ) + vws_web_tools.delete_license( + driver=driver, license_name=vumark_license_name + ) + return vumark_database_details + + def _create_vuforia_resource_names() -> tuple[str, str, str, str]: """Create names for Vuforia resources.""" time = datetime.datetime.now(tz=datetime.UTC).strftime( @@ -191,6 +230,16 @@ def main() -> None: ) inactive_driver.quit() + inactive_vumark_driver = vws_web_tools.create_chrome_driver() + inactive_vumark_details = _create_and_get_inactive_vumark_details( + driver=inactive_vumark_driver, + email_address=email_address, + password=password, + vumark_license_name=f"my-inactive-vumark-license-{time}", + vumark_database_name=f"my-inactive-vumark-database-{time}", + ) + inactive_vumark_driver.quit() + num_databases = 100 required_files = [ (new_secrets_dir / f"vuforia_secrets_{i}.env") @@ -244,6 +293,7 @@ def main() -> None: cloud_database_details=cloud_database_details, vumark_details=vumark_details, inactive_database_details=inactive_database_details, + inactive_vumark_details=inactive_vumark_details, vumark_target_id=vumark_target_id, ) file.write_text(data=file_contents) From bac1c011a2df62334e234548e22849fd020e79a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 08:30:21 +0000 Subject: [PATCH 2342/2467] Bump vws-python from 2026.2.21 to 2026.2.22 (#3010) Bumps [vws-python](https://github.com/VWS-Python/vws-python) from 2026.2.21 to 2026.2.22. - [Release notes](https://github.com/VWS-Python/vws-python/releases) - [Changelog](https://github.com/VWS-Python/vws-python/blob/main/CHANGELOG.rst) - [Commits](https://github.com/VWS-Python/vws-python/compare/2026.02.21...2026.02.22) --- updated-dependencies: - dependency-name: vws-python dependency-version: 2026.2.22 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 25379d07f..abdbec39b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,7 +104,7 @@ optional-dependencies.dev = [ "types-requests==2.32.4.20260107", "urllib3==2.6.3", "vulture==2.14", - "vws-python==2026.2.21", + "vws-python==2026.2.22", "vws-test-fixtures==2023.3.5", "vws-web-tools==2026.2.22", "yamlfix==1.19.1", From c2115f74c12c911ed5327dbdc2c6990638f4dc82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 08:30:54 +0000 Subject: [PATCH 2343/2467] Bump vws-web-tools from 2026.2.22 to 2026.2.22.1 (#3008) Bumps [vws-web-tools](https://github.com/VWS-Python/vws-web-tools) from 2026.2.22 to 2026.2.22.1. - [Release notes](https://github.com/VWS-Python/vws-web-tools/releases) - [Changelog](https://github.com/VWS-Python/vws-web-tools/blob/main/CHANGELOG.rst) - [Commits](https://github.com/VWS-Python/vws-web-tools/compare/2026.02.22...2026.02.22.1) --- updated-dependencies: - dependency-name: vws-web-tools dependency-version: 2026.2.22.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index abdbec39b..3b3dcf738 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,7 +106,7 @@ optional-dependencies.dev = [ "vulture==2.14", "vws-python==2026.2.22", "vws-test-fixtures==2023.3.5", - "vws-web-tools==2026.2.22", + "vws-web-tools==2026.2.22.1", "yamlfix==1.19.1", "zizmor==1.22.0", ] From d30a49110a51163f88c5c365f9a05eb884700ef4 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 23 Feb 2026 08:56:24 +0000 Subject: [PATCH 2344/2467] Use verify_mock_vuforia on TestTargetStatusNotSuccess (#3006) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor TestTargetStatusNotSuccess to use verify_mock_vuforia fixture Updated TestTargetStatusNotSuccess class to use the verify_mock_vuforia fixture, enabling tests to run against multiple Vuforia backends (real, in-memory mock, and Docker mock). Extended VuMarkCloudDatabase with a processing_target_id field to support testing target processing states across all backends. Refactored test methods to use fixture-provided credentials instead of creating their own MockVWS contexts. Co-Authored-By: Claude Haiku 4.5 * Give processing_target_id a generated default The CI secrets files do not yet include VUMARK_VUFORIA_PROCESSING_TARGET_ID, so making it required broke all tests that depend on verify_mock_vuforia (which uses vumark_vuforia_database) at setup. Giving the field a default_factory restores fixture setup for existing CI runs while still allowing the env var to override it when a real Vuforia processing target is available. Co-Authored-By: Claude Sonnet 4.6 * Trigger CI * Move processing target tests to mock_only_vuforia class VuMark targets cannot be added via the VWS API — they are configured through the Vuforia Target Manager portal — so there is no way to keep a target in PROCESSING state indefinitely on real Vuforia. Move the processing tests into a new TestProcessingTarget class that uses mock_only_vuforia, and keep test_successful_target in TestTargetStatusNotSuccess with verify_mock_vuforia. Co-Authored-By: Claude Opus 4.6 * Move test_successful_target into TestGenerateInstance test_successful_target was the only method left in TestTargetStatusNotSuccess, contradicting the class name. It fits naturally with the other generation tests in TestGenerateInstance. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Haiku 4.5 --- tests/mock_vws/fixtures/credentials.py | 5 + tests/mock_vws/fixtures/vuforia_backends.py | 25 ++-- tests/mock_vws/test_vumark_generation_api.py | 133 ++++++++----------- 3 files changed, 78 insertions(+), 85 deletions(-) diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index 6a8b9d7b8..55f54224a 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -2,8 +2,10 @@ from dataclasses import dataclass, field from pathlib import Path +from uuid import uuid4 import pytest +from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict from mock_vws.database import CloudDatabase @@ -43,6 +45,7 @@ class _VuMarkCloudDatabaseSettings(BaseSettings): server_access_key: str server_secret_key: str target_id: str + processing_target_id: str = Field(default_factory=lambda: uuid4().hex) model_config = SettingsConfigDict( env_prefix="VUMARK_VUFORIA_", @@ -59,6 +62,7 @@ class VuMarkCloudDatabase: server_access_key: str = field(repr=False) server_secret_key: str = field(repr=False) target_id: str = field(repr=False) + processing_target_id: str = field(repr=False) @pytest.fixture @@ -102,4 +106,5 @@ def vumark_vuforia_database() -> VuMarkCloudDatabase: server_access_key=settings.server_access_key, server_secret_key=settings.server_secret_key, target_id=settings.target_id, + processing_target_id=settings.processing_target_id, ) diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 572281cd7..da74c8838 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -72,11 +72,16 @@ def _vumark_database( name="mock-vumark-target", target_id=vumark_vuforia_database.target_id, ) + processing_target = VuMarkTarget( + name="mock-processing-vumark-target", + target_id=vumark_vuforia_database.processing_target_id, + processing_time_seconds=9999, + ) return VuMarkDatabase( database_name=vumark_vuforia_database.target_manager_database_name, server_access_key=vumark_vuforia_database.server_access_key, server_secret_key=vumark_vuforia_database.server_secret_key, - vumark_targets={vumark_target}, + vumark_targets={vumark_target, processing_target}, ) @@ -165,7 +170,6 @@ def _enable_use_docker_in_memory( vumark_database = _vumark_database( vumark_vuforia_database=vumark_vuforia_database, ) - (vumark_target,) = vumark_database.vumark_targets with responses.RequestsMock(assert_all_requests_are_fired=False) as mock: add_flask_app_to_mock( @@ -219,14 +223,15 @@ def _enable_use_docker_in_memory( json=vumark_database.to_dict(), timeout=30, ) - requests.post( - url=( - f"{vumark_databases_url}" - f"/{vumark_database.database_name}/vumark_targets" - ), - json=vumark_target.to_dict(), - timeout=30, - ) + for vumark_target in vumark_database.vumark_targets: + requests.post( + url=( + f"{vumark_databases_url}" + f"/{vumark_database.database_name}/vumark_targets" + ), + json=vumark_target.to_dict(), + timeout=30, + ) yield diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 22e11b6a3..1f5146955 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -16,10 +16,8 @@ from vws.vumark_accept import VuMarkAccept from vws_auth_tools import authorization_header, rfc_1123_date -from mock_vws import MockVWS from mock_vws._constants import ResultCodes -from mock_vws.database import CloudDatabase, VuMarkDatabase -from mock_vws.target import VuMarkTarget +from mock_vws.database import CloudDatabase from tests.mock_vws.fixtures.credentials import VuMarkCloudDatabase from tests.mock_vws.utils import make_image_file @@ -263,98 +261,83 @@ def test_non_vumark_database( == ResultCodes.INVALID_TARGET_TYPE.value ) + @staticmethod + def test_successful_target( + vumark_vuforia_database: VuMarkCloudDatabase, + ) -> None: + """A VuMark target that has finished processing succeeds.""" + vumark_client = _make_vumark_service( + server_access_key=vumark_vuforia_database.server_access_key, + server_secret_key=vumark_vuforia_database.server_secret_key, + ) + vumark_bytes = vumark_client.generate_vumark_instance( + target_id=vumark_vuforia_database.target_id, + instance_id=uuid4().hex, + accept=VuMarkAccept.PNG, + ) + + assert vumark_bytes.strip().startswith(_PNG_SIGNATURE) + -class TestTargetStatusNotSuccess: - """Tests for VuMark generation when the target is not in success - state. +# VuMark targets cannot be added via the VWS API — they are configured +# through the Vuforia Target Manager portal. This means we cannot +# create a target that is perpetually in PROCESSING state against real +# Vuforia. The mock controls processing time via the +# ``processing_time_seconds`` attribute on ``VuMarkTarget``, so these +# tests are inherently mock-only. +@pytest.mark.usefixtures("mock_only_vuforia") +class TestProcessingTarget: + """Tests for VuMark generation when the target is still processing. + + These use ``mock_only_vuforia`` because there is no way to keep a + VuMark target in PROCESSING state indefinitely on real Vuforia. """ @staticmethod - def test_processing_target() -> None: + def test_processing_target( + vumark_vuforia_database: VuMarkCloudDatabase, + ) -> None: """A VuMark target still processing returns TargetStatusNotSuccess. """ - vumark_target = VuMarkTarget( - name="processing-target", - processing_time_seconds=9999, - ) - vumark_database = VuMarkDatabase( - vumark_targets={vumark_target}, - ) vumark_client = _make_vumark_service( - server_access_key=vumark_database.server_access_key, - server_secret_key=vumark_database.server_secret_key, + server_access_key=vumark_vuforia_database.server_access_key, + server_secret_key=vumark_vuforia_database.server_secret_key, ) - - with MockVWS() as mock: - mock.add_vumark_database(vumark_database=vumark_database) - with pytest.raises( - expected_exception=TargetStatusNotSuccessError, - ) as exc: - vumark_client.generate_vumark_instance( - target_id=vumark_target.target_id, - instance_id=uuid4().hex, - accept=VuMarkAccept.PNG, - ) - - assert exc.value.response.status_code == HTTPStatus.FORBIDDEN - response_json = json.loads(s=exc.value.response.text) - assert ( - response_json["result_code"] - == ResultCodes.TARGET_STATUS_NOT_SUCCESS.value + with pytest.raises( + expected_exception=TargetStatusNotSuccessError, + ) as exc: + vumark_client.generate_vumark_instance( + target_id=vumark_vuforia_database.processing_target_id, + instance_id=uuid4().hex, + accept=VuMarkAccept.PNG, ) + assert exc.value.response.status_code == HTTPStatus.FORBIDDEN + response_json = json.loads(s=exc.value.response.text) + assert ( + response_json["result_code"] + == ResultCodes.TARGET_STATUS_NOT_SUCCESS.value + ) + @staticmethod - def test_processing_target_raw_response() -> None: + def test_processing_target_raw_response( + vumark_vuforia_database: VuMarkCloudDatabase, + ) -> None: """The raw HTTP response for a processing target has the expected status code and result code. """ - vumark_target = VuMarkTarget( - name="processing-target", - processing_time_seconds=9999, - ) - vumark_database = VuMarkDatabase( - vumark_targets={vumark_target}, + response = _make_vumark_request( + server_access_key=vumark_vuforia_database.server_access_key, + server_secret_key=vumark_vuforia_database.server_secret_key, + target_id=vumark_vuforia_database.processing_target_id, + instance_id=uuid4().hex, + accept="image/png", ) - with MockVWS() as mock: - mock.add_vumark_database(vumark_database=vumark_database) - response = _make_vumark_request( - server_access_key=vumark_database.server_access_key, - server_secret_key=vumark_database.server_secret_key, - target_id=vumark_target.target_id, - instance_id=uuid4().hex, - accept="image/png", - ) - assert response.status_code == HTTPStatus.FORBIDDEN response_json = response.json() assert ( response_json["result_code"] == ResultCodes.TARGET_STATUS_NOT_SUCCESS.value ) - - @staticmethod - def test_successful_target() -> None: - """A VuMark target that has finished processing succeeds.""" - vumark_target = VuMarkTarget( - name="ready-target", - processing_time_seconds=0, - ) - vumark_database = VuMarkDatabase( - vumark_targets={vumark_target}, - ) - vumark_client = _make_vumark_service( - server_access_key=vumark_database.server_access_key, - server_secret_key=vumark_database.server_secret_key, - ) - - with MockVWS() as mock: - mock.add_vumark_database(vumark_database=vumark_database) - vumark_bytes = vumark_client.generate_vumark_instance( - target_id=vumark_target.target_id, - instance_id=uuid4().hex, - accept=VuMarkAccept.PNG, - ) - - assert vumark_bytes.strip().startswith(_PNG_SIGNATURE) From be00f28a95f928f5c3ab133bca112b5deff76bc8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 23 Feb 2026 09:17:41 +0000 Subject: [PATCH 2345/2467] Add inactive VuMark credentials to secrets (#3013) * Add inactive VuMark credentials to secrets archive Co-Authored-By: Claude Sonnet 4.6 * Add inactive VuMark credentials to secrets archive Co-Authored-By: Claude Sonnet 4.6 * Remove add_inactive_vumark_secrets script Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Sonnet 4.6 --- secrets.tar.gpg | Bin 19046 -> 18110 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/secrets.tar.gpg b/secrets.tar.gpg index 32c9b9c6f5e398b64161ca4cf9bd9aa193cca373..576c697502629868f233e409af31385a775c4b9c 100644 GIT binary patch literal 18110 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 From 5c84590edb4f30133c536921bfe48fc57215c0f2 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 23 Feb 2026 09:45:06 +0000 Subject: [PATCH 2346/2467] Use * for keyword-only arguments in pytest fixtures Co-authored-by: Cursor --- tests/conftest.py | 17 +++++++++++------ tests/mock_vws/fixtures/prepared_requests.py | 12 ++++++++++-- tests/mock_vws/fixtures/vuforia_backends.py | 2 ++ tests/mock_vws/test_flask_app_usage.py | 2 +- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 957422d2c..c6af5c266 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,7 +22,7 @@ @pytest.fixture(name="vws_client") -def fixture_vws_client(vuforia_database: CloudDatabase) -> VWS: +def fixture_vws_client(*, vuforia_database: CloudDatabase) -> VWS: """A VWS client for an active VWS database.""" return VWS( server_access_key=vuforia_database.server_access_key, @@ -31,7 +31,7 @@ def fixture_vws_client(vuforia_database: CloudDatabase) -> VWS: @pytest.fixture -def cloud_reco_client(vuforia_database: CloudDatabase) -> CloudRecoService: +def cloud_reco_client(*, vuforia_database: CloudDatabase) -> CloudRecoService: """A query client for an active VWS database.""" return CloudRecoService( client_access_key=vuforia_database.client_access_key, @@ -40,7 +40,7 @@ def cloud_reco_client(vuforia_database: CloudDatabase) -> CloudRecoService: @pytest.fixture(name="inactive_vws_client") -def fixture_inactive_vws_client(inactive_database: CloudDatabase) -> VWS: +def fixture_inactive_vws_client(*, inactive_database: CloudDatabase) -> VWS: """A client for an inactive VWS database.""" return VWS( server_access_key=inactive_database.server_access_key, @@ -50,6 +50,7 @@ def fixture_inactive_vws_client(inactive_database: CloudDatabase) -> VWS: @pytest.fixture def inactive_cloud_reco_client( + *, inactive_database: CloudDatabase, ) -> CloudRecoService: """A query client for an inactive VWS database.""" @@ -61,6 +62,7 @@ def inactive_cloud_reco_client( @pytest.fixture def target_id( + *, image_file_success_state_low_rating: io.BytesIO, vws_client: VWS, ) -> str: @@ -91,7 +93,7 @@ def target_id( "vumark_generate_instance", ], ) -def endpoint(request: pytest.FixtureRequest) -> Endpoint: +def endpoint(*, request: pytest.FixtureRequest) -> Endpoint: """ Return details of an endpoint for the Target API or the Query API. @@ -120,7 +122,7 @@ def endpoint(request: pytest.FixtureRequest) -> Endpoint: ), ], ) -def not_base64_encoded_processable(request: pytest.FixtureRequest) -> str: +def not_base64_encoded_processable(*, request: pytest.FixtureRequest) -> str: """Return a string which is not decodable as base64 data, but Vuforia will respond as if this is valid base64 data. @@ -144,7 +146,10 @@ def not_base64_encoded_processable(request: pytest.FixtureRequest) -> str: pytest.param('"', id="Not a base64 character."), ], ) -def not_base64_encoded_not_processable(request: pytest.FixtureRequest) -> str: +def not_base64_encoded_not_processable( + *, + request: pytest.FixtureRequest, +) -> str: """ Return a string which is not decodable as base64 data, and Vuforia will diff --git a/tests/mock_vws/fixtures/prepared_requests.py b/tests/mock_vws/fixtures/prepared_requests.py index 4bd4ec33e..c3cf48669 100644 --- a/tests/mock_vws/fixtures/prepared_requests.py +++ b/tests/mock_vws/fixtures/prepared_requests.py @@ -39,6 +39,7 @@ def _wait_for_target_processed(*, vws_client: VWS, target_id: str) -> None: @pytest.fixture def add_target( + *, vuforia_database: CloudDatabase, image_file_failed_state: io.BytesIO, ) -> Endpoint: @@ -93,6 +94,7 @@ def add_target( @pytest.fixture def delete_target( + *, vuforia_database: CloudDatabase, target_id: str, vws_client: VWS, @@ -136,7 +138,7 @@ def delete_target( @pytest.fixture -def database_summary(vuforia_database: CloudDatabase) -> Endpoint: +def database_summary(*, vuforia_database: CloudDatabase) -> Endpoint: """ Return details of the endpoint for getting details about the database. @@ -180,6 +182,7 @@ def database_summary(vuforia_database: CloudDatabase) -> Endpoint: @pytest.fixture def get_duplicates( + *, vuforia_database: CloudDatabase, target_id: str, vws_client: VWS, @@ -228,6 +231,7 @@ def get_duplicates( @pytest.fixture def get_target( + *, vuforia_database: CloudDatabase, target_id: str, vws_client: VWS, @@ -272,7 +276,7 @@ def get_target( @pytest.fixture -def target_list(vuforia_database: CloudDatabase) -> Endpoint: +def target_list(*, vuforia_database: CloudDatabase) -> Endpoint: """Return details of the endpoint for getting a list of targets.""" date = rfc_1123_date() request_path = "/targets" @@ -313,6 +317,7 @@ def target_list(vuforia_database: CloudDatabase) -> Endpoint: @pytest.fixture def target_summary( + *, vuforia_database: CloudDatabase, target_id: str, vws_client: VWS, @@ -361,6 +366,7 @@ def target_summary( @pytest.fixture def update_target( + *, vuforia_database: CloudDatabase, target_id: str, vws_client: VWS, @@ -409,6 +415,7 @@ def update_target( @pytest.fixture def query( + *, vuforia_database: CloudDatabase, high_quality_image: io.BytesIO, ) -> Endpoint: @@ -459,6 +466,7 @@ def query( @pytest.fixture def vumark_generate_instance( + *, vumark_vuforia_database: VuMarkCloudDatabase, ) -> Endpoint: """Return details of the endpoint for generating a VuMark instance.""" diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index d975546ac..94273450d 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -287,6 +287,7 @@ def pytest_collection_modifyitems( ids=[backend.value for backend in list(VuforiaBackend)], ) def fixture_verify_mock_vuforia( + *, request: pytest.FixtureRequest, vuforia_database: CloudDatabase, inactive_database: CloudDatabase, @@ -333,6 +334,7 @@ def fixture_verify_mock_vuforia( ], ) def mock_only_vuforia( + *, request: pytest.FixtureRequest, vuforia_database: CloudDatabase, inactive_database: CloudDatabase, diff --git a/tests/mock_vws/test_flask_app_usage.py b/tests/mock_vws/test_flask_app_usage.py index 6b7f7eee2..4db00b7e5 100644 --- a/tests/mock_vws/test_flask_app_usage.py +++ b/tests/mock_vws/test_flask_app_usage.py @@ -33,7 +33,7 @@ @pytest.fixture(autouse=True) -def _(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]: +def _(*, monkeypatch: pytest.MonkeyPatch) -> Iterator[None]: """Enable a mock service backed by the Flask applications.""" with responses.RequestsMock( assert_all_requests_are_fired=False, From 555a2d1bd16a53667cc84a9bbea0dc35f9dacce0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 23 Feb 2026 10:54:01 +0000 Subject: [PATCH 2347/2467] Add test for inactive database in VuMark API (#3003) * Add test for inactive database in VuMark generation API This test verifies that calling the VuMark generation endpoint with credentials from an inactive database returns a ProjectInactive error. Co-Authored-By: Claude Haiku 4.5 * Move inactive database test to existing test file Avoids duplicating _make_vumark_request by placing TestInactiveDatabase in test_vumark_generation_api.py where the helper already exists. Co-Authored-By: Claude Haiku 4.5 * [pre-commit.ci lite] apply automatic fixes * Use verify_mock_vuforia for inactive database test Co-Authored-By: Claude Haiku 4.5 * Use inactive VuMark database fixture for TestInactiveDatabase Add state support to VuMarkDatabase, add InactiveVuMarkCloudDatabase fixture using INACTIVE_VUMARK_VUFORIA_* env vars, and update project_state_validators to raise ProjectInactiveError for inactive VuMark databases. Co-Authored-By: Claude Sonnet 4.6 * Use keyword-only args in fixture functions to fix too-many-positional-arguments Co-Authored-By: Claude Sonnet 4.6 * Fix inactive VuMark database to return UnknownTarget (404) not ProjectInactive Real Vuforia returns 404 UnknownTarget for inactive VuMark databases on the generation endpoint, not 403 ProjectInactive. Skip the project-inactive check for VuMarkDatabase so target validation runs and returns the correct response. Co-Authored-By: Claude Sonnet 4.6 * Add INACTIVE_VUMARK_VUFORIA_* to vuforia_secrets.env.example Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Haiku 4.5 Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- src/mock_vws/_flask_server/target_manager.py | 5 +++ .../project_state_validators.py | 12 ++++--- src/mock_vws/database.py | 4 +++ tests/mock_vws/fixtures/credentials.py | 34 +++++++++++++++++++ tests/mock_vws/fixtures/vuforia_backends.py | 33 +++++++++++++++++- tests/mock_vws/test_vumark_generation_api.py | 29 +++++++++++++++- vuforia_secrets.env.example | 5 +++ 7 files changed, 116 insertions(+), 6 deletions(-) diff --git a/src/mock_vws/_flask_server/target_manager.py b/src/mock_vws/_flask_server/target_manager.py index 8d7ba28f0..dcdced8ec 100644 --- a/src/mock_vws/_flask_server/target_manager.py +++ b/src/mock_vws/_flask_server/target_manager.py @@ -252,6 +252,10 @@ def create_vumark_database() -> Response: """ request_json = json.loads(s=request.data) random_vumark_database = VuMarkDatabase() + state_name = request_json.get( + "state_name", + random_vumark_database.state.name, + ) database = VuMarkDatabase( server_access_key=request_json.get( "server_access_key", @@ -265,6 +269,7 @@ def create_vumark_database() -> Response: "database_name", random_vumark_database.database_name, ), + state=States[state_name], ) try: diff --git a/src/mock_vws/_services_validators/project_state_validators.py b/src/mock_vws/_services_validators/project_state_validators.py index ac1fe97d2..d0a07b0fb 100644 --- a/src/mock_vws/_services_validators/project_state_validators.py +++ b/src/mock_vws/_services_validators/project_state_validators.py @@ -11,7 +11,7 @@ get_database_matching_server_keys, ) from mock_vws._services_validators.exceptions import ProjectInactiveError -from mock_vws.database import CloudDatabase +from mock_vws.database import CloudDatabase, VuMarkDatabase from mock_vws.states import States _LOGGER = logging.getLogger(name=__name__) @@ -47,13 +47,17 @@ def validate_project_state( databases=databases, ) - if not isinstance(database, CloudDatabase): + if database.state != States.PROJECT_INACTIVE: return - if database.state != States.PROJECT_INACTIVE: + if ( + isinstance(database, CloudDatabase) + and request_method == HTTPMethod.GET + and "duplicates" not in request_path + ): return - if request_method == HTTPMethod.GET and "duplicates" not in request_path: + if isinstance(database, VuMarkDatabase): return _LOGGER.warning(msg="The project is inactive.") diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py index 61ec48377..7b27c238a 100644 --- a/src/mock_vws/database.py +++ b/src/mock_vws/database.py @@ -40,6 +40,7 @@ class VuMarkDatabaseDict(TypedDict): server_access_key: str server_secret_key: str vumark_targets: Iterable[VuMarkTargetDict] + state_name: str @beartype @@ -202,6 +203,7 @@ class VuMarkDatabase: default_factory=set[VuMarkTarget], hash=False, ) + state: States = States.WORKING def get_vumark_target(self, target_id: str) -> VuMarkTarget: """Return a VuMark target from the database with the given ID.""" @@ -222,6 +224,7 @@ def to_dict(self) -> VuMarkDatabaseDict: "server_access_key": self.server_access_key, "server_secret_key": self.server_secret_key, "vumark_targets": vumark_targets, + "state_name": self.state.name, } @classmethod @@ -235,6 +238,7 @@ def from_dict(cls, database_dict: VuMarkDatabaseDict) -> Self: VuMarkTarget.from_dict(target_dict=target_dict) for target_dict in database_dict["vumark_targets"] }, + state=States[database_dict["state_name"]], ) @property diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index 55f54224a..a9fb73451 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -38,6 +38,20 @@ class _InactiveCloudDatabaseSettings(_CloudDatabaseSettings): ) +class _InactiveVuMarkDatabaseSettings(BaseSettings): + """Settings for an inactive VuMark database.""" + + target_manager_database_name: str + server_access_key: str + server_secret_key: str + + model_config = SettingsConfigDict( + env_prefix="INACTIVE_VUMARK_VUFORIA_", + env_file=Path("vuforia_secrets.env"), + extra="allow", + ) + + class _VuMarkCloudDatabaseSettings(BaseSettings): """Settings for a VuMark Vuforia database.""" @@ -54,6 +68,15 @@ class _VuMarkCloudDatabaseSettings(BaseSettings): ) +@dataclass(frozen=True) +class InactiveVuMarkCloudDatabase: + """Credentials for an inactive VuMark database.""" + + target_manager_database_name: str = field(repr=False) + server_access_key: str = field(repr=False) + server_secret_key: str = field(repr=False) + + @dataclass(frozen=True) class VuMarkCloudDatabase: """Credentials for the VuMark generation API.""" @@ -96,6 +119,17 @@ def inactive_cloud_database() -> CloudDatabase: ) +@pytest.fixture +def inactive_vumark_database() -> InactiveVuMarkCloudDatabase: + """Return inactive VuMark credentials from environment variables.""" + settings = _InactiveVuMarkDatabaseSettings.model_validate(obj={}) + return InactiveVuMarkCloudDatabase( + target_manager_database_name=settings.target_manager_database_name, + server_access_key=settings.server_access_key, + server_secret_key=settings.server_secret_key, + ) + + @pytest.fixture def vumark_vuforia_database() -> VuMarkCloudDatabase: """Return VuMark VWS credentials from environment variables.""" diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index da74c8838..3aacf286e 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -22,7 +22,10 @@ from mock_vws.database import CloudDatabase, VuMarkDatabase from mock_vws.states import States from mock_vws.target import VuMarkTarget -from tests.mock_vws.fixtures.credentials import VuMarkCloudDatabase +from tests.mock_vws.fixtures.credentials import ( + InactiveVuMarkCloudDatabase, + VuMarkCloudDatabase, +) from tests.mock_vws.utils.retries import RETRY_ON_TOO_MANY_REQUESTS LOGGER = logging.getLogger(name=__name__) @@ -91,12 +94,14 @@ def _enable_use_real_vuforia( working_database: CloudDatabase, inactive_cloud_database: CloudDatabase, vumark_vuforia_database: VuMarkCloudDatabase, + inactive_vumark_database: InactiveVuMarkCloudDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test against the real Vuforia.""" assert monkeypatch assert inactive_cloud_database assert vumark_vuforia_database + assert inactive_vumark_database _delete_all_targets(database_keys=working_database) yield @@ -107,6 +112,7 @@ def _enable_use_mock_vuforia( working_database: CloudDatabase, inactive_cloud_database: CloudDatabase, vumark_vuforia_database: VuMarkCloudDatabase, + inactive_vumark_database: InactiveVuMarkCloudDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test against the in-memory mock Vuforia.""" @@ -130,11 +136,18 @@ def _enable_use_mock_vuforia( vumark_database = _vumark_database( vumark_vuforia_database=vumark_vuforia_database, ) + inactive_vumark_db = VuMarkDatabase( + state=States.PROJECT_INACTIVE, + database_name=inactive_vumark_database.target_manager_database_name, + server_access_key=inactive_vumark_database.server_access_key, + server_secret_key=inactive_vumark_database.server_secret_key, + ) with MockVWS() as mock: mock.add_cloud_database(cloud_database=working_database) mock.add_cloud_database(cloud_database=inactive_cloud_database) mock.add_vumark_database(vumark_database=vumark_database) + mock.add_vumark_database(vumark_database=inactive_vumark_db) yield @@ -144,6 +157,7 @@ def _enable_use_docker_in_memory( working_database: CloudDatabase, inactive_cloud_database: CloudDatabase, vumark_vuforia_database: VuMarkCloudDatabase, + inactive_vumark_database: InactiveVuMarkCloudDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test against mock Vuforia created to be run in a container.""" @@ -170,6 +184,12 @@ def _enable_use_docker_in_memory( vumark_database = _vumark_database( vumark_vuforia_database=vumark_vuforia_database, ) + inactive_vumark_db = VuMarkDatabase( + state=States.PROJECT_INACTIVE, + database_name=inactive_vumark_database.target_manager_database_name, + server_access_key=inactive_vumark_database.server_access_key, + server_secret_key=inactive_vumark_database.server_secret_key, + ) with responses.RequestsMock(assert_all_requests_are_fired=False) as mock: add_flask_app_to_mock( @@ -223,6 +243,11 @@ def _enable_use_docker_in_memory( json=vumark_database.to_dict(), timeout=30, ) + requests.post( + url=vumark_databases_url, + json=inactive_vumark_db.to_dict(), + timeout=30, + ) for vumark_target in vumark_database.vumark_targets: requests.post( url=( @@ -292,10 +317,12 @@ def pytest_collection_modifyitems( ids=[backend.value for backend in list(VuforiaBackend)], ) def fixture_verify_mock_vuforia( + *, request: pytest.FixtureRequest, vuforia_database: CloudDatabase, inactive_cloud_database: CloudDatabase, vumark_vuforia_database: VuMarkCloudDatabase, + inactive_vumark_database: InactiveVuMarkCloudDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test functions which use this fixture are run multiple times. Once @@ -324,6 +351,7 @@ def fixture_verify_mock_vuforia( working_database=vuforia_database, inactive_cloud_database=inactive_cloud_database, vumark_vuforia_database=vumark_vuforia_database, + inactive_vumark_database=inactive_vumark_database, monkeypatch=monkeypatch, ) @@ -338,10 +366,12 @@ def fixture_verify_mock_vuforia( ], ) def mock_only_vuforia( + *, request: pytest.FixtureRequest, vuforia_database: CloudDatabase, inactive_cloud_database: CloudDatabase, vumark_vuforia_database: VuMarkCloudDatabase, + inactive_vumark_database: InactiveVuMarkCloudDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test functions which use this fixture are run multiple times. Once @@ -370,5 +400,6 @@ def mock_only_vuforia( working_database=vuforia_database, inactive_cloud_database=inactive_cloud_database, vumark_vuforia_database=vumark_vuforia_database, + inactive_vumark_database=inactive_vumark_database, monkeypatch=monkeypatch, ) diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 1f5146955..58de8abb5 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -18,7 +18,10 @@ from mock_vws._constants import ResultCodes from mock_vws.database import CloudDatabase -from tests.mock_vws.fixtures.credentials import VuMarkCloudDatabase +from tests.mock_vws.fixtures.credentials import ( + InactiveVuMarkCloudDatabase, + VuMarkCloudDatabase, +) from tests.mock_vws.utils import make_image_file _VWS_HOST = "https://vws.vuforia.com" @@ -341,3 +344,27 @@ def test_processing_target_raw_response( response_json["result_code"] == ResultCodes.TARGET_STATUS_NOT_SUCCESS.value ) + + +@pytest.mark.usefixtures("verify_mock_vuforia") +class TestInactiveDatabase: + """Tests for VuMark generation with an inactive database.""" + + @staticmethod + def test_inactive_database( + inactive_vumark_database: InactiveVuMarkCloudDatabase, + ) -> None: + """Calling the VuMark generation API with credentials for an + inactive database returns ProjectInactive. + """ + response = _make_vumark_request( + server_access_key=inactive_vumark_database.server_access_key, + server_secret_key=inactive_vumark_database.server_secret_key, + target_id=uuid4().hex, + instance_id=uuid4().hex, + accept="image/png", + ) + + assert response.status_code == HTTPStatus.NOT_FOUND + response_json = response.json() + assert response_json["result_code"] == ResultCodes.UNKNOWN_TARGET.value diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index a4099f736..760e0407e 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -19,3 +19,8 @@ VUMARK_VUFORIA_TARGET_ID=examplevumarktargetid VUMARK_VUFORIA_SERVER_ACCESS_KEY=example_vumark_server_access_key VUMARK_VUFORIA_SERVER_SECRET_KEY=example_vumark_server_secret_key + +INACTIVE_VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME=example_inactive_vumark_database_name + +INACTIVE_VUMARK_VUFORIA_SERVER_ACCESS_KEY=example_inactive_vumark_server_access_key +INACTIVE_VUMARK_VUFORIA_SERVER_SECRET_KEY=example_inactive_vumark_server_secret_key From f32ace0cc2feef376c77328c816135db9caa10b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 05:03:38 +0000 Subject: [PATCH 2348/2467] Bump vws-python from 2026.2.22 to 2026.2.23 Bumps [vws-python](https://github.com/VWS-Python/vws-python) from 2026.2.22 to 2026.2.23. - [Release notes](https://github.com/VWS-Python/vws-python/releases) - [Changelog](https://github.com/VWS-Python/vws-python/blob/main/CHANGELOG.rst) - [Commits](https://github.com/VWS-Python/vws-python/compare/2026.02.22...2026.02.23) --- updated-dependencies: - dependency-name: vws-python dependency-version: 2026.2.23 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3b3dcf738..56a91a062 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,7 +104,7 @@ optional-dependencies.dev = [ "types-requests==2.32.4.20260107", "urllib3==2.6.3", "vulture==2.14", - "vws-python==2026.2.22", + "vws-python==2026.2.23", "vws-test-fixtures==2023.3.5", "vws-web-tools==2026.2.22.1", "yamlfix==1.19.1", From b1da9654ee41caadf21f4e7ac2f9b6143b683135 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 05:04:13 +0000 Subject: [PATCH 2349/2467] Bump pyproject-fmt from 2.16.1 to 2.16.2 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.16.1 to 2.16.2. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.16.1...pyproject-fmt/2.16.2) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.16.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3b3dcf738..185e0a51f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.16.1", + "pyproject-fmt==2.16.2", "pyrefly==0.53.0", "pyright==1.1.408", "pyroma==5.0.1", From 6a4826f3856cfbc99ae1bc5f35bc4f34fac4542b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 05:23:41 +0000 Subject: [PATCH 2350/2467] Bump pyrefly from 0.53.0 to 0.54.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.53.0 to 0.54.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.53.0...0.54.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.54.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ccdde33c3..c44651c19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.16.2", - "pyrefly==0.53.0", + "pyrefly==0.54.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From ee3ab8f0b9a44124987efb3c73727f80c8e8218f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 05:04:06 +0000 Subject: [PATCH 2351/2467] Bump vws-python from 2026.2.23 to 2026.2.24 Bumps [vws-python](https://github.com/VWS-Python/vws-python) from 2026.2.23 to 2026.2.24. - [Release notes](https://github.com/VWS-Python/vws-python/releases) - [Changelog](https://github.com/VWS-Python/vws-python/blob/main/CHANGELOG.rst) - [Commits](https://github.com/VWS-Python/vws-python/compare/2026.02.23...2026.02.24) --- updated-dependencies: - dependency-name: vws-python dependency-version: 2026.2.24 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c44651c19..eff6f44e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,7 +104,7 @@ optional-dependencies.dev = [ "types-requests==2.32.4.20260107", "urllib3==2.6.3", "vulture==2.14", - "vws-python==2026.2.23", + "vws-python==2026.2.24", "vws-test-fixtures==2023.3.5", "vws-web-tools==2026.2.22.1", "yamlfix==1.19.1", From ab425f12dbbd655c4e28ab8f2f3c79aabede2995 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 25 Feb 2026 09:26:12 +0000 Subject: [PATCH 2352/2467] Use vws-python 2026.2.25.1 and simplify respx tests (#3019) * Use vws-python 2026.2.25.1 in respx tests * Fix pylint spelling in respx test docstrings --- pyproject.toml | 2 +- tests/mock_vws/test_respx_mock_usage.py | 433 +++++++----------------- 2 files changed, 132 insertions(+), 303 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index eff6f44e5..71393044e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,7 +104,7 @@ optional-dependencies.dev = [ "types-requests==2.32.4.20260107", "urllib3==2.6.3", "vulture==2.14", - "vws-python==2026.2.24", + "vws-python==2026.2.25.1", "vws-test-fixtures==2023.3.5", "vws-web-tools==2026.2.22.1", "yamlfix==1.19.1", diff --git a/tests/mock_vws/test_respx_mock_usage.py b/tests/mock_vws/test_respx_mock_usage.py index 3a5294225..f4fb321bd 100644 --- a/tests/mock_vws/test_respx_mock_usage.py +++ b/tests/mock_vws/test_respx_mock_usage.py @@ -1,346 +1,175 @@ -"""Tests for ``MockVWS`` intercepting ``httpx`` requests.""" +"""Tests for ``MockVWS`` intercepting ``httpx`` via asynchronous ``vws`` +clients. +""" -import json -import socket +import asyncio +import io import uuid -from http import HTTPMethod, HTTPStatus import httpx import pytest -from vws_auth_tools import authorization_header, rfc_1123_date +from vws import AsyncCloudRecoService, AsyncVuMarkService, AsyncVWS +from vws.exceptions.vws_exceptions import UnknownTargetError +from vws.reports import TargetStatuses +from vws.vumark_accept import VuMarkAccept from mock_vws import MockVWS from mock_vws.database import CloudDatabase, VuMarkDatabase +from mock_vws.image_matchers import ExactMatcher from mock_vws.target import VuMarkTarget -def _request_unmocked_address() -> None: - """Make a request using ``httpx`` to an unmocked, free local address. - - Raises: - Exception: A connection error is expected, as there is nothing - to connect to. - """ - sock = socket.socket() - sock.bind(("", 0)) - port = sock.getsockname()[1] - sock.close() - httpx.get(url=f"http://localhost:{port}", timeout=30) - - -def _request_mocked_address() -> None: - """Make a request using ``httpx`` to a mocked Vuforia endpoint.""" - httpx.get( - url="https://vws.vuforia.com/summary", - headers={ - "Date": rfc_1123_date(), - "Authorization": "bad_auth_token", - }, - timeout=30, - ) - - -class TestRealHTTP: - """Tests for making requests to mocked and unmocked addresses.""" - - @staticmethod - def test_default() -> None: - """By default, the mock stops any requests made with ``httpx`` to - non-Vuforia addresses, but not to mocked Vuforia endpoints. - """ - with MockVWS(): - with pytest.raises(expected_exception=httpx.ConnectError): - _request_unmocked_address() - - # No exception is raised when making a request to a mocked - # endpoint. - _request_mocked_address() - - # The mocking stops when the context manager stops. - with pytest.raises(expected_exception=httpx.ConnectError): - _request_unmocked_address() - - @staticmethod - def test_real_http() -> None: - """When the ``real_http`` parameter is ``True``, requests to - unmocked addresses are not stopped. - """ - with ( - MockVWS(real_http=True), - pytest.raises(expected_exception=httpx.ConnectError), - ): - _request_unmocked_address() - - -class TestResponseDelay: - """Tests for the response delay feature.""" - - @staticmethod - def test_default_no_delay() -> None: - """By default, there is no response delay.""" - with MockVWS(): - response = httpx.get( - url="https://vws.vuforia.com/summary", - headers={ - "Date": rfc_1123_date(), - "Authorization": "bad_auth_token", - }, - timeout=0.5, - ) - assert response.status_code is not None +class TestAsyncVWS: + """Asynchronous ``vws-python`` client usage through the mock.""" @staticmethod - def test_delay_causes_timeout() -> None: - """When ``response_delay_seconds`` is set higher than the client - timeout, a ``ReadTimeout`` exception is raised. - """ - with ( - MockVWS(response_delay_seconds=0.5), - pytest.raises(expected_exception=httpx.ReadTimeout), - ): - httpx.get( - url="https://vws.vuforia.com/summary", - headers={ - "Date": rfc_1123_date(), - "Authorization": "bad_auth_token", - }, - timeout=0.1, - ) + def test_response_delay_causes_httpx_timeout() -> None: + """``httpx`` timeouts are surfaced through ``AsyncVWS``.""" + database = CloudDatabase() + calls: list[float] = [] - @staticmethod - def test_delay_allows_completion() -> None: - """When ``response_delay_seconds`` is set lower than the client - timeout, the request completes successfully. - """ - with MockVWS(response_delay_seconds=0.1): - response = httpx.get( - url="https://vws.vuforia.com/summary", - headers={ - "Date": rfc_1123_date(), - "Authorization": "bad_auth_token", - }, - timeout=2.0, - ) - assert response.status_code is not None + async def run_test() -> None: + """Trigger a timed request through the client.""" + async with AsyncVWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + request_timeout_seconds=0.1, + ) as client: + await client.get_database_summary_report() - @staticmethod - def test_custom_sleep_fn_called_on_delay() -> None: - """When a custom ``sleep_fn`` is provided, it is called instead of - ``time.sleep`` for the non-timeout delay path. - """ - calls: list[float] = [] with MockVWS( response_delay_seconds=5.0, sleep_fn=calls.append, - ): - httpx.get( - url="https://vws.vuforia.com/summary", - headers={ - "Date": rfc_1123_date(), - "Authorization": "bad_auth_token", - }, - timeout=30, - ) - assert calls == [5.0] - - @staticmethod - def test_custom_sleep_fn_called_on_timeout() -> None: - """When a custom ``sleep_fn`` is provided, it is called with the - effective timeout when the delay exceeds it. - """ - calls: list[float] = [] - with ( - MockVWS( - response_delay_seconds=5.0, - sleep_fn=calls.append, - ), - pytest.raises(expected_exception=httpx.ReadTimeout), - ): - httpx.get( - url="https://vws.vuforia.com/summary", - headers={ - "Date": rfc_1123_date(), - "Authorization": "bad_auth_token", - }, - timeout=1.0, - ) - assert calls == [1.0] - + processing_time_seconds=0, + ) as mock: + mock.add_cloud_database(cloud_database=database) + with pytest.raises(expected_exception=httpx.ReadTimeout): + asyncio.run(run_test()) -class TestCustomBaseURLs: - """Tests for using custom base URLs.""" + assert calls == [0.1] @staticmethod - def test_custom_base_vws_url() -> None: - """It is possible to use a custom base VWS URL.""" - with MockVWS( - base_vws_url="https://vuforia.vws.example.com", - real_http=False, - ): - with pytest.raises(expected_exception=httpx.ConnectError): - httpx.get(url="https://vws.vuforia.com/summary", timeout=30) + def test_custom_base_vws_url_with_path_prefix() -> None: + """``AsyncVWS`` works with a custom VWS base URL path prefix.""" + database = CloudDatabase() + base_vws_url = "https://vuforia.vws.example.com/prefix" - httpx.get( - url="https://vuforia.vws.example.com/summary", - timeout=30, - ) - httpx.post( - url="https://cloudreco.vuforia.com/v1/query", - timeout=30, - ) + async def run_test() -> str: + """Return the database name via the custom base URL.""" + async with AsyncVWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + base_vws_url=base_vws_url, + ) as client: + report = await client.get_database_summary_report() + return report.name - @staticmethod - def test_custom_base_vwq_url() -> None: - """It is possible to use a custom base cloud recognition URL.""" - with MockVWS( - base_vwq_url="https://vuforia.vwq.example.com", - real_http=False, - ): - with pytest.raises(expected_exception=httpx.ConnectError): - httpx.post( - url="https://cloudreco.vuforia.com/v1/query", - timeout=30, - ) + with MockVWS(base_vws_url=base_vws_url) as mock: + mock.add_cloud_database(cloud_database=database) + database_name = asyncio.run(run_test()) - httpx.post( - url="https://vuforia.vwq.example.com/v1/query", - timeout=30, - ) - httpx.get( - url="https://vws.vuforia.com/summary", - timeout=30, - ) + assert database_name == database.database_name @staticmethod - def test_custom_base_vws_url_with_path_prefix() -> None: - """A custom base VWS URL with a path prefix intercepts at the - prefix. - """ - with MockVWS( - base_vws_url="https://vuforia.vws.example.com/prefix", - real_http=False, - ): - with pytest.raises(expected_exception=httpx.ConnectError): - httpx.get( - url="https://vuforia.vws.example.com/summary", - timeout=30, + def test_add_get_and_delete_target( + image_file_success_state_low_rating: io.BytesIO, + ) -> None: + """A target life cycle works through ``AsyncVWS``.""" + database = CloudDatabase() + target_name = "async-target" + + async def run_test() -> None: + """Exercise the target life cycle.""" + async with AsyncVWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) as client: + target_id = await client.add_target( + name=target_name, + width=1, + image=image_file_success_state_low_rating, + application_metadata=None, + active_flag=True, ) - - httpx.get( - url="https://vuforia.vws.example.com/prefix/summary", - timeout=30, - ) - - @staticmethod - def test_custom_base_vwq_url_with_path_prefix() -> None: - """A custom base VWQ URL with a path prefix intercepts at the - prefix. - """ - with MockVWS( - base_vwq_url="https://vuforia.vwq.example.com/prefix", - real_http=False, - ): - with pytest.raises(expected_exception=httpx.ConnectError): - httpx.post( - url="https://vuforia.vwq.example.com/v1/query", - timeout=30, + await client.wait_for_target_processed(target_id=target_id) + target_record = await client.get_target_record( + target_id=target_id, ) + assert target_record.status == TargetStatuses.SUCCESS + assert target_record.target_record.name == target_name - httpx.post( - url="https://vuforia.vwq.example.com/prefix/v1/query", - timeout=30, - ) + await client.delete_target(target_id=target_id) - @staticmethod - def test_vws_operations_work_with_path_prefix() -> None: - """VWS API operations work correctly with a base URL path - prefix. - """ - database = CloudDatabase() - base_vws_url = "https://vuforia.vws.example.com/prefix" + with pytest.raises(expected_exception=UnknownTargetError): + await client.get_target_record(target_id=target_id) - with MockVWS(base_vws_url=base_vws_url) as mock: + with MockVWS(processing_time_seconds=0) as mock: mock.add_cloud_database(cloud_database=database) + asyncio.run(run_test()) - request_path = "/targets" - date = rfc_1123_date() - auth = authorization_header( - access_key=database.server_access_key, - secret_key=database.server_secret_key, - method="GET", - content=b"", - content_type="", - date=date, - request_path=request_path, - ) - response = httpx.get( - url=base_vws_url + request_path, - headers={ - "Authorization": auth, - "Date": date, - }, - timeout=30, - ) - - assert response.status_code == HTTPStatus.OK - response_json = response.json() - assert response_json["result_code"] == "Success" - assert response_json["results"] == [] - -class TestVWSEndpoints: - """Tests that VWS endpoints are accessible via httpx.""" +class TestAsyncCloudRecoService: + """Asynchronous cloud query usage through the mock.""" @staticmethod - def test_database_summary() -> None: - """The database summary endpoint is accessible via httpx.""" + def test_query_returns_match(high_quality_image: io.BytesIO) -> None: + """``AsyncCloudRecoService`` returns a match via the mock.""" database = CloudDatabase() - with MockVWS() as mock: + + async def run_test() -> None: + """Add a target and query it using the clients.""" + async with ( + AsyncVWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) as vws_client, + AsyncCloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + ) as query_client, + ): + target_id = await vws_client.add_target( + name="query-target", + width=1, + image=high_quality_image, + application_metadata=None, + active_flag=True, + ) + await vws_client.wait_for_target_processed(target_id=target_id) + results = await query_client.query(image=high_quality_image) + assert [result.target_id for result in results] == [target_id] + + with MockVWS( + processing_time_seconds=0, + query_match_checker=ExactMatcher(), + ) as mock: mock.add_cloud_database(cloud_database=database) - response = httpx.get( - url="https://vws.vuforia.com/summary", - headers={ - "Date": rfc_1123_date(), - "Authorization": "bad_auth_token", - }, - timeout=30, - ) - # We just verify we get a response (auth will fail but endpoint works) - assert response.status_code is not None + asyncio.run(run_test()) + + +class TestAsyncVuMarkService: + """Asynchronous VuMark generation usage through the mock.""" @staticmethod - def test_vumark_bytes_response() -> None: - """The VuMark endpoint returns bytes content via httpx.""" + def test_generate_vumark_instance_returns_png_bytes() -> None: + """``AsyncVuMarkService`` returns VuMark image bytes.""" vumark_target = VuMarkTarget(name="test-target") database = VuMarkDatabase(vumark_targets={vumark_target}) - target_id = vumark_target.target_id - request_path = f"/targets/{target_id}/instances" - content_type = "application/json" - content = json.dumps(obj={"instance_id": uuid.uuid4().hex}).encode( - encoding="utf-8" - ) - date = rfc_1123_date() - auth = authorization_header( - access_key=database.server_access_key, - secret_key=database.server_secret_key, - method=HTTPMethod.POST, - content=content, - content_type=content_type, - date=date, - request_path=request_path, - ) + + async def run_test() -> bytes: + """Generate a VuMark instance image and return its bytes.""" + async with AsyncVuMarkService( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) as client: + return await client.generate_vumark_instance( + target_id=vumark_target.target_id, + instance_id=uuid.uuid4().hex, + accept=VuMarkAccept.PNG, + ) + with MockVWS() as mock: mock.add_vumark_database(vumark_database=database) - response = httpx.post( - url="https://vws.vuforia.com" + request_path, - headers={ - "Accept": "image/png", - "Authorization": auth, - "Content-Length": str(object=len(content)), - "Content-Type": content_type, - "Date": date, - }, - content=content, - timeout=30, - ) - assert response.status_code == HTTPStatus.OK + response_content = asyncio.run(run_test()) + + assert response_content.startswith(b"\x89PNG") From 7b2a3036076041de28a1282eae293a4c838ca19e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 25 Feb 2026 10:21:35 +0000 Subject: [PATCH 2353/2467] Use sync clients in respx transport tests (#3020) --- tests/mock_vws/test_respx_mock_usage.py | 182 +++++++++++------------- 1 file changed, 84 insertions(+), 98 deletions(-) diff --git a/tests/mock_vws/test_respx_mock_usage.py b/tests/mock_vws/test_respx_mock_usage.py index f4fb321bd..5db88b2c5 100644 --- a/tests/mock_vws/test_respx_mock_usage.py +++ b/tests/mock_vws/test_respx_mock_usage.py @@ -1,16 +1,16 @@ -"""Tests for ``MockVWS`` intercepting ``httpx`` via asynchronous ``vws`` +"""Tests for ``MockVWS`` intercepting ``httpx`` via synchronous ``vws`` clients. """ -import asyncio import io import uuid import httpx import pytest -from vws import AsyncCloudRecoService, AsyncVuMarkService, AsyncVWS +from vws import VWS, CloudRecoService, VuMarkService from vws.exceptions.vws_exceptions import UnknownTargetError from vws.reports import TargetStatuses +from vws.transports import HTTPXTransport from vws.vumark_accept import VuMarkAccept from mock_vws import MockVWS @@ -19,54 +19,50 @@ from mock_vws.target import VuMarkTarget -class TestAsyncVWS: - """Asynchronous ``vws-python`` client usage through the mock.""" +class TestVWS: + """Synchronous ``vws-python`` client usage through the mock via + ``httpx``. + """ @staticmethod def test_response_delay_causes_httpx_timeout() -> None: - """``httpx`` timeouts are surfaced through ``AsyncVWS``.""" + """``httpx`` timeouts are surfaced through ``VWS``.""" database = CloudDatabase() calls: list[float] = [] - async def run_test() -> None: - """Trigger a timed request through the client.""" - async with AsyncVWS( - server_access_key=database.server_access_key, - server_secret_key=database.server_secret_key, - request_timeout_seconds=0.1, - ) as client: - await client.get_database_summary_report() - with MockVWS( response_delay_seconds=5.0, sleep_fn=calls.append, processing_time_seconds=0, ) as mock: mock.add_cloud_database(cloud_database=database) + client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + request_timeout_seconds=0.1, + transport=HTTPXTransport(), + ) with pytest.raises(expected_exception=httpx.ReadTimeout): - asyncio.run(run_test()) + client.get_database_summary_report() assert calls == [0.1] @staticmethod def test_custom_base_vws_url_with_path_prefix() -> None: - """``AsyncVWS`` works with a custom VWS base URL path prefix.""" + """``VWS`` works with a custom VWS base URL path prefix.""" database = CloudDatabase() base_vws_url = "https://vuforia.vws.example.com/prefix" - async def run_test() -> str: - """Return the database name via the custom base URL.""" - async with AsyncVWS( + with MockVWS(base_vws_url=base_vws_url) as mock: + mock.add_cloud_database(cloud_database=database) + client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, base_vws_url=base_vws_url, - ) as client: - report = await client.get_database_summary_report() - return report.name - - with MockVWS(base_vws_url=base_vws_url) as mock: - mock.add_cloud_database(cloud_database=database) - database_name = asyncio.run(run_test()) + transport=HTTPXTransport(), + ) + report = client.get_database_summary_report() + database_name = report.name assert database_name == database.database_name @@ -74,102 +70,92 @@ async def run_test() -> str: def test_add_get_and_delete_target( image_file_success_state_low_rating: io.BytesIO, ) -> None: - """A target life cycle works through ``AsyncVWS``.""" + """A target life cycle works through ``VWS``.""" database = CloudDatabase() target_name = "async-target" - async def run_test() -> None: - """Exercise the target life cycle.""" - async with AsyncVWS( + with MockVWS(processing_time_seconds=0) as mock: + mock.add_cloud_database(cloud_database=database) + client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, - ) as client: - target_id = await client.add_target( - name=target_name, - width=1, - image=image_file_success_state_low_rating, - application_metadata=None, - active_flag=True, - ) - await client.wait_for_target_processed(target_id=target_id) - target_record = await client.get_target_record( - target_id=target_id, - ) - assert target_record.status == TargetStatuses.SUCCESS - assert target_record.target_record.name == target_name - - await client.delete_target(target_id=target_id) - - with pytest.raises(expected_exception=UnknownTargetError): - await client.get_target_record(target_id=target_id) + transport=HTTPXTransport(), + ) + target_id = client.add_target( + name=target_name, + width=1, + image=image_file_success_state_low_rating, + application_metadata=None, + active_flag=True, + ) + client.wait_for_target_processed(target_id=target_id) + target_record = client.get_target_record(target_id=target_id) + assert target_record.status == TargetStatuses.SUCCESS + assert target_record.target_record.name == target_name - with MockVWS(processing_time_seconds=0) as mock: - mock.add_cloud_database(cloud_database=database) - asyncio.run(run_test()) + client.delete_target(target_id=target_id) + with pytest.raises(expected_exception=UnknownTargetError): + client.get_target_record(target_id=target_id) -class TestAsyncCloudRecoService: - """Asynchronous cloud query usage through the mock.""" + +class TestCloudRecoService: + """Synchronous cloud query usage through the mock via ``httpx``.""" @staticmethod def test_query_returns_match(high_quality_image: io.BytesIO) -> None: - """``AsyncCloudRecoService`` returns a match via the mock.""" + """``CloudRecoService`` returns a match via the mock.""" database = CloudDatabase() - async def run_test() -> None: - """Add a target and query it using the clients.""" - async with ( - AsyncVWS( - server_access_key=database.server_access_key, - server_secret_key=database.server_secret_key, - ) as vws_client, - AsyncCloudRecoService( - client_access_key=database.client_access_key, - client_secret_key=database.client_secret_key, - ) as query_client, - ): - target_id = await vws_client.add_target( - name="query-target", - width=1, - image=high_quality_image, - application_metadata=None, - active_flag=True, - ) - await vws_client.wait_for_target_processed(target_id=target_id) - results = await query_client.query(image=high_quality_image) - assert [result.target_id for result in results] == [target_id] - with MockVWS( processing_time_seconds=0, query_match_checker=ExactMatcher(), ) as mock: mock.add_cloud_database(cloud_database=database) - asyncio.run(run_test()) - - -class TestAsyncVuMarkService: - """Asynchronous VuMark generation usage through the mock.""" + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + transport=HTTPXTransport(), + ) + query_client = CloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + transport=HTTPXTransport(), + ) + target_id = vws_client.add_target( + name="query-target", + width=1, + image=high_quality_image, + application_metadata=None, + active_flag=True, + ) + vws_client.wait_for_target_processed(target_id=target_id) + results = query_client.query(image=high_quality_image) + assert [result.target_id for result in results] == [target_id] + + +class TestVuMarkService: + """Synchronous VuMark generation usage through the mock via + ``httpx``. + """ @staticmethod def test_generate_vumark_instance_returns_png_bytes() -> None: - """``AsyncVuMarkService`` returns VuMark image bytes.""" + """``VuMarkService`` returns VuMark image bytes.""" vumark_target = VuMarkTarget(name="test-target") database = VuMarkDatabase(vumark_targets={vumark_target}) - async def run_test() -> bytes: - """Generate a VuMark instance image and return its bytes.""" - async with AsyncVuMarkService( - server_access_key=database.server_access_key, - server_secret_key=database.server_secret_key, - ) as client: - return await client.generate_vumark_instance( - target_id=vumark_target.target_id, - instance_id=uuid.uuid4().hex, - accept=VuMarkAccept.PNG, - ) - with MockVWS() as mock: mock.add_vumark_database(vumark_database=database) - response_content = asyncio.run(run_test()) + client = VuMarkService( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + transport=HTTPXTransport(), + ) + response_content = client.generate_vumark_instance( + target_id=vumark_target.target_id, + instance_id=uuid.uuid4().hex, + accept=VuMarkAccept.PNG, + ) assert response_content.startswith(b"\x89PNG") From 400231896a1b46f2d8cdd30c43a81999e7b080bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 05:03:13 +0000 Subject: [PATCH 2354/2467] Bump actions/upload-artifact from 6 to 7 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cc06d4323..7ae6a916b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -174,7 +174,7 @@ jobs: echo "name=coverage-data-ci-${{ matrix.python-version }}-${SANITIZED_PATTERN}" >> "$GITHUB_OUTPUT" - name: Upload coverage data - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: ${{ steps.sanitize.outputs.name }} path: .coverage.* @@ -220,7 +220,7 @@ jobs: UV_PYTHON: ${{ matrix.python-version }} - name: Upload coverage data - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: coverage-data-skip-tests-${{ matrix.python-version }} path: .coverage.* @@ -307,7 +307,7 @@ jobs: coverage report - name: Upload HTML report if check failed - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: html-report path: htmlcov From f8b306ae368ea1f630bf60429421840e2fc51901 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 05:04:18 +0000 Subject: [PATCH 2355/2467] Bump ruff from 0.15.2 to 0.15.4 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.2 to 0.15.4. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.2...0.15.4) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 71393044e..ecfc0f099 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ "pytest-xdist==3.8.0", "pyyaml==6.0.3", "requests-mock-flask==2026.2.16", - "ruff==0.15.2", + "ruff==0.15.4", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 4be83107542be687fd5adcfe000a25073c4de35c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 08:53:51 +0000 Subject: [PATCH 2356/2467] Bump ty from 0.0.18 to 0.0.19 (#3024) Bumps [ty](https://github.com/astral-sh/ty) from 0.0.18 to 0.0.19. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.18...0.0.19) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.19 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ecfc0f099..51673bdbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.4", - "ty==0.0.18", + "ty==0.0.19", "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", From 943b5c076f8ef0f177979b25b16a35cae619808b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 08:54:15 +0000 Subject: [PATCH 2357/2467] Bump doccmd from 2026.2.15 to 2026.2.26 (#3023) Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.2.15 to 2026.2.26. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2026.02.15...2026.02.26) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.2.26 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 51673bdbd..f4703216f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==2.0.0", - "doccmd==2026.2.15", + "doccmd==2026.2.26", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", "freezegun==1.5.5", From 7b4ee0bc2016a72e895090078a128f3bfb230e0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 08:54:29 +0000 Subject: [PATCH 2358/2467] Bump actions/download-artifact from 7 to 8 (#3022) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7ae6a916b..bcec038c2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -287,7 +287,7 @@ jobs: enable-cache: true cache-dependency-glob: '**/pyproject.toml' - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: pattern: coverage-data-* merge-multiple: true From 71d7783030f1d31e8b1151a6c96faba426d8fc25 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 1 Mar 2026 21:55:47 +0000 Subject: [PATCH 2359/2467] Bump doccmd to 2026.3.1 Made-with: Cursor --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f4703216f..6ccc29fab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==2.0.0", - "doccmd==2026.2.26", + "doccmd==2026.3.1", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", "freezegun==1.5.5", From 49e360b03f82aed7bc9c0347bf87bbddf3eb4f4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 05:04:08 +0000 Subject: [PATCH 2360/2467] Bump pyproject-fmt from 2.16.2 to 2.17.0 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.16.2 to 2.17.0. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.16.2...pyproject-fmt/2.17.0) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.17.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6ccc29fab..c5fbc66cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.16.2", + "pyproject-fmt==2.17.0", "pyrefly==0.54.0", "pyright==1.1.408", "pyroma==5.0.1", From 4cca3056e270c19827f13c56ad9ab626e8d5e558 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 05:04:33 +0000 Subject: [PATCH 2361/2467] Bump prek from 0.3.3 to 0.3.4 Bumps [prek](https://github.com/j178/prek) from 0.3.3 to 0.3.4. - [Release notes](https://github.com/j178/prek/releases) - [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md) - [Commits](https://github.com/j178/prek/compare/v0.3.3...v0.3.4) --- updated-dependencies: - dependency-name: prek dependency-version: 0.3.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6ccc29fab..6d2387b55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2026.1.12", - "prek==0.3.3", + "prek==0.3.4", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pylint[spelling]==4.0.5", From 14396a75e41d916efd8c2a060ead884a52735fe4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 05:04:10 +0000 Subject: [PATCH 2362/2467] Bump ty from 0.0.19 to 0.0.20 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.19 to 0.0.20. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.19...0.0.20) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.20 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a67252e65..41e3a4fb0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.4", - "ty==0.0.19", + "ty==0.0.20", "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", From 9e7d37bcb709410a1f3fb0465381fec7360f65f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 05:04:27 +0000 Subject: [PATCH 2363/2467] Bump doccmd from 2026.3.1 to 2026.3.2 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.3.1 to 2026.3.2. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2026.03.01...2026.03.02) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.3.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a67252e65..7f56773b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==2.0.0", - "doccmd==2026.3.1", + "doccmd==2026.3.2", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", "freezegun==1.5.5", From 2fdca1ff2f2dd94b59135bbd52f9d4d07d09403e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 08:00:41 +0000 Subject: [PATCH 2364/2467] Bump pyrefly from 0.54.0 to 0.55.0 (#3030) Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.54.0 to 0.55.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.54.0...0.55.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.55.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 666d83a3d..ee4688657 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.17.0", - "pyrefly==0.54.0", + "pyrefly==0.55.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 00793262d6bc603a3718fdb0e4a68510e1141304 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 05:03:51 +0000 Subject: [PATCH 2365/2467] Bump pyproject-fmt from 2.17.0 to 2.18.1 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.17.0 to 2.18.1. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.17.0...pyproject-fmt/2.18.1) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.18.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ee4688657..eb18e4435 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.17.0", + "pyproject-fmt==2.18.1", "pyrefly==0.55.0", "pyright==1.1.408", "pyroma==5.0.1", From ef9a1bef6405e5f614819313be150e0e3a2addb7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 05:03:16 +0000 Subject: [PATCH 2366/2467] Bump docker/setup-qemu-action from 3 to 4 Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-build.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index b7828a7dc..f52716151 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -29,7 +29,7 @@ jobs: persist-credentials: false - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 365d226bc..49d26db1b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -157,7 +157,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Build and push Docker images uses: docker/bake-action@v6.10.0 From 46372c479428f7b82a7f4e96098df9fd99fb55d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 05:04:08 +0000 Subject: [PATCH 2367/2467] Bump vulture from 2.14 to 2.15 Bumps [vulture](https://github.com/jendrikseipp/vulture) from 2.14 to 2.15. - [Release notes](https://github.com/jendrikseipp/vulture/releases) - [Changelog](https://github.com/jendrikseipp/vulture/blob/main/CHANGELOG.md) - [Commits](https://github.com/jendrikseipp/vulture/compare/v2.14...v2.15) --- updated-dependencies: - dependency-name: vulture dependency-version: '2.15' dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index eb18e4435..50e0587a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,7 +103,7 @@ optional-dependencies.dev = [ "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", "urllib3==2.6.3", - "vulture==2.14", + "vulture==2.15", "vws-python==2026.2.25.1", "vws-test-fixtures==2023.3.5", "vws-web-tools==2026.2.22.1", From 9a7c33484bd42de1b227afb302f1c8695035f5b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 05:03:19 +0000 Subject: [PATCH 2368/2467] Bump docker/bake-action from 6.10.0 to 7.0.0 Bumps [docker/bake-action](https://github.com/docker/bake-action) from 6.10.0 to 7.0.0. - [Release notes](https://github.com/docker/bake-action/releases) - [Commits](https://github.com/docker/bake-action/compare/v6.10.0...v7.0.0) --- updated-dependencies: - dependency-name: docker/bake-action dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-build.yml | 4 ++-- .github/workflows/release.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index f52716151..4694697b0 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -35,11 +35,11 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Check Docker bake definition - uses: docker/bake-action@v6.10.0 + uses: docker/bake-action@v7.0.0 with: call: check - name: Build Docker images - uses: docker/bake-action@v6.10.0 + uses: docker/bake-action@v7.0.0 with: push: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 49d26db1b..54a5c015b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -160,7 +160,7 @@ jobs: uses: docker/setup-qemu-action@v4 - name: Build and push Docker images - uses: docker/bake-action@v6.10.0 + uses: docker/bake-action@v7.0.0 with: push: true env: From c0a301f52dd47f93257de361378dfb7128d0ba17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 05:04:32 +0000 Subject: [PATCH 2369/2467] Bump ty from 0.0.20 to 0.0.21 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.20 to 0.0.21. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.20...0.0.21) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.21 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 50e0587a8..561080f2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.4", - "ty==0.0.20", + "ty==0.0.21", "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", From 4977869d0d1cd68547f7f438da2a71c2895783d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 05:15:45 +0000 Subject: [PATCH 2370/2467] Bump docker/setup-buildx-action from 3 to 4 Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-build.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 4694697b0..906e5fe87 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -32,7 +32,7 @@ jobs: uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Check Docker bake definition uses: docker/bake-action@v7.0.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 54a5c015b..7627f0e91 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -154,7 +154,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v4 From 9d0ec5b15877b81452a11df500c0ba4cc3a16974 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 8 Mar 2026 08:33:23 +0000 Subject: [PATCH 2371/2467] Apply pyproject-fmt 2.18.1 formatting fixes (#3036) pyproject-fmt 2.18.1 was bumped in #3032 but the accompanying formatting changes were not applied. This updates the pylint and spelling config keys to use unquoted format (e.g. FORMAT.single-line-if-stmt instead of "FORMAT".single-line-if-stmt) as required by the new version. Made-with: Cursor --- pyproject.toml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 561080f2d..6728b0a93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -193,11 +193,11 @@ lint.pydocstyle.convention = "google" [tool.pylint] # Allow the body of an if to be on the same line as the test if there is no # else. -"FORMAT".single-line-if-stmt = false +FORMAT.single-line-if-stmt = false # Pickle collected data for later comparisons. -"MASTER".persistent = true +MASTER.persistent = true # Use multiple processes to speed up Pylint. -"MASTER".jobs = 0 +MASTER.jobs = 0 # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. # See https://chezsoi.org/lucas/blog/pylint-strict-base-configuration.html. @@ -206,7 +206,7 @@ lint.pydocstyle.convention = "google" # - pylint.extensions.magic_value # - pylint.extensions.while_used # as they seemed to get in the way. -"MASTER".load-plugins = [ +MASTER.load-plugins = [ "pylint_per_file_ignores", "pylint.extensions.bad_builtin", "pylint.extensions.comparison_placement", @@ -227,7 +227,7 @@ lint.pydocstyle.convention = "google" # We ignore invalid names because: # - We want to use generated module names, which may not be valid, but are never seen. # - We want to use global variables in documentation, which may not be uppercase -"MASTER".per-file-ignores = [ +MASTER.per-file-ignores = [ "docs/source/conf.py:invalid-name", "docs/source/doccmd_*.py:invalid-name", "doccmd_README_rst_*.py:invalid-name", @@ -277,12 +277,12 @@ lint.pydocstyle.convention = "google" ] # Spelling dictionary name. Available dictionaries: none. To make it working # install python-enchant package. -"SPELLING".spelling-dict = "en_US" +SPELLING.spelling-dict = "en_US" # A path to a file that contains private dictionary; one word per line. -"SPELLING".spelling-private-dict-file = "spelling_private_dict.txt" +SPELLING.spelling-private-dict-file = "spelling_private_dict.txt" # Tells whether to store unknown words to indicated private dictionary in # --spelling-private-dict-file option instead of raising a message. -"SPELLING".spelling-store-unknown-words = "no" +SPELLING.spelling-store-unknown-words = "no" [tool.check-manifest] ignore = [ From b23f0b5432e2cab6388e5f730dd00e4068b4d5c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Mar 2026 08:33:53 +0000 Subject: [PATCH 2372/2467] Bump docker/login-action from 3 to 4 (#3034) Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7627f0e91..78d5770d7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -148,7 +148,7 @@ jobs: persist-credentials: false - name: Login to DockerHub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} From 03a6e0acb24910f9ac72d86ab0cb4195768a28ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Mar 2026 08:34:56 +0000 Subject: [PATCH 2373/2467] Bump ruff from 0.15.4 to 0.15.5 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.4 to 0.15.5. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.4...0.15.5) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6728b0a93..9404214e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ "pytest-xdist==3.8.0", "pyyaml==6.0.3", "requests-mock-flask==2026.2.16", - "ruff==0.15.4", + "ruff==0.15.5", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From d7ea250b954c98475c9ba3fdae32427811f7dc75 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 10 Mar 2026 15:31:45 +0000 Subject: [PATCH 2374/2467] Update multipart boundary error to match Vuforia (#3044) * Update multipart boundary error message to match Vuforia change Vuforia dropped the "RESTEASY007550: " prefix from the error message for missing multipart boundaries. Co-Authored-By: Claude Opus 4.6 * Update */* boundary error response to match Vuforia (400, text/plain) Vuforia now returns 400 with text/plain;charset=utf-8 instead of 500 with application/json for the no-boundary multipart error. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- src/mock_vws/_query_validators/exceptions.py | 8 +++----- tests/mock_vws/test_query.py | 12 ++++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py index 6417595f4..bb830651b 100644 --- a/src/mock_vws/_query_validators/exceptions.py +++ b/src/mock_vws/_query_validators/exceptions.py @@ -604,10 +604,8 @@ def __init__(self) -> None: raised. """ super().__init__() - self.status_code = HTTPStatus.INTERNAL_SERVER_ERROR - self.response_text = ( - "RESTEASY007550: Unable to get boundary for multipart" - ) + self.status_code = HTTPStatus.BAD_REQUEST + self.response_text = "Unable to get boundary for multipart" date = email.utils.formatdate( timeval=None, @@ -615,7 +613,7 @@ def __init__(self) -> None: usegmt=True, ) self.headers = { - "Content-Type": "application/json", + "Content-Type": "text/plain;charset=utf-8", "Connection": "keep-alive", "Server": "nginx", "Date": date, diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index 0a1102643..aa1461abe 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -179,10 +179,10 @@ class TestContentType: ), ( "*/*", - HTTPStatus.INTERNAL_SERVER_ERROR, - "application/json", + HTTPStatus.BAD_REQUEST, + "text/plain;charset=utf-8", None, - "RESTEASY007550: Unable to get boundary for multipart", + "Unable to get boundary for multipart", ), ( "text/*", @@ -404,12 +404,12 @@ def test_no_boundary( tell_position=requests_response.raw.tell(), content=requests_response.content, ) - expected_text = "RESTEASY007550: Unable to get boundary for multipart" + expected_text = "Unable to get boundary for multipart" assert requests_response.text == expected_text assert_vwq_failure( response=vws_response, - status_code=HTTPStatus.INTERNAL_SERVER_ERROR, - content_type="application/json", + status_code=HTTPStatus.BAD_REQUEST, + content_type="text/plain;charset=utf-8", cache_control=None, www_authenticate=None, connection="keep-alive", From f3ce33d98f52870070846ed187acc71480b403ac Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 10 Mar 2026 15:32:05 +0000 Subject: [PATCH 2375/2467] Rename completion to completion-ci (#3043) Made-with: Cursor --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bcec038c2..3ef0b6799 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -314,7 +314,7 @@ jobs: if: ${{ failure() }} # Final completion check - completion: + completion-ci: needs: [ci-tests, skip-tests, windows-tests, coverage] runs-on: ubuntu-latest if: always() From 4e0eef4e8942da95c41301fc8214e8d8f7942d03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:32:24 +0000 Subject: [PATCH 2376/2467] Bump prek from 0.3.4 to 0.3.5 (#3042) Bumps [prek](https://github.com/j178/prek) from 0.3.4 to 0.3.5. - [Release notes](https://github.com/j178/prek/releases) - [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md) - [Commits](https://github.com/j178/prek/compare/v0.3.4...v0.3.5) --- updated-dependencies: - dependency-name: prek dependency-version: 0.3.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9404214e6..e9d3727d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2026.1.12", - "prek==0.3.4", + "prek==0.3.5", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pylint[spelling]==4.0.5", From 0419350b635fe1b80efc1b68b1c64fefed32ae73 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 10 Mar 2026 16:07:27 +0000 Subject: [PATCH 2377/2467] Switch from Docker Hub to GHCR for image registry (#3046) Update docker-bake.hcl to use ghcr.io/vws-python/ instead of adamtheturtle/ Update release workflow to authenticate with GHCR using GITHUB_TOKEN Update documentation with new GHCR image references Co-authored-by: Claude Haiku 4.5 --- .github/workflows/release.yml | 10 ++++++---- docker-bake.hcl | 12 ++++++------ docs/source/docker.rst | 12 ++++++------ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 78d5770d7..8182d0337 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -139,7 +139,8 @@ jobs: needs: release runs-on: ubuntu-latest - permissions: {} + permissions: + packages: write steps: - uses: actions/checkout@v6 @@ -147,11 +148,12 @@ jobs: ref: ${{ needs.release.outputs.tag }} persist-credentials: false - - name: Login to DockerHub + - name: Login to GHCR uses: docker/login-action@v4 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 diff --git a/docker-bake.hcl b/docker-bake.hcl index 46abc827e..3bed9c2a0 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -15,8 +15,8 @@ target "vws" { inherits = ["_base"] target = "vws" tags = [ - "adamtheturtle/vuforia-vws-mock:latest", - "adamtheturtle/vuforia-vws-mock:${VERSION}", + "ghcr.io/vws-python/vuforia-vws-mock:latest", + "ghcr.io/vws-python/vuforia-vws-mock:${VERSION}", ] } @@ -24,8 +24,8 @@ target "vwq" { inherits = ["_base"] target = "vwq" tags = [ - "adamtheturtle/vuforia-vwq-mock:latest", - "adamtheturtle/vuforia-vwq-mock:${VERSION}", + "ghcr.io/vws-python/vuforia-vwq-mock:latest", + "ghcr.io/vws-python/vuforia-vwq-mock:${VERSION}", ] } @@ -33,7 +33,7 @@ target "target-manager" { inherits = ["_base"] target = "target-manager" tags = [ - "adamtheturtle/vuforia-target-manager-mock:latest", - "adamtheturtle/vuforia-target-manager-mock:${VERSION}", + "ghcr.io/vws-python/vuforia-target-manager-mock:latest", + "ghcr.io/vws-python/vuforia-target-manager-mock:${VERSION}", ] } diff --git a/docs/source/docker.rst b/docs/source/docker.rst index c33b669b7..ab5c2220c 100644 --- a/docs/source/docker.rst +++ b/docs/source/docker.rst @@ -28,19 +28,19 @@ Creating containers --publish 5005:5000 \ --name vuforia-target-manager-mock \ --network vws-bridge-network \ - adamtheturtle/vuforia-target-manager-mock + ghcr.io/vws-python/vuforia-target-manager-mock $ docker run \ --detach \ --publish 5006:5000 \ -e TARGET_MANAGER_BACKEND=vuforia-target-manager-mock:5000 \ --network vws-bridge-network \ - adamtheturtle/vuforia-vws-mock + ghcr.io/vws-python/vuforia-vws-mock $ docker run \ --detach \ --publish 5007:5000 \ -e TARGET_MANAGER_BACKEND=vuforia-target-manager-mock:5000 \ --network vws-bridge-network \ - adamtheturtle/vuforia-vwq-mock + ghcr.io/vws-python/vuforia-vwq-mock Adding a database to the mock target manager @@ -157,9 +157,9 @@ Building images from source $ export REPOSITORY_ROOT="$PWD" $ export DOCKERFILE="$REPOSITORY_ROOT/src/mock_vws/_flask_server/Dockerfile" - $ export TARGET_MANAGER_TAG=adamtheturtle/vuforia-target-manager-mock:latest - $ export VWS_TAG=adamtheturtle/vuforia-vws-mock:latest - $ export VWQ_TAG=adamtheturtle/vuforia-vwq-mock:latest + $ export TARGET_MANAGER_TAG=ghcr.io/vws-python/vuforia-target-manager-mock:latest + $ export VWS_TAG=ghcr.io/vws-python/vuforia-vws-mock:latest + $ export VWQ_TAG=ghcr.io/vws-python/vuforia-vwq-mock:latest $ docker buildx build "$REPOSITORY_ROOT" --file "$DOCKERFILE" --target target-manager --tag "$TARGET_MANAGER_TAG" $ docker buildx build "$REPOSITORY_ROOT" --file "$DOCKERFILE" --target vws --tag "$VWS_TAG" From ae4172eee130a845d3312cb7e6ba1c5382a6132c Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 10 Mar 2026 16:25:12 +0000 Subject: [PATCH 2378/2467] Fix zizmor lint (#3045) * Fix zizmor lint: upgrade to 1.23.1, disable superfluous-actions and secrets-outside-env Made-with: Cursor * Remove secrets-outside-env disable, add environment to jobs that use secrets Made-with: Cursor --- .github/workflows/release.yml | 2 ++ .github/workflows/test.yml | 1 + pyproject.toml | 2 +- zizmor.yml | 2 ++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8182d0337..097227229 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,7 @@ jobs: release: name: Create release runs-on: ubuntu-latest + environment: release permissions: # This is needed for https://github.com/stefanzweifel/git-auto-commit-action. @@ -138,6 +139,7 @@ jobs: name: Publish Docker images needs: release runs-on: ubuntu-latest + environment: dockerhub permissions: packages: write diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ef0b6799..ae8be6bd2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,7 @@ jobs: # CI tests with matrix ci-tests: runs-on: ubuntu-latest + environment: vuforia strategy: fail-fast: false matrix: diff --git a/pyproject.toml b/pyproject.toml index e9d3727d0..506d06267 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,7 +108,7 @@ optional-dependencies.dev = [ "vws-test-fixtures==2023.3.5", "vws-web-tools==2026.2.22.1", "yamlfix==1.19.1", - "zizmor==1.22.0", + "zizmor==1.23.1", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python-mock/" diff --git a/zizmor.yml b/zizmor.yml index f63e179d2..fab119cb0 100644 --- a/zizmor.yml +++ b/zizmor.yml @@ -8,5 +8,7 @@ rules: disable: true dependabot-cooldown: disable: true + superfluous-actions: + disable: true template-injection: disable: true From ac6713258c0b4c72047240fdf37942a66f4ca5a0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 13 Mar 2026 16:35:14 +0000 Subject: [PATCH 2379/2467] Remove unused dependabot/fetch-metadata step --- .github/workflows/dependabot-merge.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/dependabot-merge.yml b/.github/workflows/dependabot-merge.yml index 5238c9f68..f2437640a 100644 --- a/.github/workflows/dependabot-merge.yml +++ b/.github/workflows/dependabot-merge.yml @@ -12,11 +12,6 @@ jobs: runs-on: ubuntu-latest if: github.actor == 'dependabot[bot]' steps: - - name: Dependabot metadata - id: metadata - uses: dependabot/fetch-metadata@v2 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - name: Enable auto-merge for Dependabot PRs run: gh pr merge --auto --merge "$PR_URL" env: From 454e9ccc2f117d170bb793ed4b105285a3a57e5b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Mar 2026 07:08:17 +0000 Subject: [PATCH 2380/2467] Fix test coverage for boundary error handling (#3052) Removed unnecessary guard condition that was always true after commit d7ea250b changed the */* boundary error from INTERNAL_SERVER_ERROR to BAD_REQUEST. Updated stale docstring in test_no_boundary that still referenced INTERNAL_SERVER_ERROR. Co-authored-by: Claude Haiku 4.5 --- tests/mock_vws/test_query.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/mock_vws/test_query.py b/tests/mock_vws/test_query.py index aa1461abe..4c47ad168 100644 --- a/tests/mock_vws/test_query.py +++ b/tests/mock_vws/test_query.py @@ -254,8 +254,7 @@ def test_incorrect_no_boundary( content=requests_response.content, ) - if resp_status_code != HTTPStatus.INTERNAL_SERVER_ERROR: - handle_server_errors(response=vws_response) + handle_server_errors(response=vws_response) repl = "Powered by Jetty://" sub = _JETTY_VERSION_RE.sub @@ -357,10 +356,7 @@ def test_no_boundary( vuforia_database: CloudDatabase, content_type: str, ) -> None: - """ - If no boundary is given, an ``INTERNAL_SERVER_ERROR`` is - returned. - """ + """If no boundary is given, a ``BAD_REQUEST`` is returned.""" image_content = high_quality_image.getvalue() date = rfc_1123_date() request_path = "/v1/query" From 0a35ca7cba9d1db7a0574ff24a84efa6f785e634 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Mar 2026 08:15:00 +0000 Subject: [PATCH 2381/2467] Move release and tag version into env vars to prevent template injection --- .github/workflows/release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 097227229..939cd0fe1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,8 +45,10 @@ jobs: - name: Get the changelog underline id: changelog_underline + env: + RELEASE: ${{ steps.calver.outputs.release }} run: | - underline="$(echo "${{ steps.calver.outputs.release }}" | tr -c '\n' '-')" + underline="$(echo "$RELEASE" | tr -c '\n' '-')" echo "underline=${underline}" >> "$GITHUB_OUTPUT" - name: Update changelog From edbd6880d8f67e67b712b754a8be247c79c3ef1a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Mar 2026 08:26:13 +0000 Subject: [PATCH 2382/2467] Enable template-injection rule in zizmor config Made-with: Cursor --- zizmor.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/zizmor.yml b/zizmor.yml index fab119cb0..863db3458 100644 --- a/zizmor.yml +++ b/zizmor.yml @@ -10,5 +10,3 @@ rules: disable: true superfluous-actions: disable: true - template-injection: - disable: true From bb1edd162a2139439728c917c9338d188b538552 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Mar 2026 08:30:10 +0000 Subject: [PATCH 2383/2467] Fix template-injection in Check steps Made-with: Cursor --- .github/workflows/release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 939cd0fe1..97a07983b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,8 +66,10 @@ jobs: regex: false - name: Check Update changelog was modified + env: + MODIFIED_FILES: ${{ steps.update_changelog.outputs.modifiedFiles }} run: | - if [ "${{ steps.update_changelog.outputs.modifiedFiles }}" = "0" ]; then + if [ "$MODIFIED_FILES" = "0" ]; then echo "Error: No files were modified when updating changelog" exit 1 fi From 457963fb6c92070f9a4aead7d9b356d5e2b53644 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Mar 2026 08:45:44 +0000 Subject: [PATCH 2384/2467] Fix remaining template-injection findings and check-manifest Made-with: Cursor --- .github/workflows/test.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ae8be6bd2..44d2bbd79 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -131,15 +131,16 @@ jobs: cache-dependency-glob: '**/pyproject.toml' - name: Set secrets file - run: | - # See the "CI Setup" document for details of how this was set up. - ci/decrypt_secret.sh - tar xvf "${HOME}"/secrets/secrets.tar - cp ./ci_secrets/vuforia_secrets_${{ strategy.job-index }}.env ./vuforia_secrets.env env: CI_PATTERN: ${{ matrix.ci_pattern }} ENCRYPTED_FILE: secrets.tar.gpg LARGE_SECRET_PASSPHRASE: ${{ secrets.PASSPHRASE_FOR_VUFORIA_SECRETS }} + JOB_INDEX: ${{ strategy.job-index }} + run: | + # See the "CI Setup" document for details of how this was set up. + ci/decrypt_secret.sh + tar xvf "${HOME}"/secrets/secrets.tar + cp ./ci_secrets/vuforia_secrets_${JOB_INDEX}.env ./vuforia_secrets.env # We have seen issues with running out of disk space on test_docker - name: Free Disk Space (Ubuntu) From 07d92f94929fb327990fbe9050f731da54295e24 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Mar 2026 08:57:01 +0000 Subject: [PATCH 2385/2467] fix: quote JOB_INDEX in cp command for shellcheck SC2086 Made-with: Cursor --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 44d2bbd79..323819b55 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -140,7 +140,7 @@ jobs: # See the "CI Setup" document for details of how this was set up. ci/decrypt_secret.sh tar xvf "${HOME}"/secrets/secrets.tar - cp ./ci_secrets/vuforia_secrets_${JOB_INDEX}.env ./vuforia_secrets.env + cp "./ci_secrets/vuforia_secrets_${JOB_INDEX}.env" ./vuforia_secrets.env # We have seen issues with running out of disk space on test_docker - name: Free Disk Space (Ubuntu) From 3068983f9681ee2881db0a63fa6faeec8963e324 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Mar 2026 09:14:21 +0000 Subject: [PATCH 2386/2467] fix: use pull_request.user.login instead of github.actor for bot check (fixes adamtheturtle/literalizer#146) Made-with: Cursor --- .github/workflows/dependabot-merge.yml | 2 +- zizmor.yml | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/dependabot-merge.yml b/.github/workflows/dependabot-merge.yml index 5238c9f68..8a1ac1844 100644 --- a/.github/workflows/dependabot-merge.yml +++ b/.github/workflows/dependabot-merge.yml @@ -10,7 +10,7 @@ permissions: jobs: dependabot: runs-on: ubuntu-latest - if: github.actor == 'dependabot[bot]' + if: github.event.pull_request.user.login == 'dependabot[bot]' steps: - name: Dependabot metadata id: metadata diff --git a/zizmor.yml b/zizmor.yml index fab119cb0..29c9c99f6 100644 --- a/zizmor.yml +++ b/zizmor.yml @@ -4,8 +4,6 @@ rules: disable: true cache-poisoning: disable: true - bot-conditions: - disable: true dependabot-cooldown: disable: true superfluous-actions: From 1748cdf9053880d19fb9f0c0dbb45663e11cbf93 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Mar 2026 05:03:58 +0000 Subject: [PATCH 2387/2467] Bump pyrefly from 0.55.0 to 0.57.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.55.0 to 0.57.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.55.0...0.57.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.57.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 506d06267..bdc9ca10c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.18.1", - "pyrefly==0.55.0", + "pyrefly==0.57.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 2e38e9f6ff3c565309e239942ea6260a54c456f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Mar 2026 05:04:04 +0000 Subject: [PATCH 2388/2467] Bump coverage from 7.13.4 to 7.13.5 Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.13.4 to 7.13.5. - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](https://github.com/coveragepy/coveragepy/compare/7.13.4...7.13.5) --- updated-dependencies: - dependency-name: coverage dependency-version: 7.13.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 506d06267..b696b2146 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "actionlint-py==1.7.11.24", "check-manifest==0.51", "check-wheel-contents==0.6.3", - "coverage==7.13.4", + "coverage==7.13.5", "deptry==0.24.0", "dirty-equals==0.11", "doc8==2.0.0", From b011739304c5b86ec825be8c9287de0603c8914c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Mar 2026 05:24:59 +0000 Subject: [PATCH 2389/2467] Bump ty from 0.0.21 to 0.0.23 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.21 to 0.0.23. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.21...0.0.23) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.23 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a7c8d2e9a..05582cbee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.4", - "ty==0.0.21", + "ty==0.0.23", "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", From d49c70d8f6af10fe82b014eee220a77c4963d110 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 05:03:25 +0000 Subject: [PATCH 2390/2467] Bump pyrefly from 0.57.0 to 0.57.1 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.57.0 to 0.57.1. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.57.0...0.57.1) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.57.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 05582cbee..aac5c217a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.18.1", - "pyrefly==0.57.0", + "pyrefly==0.57.1", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 71d61e0c22f417e80176c4bfe614c14c500f7c8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 05:15:01 +0000 Subject: [PATCH 2391/2467] Bump pyproject-fmt from 2.18.1 to 2.20.0 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.18.1 to 2.20.0. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.18.1...pyproject-fmt/2.20.0) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.20.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index aac5c217a..0faafbd8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.18.1", + "pyproject-fmt==2.20.0", "pyrefly==0.57.1", "pyright==1.1.408", "pyroma==5.0.1", From 7d18e6e9fb4bfec152fef63bfdbb18a36f9cde22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 05:03:39 +0000 Subject: [PATCH 2392/2467] Bump ruff from 0.15.5 to 0.15.7 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.5 to 0.15.7. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.5...0.15.7) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.7 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index aac5c217a..48e5d8f04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ "pytest-xdist==3.8.0", "pyyaml==6.0.3", "requests-mock-flask==2026.2.16", - "ruff==0.15.5", + "ruff==0.15.7", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From b75ca2f3c9651ca107738d16bf776d540c7c6335 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 05:04:03 +0000 Subject: [PATCH 2393/2467] Bump ty from 0.0.23 to 0.0.24 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.23 to 0.0.24. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.23...0.0.24) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.24 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index aac5c217a..5ede63026 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.4", - "ty==0.0.23", + "ty==0.0.24", "types-docker==7.1.0.20260109", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", From 930b36db746b9dbed397daa8077cf706a0339efd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 05:04:24 +0000 Subject: [PATCH 2394/2467] Bump types-docker from 7.1.0.20260109 to 7.1.0.20260322 Bumps [types-docker](https://github.com/typeshed-internal/stub_uploader) from 7.1.0.20260109 to 7.1.0.20260322. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20260322 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5330d2660..fbceb03f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ optional-dependencies.dev = [ "sybil==9.3.0", "tenacity==9.1.4", "ty==0.0.24", - "types-docker==7.1.0.20260109", + "types-docker==7.1.0.20260322", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260107", "urllib3==2.6.3", From 61aff7d4bc95c93076b58d7910833475d4fe02ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 05:03:32 +0000 Subject: [PATCH 2395/2467] Bump types-requests from 2.32.4.20260107 to 2.32.4.20260324 Bumps [types-requests](https://github.com/typeshed-internal/stub_uploader) from 2.32.4.20260107 to 2.32.4.20260324. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.32.4.20260324 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fbceb03f4..a21d9dac2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,7 +101,7 @@ optional-dependencies.dev = [ "ty==0.0.24", "types-docker==7.1.0.20260322", "types-pyyaml==6.0.12.20250915", - "types-requests==2.32.4.20260107", + "types-requests==2.32.4.20260324", "urllib3==2.6.3", "vulture==2.15", "vws-python==2026.2.25.1", From bda029f35ef973f2e720f896e15e06b569e4357f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 05:03:58 +0000 Subject: [PATCH 2396/2467] Bump prek from 0.3.5 to 0.3.8 Bumps [prek](https://github.com/j178/prek) from 0.3.5 to 0.3.8. - [Release notes](https://github.com/j178/prek/releases) - [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md) - [Commits](https://github.com/j178/prek/compare/v0.3.5...v0.3.8) --- updated-dependencies: - dependency-name: prek dependency-version: 0.3.8 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fbceb03f4..d983d852a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2026.1.12", - "prek==0.3.5", + "prek==0.3.8", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pylint[spelling]==4.0.5", From 358fa0f1a37eaa36b87a0cc202820d174010313e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 26 Mar 2026 08:35:04 +0000 Subject: [PATCH 2397/2467] Apply pyproject-fmt 2.20.0 formatting --- pyproject.toml | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0faafbd8d..2b77345dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -329,6 +329,28 @@ indent = 4 keep_full_version = true max_supported_python = "3.13" +[tool.mypy] +strict = true +files = [ "." ] +exclude = [ "build" ] +plugins = [ + "pydantic.mypy", + "mypy_strict_kwargs", +] +follow_untyped_imports = true + +[tool.pyrefly] +search_path = [ + ".", + "src", +] +errors.non-exhaustive-match = "error" + +[tool.pyright] +enableTypeIgnoreComments = false +reportUnnecessaryTypeIgnoreComment = true +typeCheckingMode = "strict" + [tool.pytest] xfail_strict = true log_cli = true @@ -357,28 +379,6 @@ report.exclude_also = [ report.fail_under = 100 report.show_missing = true -[tool.mypy] -strict = true -files = [ "." ] -exclude = [ "build" ] -plugins = [ - "pydantic.mypy", - "mypy_strict_kwargs", -] -follow_untyped_imports = true - -[tool.pyrefly] -search_path = [ - ".", - "src", -] -errors.non-exhaustive-match = "error" - -[tool.pyright] -enableTypeIgnoreComments = false -reportUnnecessaryTypeIgnoreComment = true -typeCheckingMode = "strict" - [tool.pydocstringformatter] write = true split-summary-body = false From 6c543d4ab22b0dba4cefd504131d51443fe0d6f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 08:48:15 +0000 Subject: [PATCH 2398/2467] Bump pyrefly from 0.57.1 to 0.58.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.57.1 to 0.58.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.57.1...0.58.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.58.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3690bbc0b..ce9d89db8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.20.0", - "pyrefly==0.57.1", + "pyrefly==0.58.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 6e20b9b53ae412c05745cf5977ea70e6b36231ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 08:48:19 +0000 Subject: [PATCH 2399/2467] Bump ty from 0.0.24 to 0.0.25 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.24 to 0.0.25. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.24...0.0.25) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.25 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3690bbc0b..75840858b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.4", - "ty==0.0.24", + "ty==0.0.25", "types-docker==7.1.0.20260322", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260324", From 6f67150005b274830992d42c148829293501676a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 26 Mar 2026 13:08:29 +0000 Subject: [PATCH 2400/2467] Bump doccmd to 2026.3.26.2 Made-with: Cursor --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d8194aef2..90e42d4fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==2.0.0", - "doccmd==2026.3.2", + "doccmd==2026.3.26.2", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", "freezegun==1.5.5", From 97a38dd2acee0da9d7ae68324349966ff5d1ec21 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 05:03:33 +0000 Subject: [PATCH 2401/2467] Bump doccmd from 2026.3.2 to 2026.3.26.2 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.3.2 to 2026.3.26.2. - [Release notes](https://github.com/adamtheturtle/doccmd/releases) - [Changelog](https://github.com/adamtheturtle/doccmd/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/doccmd/compare/2026.03.02...2026.03.26.2) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.3.26.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d8194aef2..90e42d4fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "deptry==0.24.0", "dirty-equals==0.11", "doc8==2.0.0", - "doccmd==2026.3.2", + "doccmd==2026.3.26.2", "docker==7.1.0", "enum-tools[sphinx]==0.13.0", "freezegun==1.5.5", From 7fa94de32dd79a3eb233e59d757c15a0c5702880 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 05:04:17 +0000 Subject: [PATCH 2402/2467] Bump ruff from 0.15.7 to 0.15.8 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.7 to 0.15.8. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.7...0.15.8) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.8 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d8194aef2..0f5a7fd92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ "pytest-xdist==3.8.0", "pyyaml==6.0.3", "requests-mock-flask==2026.2.16", - "ruff==0.15.7", + "ruff==0.15.8", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 7335e150f38f192f41a8f641021543cd47981cd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 05:16:23 +0000 Subject: [PATCH 2403/2467] Bump deptry from 0.24.0 to 0.25.1 Bumps [deptry](https://github.com/osprey-oss/deptry) from 0.24.0 to 0.25.1. - [Release notes](https://github.com/osprey-oss/deptry/releases) - [Changelog](https://github.com/osprey-oss/deptry/blob/main/CHANGELOG.md) - [Commits](https://github.com/osprey-oss/deptry/compare/0.24.0...0.25.1) --- updated-dependencies: - dependency-name: deptry dependency-version: 0.25.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 90e42d4fe..e56666040 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "check-wheel-contents==0.6.3", "coverage==7.13.5", - "deptry==0.24.0", + "deptry==0.25.1", "dirty-equals==0.11", "doc8==2.0.0", "doccmd==2026.3.26.2", From 40aa59567d6b691f29b3f51665fccddef3da3e4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 05:16:26 +0000 Subject: [PATCH 2404/2467] Bump ty from 0.0.25 to 0.0.26 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.25 to 0.0.26. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.25...0.0.26) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.26 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 90e42d4fe..161a32767 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "tenacity==9.1.4", - "ty==0.0.25", + "ty==0.0.26", "types-docker==7.1.0.20260322", "types-pyyaml==6.0.12.20250915", "types-requests==2.32.4.20260324", From 0eee524385421bf0aebd34e3589d5852725bef23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 05:34:45 +0000 Subject: [PATCH 2405/2467] Bump sybil from 9.3.0 to 10.0.1 Bumps [sybil](https://github.com/simplistix/sybil) from 9.3.0 to 10.0.1. - [Changelog](https://github.com/simplistix/sybil/blob/main/CHANGELOG.rst) - [Commits](https://github.com/simplistix/sybil/compare/9.3.0...10.0.1) --- updated-dependencies: - dependency-name: sybil dependency-version: 10.0.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 161a32767..9c8d140b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,7 @@ optional-dependencies.dev = [ "sphinx-toolbox==4.1.2", "sphinxcontrib-httpdomain==2.0.0", "sphinxcontrib-spelling==8.0.2", - "sybil==9.3.0", + "sybil==10.0.1", "tenacity==9.1.4", "ty==0.0.26", "types-docker==7.1.0.20260322", From 41f6f62fa5810483c5ae91586771bb8ef2ca349f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 05:34:52 +0000 Subject: [PATCH 2406/2467] Bump types-requests from 2.32.4.20260324 to 2.33.0.20260327 Bumps [types-requests](https://github.com/python/typeshed) from 2.32.4.20260324 to 2.33.0.20260327. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.33.0.20260327 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 161a32767..e1ca16a3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,7 +101,7 @@ optional-dependencies.dev = [ "ty==0.0.26", "types-docker==7.1.0.20260322", "types-pyyaml==6.0.12.20250915", - "types-requests==2.32.4.20260324", + "types-requests==2.33.0.20260327", "urllib3==2.6.3", "vulture==2.15", "vws-python==2026.2.25.1", From 287de9c73241e1644dfc5ae014e85965323a773c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 05:55:47 +0000 Subject: [PATCH 2407/2467] Bump vulture from 2.15 to 2.16 Bumps [vulture](https://github.com/jendrikseipp/vulture) from 2.15 to 2.16. - [Release notes](https://github.com/jendrikseipp/vulture/releases) - [Changelog](https://github.com/jendrikseipp/vulture/blob/main/CHANGELOG.md) - [Commits](https://github.com/jendrikseipp/vulture/compare/v2.15...v2.16) --- updated-dependencies: - dependency-name: vulture dependency-version: '2.16' dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 097bb851e..e24a639b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,7 +103,7 @@ optional-dependencies.dev = [ "types-pyyaml==6.0.12.20250915", "types-requests==2.33.0.20260327", "urllib3==2.6.3", - "vulture==2.15", + "vulture==2.16", "vws-python==2026.2.25.1", "vws-test-fixtures==2023.3.5", "vws-web-tools==2026.2.22.1", From 7b6817e25d72a5a9fde5298175df30d35c0098e1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 29 Mar 2026 07:40:26 +0100 Subject: [PATCH 2408/2467] Harden mock dataclasses with kw_only and freeze route types - CloudDatabase, VuMarkDatabase, ImageTarget, VuMarkTarget: kw_only=True alongside existing frozen equality semantics. - RequestData, Route: kw_only=True for clearer HTTP/route construction. - key_validators._Route: frozen and kw_only as static endpoint metadata. - Test fixtures Endpoint, VuMarkCloudDatabase, InactiveVuMarkCloudDatabase: kw_only=True for multi-field credentials helpers. Made-with: Cursor --- src/mock_vws/_mock_common.py | 4 ++-- src/mock_vws/_services_validators/key_validators.py | 2 +- src/mock_vws/database.py | 4 ++-- src/mock_vws/target.py | 4 ++-- tests/mock_vws/fixtures/credentials.py | 4 ++-- tests/mock_vws/utils/__init__.py | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/mock_vws/_mock_common.py b/src/mock_vws/_mock_common.py index 15c3776ae..2c975d86a 100644 --- a/src/mock_vws/_mock_common.py +++ b/src/mock_vws/_mock_common.py @@ -32,7 +32,7 @@ def __str__(self) -> str: @beartype -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class RequestData: """A library-agnostic representation of an HTTP request. @@ -50,7 +50,7 @@ class RequestData: @beartype -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class Route: """A representation of a VWS route. diff --git a/src/mock_vws/_services_validators/key_validators.py b/src/mock_vws/_services_validators/key_validators.py index 379daaddd..708d3b09d 100644 --- a/src/mock_vws/_services_validators/key_validators.py +++ b/src/mock_vws/_services_validators/key_validators.py @@ -15,7 +15,7 @@ @beartype -@dataclass +@dataclass(frozen=True, kw_only=True) class _Route: """A representation of a VWS route. diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py index 7b27c238a..0d1d46fb1 100644 --- a/src/mock_vws/database.py +++ b/src/mock_vws/database.py @@ -50,7 +50,7 @@ def _random_hex() -> str: @beartype -@dataclass(eq=True, frozen=True) +@dataclass(eq=True, frozen=True, kw_only=True) class CloudDatabase: """Credentials for VWS APIs. @@ -180,7 +180,7 @@ def processing_targets(self) -> set[ImageTarget]: @beartype -@dataclass(eq=True, frozen=True) +@dataclass(eq=True, frozen=True, kw_only=True) class VuMarkDatabase: """Credentials for the VuMark generation API. diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py index 0c567a799..557c0d2be 100644 --- a/src/mock_vws/target.py +++ b/src/mock_vws/target.py @@ -59,7 +59,7 @@ def _time_now() -> datetime.datetime: @beartype(conf=BeartypeConf(is_pep484_tower=True)) -@dataclass(frozen=True, eq=True) +@dataclass(frozen=True, eq=True, kw_only=True) class ImageTarget: """A Vuforia image target as managed in https://developer.vuforia.com/target-manager. @@ -219,7 +219,7 @@ def to_dict(self) -> ImageTargetDict: @beartype(conf=BeartypeConf(is_pep484_tower=True)) -@dataclass(frozen=True, eq=True) +@dataclass(frozen=True, eq=True, kw_only=True) class VuMarkTarget: """ A VuMark target as managed in diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index a9fb73451..ba357b30d 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -68,7 +68,7 @@ class _VuMarkCloudDatabaseSettings(BaseSettings): ) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class InactiveVuMarkCloudDatabase: """Credentials for an inactive VuMark database.""" @@ -77,7 +77,7 @@ class InactiveVuMarkCloudDatabase: server_secret_key: str = field(repr=False) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class VuMarkCloudDatabase: """Credentials for the VuMark generation API.""" diff --git a/tests/mock_vws/utils/__init__.py b/tests/mock_vws/utils/__init__.py index 08764a520..5f48d0664 100644 --- a/tests/mock_vws/utils/__init__.py +++ b/tests/mock_vws/utils/__init__.py @@ -16,7 +16,7 @@ from mock_vws._constants import ResultCodes -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class Endpoint: """Details of endpoints to be called in tests. From fcc7913b900162241aa307fd77704a349f0adf21 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 05:07:46 +0000 Subject: [PATCH 2409/2467] Bump types-docker from 7.1.0.20260322 to 7.1.0.20260328 Bumps [types-docker](https://github.com/python/typeshed) from 7.1.0.20260322 to 7.1.0.20260328. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20260328 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cbddbe044..e7eb51b29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ optional-dependencies.dev = [ "sybil==10.0.1", "tenacity==9.1.4", "ty==0.0.26", - "types-docker==7.1.0.20260322", + "types-docker==7.1.0.20260328", "types-pyyaml==6.0.12.20250915", "types-requests==2.33.0.20260327", "urllib3==2.6.3", From 3844ba9fd6711d43e5606b5aa8d40d94db38f2e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 05:03:46 +0000 Subject: [PATCH 2410/2467] Bump pyproject-fmt from 2.20.0 to 2.21.0 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.20.0 to 2.21.0. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/commits) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.21.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e7eb51b29..26734fa6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.20.0", + "pyproject-fmt==2.21.0", "pyrefly==0.58.0", "pyright==1.1.408", "pyroma==5.0.1", From c526074c141185193e08346ae25fc0d9e61b1aa9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 05:12:36 +0000 Subject: [PATCH 2411/2467] Bump ty from 0.0.26 to 0.0.27 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.26 to 0.0.27. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.26...0.0.27) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.27 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 26734fa6c..adfa99e76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==10.0.1", "tenacity==9.1.4", - "ty==0.0.26", + "ty==0.0.27", "types-docker==7.1.0.20260328", "types-pyyaml==6.0.12.20250915", "types-requests==2.33.0.20260327", From 9ec857b66662ddaf921ecc1d53c902bd7d2b8e38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 05:12:40 +0000 Subject: [PATCH 2412/2467] Bump mypy from 1.19.1 to 1.20.0 Bumps [mypy](https://github.com/python/mypy) from 1.19.1 to 1.20.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.19.1...v1.20.0) --- updated-dependencies: - dependency-name: mypy dependency-version: 1.20.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 26734fa6c..326aa7e7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional-dependencies.dev = [ "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", - "mypy[faster-cache]==1.19.1", + "mypy[faster-cache]==1.20.0", "mypy-strict-kwargs==2026.1.12", "prek==0.3.8", "pydocstringformatter==0.7.5", From 573bb98fb385a7ba3270a446486b379b415de48a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 05:13:07 +0000 Subject: [PATCH 2413/2467] Bump pyrefly from 0.58.0 to 0.59.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.58.0 to 0.59.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.58.0...0.59.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.59.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 26734fa6c..8e8553eca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.21.0", - "pyrefly==0.58.0", + "pyrefly==0.59.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From d9debf958b45e7099decbefb6e4a5e393018548d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 05:13:23 +0000 Subject: [PATCH 2414/2467] Bump actionlint-py from 1.7.11.24 to 1.7.12.24 Bumps [actionlint-py](https://github.com/Mateusz-Grzelinski/actionlint-py) from 1.7.11.24 to 1.7.12.24. - [Commits](https://github.com/Mateusz-Grzelinski/actionlint-py/compare/v1.7.11.24...v1.7.12.24) --- updated-dependencies: - dependency-name: actionlint-py dependency-version: 1.7.12.24 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 26734fa6c..e3f52dc5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ dependencies = [ "werkzeug>=3.1.2", ] optional-dependencies.dev = [ - "actionlint-py==1.7.11.24", + "actionlint-py==1.7.12.24", "check-manifest==0.51", "check-wheel-contents==0.6.3", "coverage==7.13.5", From 7d0f2652158b00c4026af2ed433d3bec94bfd77f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 06:46:03 +0000 Subject: [PATCH 2415/2467] Bump pyrefly from 0.59.0 to 0.59.1 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.59.0 to 0.59.1. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.59.0...0.59.1) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.59.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a2bdf65d0..341d339cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.21.0", - "pyrefly==0.59.0", + "pyrefly==0.59.1", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 13b422dbbbb2dfa9a02469a117e867ca790475a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 06:46:28 +0000 Subject: [PATCH 2416/2467] Bump types-docker from 7.1.0.20260328 to 7.1.0.20260402 Bumps [types-docker](https://github.com/python/typeshed) from 7.1.0.20260328 to 7.1.0.20260402. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20260402 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a2bdf65d0..63f723e2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ optional-dependencies.dev = [ "sybil==10.0.1", "tenacity==9.1.4", "ty==0.0.27", - "types-docker==7.1.0.20260328", + "types-docker==7.1.0.20260402", "types-pyyaml==6.0.12.20250915", "types-requests==2.33.0.20260327", "urllib3==2.6.3", From 65350bd07d63ffcb394a8431d91b01b3b860205e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 07:06:38 +0000 Subject: [PATCH 2417/2467] Bump types-requests from 2.33.0.20260327 to 2.33.0.20260402 Bumps [types-requests](https://github.com/python/typeshed) from 2.33.0.20260327 to 2.33.0.20260402. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.33.0.20260402 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 63f723e2b..d29aa8188 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,7 +101,7 @@ optional-dependencies.dev = [ "ty==0.0.27", "types-docker==7.1.0.20260402", "types-pyyaml==6.0.12.20250915", - "types-requests==2.33.0.20260327", + "types-requests==2.33.0.20260402", "urllib3==2.6.3", "vulture==2.16", "vws-python==2026.2.25.1", From 9f06ef56d36a2762f176c1f202e541bfddc0e505 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 05:03:53 +0000 Subject: [PATCH 2418/2467] Bump ruff from 0.15.8 to 0.15.9 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.8 to 0.15.9. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.8...0.15.9) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.9 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d29aa8188..4d2c5e299 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ "pytest-xdist==3.8.0", "pyyaml==6.0.3", "requests-mock-flask==2026.2.16", - "ruff==0.15.8", + "ruff==0.15.9", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 74e4312d3ca28c30364de9e3e290864e2b47e9e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 05:04:36 +0000 Subject: [PATCH 2419/2467] Bump ty from 0.0.27 to 0.0.28 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.27 to 0.0.28. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.27...0.0.28) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.28 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d29aa8188..b1142352d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==10.0.1", "tenacity==9.1.4", - "ty==0.0.27", + "ty==0.0.28", "types-docker==7.1.0.20260402", "types-pyyaml==6.0.12.20250915", "types-requests==2.33.0.20260402", From 315ca0da03ec55f43add3e29ff9005cd30911c06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 05:16:59 +0000 Subject: [PATCH 2420/2467] Bump requests-mock-flask from 2026.2.16 to 2026.4.2 Bumps [requests-mock-flask](https://github.com/adamtheturtle/requests-mock-flask) from 2026.2.16 to 2026.4.2. - [Release notes](https://github.com/adamtheturtle/requests-mock-flask/releases) - [Changelog](https://github.com/adamtheturtle/requests-mock-flask/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/requests-mock-flask/compare/2026.02.16...2026.04.02) --- updated-dependencies: - dependency-name: requests-mock-flask dependency-version: 2026.4.2 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4d2c5e299..d587eef7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "pytest-retry==1.7.0", "pytest-xdist==3.8.0", "pyyaml==6.0.3", - "requests-mock-flask==2026.2.16", + "requests-mock-flask==2026.4.2", "ruff==0.15.9", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will From 21d26adab76d8548454c0fc6de5f6925a38a650e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 05:26:42 +0000 Subject: [PATCH 2421/2467] Bump types-docker from 7.1.0.20260402 to 7.1.0.20260403 Bumps [types-docker](https://github.com/python/typeshed) from 7.1.0.20260402 to 7.1.0.20260403. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20260403 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2c2cfe3e8..c27224641 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ optional-dependencies.dev = [ "sybil==10.0.1", "tenacity==9.1.4", "ty==0.0.28", - "types-docker==7.1.0.20260402", + "types-docker==7.1.0.20260403", "types-pyyaml==6.0.12.20250915", "types-requests==2.33.0.20260402", "urllib3==2.6.3", From e3547b92baff133ffcc059d959e7fcddcbb65727 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 05:09:08 +0000 Subject: [PATCH 2422/2467] Bump ty from 0.0.28 to 0.0.29 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.28 to 0.0.29. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.28...0.0.29) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.29 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8cfc25f17..5c5a4b317 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==10.0.1", "tenacity==9.1.4", - "ty==0.0.28", + "ty==0.0.29", "types-docker==7.1.0.20260403", "types-pyyaml==6.0.12.20250915", "types-requests==2.33.0.20260402", From 07b9d7d5abe013a23c37fab91952144b1f60c77e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 05:09:17 +0000 Subject: [PATCH 2423/2467] Bump pylint-per-file-ignores from 3.2.0 to 3.2.1 Bumps [pylint-per-file-ignores](https://github.com/SAP/pylint-per-file-ignores) from 3.2.0 to 3.2.1. - [Release notes](https://github.com/SAP/pylint-per-file-ignores/releases) - [Changelog](https://github.com/SAP/pylint-per-file-ignores/blob/main/CHANGELOG.md) - [Commits](https://github.com/SAP/pylint-per-file-ignores/compare/v3.2.0...v3.2.1) --- updated-dependencies: - dependency-name: pylint-per-file-ignores dependency-version: 3.2.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8cfc25f17..ec92a9871 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,7 @@ optional-dependencies.dev = [ "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pylint[spelling]==4.0.5", - "pylint-per-file-ignores==3.2.0", + "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.21.0", "pyrefly==0.59.1", "pyright==1.1.408", From d1144552cc8ecdb3532fe9153fdb8965ff17b0eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 05:04:11 +0000 Subject: [PATCH 2424/2467] Bump pyrefly from 0.59.1 to 0.60.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.59.1 to 0.60.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.59.1...0.60.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.60.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 52aa2b1c6..f912e3294 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.21.0", - "pyrefly==0.59.1", + "pyrefly==0.60.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 29c6638323d849b95af5bd0fe778dd0369dcbaef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 05:03:46 +0000 Subject: [PATCH 2425/2467] Bump types-pyyaml from 6.0.12.20250915 to 6.0.12.20260408 Bumps [types-pyyaml](https://github.com/python/typeshed) from 6.0.12.20250915 to 6.0.12.20260408. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-pyyaml dependency-version: 6.0.12.20260408 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f912e3294..a72d01393 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,7 @@ optional-dependencies.dev = [ "tenacity==9.1.4", "ty==0.0.29", "types-docker==7.1.0.20260403", - "types-pyyaml==6.0.12.20250915", + "types-pyyaml==6.0.12.20260408", "types-requests==2.33.0.20260402", "urllib3==2.6.3", "vulture==2.16", From 2e8116be8944a399e415b90c5cb17f1d972c41bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 05:04:30 +0000 Subject: [PATCH 2426/2467] Bump pytest from 9.0.2 to 9.0.3 Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.2 to 9.0.3. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/9.0.2...9.0.3) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f912e3294..87bbc67e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ optional-dependencies.dev = [ "pyrefly==0.60.0", "pyright==1.1.408", "pyroma==5.0.1", - "pytest==9.0.2", + "pytest==9.0.3", "pytest-retry==1.7.0", "pytest-xdist==3.8.0", "pyyaml==6.0.3", From 6e79e641bea191b03dc34b1ab750e40c454d9ab8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 8 Apr 2026 08:31:54 +0100 Subject: [PATCH 2427/2467] Fix beartype crash on Windows by widening type hint to pytest.Item The pytest_collection_modifyitems hook in vuforia_backends.py had items typed as list[pytest.Function], but Sybil doctest items (SybilItem) are also collected and don't inherit from Function. The @beartype decorator enforced this at runtime, crashing the Windows CI with a BeartypeCallHintParamViolation. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/mock_vws/fixtures/vuforia_backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 3aacf286e..adb281569 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -295,7 +295,7 @@ def pytest_addoption(parser: pytest.Parser) -> None: @beartype def pytest_collection_modifyitems( config: pytest.Config, - items: list[pytest.Function], + items: list[pytest.Item], ) -> None: """Skip Docker tests if requested.""" skip_docker_build_tests_option = "--skip-docker_build_tests" From d4fd2a6a146ffb2c99ab81d650358210413ba013 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 05:04:10 +0000 Subject: [PATCH 2428/2467] Bump types-docker from 7.1.0.20260403 to 7.1.0.20260409 Bumps [types-docker](https://github.com/python/typeshed) from 7.1.0.20260403 to 7.1.0.20260409. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-docker dependency-version: 7.1.0.20260409 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ee497965e..5423bd022 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ optional-dependencies.dev = [ "sybil==10.0.1", "tenacity==9.1.4", "ty==0.0.29", - "types-docker==7.1.0.20260403", + "types-docker==7.1.0.20260409", "types-pyyaml==6.0.12.20260408", "types-requests==2.33.0.20260402", "urllib3==2.6.3", From f98e3e86d9e93b8b7e0495ab6f3fcae8f8426dac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 05:14:42 +0000 Subject: [PATCH 2429/2467] Bump types-requests from 2.33.0.20260402 to 2.33.0.20260408 Bumps [types-requests](https://github.com/python/typeshed) from 2.33.0.20260402 to 2.33.0.20260408. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.33.0.20260408 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5423bd022..99888b24c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,7 +101,7 @@ optional-dependencies.dev = [ "ty==0.0.29", "types-docker==7.1.0.20260409", "types-pyyaml==6.0.12.20260408", - "types-requests==2.33.0.20260402", + "types-requests==2.33.0.20260408", "urllib3==2.6.3", "vulture==2.16", "vws-python==2026.2.25.1", From a37a7fcd1ca16b16428619d5be62f77284833522 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 05:03:47 +0000 Subject: [PATCH 2430/2467] Bump pyrefly from 0.60.0 to 0.60.1 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.60.0 to 0.60.1. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.60.0...0.60.1) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.60.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 99888b24c..2b916bf9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.21.0", - "pyrefly==0.60.0", + "pyrefly==0.60.1", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.3", From bd44a95954f62b335b1bbf38096f205619510c4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 05:04:05 +0000 Subject: [PATCH 2431/2467] Bump ruff from 0.15.9 to 0.15.10 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.9 to 0.15.10. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.9...0.15.10) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.10 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 99888b24c..4858a09c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ "pytest-xdist==3.8.0", "pyyaml==6.0.3", "requests-mock-flask==2026.4.2", - "ruff==0.15.9", + "ruff==0.15.10", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From 6367418966f52b8d7dde163c6b8e8952cf4f36ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 05:10:41 +0000 Subject: [PATCH 2432/2467] Bump docker/bake-action from 7.0.0 to 7.1.0 Bumps [docker/bake-action](https://github.com/docker/bake-action) from 7.0.0 to 7.1.0. - [Release notes](https://github.com/docker/bake-action/releases) - [Commits](https://github.com/docker/bake-action/compare/v7.0.0...v7.1.0) --- updated-dependencies: - dependency-name: docker/bake-action dependency-version: 7.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-build.yml | 4 ++-- .github/workflows/release.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 906e5fe87..70797f6be 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -35,11 +35,11 @@ jobs: uses: docker/setup-buildx-action@v4 - name: Check Docker bake definition - uses: docker/bake-action@v7.0.0 + uses: docker/bake-action@v7.1.0 with: call: check - name: Build Docker images - uses: docker/bake-action@v7.0.0 + uses: docker/bake-action@v7.1.0 with: push: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 97a07983b..1bad745ad 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -168,7 +168,7 @@ jobs: uses: docker/setup-qemu-action@v4 - name: Build and push Docker images - uses: docker/bake-action@v7.0.0 + uses: docker/bake-action@v7.1.0 with: push: true env: From d0f2daef131d2d4c97f85db8e46eecab05184954 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 05:11:28 +0000 Subject: [PATCH 2433/2467] Bump pyrefly from 0.60.1 to 0.60.2 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.60.1 to 0.60.2. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.60.1...0.60.2) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.60.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7c0081e36..be7bb71e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.21.0", - "pyrefly==0.60.1", + "pyrefly==0.60.2", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.3", From 412b3f98208440fcc1f524814cb261dbf79e8224 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 05:04:01 +0000 Subject: [PATCH 2434/2467] Bump pyproject-fmt from 2.21.0 to 2.21.1 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.21.0 to 2.21.1. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.21.0...pyproject-fmt/2.21.1) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.21.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index be7bb71e4..9aad437fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", - "pyproject-fmt==2.21.0", + "pyproject-fmt==2.21.1", "pyrefly==0.60.2", "pyright==1.1.408", "pyroma==5.0.1", From 65da5bdb1978612d09f9fc0d33358655eac46346 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 05:04:19 +0000 Subject: [PATCH 2435/2467] Bump zizmor from 1.23.1 to 1.24.1 Bumps [zizmor](https://github.com/zizmorcore/zizmor) from 1.23.1 to 1.24.1. - [Release notes](https://github.com/zizmorcore/zizmor/releases) - [Changelog](https://github.com/zizmorcore/zizmor/blob/main/docs/release-notes.md) - [Commits](https://github.com/zizmorcore/zizmor/compare/v1.23.1...v1.24.1) --- updated-dependencies: - dependency-name: zizmor dependency-version: 1.24.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index be7bb71e4..6b78396c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,7 +108,7 @@ optional-dependencies.dev = [ "vws-test-fixtures==2023.3.5", "vws-web-tools==2026.2.22.1", "yamlfix==1.19.1", - "zizmor==1.23.1", + "zizmor==1.24.1", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python-mock/" From 91221119debc18bfe34f9e1b9e251f6488fe5a3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 05:04:51 +0000 Subject: [PATCH 2436/2467] Bump prek from 0.3.8 to 0.3.9 Bumps [prek](https://github.com/j178/prek) from 0.3.8 to 0.3.9. - [Release notes](https://github.com/j178/prek/releases) - [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md) - [Commits](https://github.com/j178/prek/compare/v0.3.8...v0.3.9) --- updated-dependencies: - dependency-name: prek dependency-version: 0.3.9 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index be7bb71e4..4ef88fc4a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.20.0", "mypy-strict-kwargs==2026.1.12", - "prek==0.3.8", + "prek==0.3.9", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pylint[spelling]==4.0.5", From 12e41be42fa4b036adea1c82901fa54c4d633889 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 05:15:25 +0000 Subject: [PATCH 2437/2467] Bump pyrefly from 0.60.2 to 0.61.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.60.2 to 0.61.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.60.2...0.61.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.61.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9aad437fd..61ebc0804 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.21.1", - "pyrefly==0.60.2", + "pyrefly==0.61.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.3", From 0a60f31c2b04191655212f8a4d4b2e28a0a862e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 05:24:01 +0000 Subject: [PATCH 2438/2467] Bump mypy from 1.20.0 to 1.20.1 Bumps [mypy](https://github.com/python/mypy) from 1.20.0 to 1.20.1. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.20.0...v1.20.1) --- updated-dependencies: - dependency-name: mypy dependency-version: 1.20.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c16ff6293..4aec5b498 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional-dependencies.dev = [ "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", - "mypy[faster-cache]==1.20.0", + "mypy[faster-cache]==1.20.1", "mypy-strict-kwargs==2026.1.12", "prek==0.3.9", "pydocstringformatter==0.7.5", From c8ccffd6b31baff694437121ae94dd9ad609649f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 05:04:08 +0000 Subject: [PATCH 2439/2467] Bump ty from 0.0.29 to 0.0.30 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.29 to 0.0.30. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.29...0.0.30) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.30 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d7555a057..0fc830a57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==10.0.1", "tenacity==9.1.4", - "ty==0.0.29", + "ty==0.0.30", "types-docker==7.1.0.20260409", "types-pyyaml==6.0.12.20260408", "types-requests==2.33.0.20260408", From 002a05cb0c2280c9bb87ba17bf0a4eada940fe64 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 15 Apr 2026 07:17:49 +0100 Subject: [PATCH 2440/2467] Refactor timeout and body handling to use match statements (#3112) * Refactor timeout and body handling to use match statements Replace isinstance chains with match/case for timeout normalization and request body conversion in _wrap_callback. Closes #3110 Co-Authored-By: Claude Opus 4.6 (1M context) * Use wildcard case for body to preserve original fallthrough behavior The original else branch handled any type, not just bytes(). Using case _ preserves that catch-all semantics. Co-Authored-By: Claude Opus 4.6 (1M context) * Handle (connect, None) timeout tuples correctly Match only int/float read values in the tuple case, so (5, None) falls through to effective = None instead of raising TypeError. Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- .../_requests_mock_server/decorators.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index 26a0b7967..ae699a3a1 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -167,23 +167,25 @@ def wrapped( # requests allows timeout as a (connect, read) # tuple. The delay simulates server response # time, so compare against the read timeout. - if isinstance(timeout, tuple): - timeout = timeout[1] - effective: float | None = None - if isinstance(timeout, (int, float)): - effective = float(timeout) + match timeout: + case (_, int() | float() as read_timeout): + effective: float | None = float(read_timeout) + case int() | float(): + effective = float(timeout) + case _: + effective = None if effective is not None and delay_seconds > effective: sleep_fn(effective) raise requests.exceptions.Timeout - raw_body = request.body - if raw_body is None: - body_bytes = b"" - elif isinstance(raw_body, str): - body_bytes = raw_body.encode(encoding="utf-8") - else: - body_bytes = raw_body + match request.body: + case None: + body_bytes = b"" + case str() as raw_body: + body_bytes = raw_body.encode(encoding="utf-8") + case _: + body_bytes = request.body path = request.path_url if base_path and path.startswith(base_path): From 3af933a0060e4545c9aca02674c28ef93d3d4fa6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 05:03:50 +0000 Subject: [PATCH 2441/2467] Bump ty from 0.0.30 to 0.0.31 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.30 to 0.0.31. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.30...0.0.31) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.31 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b4f421bfe..130303868 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==10.0.1", "tenacity==9.1.4", - "ty==0.0.30", + "ty==0.0.31", "types-docker==7.1.0.20260409", "types-pyyaml==6.0.12.20260408", "types-requests==2.33.0.20260408", From 381bbfc914bc17652872e07032706e1d7426422f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 05:04:10 +0000 Subject: [PATCH 2442/2467] Bump ruff from 0.15.10 to 0.15.11 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.10 to 0.15.11. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.10...0.15.11) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.11 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 130303868..8e1dbbb59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ "pytest-xdist==3.8.0", "pyyaml==6.0.3", "requests-mock-flask==2026.4.2", - "ruff==0.15.10", + "ruff==0.15.11", # We add shellcheck-py not only for shell scripts and shell code blocks, # but also because having it installed means that ``actionlint-py`` will # use it to lint shell commands in GitHub workflow files. From ca5dfd31c35e5202819001da0c8c82fdbbe90e74 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 19 Apr 2026 08:21:05 +0100 Subject: [PATCH 2443/2467] chore: rename deptry pep621_dev_dependency_groups (#3115) Use optional_dependencies_dev_groups (deptry 0.25+). Made-with: Cursor --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8e1dbbb59..293256d0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -311,7 +311,7 @@ ignore = [ ] [tool.deptry] -pep621_dev_dependency_groups = [ +optional_dependencies_dev_groups = [ "dev", "release", ] From 5c9b7a6ab1b9430432e58ceda38d2524e62d8947 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 19 Apr 2026 09:59:59 +0100 Subject: [PATCH 2444/2467] Use pytest-beartype-tests plugin for test beartype Replace manual pytest_collection_modifyitems hooks with the https://github.com/adamtheturtle/pytest-beartype-tests dev dependency. Made-with: Cursor --- conftest.py | 9 --------- pyproject.toml | 4 ++++ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/conftest.py b/conftest.py index 93484cd3e..72eedd78c 100644 --- a/conftest.py +++ b/conftest.py @@ -12,15 +12,6 @@ from tests.mock_vws.utils.retries import RETRY_EXCEPTIONS - -@beartype -def pytest_collection_modifyitems(items: list[pytest.Item]) -> None: - """Apply the beartype decorator to all collected test functions.""" - for item in items: - if isinstance(item, pytest.Function): - item.obj = beartype(obj=item.obj) - - pytest_collect_file = Sybil( parsers=[ DocTestParser(optionflags=ELLIPSIS), diff --git a/pyproject.toml b/pyproject.toml index 293256d0d..8e111ccd8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,7 @@ optional-dependencies.dev = [ "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.3", + "pytest-beartype-tests==2026.4.19.1", "pytest-retry==1.7.0", "pytest-xdist==3.8.0", "pyyaml==6.0.3", @@ -114,6 +115,9 @@ optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python-mock/" urls.Source = "https://github.com/VWS-Python/vws-python-mock" +[dependency-groups] +dev = [] + [tool.setuptools] zip-safe = false package-data.mock_vws = [ From 43b5b5186daeb6df89ce20a736d50adf0c983d74 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 19 Apr 2026 10:10:53 +0100 Subject: [PATCH 2445/2467] Pin pytest-beartype-tests to git revision (Sybil-safe) Use git+https dependency until the next PyPI release includes https://github.com/adamtheturtle/pytest-beartype-tests/commit/bc81d99. --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8e111ccd8..ec141ca87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ optional-dependencies.dev = [ "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.3", - "pytest-beartype-tests==2026.4.19.1", + "pytest-beartype-tests", "pytest-retry==1.7.0", "pytest-xdist==3.8.0", "pyyaml==6.0.3", @@ -145,6 +145,7 @@ fallback_version = "0.0.0" version_scheme = "post-release" [tool.uv] +sources.pytest-beartype-tests = { git = "https://github.com/adamtheturtle/pytest-beartype-tests.git", rev = "bc81d99" } sources.torch = { index = "pytorch-cpu" } sources.torchvision = { index = "pytorch-cpu" } index = [ { name = "pytorch-cpu", url = "https://download.pytorch.org/whl/cpu", explicit = true } ] From f0f8c0d9084cd5a84317267c8bcfb9d537be2cd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 05:14:50 +0000 Subject: [PATCH 2446/2467] Bump pyrefly from 0.61.0 to 0.61.1 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.61.0 to 0.61.1. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.61.0...0.61.1) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.61.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ec141ca87..20037dac5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.21.1", - "pyrefly==0.61.0", + "pyrefly==0.61.1", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.3", From 41d53a5eac10385f554c21c395f0cd8d0a9b267f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 05:04:00 +0000 Subject: [PATCH 2447/2467] Bump pyrefly from 0.61.1 to 0.62.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.61.1 to 0.62.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.61.1...0.62.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.62.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 20037dac5..4652b1cc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.21.1", - "pyrefly==0.61.1", + "pyrefly==0.62.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.3", From 14d7f679c95a120ba203796091f0c7e21285bb2f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 21 Apr 2026 06:33:56 +0100 Subject: [PATCH 2448/2467] Pin pytest-beartype-tests to 2026.4.20 on PyPI (#3118) Replace the temporary [tool.uv.sources] git pin to bc81d99 with the published wheel on PyPI (same Sybil-safe plugin behavior). Made-with: Cursor --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4652b1cc3..032be5cf2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ optional-dependencies.dev = [ "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.3", - "pytest-beartype-tests", + "pytest-beartype-tests==2026.4.20", "pytest-retry==1.7.0", "pytest-xdist==3.8.0", "pyyaml==6.0.3", @@ -145,7 +145,6 @@ fallback_version = "0.0.0" version_scheme = "post-release" [tool.uv] -sources.pytest-beartype-tests = { git = "https://github.com/adamtheturtle/pytest-beartype-tests.git", rev = "bc81d99" } sources.torch = { index = "pytorch-cpu" } sources.torchvision = { index = "pytorch-cpu" } index = [ { name = "pytorch-cpu", url = "https://download.pytorch.org/whl/cpu", explicit = true } ] From b9cb42c355df4c07d3f6fab921bd6c1bad259bf8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 05:35:52 +0000 Subject: [PATCH 2449/2467] Bump ty from 0.0.31 to 0.0.32 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.31 to 0.0.32. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.31...0.0.32) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.32 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 032be5cf2..be1aae5fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==10.0.1", "tenacity==9.1.4", - "ty==0.0.31", + "ty==0.0.32", "types-docker==7.1.0.20260409", "types-pyyaml==6.0.12.20260408", "types-requests==2.33.0.20260408", From 2ae62da519f410815f3338563b0f9f6e13779312 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 05:04:03 +0000 Subject: [PATCH 2450/2467] Bump prek from 0.3.9 to 0.3.10 Bumps [prek](https://github.com/j178/prek) from 0.3.9 to 0.3.10. - [Release notes](https://github.com/j178/prek/releases) - [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md) - [Commits](https://github.com/j178/prek/compare/v0.3.9...v0.3.10) --- updated-dependencies: - dependency-name: prek dependency-version: 0.3.10 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index be1aae5fb..7f1644a75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.20.1", "mypy-strict-kwargs==2026.1.12", - "prek==0.3.9", + "prek==0.3.10", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pylint[spelling]==4.0.5", From fed8001d2fb404ec4b4bc862b55fece69104c65d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 05:14:59 +0000 Subject: [PATCH 2451/2467] Bump mypy from 1.20.1 to 1.20.2 Bumps [mypy](https://github.com/python/mypy) from 1.20.1 to 1.20.2. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.20.1...v1.20.2) --- updated-dependencies: - dependency-name: mypy dependency-version: 1.20.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7f1644a75..4ace332fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional-dependencies.dev = [ "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", - "mypy[faster-cache]==1.20.1", + "mypy[faster-cache]==1.20.2", "mypy-strict-kwargs==2026.1.12", "prek==0.3.10", "pydocstringformatter==0.7.5", From d45c3d6ebdc32e96e155f31b2a38b6ab7de95942 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 22 Apr 2026 08:46:49 +0100 Subject: [PATCH 2452/2467] chore: define uv version once in pre-commit config (#3123) * chore: define uv version once in pre-commit config Made-with: Cursor * [pre-commit.ci lite] apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 98 +++++++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 32 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5993b1976..1dd0c9199 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,8 @@ --- fail_fast: true +.uv_version: &uv_version uv==0.9.5 + # We use system Python, with required dependencies specified in pyproject.toml. # We therefore cannot use those dependencies in pre-commit CI. ci: @@ -108,7 +110,8 @@ repos: language: python types_or: [yaml, python] pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version - id: actionlint name: actionlint @@ -116,7 +119,8 @@ repos: language: python pass_filenames: false types_or: [yaml] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: pydocstringformatter @@ -124,7 +128,8 @@ repos: entry: uv run --extra=dev pydocstringformatter language: python types_or: [python] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: shellcheck @@ -132,7 +137,8 @@ repos: entry: uv run --extra=dev shellcheck --shell=bash language: python types_or: [shell] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: shellcheck-docs @@ -142,7 +148,8 @@ repos: --command="shellcheck --shell=bash --exclude=SC2215" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: shfmt @@ -150,7 +157,8 @@ repos: entry: shfmt --write --space-redirects --indent=4 language: python types_or: [shell] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: shfmt-docs @@ -159,7 +167,8 @@ repos: --no-pad-file --command="shfmt --write --space-redirects --indent=4" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: mypy @@ -169,7 +178,8 @@ repos: language: python types_or: [python, toml] pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version # We do not use --example-workers 0 due to https://github.com/python/mypy/issues/18283 - id: mypy-docs @@ -185,7 +195,8 @@ repos: entry: uv run --extra=dev -m check_manifest language: python pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version - id: pyright name: pyright @@ -194,7 +205,8 @@ repos: language: python types_or: [python, toml] pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version - id: pyright-docs name: pyright-docs @@ -210,7 +222,8 @@ repos: language: python pass_filenames: false types_or: [python] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version - id: ty name: ty @@ -219,7 +232,8 @@ repos: language: python types_or: [python, toml] pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version - id: ty-docs name: ty-docs @@ -228,7 +242,8 @@ repos: check" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version - id: vulture name: vulture @@ -236,7 +251,8 @@ repos: language: python types_or: [python] pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: vulture-docs @@ -245,7 +261,8 @@ repos: language: python types_or: [python] pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: pyroma @@ -254,7 +271,8 @@ repos: language: python pass_filenames: false types_or: [toml] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: deptry @@ -262,7 +280,8 @@ repos: entry: uv run --extra=dev -m deptry src/ language: python pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: pylint @@ -271,7 +290,8 @@ repos: language: python stages: [manual] pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version - id: pylint-docs name: pylint-docs @@ -285,7 +305,8 @@ repos: entry: uv run --extra=dev -m ruff check --fix language: python types_or: [python] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: ruff-check-fix-docs @@ -293,7 +314,8 @@ repos: entry: uv run --extra=dev doccmd --language=python --command="ruff check --fix" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: ruff-format-fix @@ -301,7 +323,8 @@ repos: entry: uv run --extra=dev -m ruff format language: python types_or: [python] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: ruff-format-fix-docs @@ -310,7 +333,8 @@ repos: format" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: doc8 @@ -318,7 +342,8 @@ repos: entry: uv run --extra=dev -m doc8 language: python types_or: [rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: interrogate @@ -334,7 +359,8 @@ repos: entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="interrogate" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: pyproject-fmt-fix @@ -353,7 +379,8 @@ repos: types_or: [rst] stages: [manual] pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version - id: spelling name: spelling @@ -363,7 +390,8 @@ repos: types_or: [rst] stages: [manual] pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version - id: docs name: Build Documentation @@ -371,14 +399,16 @@ repos: language: python stages: [manual] pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version - id: yamlfix name: pyproject-fmt entry: uv run --extra=dev yamlfix language: python types_or: [yaml] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: zizmor @@ -387,7 +417,8 @@ repos: language: python pass_filenames: false types_or: [yaml] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: sphinx-lint @@ -395,7 +426,8 @@ repos: entry: uv run --extra=dev sphinx-lint --enable=all --disable=line-too-long language: python types_or: [rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: pyrefly @@ -405,7 +437,8 @@ repos: language: python types_or: [python, toml] pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version - id: pyrefly-docs name: pyrefly-docs @@ -414,7 +447,8 @@ repos: check" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: + - *uv_version - id: hclfmt name: hclfmt From afd6d0644d1688d64c50ab499b4cf3a81e4451c2 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 22 Apr 2026 13:43:12 +0100 Subject: [PATCH 2453/2467] chore: bump uv to 0.11.7 for pre-commit hooks (#3124) Made-with: Cursor --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1dd0c9199..b6a7e663f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ --- fail_fast: true -.uv_version: &uv_version uv==0.9.5 +.uv_version: &uv_version uv==0.11.7 # We use system Python, with required dependencies specified in pyproject.toml. # We therefore cannot use those dependencies in pre-commit CI. From 5da47e8adad60a98cee3dca6a691fe6339c6f3d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 05:04:38 +0000 Subject: [PATCH 2454/2467] chore(deps-dev): Bump pyright from 1.1.408 to 1.1.409 Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.408 to 1.1.409. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.408...v1.1.409) --- updated-dependencies: - dependency-name: pyright dependency-version: 1.1.409 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4ace332fd..ab51a5161 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ optional-dependencies.dev = [ "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.21.1", "pyrefly==0.62.0", - "pyright==1.1.408", + "pyright==1.1.409", "pyroma==5.0.1", "pytest==9.0.3", "pytest-beartype-tests==2026.4.20", From ae9ddfd414f1ee36c0385e15e86eb21081581d27 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 25 Apr 2026 04:02:20 +0100 Subject: [PATCH 2455/2467] fix: silence pyright 1.1.409 reportPrivateImportUsage on torch.tensor Co-Authored-By: Claude Opus 4.7 (1M context) --- src/mock_vws/image_matchers.py | 14 ++++++++++++-- src/mock_vws/target_raters.py | 7 ++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/mock_vws/image_matchers.py b/src/mock_vws/image_matchers.py index 686957ad2..363bf75bb 100644 --- a/src/mock_vws/image_matchers.py +++ b/src/mock_vws/image_matchers.py @@ -81,7 +81,12 @@ def __call__( second_image_resized = second_image.resize(size=target_size) first_image_np = np.array(object=first_image_resized, dtype=np.float32) - first_image_tensor = torch.tensor(data=first_image_np).float() / 255 + first_image_tensor = ( + torch.tensor( # pyright: ignore[reportPrivateImportUsage] + data=first_image_np, + ).float() + / 255 + ) first_image_tensor = first_image_tensor.view( first_image_resized.size[1], first_image_resized.size[0], @@ -92,7 +97,12 @@ def __call__( object=second_image_resized, dtype=np.float32, ) - second_image_tensor = torch.tensor(data=second_image_np).float() / 255 + second_image_tensor = ( + torch.tensor( # pyright: ignore[reportPrivateImportUsage] + data=second_image_np, + ).float() + / 255 + ) second_image_tensor = second_image_tensor.view( second_image_resized.size[1], second_image_resized.size[0], diff --git a/src/mock_vws/target_raters.py b/src/mock_vws/target_raters.py index 3358ca483..c844db550 100644 --- a/src/mock_vws/target_raters.py +++ b/src/mock_vws/target_raters.py @@ -28,7 +28,12 @@ def _get_brisque_target_tracking_rating(*, image_content: bytes) -> int: image_file = io.BytesIO(initial_bytes=image_content) with Image.open(fp=image_file) as image: image_np = np.array(object=image, dtype=np.float32) - image_tensor = torch.tensor(data=image_np).float() / 255 + image_tensor = ( + torch.tensor( # pyright: ignore[reportPrivateImportUsage] + data=image_np, + ).float() + / 255 + ) image_tensor = image_tensor.view( image.size[1], image.size[0], From 1ec83983c94bb4c423c7dd36bdcfc530f2fc2488 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 07:54:31 +0000 Subject: [PATCH 2456/2467] Bump sphinx from 8.2.3 to 9.1.0 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 8.2.3 to 9.1.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES.rst) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v8.2.3...v9.1.0) --- updated-dependencies: - dependency-name: sphinx dependency-version: 9.1.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4ace332fd..905f1e40b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,7 +88,7 @@ optional-dependencies.dev = [ # use it to lint shell commands in GitHub workflow files. "shellcheck-py==0.11.0.1", "shfmt-py==3.12.0.2", - "sphinx==8.2.3", + "sphinx==9.1.0", "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.2", "sphinx-paramlinks==0.6", From 0aaf0a2dac0e38bcc7e0c1b60bd24b561ca5a90e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 25 Apr 2026 04:02:38 +0100 Subject: [PATCH 2457/2467] Use sphinx-toolbox 4.2.0rc1 for Sphinx 9 compatibility Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/source/conf.py | 2 ++ pyproject.toml | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 72d9d43da..451f60926 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -29,6 +29,8 @@ "enum_tools.autoenum", ] +autodoc_use_legacy_class_based = True + templates_path = ["_templates"] source_suffix = ".rst" master_doc = "index" diff --git a/pyproject.toml b/pyproject.toml index 905f1e40b..bc8d40547 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,7 +94,7 @@ optional-dependencies.dev = [ "sphinx-paramlinks==0.6", "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2026.1.12", - "sphinx-toolbox==4.1.2", + "sphinx-toolbox==4.2.0rc1", "sphinxcontrib-httpdomain==2.0.0", "sphinxcontrib-spelling==8.0.2", "sybil==10.0.1", @@ -427,6 +427,7 @@ ignore_names = [ "autoclass_content", "autoclass_content", "autodoc_member_order", + "autodoc_use_legacy_class_based", "copybutton_exclude", "extensions", "html_show_copyright", From fa2bf77c64c0615e1a3b988c2778858543e785d0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 25 Apr 2026 04:07:06 +0100 Subject: [PATCH 2458/2467] Document why autodoc_use_legacy_class_based is set Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/source/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 451f60926..dc3968ca9 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -29,6 +29,8 @@ "enum_tools.autoenum", ] +# Required by sphinx-toolbox 4.2.0rc1 for compatibility with Sphinx 9. +# See https://github.com/sphinx-toolbox/sphinx-toolbox/issues/201#issuecomment-4313483053. autodoc_use_legacy_class_based = True templates_path = ["_templates"] From 457e0fbec522c9339f5b23d118c4d46117eeb1c9 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 25 Apr 2026 04:17:11 +0100 Subject: [PATCH 2459/2467] Temporarily remove enum_tools usage enum_tools.autoenum is incompatible with Sphinx 9. See https://github.com/domdfcoding/enum_tools/issues/118. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/source/conf.py | 1 - docs/source/mock-api-reference.rst | 4 ++-- pyproject.toml | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index dc3968ca9..760f0dde1 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -26,7 +26,6 @@ "sphinxcontrib.spelling", "sphinxcontrib.autohttp.flask", "sphinx_toolbox.more_autodoc.autoprotocol", - "enum_tools.autoenum", ] # Required by sphinx-toolbox 4.2.0rc1 for compatibility with Sphinx 9. diff --git a/docs/source/mock-api-reference.rst b/docs/source/mock-api-reference.rst index 1b2ea255a..19a6185c4 100644 --- a/docs/source/mock-api-reference.rst +++ b/docs/source/mock-api-reference.rst @@ -25,11 +25,11 @@ API Reference :undoc-members: :exclude-members: to_dict, from_dict, not_deleted_targets -.. autoenum:: mock_vws.states.States +.. autoclass:: mock_vws.states.States :members: :undoc-members: -.. autoenum:: mock_vws.database_type.DatabaseType +.. autoclass:: mock_vws.database_type.DatabaseType :members: :undoc-members: diff --git a/pyproject.toml b/pyproject.toml index bc8d40547..eba475b4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,6 @@ optional-dependencies.dev = [ "doc8==2.0.0", "doccmd==2026.3.26.2", "docker==7.1.0", - "enum-tools[sphinx]==0.13.0", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From 5c71072dd8298a523c9ee6d76bbcb759702b9104 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 25 Apr 2026 05:24:45 +0100 Subject: [PATCH 2460/2467] Unquote type annotations in create_secrets_files (#3126) Co-authored-by: Claude Opus 4.7 (1M context) --- admin/create_secrets_files.py | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 739094098..a3fdfaf5f 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -8,15 +8,11 @@ import sys import textwrap from pathlib import Path -from typing import TYPE_CHECKING import vws_web_tools from selenium.common.exceptions import TimeoutException - -if TYPE_CHECKING: - from selenium.webdriver.remote.webdriver import WebDriver - from vws_web_tools import DatabaseDict, VuMarkDatabaseDict - +from selenium.webdriver.remote.webdriver import WebDriver +from vws_web_tools import DatabaseDict, VuMarkDatabaseDict VUMARK_TEMPLATE_SVG_FILE_PATH = Path(__file__).with_name( name="vumark_template.svg", @@ -24,12 +20,12 @@ def _create_and_get_cloud_database_details( - driver: "WebDriver", + driver: WebDriver, email_address: str, password: str, cloud_license_name: str, cloud_database_name: str, -) -> "DatabaseDict": +) -> DatabaseDict: """Create a cloud database and get its details. Returns database details. @@ -57,9 +53,9 @@ def _create_and_get_cloud_database_details( def _create_and_get_vumark_details( - driver: "WebDriver", + driver: WebDriver, vumark_database_name: str, -) -> "VuMarkDatabaseDict": +) -> VuMarkDatabaseDict: """Create a VuMark database and get its details. Returns VuMark database details. @@ -76,10 +72,10 @@ def _create_and_get_vumark_details( def _generate_secrets_file_content( - cloud_database_details: "DatabaseDict", - vumark_details: "VuMarkDatabaseDict", - inactive_database_details: "DatabaseDict", - inactive_vumark_details: "VuMarkDatabaseDict", + cloud_database_details: DatabaseDict, + vumark_details: VuMarkDatabaseDict, + inactive_database_details: DatabaseDict, + inactive_vumark_details: VuMarkDatabaseDict, vumark_target_id: str, ) -> str: """Generate the content of a secrets file.""" @@ -110,7 +106,7 @@ def _generate_secrets_file_content( def _create_and_get_vumark_target_id( - driver: "WebDriver", + driver: WebDriver, vumark_database_name: str, vumark_template_name: str, ) -> str: @@ -130,12 +126,12 @@ def _create_and_get_vumark_target_id( def _create_and_get_inactive_database_details( - driver: "WebDriver", + driver: WebDriver, email_address: str, password: str, cloud_license_name: str, cloud_database_name: str, -) -> "DatabaseDict": +) -> DatabaseDict: """Create a cloud database, get its details, then delete the license to make it inactive. """ @@ -164,12 +160,12 @@ def _create_and_get_inactive_database_details( def _create_and_get_inactive_vumark_details( - driver: "WebDriver", + driver: WebDriver, email_address: str, password: str, vumark_license_name: str, vumark_database_name: str, -) -> "VuMarkDatabaseDict": +) -> VuMarkDatabaseDict: """Create a VuMark database, get its details, then delete the license to make it inactive. From b99d99b86ff4914ab8f337d6e515bf1d1f69caf4 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 25 Apr 2026 05:25:06 +0100 Subject: [PATCH 2461/2467] Ignore .claude/scheduled_tasks.lock (#3127) --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 556e31308..a008c6937 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,5 @@ secrets.tar src/*/_setuptools_scm_version.txt uv.lock + +.claude/scheduled_tasks.lock From 12091066258ea44b23bdd96417d6092c1fe013ef Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 25 Apr 2026 22:02:15 +0100 Subject: [PATCH 2462/2467] Make test_tests_collected_once actually verify coverage (#3130) * Fix test_tests_collected_once collecting nothing Use a pytest plugin to record collected node IDs directly instead of parsing captured stdout, and disable pytest-retry in the nested run so it does not exit with INTERNAL_ERROR. Also remove a stray breakpoint and assert that the baseline collection is non-empty. Co-Authored-By: Claude Opus 4.7 (1M context) * Add missing test files to CI matrix The matrix in test.yml was missing entries for several test files and docs, so those tests were never run in CI. Add them now that test_tests_collected_once actually verifies coverage. Co-Authored-By: Claude Opus 4.7 (1M context) * Use a single **/*.rst entry in the CI matrix Replaces the per-file rst entries with a glob. Enable bash globstar in the workflow run step so the pattern expands, and expand globs in the linter helper since pytest.main is invoked without a shell. Co-Authored-By: Claude Opus 4.7 (1M context) * Use docs/ as a single CI matrix entry instead of per-rst entries Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .github/workflows/test.yml | 5 +++- ci/test_custom_linters.py | 59 +++++++++++++++++++++----------------- pyproject.toml | 1 + 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 323819b55..1b61c7ff8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -113,11 +113,14 @@ jobs: - tests/mock_vws/test_update_target.py::TestWidth - tests/mock_vws/test_update_target.py::TestInactiveProject - tests/mock_vws/test_requests_mock_usage.py + - tests/mock_vws/test_respx_mock_usage.py - tests/mock_vws/test_flask_app_usage.py - tests/mock_vws/test_vumark_generation_api.py + - tests/mock_vws/test_target_validators.py - tests/mock_vws/test_docker.py + - ci/test_custom_linters.py - README.rst - - docs/source/basic-example.rst + - docs/ steps: - uses: actions/checkout@v6 diff --git a/ci/test_custom_linters.py b/ci/test_custom_linters.py index c02fcacc6..4911e8fd9 100644 --- a/ci/test_custom_linters.py +++ b/ci/test_custom_linters.py @@ -1,15 +1,11 @@ """Custom lint tests.""" from pathlib import Path -from typing import TYPE_CHECKING import pytest import yaml from beartype import beartype -if TYPE_CHECKING: - from collections.abc import Iterable - @beartype def _ci_patterns(*, repository_root: Path) -> set[str]: @@ -23,32 +19,44 @@ def _ci_patterns(*, repository_root: Path) -> set[str]: return ci_patterns +class _CollectPlugin: + """Pytest plugin that records the node IDs of collected items.""" + + def __init__(self) -> None: + """Start with an empty set of collected node IDs.""" + self.collected: set[str] = set() + + def pytest_itemcollected(self, item: pytest.Item) -> None: + """Record each collected item's node ID.""" + self.collected.add(item.nodeid) + + @beartype -def _tests_from_pattern( - *, - ci_pattern: str, - capsys: pytest.CaptureFixture[str], -) -> set[str]: +def _tests_from_pattern(*, ci_pattern: str) -> set[str]: """From a CI pattern, get all tests ``pytest`` would collect.""" - # Clear the captured output. - capsys.readouterr() - tests: Iterable[str] = set() + plugin = _CollectPlugin() pytest.main( args=[ "-q", "--collect-only", - # If there are any warnings, these obscure the output. + # Disable pytest-retry to avoid: + # ``` + # ValueError: no option named 'filtered_exceptions' + # ``` + # which causes the nested run to exit with INTERNAL_ERROR + # before any items are collected. + "-p", + "no:pytest-retry", + # Disable warnings to avoid many instances of: + # ``` + # Unknown config option: retry_delay + # ``` "--disable-warnings", ci_pattern, ], + plugins=[plugin], ) - data = capsys.readouterr().out - for line in data.splitlines(): - # We filter empty lines and lines which look like - # "9 tests collected in 0.01s". - if line and "collected in" not in line: - tests = {*tests, line} - return set(tests) + return plugin.collected def test_ci_patterns_valid(request: pytest.FixtureRequest) -> None: @@ -82,20 +90,18 @@ def test_ci_patterns_valid(request: pytest.FixtureRequest) -> None: assert collect_only_result == 0, message -def test_tests_collected_once( - *, - capsys: pytest.CaptureFixture[str], - request: pytest.FixtureRequest, -) -> None: +def test_tests_collected_once(request: pytest.FixtureRequest) -> None: """Each test in the test suite is collected exactly once. This does not necessarily mean that they are run - they may be skipped. """ ci_patterns = _ci_patterns(repository_root=request.config.rootpath) + all_tests = _tests_from_pattern(ci_pattern=".") + assert all_tests tests_to_patterns: dict[str, set[str]] = {} for pattern in ci_patterns: - tests = _tests_from_pattern(ci_pattern=pattern, capsys=capsys) + tests = _tests_from_pattern(ci_pattern=pattern) for test in tests: if test in tests_to_patterns: tests_to_patterns[test].add(pattern) @@ -110,6 +116,5 @@ def test_tests_collected_once( ) assert len(patterns) == 1, message - all_tests = _tests_from_pattern(ci_pattern=".", capsys=capsys) assert tests_to_patterns.keys() - all_tests == set() assert all_tests - tests_to_patterns.keys() == set() diff --git a/pyproject.toml b/pyproject.toml index eba475b4e..4ef7618a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -417,6 +417,7 @@ ignore_names = [ # pytest configuration "pytest_collect_file", "pytest_collection_modifyitems", + "pytest_itemcollected", "pytest_plugins", "pytest_set_filtered_exceptions", "pytest_addoption", From 0aaacf5ab07c871fd24e18d241aba0a15a85ef37 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 26 Apr 2026 08:31:54 +0100 Subject: [PATCH 2463/2467] Include ci/ in coverage source for 100% requirement (#3131) * Include ci/ in coverage source Co-Authored-By: Claude Opus 4.7 (1M context) * Use setdefault to collect patterns per test Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- ci/test_custom_linters.py | 5 +---- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/ci/test_custom_linters.py b/ci/test_custom_linters.py index 4911e8fd9..77ff03cb1 100644 --- a/ci/test_custom_linters.py +++ b/ci/test_custom_linters.py @@ -103,10 +103,7 @@ def test_tests_collected_once(request: pytest.FixtureRequest) -> None: for pattern in ci_patterns: tests = _tests_from_pattern(ci_pattern=pattern) for test in tests: - if test in tests_to_patterns: - tests_to_patterns[test].add(pattern) - else: - tests_to_patterns[test] = {pattern} + tests_to_patterns.setdefault(test, set()).add(pattern) for test_name, patterns in tests_to_patterns.items(): message = ( diff --git a/pyproject.toml b/pyproject.toml index 4ef7618a7..63b0eeac3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -374,7 +374,7 @@ run.omit = [ "src/mock_vws/_flask_server/healthcheck.py", ] run.parallel = true -run.source = [ "src/", "tests/" ] +run.source = [ "ci/", "src/", "tests/" ] report.exclude_also = [ "class .*\\bProtocol\\):", "if TYPE_CHECKING:", From b08336a99d87c5a23d3d851a59845c0ff32072fb Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 26 Apr 2026 08:57:04 +0100 Subject: [PATCH 2464/2467] fix: allowlist reportPrivateImportUsage in pylint spelling dict Co-Authored-By: Claude Opus 4.7 (1M context) --- spelling_private_dict.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index b1ad02e08..51024a882 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -89,6 +89,7 @@ reportAssignmentType reportAttributeAccessIssue reportGeneralTypeIssues reportMissingTypeStubs +reportPrivateImportUsage reportUnknownArgumentType reportUnknownMemberType reportUnknownVariableType From 39e5296df519e1f1e5d99b987f3a7349baea6e38 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 26 Apr 2026 16:22:53 +0100 Subject: [PATCH 2465/2467] fix: disable pytest-beartype-tests in nested collection runs (#3132) Avoids https://github.com/beartype/beartype/issues/637 which prevented ci/test_custom_linters.py from passing on Python 3.14. Co-authored-by: Claude Opus 4.7 (1M context) --- ci/test_custom_linters.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ci/test_custom_linters.py b/ci/test_custom_linters.py index 77ff03cb1..3ba81680b 100644 --- a/ci/test_custom_linters.py +++ b/ci/test_custom_linters.py @@ -47,6 +47,14 @@ def _tests_from_pattern(*, ci_pattern: str) -> set[str]: # before any items are collected. "-p", "no:pytest-retry", + # Disable pytest-beartype-tests to avoid + # https://github.com/beartype/beartype/issues/637 — wrapping + # collected items with @beartype installs a buggy + # __annotate_beartype__ closure on the underlying test + # function, which crashes a subsequent nested collection on + # Python 3.14. + "-p", + "no:pytest_beartype_tests", # Disable warnings to avoid many instances of: # ``` # Unknown config option: retry_delay @@ -78,6 +86,14 @@ def test_ci_patterns_valid(request: pytest.FixtureRequest) -> None: # ```` "-p", "no:pytest-retry", + # Disable pytest-beartype-tests to avoid + # https://github.com/beartype/beartype/issues/637 — + # wrapping collected items with @beartype installs a + # buggy __annotate_beartype__ closure on the underlying + # test function, which crashes a subsequent nested + # collection on Python 3.14. + "-p", + "no:pytest_beartype_tests", # Disable warnings to avoid many instances of: # ``` # Unknown config option: retry_delay From 5f767ae06f742b6b0a000e4e74c80421b7abdaa9 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 26 Apr 2026 19:12:07 +0100 Subject: [PATCH 2466/2467] Support Python 3.14 (#2871) * Bump sphinx from 8.2.3 to 9.1.0 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 8.2.3 to 9.1.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES.rst) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v8.2.3...v9.1.0) --- updated-dependencies: - dependency-name: sphinx dependency-version: 9.1.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Bump doc8 * Support Python 3.14 * Don't bump Sphinx * Fix secrets file lookup for multi-Python matrix Use modulo arithmetic to map job index to secrets file index, ensuring that adding Python versions to the matrix doesn't break the secrets file lookup. Each test pattern maps to the same secrets file regardless of Python version. The number of secrets files is determined dynamically from the extracted tarball. * Support only 3.14 * Fix ruff * Revert "Fix ruff" This reverts commit 9a342f4fca65057fed2911553986affd84f92d0d. * Ignore ruff TC rules until beartype supports TYPE_CHECKING Beartype requires imports to be available at runtime, not just under `if TYPE_CHECKING`. See https://github.com/beartype/beartype/discussions/594 for when beartype 0.23 will add support for TYPE_CHECKING imports. Co-Authored-By: Claude Opus 4.5 * Remove runtime beartype application to test functions Python 3.14's deferred annotation evaluation (PEP 649) causes `_Stringifier.__format__` to raise a TypeError when beartype inspects function signatures at test collection time. This is a workaround until beartype 0.23 is released with full Python 3.14 support. See: - https://github.com/beartype/beartype/discussions/594 - https://github.com/beartype/beartype/pull/440 Co-Authored-By: Claude Opus 4.5 * Drop pytest-beartype-tests due to Python 3.14 annotation issue * [pre-commit.ci lite] apply automatic fixes * Apply ruff format (PEP 758 except syntax) * Restore pytest-beartype-tests; disable plugin in repeated pytest.main calls * Ignore .claude/scheduled_tasks.lock * Add 'stringify' to spelling dict * Use real subprocess for pytest collection to avoid plugin state accumulation * Add new tests to CI matrix; exclude meta-tests from collected-once check * Restore Python 3.14 in test workflow matrix after merge * Use real subprocess in test_custom_linters to avoid beartype state accumulation * Add 'subprocess' to spelling dict * Re-apply subprocess collection: in-process approach fails on Python 3.14 full-suite runs * Bump pytest-beartype-tests to 2026.4.26; revert subprocess workaround in ci/test_custom_linters.py * Run coverage tool on Python 3.14 to parse PEP 758 except syntax * Set Python 3.14 via setup-uv python-version on coverage job * Drop comment on coverage job python-version --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- .github/workflows/publish-site.yml | 2 +- .github/workflows/test.yml | 7 ++++--- README.rst | 2 +- pyproject.toml | 14 ++++++++++---- spelling_private_dict.txt | 2 ++ src/mock_vws/_flask_server/Dockerfile | 2 +- src/mock_vws/_flask_server/healthcheck.py | 2 +- src/mock_vws/target_raters.py | 2 +- 9 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 51da7df5a..bdac5ec0b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: build: strategy: matrix: - python-version: ['3.13'] + python-version: ['3.14'] platform: [ubuntu-latest, windows-latest] hook-stage: [pre-commit, pre-push, manual] diff --git a/.github/workflows/publish-site.yml b/.github/workflows/publish-site.yml index 4ca927ccb..8172787fb 100644 --- a/.github/workflows/publish-site.yml +++ b/.github/workflows/publish-site.yml @@ -22,6 +22,6 @@ jobs: with: documentation_path: docs/source pyproject_extras: dev - python_version: '3.13' + python_version: '3.14' sphinx_build_options: -W publish: ${{ github.ref_name == 'main' }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1b61c7ff8..3b7938570 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.13'] + python-version: ['3.14'] ci_pattern: - tests/mock_vws/test_query.py::TestContentType - tests/mock_vws/test_query.py::TestSuccess @@ -191,7 +191,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.13'] + python-version: ['3.14'] platform: [ubuntu-latest] steps: @@ -237,7 +237,7 @@ jobs: runs-on: windows-latest strategy: matrix: - python-version: ['3.13'] + python-version: ['3.14'] steps: - uses: actions/checkout@v6 @@ -291,6 +291,7 @@ jobs: with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' + python-version: '3.14' - uses: actions/download-artifact@v8 with: diff --git a/README.rst b/README.rst index 81e350071..f49e6924f 100644 --- a/README.rst +++ b/README.rst @@ -76,4 +76,4 @@ This includes details on how to use the mock, options, and details of the differ :target: https://github.com/VWS-Python/vws-python-mock/actions .. |PyPI| image:: https://badge.fury.io/py/VWS-Python-Mock.svg :target: https://badge.fury.io/py/VWS-Python-Mock -.. |minimum-python-version| replace:: 3.13 +.. |minimum-python-version| replace:: 3.14 diff --git a/pyproject.toml b/pyproject.toml index 4f00572d7..d496ab87d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ license = "MIT" authors = [ { name = "Adam Dangoor", email = "adamdangoor@gmail.com" }, ] -requires-python = ">=3.13" +requires-python = ">=3.14" classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", @@ -28,7 +28,7 @@ classifiers = [ "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] dynamic = [ "version", @@ -76,7 +76,7 @@ optional-dependencies.dev = [ "pyright==1.1.409", "pyroma==5.0.1", "pytest==9.0.3", - "pytest-beartype-tests==2026.4.20", + "pytest-beartype-tests==2026.4.26", "pytest-retry==1.7.0", "pytest-xdist==3.8.0", "pyyaml==6.0.3", @@ -166,6 +166,12 @@ lint.ignore = [ # Ignore 'too-many-*' errors as they seem to get in the way more than # helping. "PLR0913", + # Beartype requires imports to be available at runtime, not just for type + # checking. See https://github.com/beartype/beartype/discussions/594 + # for when beartype will support `if TYPE_CHECKING` imports. + "TC001", + "TC002", + "TC003", ] lint.per-file-ignores."ci/test_custom_linters.py" = [ # Allow asserts in tests. @@ -330,7 +336,7 @@ per_rule_ignores.DEP002 = [ [tool.pyproject-fmt] indent = 4 keep_full_version = true -max_supported_python = "3.13" +max_supported_python = "3.14" [tool.mypy] strict = true diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index 51024a882..d4ddc4a7e 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -104,6 +104,8 @@ respx rfc rgb str +stringify +subprocess timestamp todo travis diff --git a/src/mock_vws/_flask_server/Dockerfile b/src/mock_vws/_flask_server/Dockerfile index bbae92ccc..d81b96c5c 100644 --- a/src/mock_vws/_flask_server/Dockerfile +++ b/src/mock_vws/_flask_server/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/astral-sh/uv:0.10.4-python3.13-trixie-slim AS base +FROM ghcr.io/astral-sh/uv:0.11.7-python3.14-trixie-slim AS base # We set this pretend version as we do not have Git in our path, and we do # not care enough about having the version correct inside the Docker container # to install it. diff --git a/src/mock_vws/_flask_server/healthcheck.py b/src/mock_vws/_flask_server/healthcheck.py index dbc5dee4b..62cfbb971 100644 --- a/src/mock_vws/_flask_server/healthcheck.py +++ b/src/mock_vws/_flask_server/healthcheck.py @@ -15,7 +15,7 @@ def flask_app_healthy(port: int) -> bool: try: conn.request(method="GET", url="/some-random-endpoint") response = conn.getresponse() - except (TimeoutError, http.client.HTTPException, socket.gaierror): + except TimeoutError, http.client.HTTPException, socket.gaierror: return False finally: conn.close() diff --git a/src/mock_vws/target_raters.py b/src/mock_vws/target_raters.py index c844db550..bc28ce529 100644 --- a/src/mock_vws/target_raters.py +++ b/src/mock_vws/target_raters.py @@ -42,7 +42,7 @@ def _get_brisque_target_tracking_rating(*, image_content: bytes) -> int: image_tensor = image_tensor.permute(2, 0, 1).unsqueeze(dim=0) try: brisque_score = brisque(x=image_tensor, data_range=255) - except (AssertionError, IndexError): + except AssertionError, IndexError: return 0 return math.ceil(int(brisque_score.item()) / 20) From 6f6ae43fad1868486a1867d7412f0cd96c17fc02 Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sun, 26 Apr 2026 18:13:06 +0000 Subject: [PATCH 2467/2467] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9387cdc62..5578d136b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.04.26 +---------- + + 2026.02.22.3 ------------