From 4726f68c8a8e1a9b49210a134cf305632108ee5f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 11:06:13 +0000 Subject: [PATCH 001/688] 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 6152cd7c7..e49d7a2cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ optional-dependencies.dev = [ "pytest==8.3.5", "pytest-cov==6.0.0", "pyyaml==6.0.2", - "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 f2e0c7c2e141f8a05f95928c48e5144ef8a41e36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Mar 2025 10:47:37 +0000 Subject: [PATCH 002/688] 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 e49d7a2cf..fca1e2784 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,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", "pygments==2.19.1", From 4adb1a576b1c5daf2d0e3dc60306f2295045727b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Mar 2025 10:47:43 +0000 Subject: [PATCH 003/688] 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 e49d7a2cf..5a49a2558 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "check-manifest==0.50", "deptry==0.23.0", "doc8==1.1.2", - "doccmd==2025.3.6", + "doccmd==2025.3.18", "docformatter==1.7.5", "freezegun==1.5.1", "furo==2024.8.6", From b12abb58534eb3ab88855c6e8b2ab1aed0133c57 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 19 Mar 2025 15:10:20 +0000 Subject: [PATCH 004/688] Pass filenames to shellcheck --- .pre-commit-config.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cf9bf80da..c480de3e9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -91,7 +91,6 @@ repos: name: shellcheck entry: uv run --extra=dev shellcheck --shell=bash language: python - pass_filenames: false types_or: [shell] additional_dependencies: [uv==0.6.3] From 9b6b8e08d285e3055743a8783e8d913bc6e9ca7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Mar 2025 10:53:46 +0000 Subject: [PATCH 005/688] 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 a14c92787..1e1580858 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,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 e72c2c9108ef72e9b6e593005188323e30db48ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 10:29:12 +0000 Subject: [PATCH 006/688] 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 1e1580858..047e4cfb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ optional-dependencies.dev = [ "pytest==8.3.5", "pytest-cov==6.0.0", "pyyaml==6.0.2", - "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 54d083cd8954d614c5e4f71c35517bd2b942ae19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 10:29:20 +0000 Subject: [PATCH 007/688] 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 1e1580858..ded6b9ed3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pyenchant==3.3.0rc1", "pygments==2.19.1", - "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 8d3c781fd34f93d8ad772954d981ad513b26a99d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 21 Mar 2025 13:00:47 +0000 Subject: [PATCH 008/688] Make it easier to change the build matrix while still keeping builds required --- .github/workflows/ci.yml | 12 ++++++++++++ .github/workflows/lint.yml | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c8d881e8..deab0867e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,3 +42,15 @@ jobs: with: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} + + completion-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 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index def06bdb9..7637891d2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -41,3 +41,15 @@ jobs: - uses: pre-commit-ci/lite-action@v1.1.0 if: always() + + completion-lint: + 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 2cfb3e3c11a27c181dc7d1d4a470ff97f0f9cdab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 11:20:52 +0000 Subject: [PATCH 009/688] 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 7a22e6b7e..6bd8f4825 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ optional-dependencies.dev = [ "pytest==8.3.5", "pytest-cov==6.0.0", "pyyaml==6.0.2", - "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 dc420554ddc1757501a0b70904d1b92eb8b6bd9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:37:35 +0000 Subject: [PATCH 010/688] 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 6bd8f4825..b41b4f468 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,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 e6017b7db627213fbc64516c356580c0896ed4f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Mar 2025 10:34:51 +0000 Subject: [PATCH 011/688] 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 b41b4f468..9290e1518 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,7 +75,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.3.3", "sphinxcontrib-spelling==8.0.1", "sybil==9.1.0", - "types-requests==2.32.0.20250306", + "types-requests==2.32.0.20250328", "vulture==2.14", "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.5", From b3cf41f56fbdc562f9e04b3c342665d3941871be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Mar 2025 10:35:02 +0000 Subject: [PATCH 012/688] 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 b41b4f468..6543f851b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "check-manifest==0.50", "deptry==0.23.0", "doc8==1.1.2", - "doccmd==2025.3.18", + "doccmd==2025.3.27", "docformatter==1.7.5", "freezegun==1.5.1", "furo==2024.8.6", From 00a7c8676a86820aa6d047d9f03d91cfd3f8de9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 10:48:55 +0000 Subject: [PATCH 013/688] 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 986e51bd8..df66297c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,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 b50071fcca3104aa06524f84ec73fbec2f56879f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Apr 2025 10:44:58 +0000 Subject: [PATCH 014/688] 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 df66297c2..2a18a426c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,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", "pyyaml==6.0.2", "ruff==0.11.2", # We add shellcheck-py not only for shell scripts and shell code blocks, From e72346e10bd38700bb2f49b4610f5f078d8fc1b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 10:12:35 +0000 Subject: [PATCH 015/688] 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 2a18a426c..662260b43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ optional-dependencies.dev = [ "pytest==8.3.5", "pytest-cov==6.1.0", "pyyaml==6.0.2", - "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 5ad4fe25aa60b45b703a5b3a9657602873010393 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 10:12:42 +0000 Subject: [PATCH 016/688] 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 2a18a426c..beee172d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.0", "sphinx-pyproject==0.3.0", - "sphinx-substitution-extensions==2025.3.3", + "sphinx-substitution-extensions==2025.4.3", "sphinxcontrib-spelling==8.0.1", "sybil==9.1.0", "types-requests==2.32.0.20250328", From dabb34e50db11a6912db9c0fc1286a0495286efd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 10:12:57 +0000 Subject: [PATCH 017/688] 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 2a18a426c..84e54941b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,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 040c9eeccac0cd8ef228d627e36411b5906e241b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 10:13:03 +0000 Subject: [PATCH 018/688] Bump doccmd from 2025.3.27 to 2025.4.4 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.3.27 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.03.27...2025.04.04) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.4.4 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 2a18a426c..422ab9f68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "check-manifest==0.50", "deptry==0.23.0", "doc8==1.1.2", - "doccmd==2025.3.27", + "doccmd==2025.4.4", "docformatter==1.7.5", "freezegun==1.5.1", "furo==2024.8.6", From 2ec8751c80a7ef45b939df84de8c20083c3d34dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 10:51:32 +0000 Subject: [PATCH 019/688] 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 341882fa4..97b4c3e7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ optional-dependencies.dev = [ "pytest==8.3.5", "pytest-cov==6.1.0", "pyyaml==6.0.2", - "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 8db63d5b94862506847d3efaa6e9f8da8978c1f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 10:51:41 +0000 Subject: [PATCH 020/688] 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 341882fa4..6c9f0d12c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,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", "pyyaml==6.0.2", "ruff==0.11.3", # We add shellcheck-py not only for shell scripts and shell code blocks, From ec21fce34aee093aead46b1ad726a236e0769141 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 10:34:43 +0000 Subject: [PATCH 021/688] Bump doccmd from 2025.4.4 to 2025.4.8 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.4.4 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.04...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 cd478f131..067d3f4c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "check-manifest==0.50", "deptry==0.23.0", "doc8==1.1.2", - "doccmd==2025.4.4", + "doccmd==2025.4.8", "docformatter==1.7.5", "freezegun==1.5.1", "furo==2024.8.6", From 418146ff50c32be014dc9748007bdc8785ab7f22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Apr 2025 10:38:17 +0000 Subject: [PATCH 022/688] 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 067d3f4c9..73eac8540 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,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 306665dc8e554a5f595994bb94076340d1be1cc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Apr 2025 10:14:21 +0000 Subject: [PATCH 023/688] 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 73eac8540..100a2c377 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ optional-dependencies.dev = [ "pytest==8.3.5", "pytest-cov==6.1.1", "pyyaml==6.0.2", - "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 ec36c614377ced346eafc5fbcd60933354642e69 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 12 Apr 2025 19:23:13 +0100 Subject: [PATCH 024/688] Remove now-unnecessary wheel build dependency --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 100a2c377..ce2a2666f 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 998576a319065d038011f70d463f08f57d9a5036 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:29:12 +0000 Subject: [PATCH 025/688] 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 ce2a2666f..890c2c280 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.3.5", "pytest-cov==6.1.1", "pyyaml==6.0.2", - "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 39afe719ca7d60c1a0f986a31a59e07bf97c7ddc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 10:13:00 +0000 Subject: [PATCH 026/688] 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 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index deab0867e..de333f893 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,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/lint.yml b/.github/workflows/lint.yml index 7637891d2..6f0dbc9c3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -26,7 +26,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 e44c847b8..f6c1579b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,7 +34,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' From 2abed5615454e92e75860dfce6e2bea7e394e374 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 10:39:27 +0000 Subject: [PATCH 027/688] 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 890c2c280..6ef7ac533 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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 53de68b91751b97faa33ae0dbb1e610fe28efde0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 10:39:46 +0000 Subject: [PATCH 028/688] 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 890c2c280..3ae74da5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.3.5", "pytest-cov==6.1.1", "pyyaml==6.0.2", - "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 4cba7d419f816ff19758e37410e1a03220c6c37a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 May 2025 10:22:12 +0000 Subject: [PATCH 029/688] 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 e27b18f9f..5f212ceca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.3.5", "pytest-cov==6.1.1", "pyyaml==6.0.2", - "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 76fd44eea02dc6c946168c5a5dfa4a3d3c2fcf71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 11:02:39 +0000 Subject: [PATCH 030/688] 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 5f212ceca..f16860469 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pyenchant==3.3.0rc1", "pygments==2.19.1", - "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 1e4c2e963e940e1dec4c949e67ee15dff12881f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 May 2025 10:43:45 +0000 Subject: [PATCH 031/688] 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 f16860469..8f9f74c7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "deptry==0.23.0", "doc8==1.1.2", "doccmd==2025.4.8", - "docformatter==1.7.5", + "docformatter==1.7.6", "freezegun==1.5.1", "furo==2024.8.6", "interrogate==1.7.0", From af28076c7e70b030e0cf2a1c06a458b2af8cc913 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 11:01:20 +0000 Subject: [PATCH 032/688] 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 8f9f74c7e..277ad86e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "deptry==0.23.0", "doc8==1.1.2", "doccmd==2025.4.8", - "docformatter==1.7.6", + "docformatter==1.7.7", "freezegun==1.5.1", "furo==2024.8.6", "interrogate==1.7.0", From fd81b24e6b3501e3d260bacc5641804081708285 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 11:01:32 +0000 Subject: [PATCH 033/688] 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 8f9f74c7e..f9a34c814 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.3.5", "pytest-cov==6.1.1", "pyyaml==6.0.2", - "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 a19799a23b5466fe7ee1d6d5040ed42ef8d3d9b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 11:04:15 +0000 Subject: [PATCH 034/688] 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8f9f74c7e..dcd082c94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "vws-test-fixtures==2023.3.5", "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/" urls.Source = "https://github.com/VWS-Python/vws-python" From 5f8ad966b3ac68bade989ad2f093752f98501182 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 10:44:27 +0000 Subject: [PATCH 035/688] 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 c5d3cade9..d508b335a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.4.3", "sphinxcontrib-spelling==8.0.1", "sybil==9.1.0", - "types-requests==2.32.0.20250328", + "types-requests==2.32.0.20250515", "vulture==2.14", "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.5", From f04d94b6da99ed1c7bb65dec7a178157c6bb41c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 10:31:32 +0000 Subject: [PATCH 036/688] 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 d508b335a..2b71c538f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.3.5", "pytest-cov==6.1.1", "pyyaml==6.0.2", - "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 32a39e3ba98ef33c3ee9b1caff19e50d06dd6fd8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 May 2025 10:09:34 +0000 Subject: [PATCH 037/688] 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 2b71c538f..08cb0d1c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.19.1", "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 320ed1403fa37b5330d1c8df3b2ac22adcc002f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 May 2025 10:47:32 +0000 Subject: [PATCH 038/688] 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 08cb0d1c9..5dc79e599 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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 30c5ae6fa01165054793fbf35bdb9a907257b74a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 11:01:39 +0000 Subject: [PATCH 039/688] 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 5dc79e599..20f21b24a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.3.5", "pytest-cov==6.1.1", "pyyaml==6.0.2", - "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 e3f229789ef5da8f1756bea87e2307837c229bd4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 11:34:30 +0000 Subject: [PATCH 040/688] 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 20f21b24a..dfa815588 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ optional-dependencies.dev = [ "doc8==1.1.2", "doccmd==2025.4.8", "docformatter==1.7.7", - "freezegun==1.5.1", + "freezegun==1.5.2", "furo==2024.8.6", "interrogate==1.7.0", "mypy[faster-cache]==1.15.0", From a0b72e8b98ea64283fb6630da853f9ae53cfed7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 10:59:33 +0000 Subject: [PATCH 041/688] 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 dfa815588..1a456d8a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,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 a60a495a201a64b309644432baf75adeec6b2be8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 10:59:47 +0000 Subject: [PATCH 042/688] 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 dfa815588..fd479b4c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.3.5", "pytest-cov==6.1.1", "pyyaml==6.0.2", - "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 218f53a6d3e65d98fb60932767ba21ae56241c22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 11:40:40 +0000 Subject: [PATCH 043/688] 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 d8f6a0845..efbc9623e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.4.3", "sphinxcontrib-spelling==8.0.1", "sybil==9.1.0", - "types-requests==2.32.0.20250515", + "types-requests==2.32.0.20250602", "vulture==2.14", "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.5", From 3362607d3b2a10b625fffe173cddeab4335c414b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 10:56:44 +0000 Subject: [PATCH 044/688] 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 efbc9623e..db6e18781 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,7 @@ optional-dependencies.dev = [ "pyproject-fmt==2.6.0", "pyright==1.1.401", "pyroma==4.2", - "pytest==8.3.5", + "pytest==8.4.0", "pytest-cov==6.1.1", "pyyaml==6.0.2", "ruff==0.11.12", From be1e4289974cb9a63a6570fa123cd0ec06628eda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Jun 2025 10:06:12 +0000 Subject: [PATCH 045/688] 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 db6e18781..f7dd6b0ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.4.0", "pytest-cov==6.1.1", "pyyaml==6.0.2", - "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 96072081932596e48d21cb80314c1c6513fc64de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:31:11 +0000 Subject: [PATCH 046/688] 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 f7dd6b0ac..476388c07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,7 @@ optional-dependencies.dev = [ "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.0", "sphinx-pyproject==0.3.0", - "sphinx-substitution-extensions==2025.4.3", + "sphinx-substitution-extensions==2025.6.6", "sphinxcontrib-spelling==8.0.1", "sybil==9.1.0", "types-requests==2.32.0.20250602", From 39c2e64574f274bfc28ddbbf719e743c3c1f8cc4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:38:55 +0000 Subject: [PATCH 047/688] 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 476388c07..5fce596b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.6.6", "sphinxcontrib-spelling==8.0.1", "sybil==9.1.0", - "types-requests==2.32.0.20250602", + "types-requests==2.32.4.20250611", "vulture==2.14", "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.5", From 7aeff5abcede031c688ded0ada5227934ea33940 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:39:19 +0000 Subject: [PATCH 048/688] 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 476388c07..7e57056b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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.4.0", "pytest-cov==6.1.1", From a548ac23414494751aa35542c546c56835464011 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:57:42 +0000 Subject: [PATCH 049/688] 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 f6c1579b4..c00447040 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,7 +63,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 6928b370bdeb08a10dd1b5959e7f0b7d796c2041 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 10:56:43 +0000 Subject: [PATCH 050/688] Bump pytest-cov from 6.1.1 to 6.2.1 Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 6.1.1 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.1.1...v6.2.1) --- updated-dependencies: - dependency-name: pytest-cov dependency-version: 6.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 15250e07c..084501226 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pyright==1.1.402", "pyroma==4.2", "pytest==8.4.0", - "pytest-cov==6.1.1", + "pytest-cov==6.2.1", "pyyaml==6.0.2", "ruff==0.11.13", # We add shellcheck-py not only for shell scripts and shell code blocks, From 50b63b1c22b31e1de5d6545618b9d83a168c800a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 14 Jun 2025 08:52:48 +0100 Subject: [PATCH 051/688] Accept all IO[bytes] as image type --- CHANGELOG.rst | 3 +-- src/vws/query.py | 5 ++--- src/vws/vws.py | 5 ++--- tests/conftest.py | 4 ++-- tests/test_query.py | 21 ++++++++++----------- tests/test_vws.py | 30 +++++++++++++++--------------- 6 files changed, 32 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5527f9da1..4b0f026ea 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,8 +4,7 @@ Changelog Next ---- -2025.03.10.1 ------------- +* Be more flexible in the types of images accepted. 2025.03.10 ---------- diff --git a/src/vws/query.py b/src/vws/query.py index 96122aa14..65f72d16f 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -3,10 +3,9 @@ """ import datetime -import io import json from http import HTTPMethod, HTTPStatus -from typing import Any, BinaryIO +from typing import IO, Any from urllib.parse import urljoin import requests @@ -29,7 +28,7 @@ from vws.reports import QueryResult, TargetData from vws.response import Response -_ImageType = io.BytesIO | BinaryIO +_ImageType = IO[bytes] @beartype diff --git a/src/vws/vws.py b/src/vws/vws.py index 5dd7503db..fc33a6861 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -3,12 +3,11 @@ """ import base64 -import io import json import time from datetime import date from http import HTTPMethod, HTTPStatus -from typing import BinaryIO +from typing import IO from urllib.parse import urljoin import requests @@ -47,7 +46,7 @@ ) from vws.response import Response -_ImageType = io.BytesIO | BinaryIO +_ImageType = IO[bytes] @beartype diff --git a/tests/conftest.py b/tests/conftest.py index 74c6fc56d..3831755a8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,7 +5,7 @@ import io from collections.abc import Generator from pathlib import Path -from typing import BinaryIO, Literal +from typing import IO, BinaryIO, Literal import pytest from mock_vws import MockVWS @@ -70,7 +70,7 @@ def image( request: pytest.FixtureRequest, high_quality_image: io.BytesIO, image_file: BinaryIO, -) -> io.BytesIO | BinaryIO: +) -> IO[bytes]: """ An image in any of the types that the API accepts. """ diff --git a/tests/test_query.py b/tests/test_query.py index e229cf95d..c255b0658 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -2,9 +2,8 @@ Tests for the ``CloudRecoService`` querying functionality. """ -import io import uuid -from typing import BinaryIO +from typing import IO from mock_vws import MockVWS from mock_vws.database import VuforiaDatabase @@ -21,7 +20,7 @@ class TestQuery: @staticmethod def test_no_matches( cloud_reco_client: CloudRecoService, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ An empty list is returned if there are no matches. @@ -33,7 +32,7 @@ def test_no_matches( def test_match( vws_client: VWS, cloud_reco_client: CloudRecoService, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ Details of matching targets are returned. @@ -56,7 +55,7 @@ class TestCustomBaseVWQURL: """ @staticmethod - def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: + def test_custom_base_url(image: IO[bytes]) -> None: """ It is possible to use query a target to a database under a custom VWQ URL. @@ -101,7 +100,7 @@ class TestMaxNumResults: def test_default( vws_client: VWS, cloud_reco_client: CloudRecoService, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ By default the maximum number of results is 1. @@ -129,7 +128,7 @@ def test_default( def test_custom( vws_client: VWS, cloud_reco_client: CloudRecoService, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ It is possible to set a custom ``max_num_results``. @@ -175,7 +174,7 @@ class TestIncludeTargetData: def test_default( vws_client: VWS, cloud_reco_client: CloudRecoService, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ By default, target data is only returned in the top match. @@ -207,7 +206,7 @@ def test_default( def test_top( vws_client: VWS, cloud_reco_client: CloudRecoService, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ When ``CloudRecoIncludeTargetData.TOP`` is given, target data is only @@ -241,7 +240,7 @@ def test_top( def test_none( vws_client: VWS, cloud_reco_client: CloudRecoService, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ When ``CloudRecoIncludeTargetData.NONE`` is given, target data is not @@ -275,7 +274,7 @@ def test_none( def test_all( vws_client: VWS, cloud_reco_client: CloudRecoService, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ When ``CloudRecoIncludeTargetData.ALL`` is given, target data is diff --git a/tests/test_vws.py b/tests/test_vws.py index e16eff555..54391d95c 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -7,7 +7,7 @@ import io import secrets import uuid -from typing import BinaryIO +from typing import IO import pytest from freezegun import freeze_time @@ -37,7 +37,7 @@ class TestAddTarget: @pytest.mark.parametrize(argnames="active_flag", argvalues=[True, False]) def test_add_target( vws_client: VWS, - image: io.BytesIO | BinaryIO, + image: IO[bytes], application_metadata: bytes | None, cloud_reco_client: CloudRecoService, *, @@ -81,7 +81,7 @@ def test_add_target( @staticmethod def test_add_two_targets( vws_client: VWS, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """No exception is raised when adding two targets with different names. @@ -103,7 +103,7 @@ class TestCustomBaseVWSURL: """ @staticmethod - def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: + def test_custom_base_url(image: IO[bytes]) -> None: """ It is possible to use add a target to a database under a custom VWS URL. @@ -135,7 +135,7 @@ class TestListTargets: @staticmethod def test_list_targets( vws_client: VWS, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ It is possible to get a list of target IDs. @@ -165,7 +165,7 @@ class TestDelete: @staticmethod def test_delete_target( vws_client: VWS, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ It is possible to delete a target. @@ -192,7 +192,7 @@ class TestGetTargetSummaryReport: @staticmethod def test_get_target_summary_report( vws_client: VWS, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ Details of a target are returned by ``get_target_summary_report``. @@ -294,7 +294,7 @@ class TestGetTargetRecord: @staticmethod def test_get_target_record( vws_client: VWS, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ Details of a target are returned by ``get_target_record``. @@ -369,7 +369,7 @@ class TestWaitForTargetProcessed: @staticmethod def test_wait_for_target_processed( vws_client: VWS, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ It is possible to wait until a target is processed. @@ -389,7 +389,7 @@ def test_wait_for_target_processed( @staticmethod def test_default_seconds_between_requests( - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ By default, 0.2 seconds are waited between polling requests. @@ -441,7 +441,7 @@ def test_default_seconds_between_requests( @staticmethod def test_custom_seconds_between_requests( - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ It is possible to customize the time waited between polling requests. @@ -492,7 +492,7 @@ def test_custom_seconds_between_requests( assert report.request_usage == expected_requests @staticmethod - def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: + def test_custom_timeout(image: IO[bytes]) -> None: """ It is possible to set a maximum timeout. """ @@ -538,7 +538,7 @@ class TestGetDuplicateTargets: @staticmethod def test_get_duplicate_targets( vws_client: VWS, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ It is possible to get the IDs of similar targets. @@ -572,7 +572,7 @@ class TestUpdateTarget: @staticmethod def test_update_target( vws_client: VWS, - image: io.BytesIO | BinaryIO, + image: IO[bytes], different_high_quality_image: io.BytesIO, cloud_reco_client: CloudRecoService, ) -> None: @@ -633,7 +633,7 @@ def test_update_target( @staticmethod def test_no_fields_given( vws_client: VWS, - image: io.BytesIO | BinaryIO, + image: IO[bytes], ) -> None: """ It is possible to give no update fields. From 6382b7a64d38611de093a7ba25692153c5a75331 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 14 Jun 2025 22:01:50 +0100 Subject: [PATCH 052/688] Revert "Accept all IO[bytes] as image type" This reverts commit 50b63b1c22b31e1de5d6545618b9d83a168c800a. --- CHANGELOG.rst | 3 ++- src/vws/query.py | 5 +++-- src/vws/vws.py | 5 +++-- tests/conftest.py | 4 ++-- tests/test_query.py | 21 +++++++++++---------- tests/test_vws.py | 30 +++++++++++++++--------------- 6 files changed, 36 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4b0f026ea..5527f9da1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,7 +4,8 @@ Changelog Next ---- -* Be more flexible in the types of images accepted. +2025.03.10.1 +------------ 2025.03.10 ---------- diff --git a/src/vws/query.py b/src/vws/query.py index 65f72d16f..96122aa14 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -3,9 +3,10 @@ """ import datetime +import io import json from http import HTTPMethod, HTTPStatus -from typing import IO, Any +from typing import Any, BinaryIO from urllib.parse import urljoin import requests @@ -28,7 +29,7 @@ from vws.reports import QueryResult, TargetData from vws.response import Response -_ImageType = IO[bytes] +_ImageType = io.BytesIO | BinaryIO @beartype diff --git a/src/vws/vws.py b/src/vws/vws.py index fc33a6861..5dd7503db 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -3,11 +3,12 @@ """ import base64 +import io import json import time from datetime import date from http import HTTPMethod, HTTPStatus -from typing import IO +from typing import BinaryIO from urllib.parse import urljoin import requests @@ -46,7 +47,7 @@ ) from vws.response import Response -_ImageType = IO[bytes] +_ImageType = io.BytesIO | BinaryIO @beartype diff --git a/tests/conftest.py b/tests/conftest.py index 3831755a8..74c6fc56d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,7 +5,7 @@ import io from collections.abc import Generator from pathlib import Path -from typing import IO, BinaryIO, Literal +from typing import BinaryIO, Literal import pytest from mock_vws import MockVWS @@ -70,7 +70,7 @@ def image( request: pytest.FixtureRequest, high_quality_image: io.BytesIO, image_file: BinaryIO, -) -> IO[bytes]: +) -> io.BytesIO | BinaryIO: """ An image in any of the types that the API accepts. """ diff --git a/tests/test_query.py b/tests/test_query.py index c255b0658..e229cf95d 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -2,8 +2,9 @@ Tests for the ``CloudRecoService`` querying functionality. """ +import io import uuid -from typing import IO +from typing import BinaryIO from mock_vws import MockVWS from mock_vws.database import VuforiaDatabase @@ -20,7 +21,7 @@ class TestQuery: @staticmethod def test_no_matches( cloud_reco_client: CloudRecoService, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ An empty list is returned if there are no matches. @@ -32,7 +33,7 @@ def test_no_matches( def test_match( vws_client: VWS, cloud_reco_client: CloudRecoService, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ Details of matching targets are returned. @@ -55,7 +56,7 @@ class TestCustomBaseVWQURL: """ @staticmethod - def test_custom_base_url(image: IO[bytes]) -> None: + def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: """ It is possible to use query a target to a database under a custom VWQ URL. @@ -100,7 +101,7 @@ class TestMaxNumResults: def test_default( vws_client: VWS, cloud_reco_client: CloudRecoService, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ By default the maximum number of results is 1. @@ -128,7 +129,7 @@ def test_default( def test_custom( vws_client: VWS, cloud_reco_client: CloudRecoService, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ It is possible to set a custom ``max_num_results``. @@ -174,7 +175,7 @@ class TestIncludeTargetData: def test_default( vws_client: VWS, cloud_reco_client: CloudRecoService, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ By default, target data is only returned in the top match. @@ -206,7 +207,7 @@ def test_default( def test_top( vws_client: VWS, cloud_reco_client: CloudRecoService, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ When ``CloudRecoIncludeTargetData.TOP`` is given, target data is only @@ -240,7 +241,7 @@ def test_top( def test_none( vws_client: VWS, cloud_reco_client: CloudRecoService, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ When ``CloudRecoIncludeTargetData.NONE`` is given, target data is not @@ -274,7 +275,7 @@ def test_none( def test_all( vws_client: VWS, cloud_reco_client: CloudRecoService, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ When ``CloudRecoIncludeTargetData.ALL`` is given, target data is diff --git a/tests/test_vws.py b/tests/test_vws.py index 54391d95c..e16eff555 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -7,7 +7,7 @@ import io import secrets import uuid -from typing import IO +from typing import BinaryIO import pytest from freezegun import freeze_time @@ -37,7 +37,7 @@ class TestAddTarget: @pytest.mark.parametrize(argnames="active_flag", argvalues=[True, False]) def test_add_target( vws_client: VWS, - image: IO[bytes], + image: io.BytesIO | BinaryIO, application_metadata: bytes | None, cloud_reco_client: CloudRecoService, *, @@ -81,7 +81,7 @@ def test_add_target( @staticmethod def test_add_two_targets( vws_client: VWS, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """No exception is raised when adding two targets with different names. @@ -103,7 +103,7 @@ class TestCustomBaseVWSURL: """ @staticmethod - def test_custom_base_url(image: IO[bytes]) -> None: + def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: """ It is possible to use add a target to a database under a custom VWS URL. @@ -135,7 +135,7 @@ class TestListTargets: @staticmethod def test_list_targets( vws_client: VWS, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ It is possible to get a list of target IDs. @@ -165,7 +165,7 @@ class TestDelete: @staticmethod def test_delete_target( vws_client: VWS, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ It is possible to delete a target. @@ -192,7 +192,7 @@ class TestGetTargetSummaryReport: @staticmethod def test_get_target_summary_report( vws_client: VWS, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ Details of a target are returned by ``get_target_summary_report``. @@ -294,7 +294,7 @@ class TestGetTargetRecord: @staticmethod def test_get_target_record( vws_client: VWS, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ Details of a target are returned by ``get_target_record``. @@ -369,7 +369,7 @@ class TestWaitForTargetProcessed: @staticmethod def test_wait_for_target_processed( vws_client: VWS, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ It is possible to wait until a target is processed. @@ -389,7 +389,7 @@ def test_wait_for_target_processed( @staticmethod def test_default_seconds_between_requests( - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ By default, 0.2 seconds are waited between polling requests. @@ -441,7 +441,7 @@ def test_default_seconds_between_requests( @staticmethod def test_custom_seconds_between_requests( - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ It is possible to customize the time waited between polling requests. @@ -492,7 +492,7 @@ def test_custom_seconds_between_requests( assert report.request_usage == expected_requests @staticmethod - def test_custom_timeout(image: IO[bytes]) -> None: + def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: """ It is possible to set a maximum timeout. """ @@ -538,7 +538,7 @@ class TestGetDuplicateTargets: @staticmethod def test_get_duplicate_targets( vws_client: VWS, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ It is possible to get the IDs of similar targets. @@ -572,7 +572,7 @@ class TestUpdateTarget: @staticmethod def test_update_target( vws_client: VWS, - image: IO[bytes], + image: io.BytesIO | BinaryIO, different_high_quality_image: io.BytesIO, cloud_reco_client: CloudRecoService, ) -> None: @@ -633,7 +633,7 @@ def test_update_target( @staticmethod def test_no_fields_given( vws_client: VWS, - image: IO[bytes], + image: io.BytesIO | BinaryIO, ) -> None: """ It is possible to give no update fields. From f1c2fb787cad1e65e94de7e40464f9914c366113 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 11:23:00 +0000 Subject: [PATCH 053/688] Bump doc8 from 1.1.2 to 2.0.0 Bumps [doc8](https://github.com/pycqa/doc8) from 1.1.2 to 2.0.0. - [Release notes](https://github.com/pycqa/doc8/releases) - [Commits](https://github.com/pycqa/doc8/compare/v1.1.2...v2.0.0) --- updated-dependencies: - dependency-name: doc8 dependency-version: 2.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 084501226..4dcc48d65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "actionlint-py==1.7.7.23", "check-manifest==0.50", "deptry==0.23.0", - "doc8==1.1.2", + "doc8==2.0.0", "doccmd==2025.4.8", "docformatter==1.7.7", "freezegun==1.5.2", From 97148361c55550be5abeb73755d5c5d080eb015d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 10:19:40 +0000 Subject: [PATCH 054/688] 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 4dcc48d65..c8e279439 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,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 da3350b638d89a5bbb00eaf385565de29f6e1753 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:40:51 +0000 Subject: [PATCH 055/688] 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 c8e279439..f3f6b63f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.4.0", "pytest-cov==6.2.1", "pyyaml==6.0.2", - "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 c40c5bc3826547686f4569fe6e89c98a05084f40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:41:03 +0000 Subject: [PATCH 056/688] 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 c8e279439..5e386c6f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,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", "pyyaml==6.0.2", "ruff==0.11.13", From 60df97d3c1fac0fdff03d57b2225e4ffff14bbc8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:02:23 +0000 Subject: [PATCH 057/688] Bump pygments from 2.19.1 to 2.19.2 Bumps [pygments](https://github.com/pygments/pygments) from 2.19.1 to 2.19.2. - [Release notes](https://github.com/pygments/pygments/releases) - [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES) - [Commits](https://github.com/pygments/pygments/compare/2.19.1...2.19.2) --- updated-dependencies: - dependency-name: pygments dependency-version: 2.19.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 2801ec9c3..fd8ca2e04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ optional-dependencies.dev = [ "pre-commit==4.2.0", "pydocstyle==6.3", "pyenchant==3.3.0rc1", - "pygments==2.19.1", + "pygments==2.19.2", "pylint==3.3.7", "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.6.0", From d507540773872f198e0674ce47c374733d6edc59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 10:19:59 +0000 Subject: [PATCH 058/688] 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 fd8ca2e04..03d1c5ca8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.4.1", "pytest-cov==6.2.1", "pyyaml==6.0.2", - "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 48e722e15a74b7ce041ce4cde19ed972f82355cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 10:43:10 +0000 Subject: [PATCH 059/688] Bump pyroma from 4.2 to 4.3.2 Bumps [pyroma](https://github.com/regebro/pyroma) from 4.2 to 4.3.2. - [Changelog](https://github.com/regebro/pyroma/blob/master/CHANGES.txt) - [Commits](https://github.com/regebro/pyroma/compare/4.2...4.3.2) --- updated-dependencies: - dependency-name: pyroma dependency-version: 4.3.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 03d1c5ca8..7ac67bf35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,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.2", "pytest==8.4.1", "pytest-cov==6.2.1", "pyyaml==6.0.2", From 7798b19e164eff0131df6ede00e794ccb5d9dcde Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 10:16:13 +0000 Subject: [PATCH 060/688] 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 7ac67bf35..d3ffe1fc6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.4.1", "pytest-cov==6.2.1", "pyyaml==6.0.2", - "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 56be23315237cf4e4b64e8789ad1468d13f90f8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 10:16:21 +0000 Subject: [PATCH 061/688] 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 7ac67bf35..455d031a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,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", "pyyaml==6.0.2", From fd85000507bb36139603758fe743e362cdad9820 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 10:38:44 +0000 Subject: [PATCH 062/688] 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 a5bdaabe4..964720136 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,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 fd163871d1ac98f6e0d593b89c84c61d1bf22571 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 10:19:03 +0000 Subject: [PATCH 063/688] 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 964720136..8dc7bbb83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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 6633e5f172b95c015ef340fce4d4170fb9811873 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:00:07 +0000 Subject: [PATCH 064/688] 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 8dc7bbb83..bca4b1652 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.4.1", "pytest-cov==6.2.1", "pyyaml==6.0.2", - "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 b2072bea50bdb4e0a838d7634780dd83ba0b8cd1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:19:55 +0000 Subject: [PATCH 065/688] 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 8dc7bbb83..0f36ca419 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ optional-dependencies.dev = [ "doc8==2.0.0", "doccmd==2025.4.8", "docformatter==1.7.7", - "freezegun==1.5.2", + "freezegun==1.5.3", "furo==2024.8.6", "interrogate==1.7.0", "mypy[faster-cache]==1.16.1", From 4edd805cdc90ca3c85490e707b2b43b18cbad90c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 10:11:06 +0000 Subject: [PATCH 066/688] Bump pyroma from 4.3.3 to 5.0 Bumps [pyroma](https://github.com/regebro/pyroma) from 4.3.3 to 5.0. - [Changelog](https://github.com/regebro/pyroma/blob/master/CHANGES.txt) - [Commits](https://github.com/regebro/pyroma/compare/4.3.3...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 8891ba97e..8250d8fe3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,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", "pyyaml==6.0.2", From b82469cc28a3f83d9b348600c345e0ee2f706439 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 10:11:13 +0000 Subject: [PATCH 067/688] 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 8891ba97e..3304982af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,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 204adca8c5e0e29c64a072a3be56c60e3af4f932 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 10:34:00 +0000 Subject: [PATCH 068/688] 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 088e0dc76..98dc1f2db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.4.1", "pytest-cov==6.2.1", "pyyaml==6.0.2", - "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 d15a9a7eaf24a02752c5b443269e4bcee4e93d35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 12:09:24 +0000 Subject: [PATCH 069/688] Bump furo from 2024.8.6 to 2025.7.19 Bumps [furo](https://github.com/pradyunsg/furo) from 2024.8.6 to 2025.7.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/2024.08.06...2025.07.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 98dc1f2db..732caca55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ optional-dependencies.dev = [ "doccmd==2025.4.8", "docformatter==1.7.7", "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 bae3e79c21d19d91f1f2ec78a824055e7ca16949 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Jul 2025 10:33:52 +0000 Subject: [PATCH 070/688] 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 732caca55..0266370f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.4.1", "pytest-cov==6.2.1", "pyyaml==6.0.2", - "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 cd7e64cd32153265b915bf9aee40907efd414fdc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 10:40:24 +0000 Subject: [PATCH 071/688] 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 0266370f1..7f7c030af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.4.1", "pytest-cov==6.2.1", "pyyaml==6.0.2", - "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 b9ad8a0698666fe868997e21dbfb65cc0f3bd1b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 11:01:53 +0000 Subject: [PATCH 072/688] 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 7f7c030af..5e56da8b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ optional-dependencies.dev = [ "doc8==2.0.0", "doccmd==2025.4.8", "docformatter==1.7.7", - "freezegun==1.5.3", + "freezegun==1.5.4", "furo==2025.7.19", "interrogate==1.7.0", "mypy[faster-cache]==1.17.0", From 5296aa6d312c1c221466302344a1c5d1b1405b70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 11:02:11 +0000 Subject: [PATCH 073/688] 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 7f7c030af..f19664862 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ optional-dependencies.dev = [ "freezegun==1.5.3", "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 f1698aad37ff4775f7aa27c649adb4ed23667dba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 11:02:17 +0000 Subject: [PATCH 074/688] 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 7f7c030af..6c6148d01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ dependencies = [ optional-dependencies.dev = [ "actionlint-py==1.7.7.23", "check-manifest==0.50", - "deptry==0.23.0", + "deptry==0.23.1", "doc8==2.0.0", "doccmd==2025.4.8", "docformatter==1.7.7", From 7d8811f7ac8cba09d46d38e4eab259a5cc282cca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 13:42:44 +0000 Subject: [PATCH 075/688] 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 89c5af819..ed9d22865 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ "vws-test-fixtures==2023.3.5", "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/" urls.Source = "https://github.com/VWS-Python/vws-python" From c6cf9596845c600399c5219ac79c49f8dad79476 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 10:49:23 +0000 Subject: [PATCH 076/688] 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 ed9d22865..44a1f850f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.4.1", "pytest-cov==6.2.1", "pyyaml==6.0.2", - "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 aada8590139173dc4551969d9ed17f95a6381f25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 10:49:35 +0000 Subject: [PATCH 077/688] 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 ed9d22865..d65cfcd2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2025.6.6", "sphinxcontrib-spelling==8.0.1", - "sybil==9.1.0", + "sybil==9.2.0", "types-requests==2.32.4.20250611", "vulture==2.14", "vws-python-mock==2025.3.10.1", From 825fc670bcb2c43f90e87f7e2544cccdf5de68f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 14:36:56 +0000 Subject: [PATCH 078/688] 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 4d19a6f77..f6c9ea55c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,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", "pygments==2.19.2", From 1b0aeb321bb09b9c4cece24425fa5a4ee9f5e17b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 14:38:03 +0000 Subject: [PATCH 079/688] 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 4d19a6f77..bdf65c3ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ optional-dependencies.dev = [ "doc8==2.0.0", "doccmd==2025.4.8", "docformatter==1.7.7", - "freezegun==1.5.4", + "freezegun==1.5.5", "furo==2025.7.19", "interrogate==1.7.0", "mypy[faster-cache]==1.17.1", From a15757763f92e5d178ff540e30ef2c44c157fc8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:44:46 +0000 Subject: [PATCH 080/688] 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 4d19a6f77..1294fe3fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.6.6", "sphinxcontrib-spelling==8.0.1", "sybil==9.2.0", - "types-requests==2.32.4.20250611", + "types-requests==2.32.4.20250809", "vulture==2.14", "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.5", From b9765629f2819351e2cae586cd18300c35b80aa7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 16:03:52 +0000 Subject: [PATCH 081/688] 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 4d19a6f77..a575f81bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pyenchant==3.3.0rc1", "pygments==2.19.2", - "pylint==3.3.7", + "pylint==3.3.8", "pylint-per-file-ignores==1.4.0", "pyproject-fmt==2.6.0", "pyright==1.1.403", From 32fea5f76c81a68b11f2fc2ae2ca75753b4de0ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 16:16:42 +0000 Subject: [PATCH 082/688] 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 4d19a6f77..85e4b6c06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,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 a80be06d2511101c34ea35ec5c0e51037cca6c1e 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:45 +0000 Subject: [PATCH 083/688] [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 c480de3e9..addfdb70e 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 00d702af3815c78a6091605393f185a2b79fc68d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:54:10 +0000 Subject: [PATCH 084/688] 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/lint.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de333f893..e8e3da5a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,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/lint.yml b/.github/workflows/lint.yml index 6f0dbc9c3..71774a6d8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -23,7 +23,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 c00447040..a19408730 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: # See # https://github.com/stefanzweifel/git-auto-commit-action?tab=readme-ov-file#push-to-protected-branches From c923eccfdde3ce628514810d2c8dc2d51411bc88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Aug 2025 10:39:43 +0000 Subject: [PATCH 085/688] 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 a917ebc9f..8e29d5389 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.4.1", "pytest-cov==6.2.1", "pyyaml==6.0.2", - "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 3859bad95c61410134fedc03eba02c81595b3d70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 10:22:41 +0000 Subject: [PATCH 086/688] 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 8e29d5389..f4e316b7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "pylint==3.3.8", "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 5bf6193ec5a314b1f3e1a0ee58fbfc5cf55a43ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 10:15:21 +0000 Subject: [PATCH 087/688] 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 f4e316b7a..624947f08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==8.4.1", "pytest-cov==6.2.1", "pyyaml==6.0.2", - "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 eb19b9083f7b61b31fe45f530e7b3bdacb4386c6 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 24 Aug 2025 17:04:52 +0100 Subject: [PATCH 088/688] Simplify 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 624947f08..3bd00d717 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ keywords = [ "vuforia", "vws", ] -license = { file = "LICENSE" } +license = "MIT" authors = [ { name = "Adam Dangoor", email = "adamdangoor@gmail.com" }, ] @@ -22,7 +22,6 @@ requires-python = ">=3.13" classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", - "License :: OSI Approved :: MIT License", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python :: 3 :: Only", From 22a2cca62a9ff655eaad04cdcbf7173a42bd16d4 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 24 Aug 2025 17:47:50 +0100 Subject: [PATCH 089/688] Run pre-commit hooks on only one stage each --- .pre-commit-config.yaml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index addfdb70e..5c164119e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,31 +45,49 @@ 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: local hooks: - id: actionlint @@ -79,6 +97,7 @@ repos: pass_filenames: false types_or: [yaml] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: docformatter name: docformatter @@ -86,6 +105,7 @@ repos: language: python types_or: [python] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: shellcheck name: shellcheck @@ -93,6 +113,7 @@ repos: language: python types_or: [shell] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: shellcheck-docs name: shellcheck-docs @@ -101,6 +122,7 @@ repos: language: python types_or: [markdown, rst] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: shfmt name: shfmt @@ -108,6 +130,7 @@ repos: language: python types_or: [shell] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: shfmt-docs name: shfmt-docs @@ -116,6 +139,7 @@ repos: language: python types_or: [markdown, rst] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: mypy name: mypy @@ -166,6 +190,7 @@ repos: types_or: [python] pass_filenames: false additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: vulture-docs name: vulture docs @@ -173,6 +198,7 @@ repos: language: python types_or: [markdown, rst] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: pyroma name: pyroma @@ -181,6 +207,7 @@ repos: pass_filenames: false types_or: [toml] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: deptry name: deptry @@ -188,6 +215,7 @@ repos: language: python pass_filenames: false additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: pylint name: pylint @@ -211,6 +239,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 @@ -218,6 +247,7 @@ repos: language: python types_or: [markdown, rst] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: ruff-format-fix name: Ruff format @@ -225,6 +255,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 @@ -233,6 +264,7 @@ repos: language: python types_or: [markdown, rst] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: doc8 name: doc8 @@ -240,6 +272,7 @@ repos: language: python types_or: [rst] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: interrogate name: interrogate @@ -247,6 +280,7 @@ repos: language: python types_or: [python] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: interrogate-docs name: interrogate docs @@ -254,6 +288,7 @@ repos: language: python types_or: [markdown, rst] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: pyproject-fmt-fix name: pyproject-fmt @@ -262,6 +297,7 @@ repos: types_or: [toml] files: pyproject.toml additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: linkcheck name: linkcheck @@ -304,6 +340,7 @@ repos: language: python types_or: [yaml] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] - id: sphinx-lint name: sphinx-lint @@ -311,3 +348,4 @@ repos: language: python types_or: [rst] additional_dependencies: [uv==0.6.3] + stages: [pre-commit] From fcaa10d1f92199c06c2b312b301ceb29672ff968 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 27 Aug 2025 23:22:03 +0100 Subject: [PATCH 090/688] Bump pylint per-file-ignores --- pyproject.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3bd00d717..cc10111b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ optional-dependencies.dev = [ "pyenchant==3.3.0rc1", "pygments==2.19.2", "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", @@ -248,7 +248,8 @@ disable = [ # - We want to use global variables in documentation, which may not be uppercase. # - conf.py is a Sphinx configuration file which requires lowercase global variable names. 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 af9bf7520cd87956fbae5de426958db1804eaa14 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 27 Aug 2025 23:29:26 +0100 Subject: [PATCH 091/688] Bump pylint per-file-ignores --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cc10111b0..b5eeff047 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -250,7 +250,7 @@ disable = [ 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.'FORMAT'] From a94bad5cf3e8ab95fd6f5d8f297c37d411c36678 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 10:07:18 +0000 Subject: [PATCH 092/688] 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 b5eeff047..77de76c3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ optional-dependencies.dev = [ "pytest==8.4.1", "pytest-cov==6.2.1", "pyyaml==6.0.2", - "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 5b22fb36ad798133c46be65f898bdc6929006fb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 13:17:00 +0000 Subject: [PATCH 093/688] 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 77de76c3e..15ca2a1d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,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 73686e48273d513e45e32bda249291be4a357b67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 10:06:46 +0000 Subject: [PATCH 094/688] 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 15ca2a1d6..7c628a437 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,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", "pyyaml==6.0.2", "ruff==0.12.11", From eaac5dcdf2b356dcc79ef2f7a66f134fdcdff4ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 10:07:02 +0000 Subject: [PATCH 095/688] 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 15ca2a1d6..d95e0b17f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ optional-dependencies.dev = [ "pytest==8.4.1", "pytest-cov==6.2.1", "pyyaml==6.0.2", - "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 063b9d365dbf517b7a06df81e28ce989372b8daa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:31:55 +0000 Subject: [PATCH 096/688] 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 d823ba952..c3f48e366 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,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", "pyyaml==6.0.2", "ruff==0.12.12", # We add shellcheck-py not only for shell scripts and shell code blocks, From 3a215e060f2c3caca35a791f256446c9d2ebb6e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:33:49 +0000 Subject: [PATCH 097/688] 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 d823ba952..a06268a8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ optional-dependencies.dev = [ "vulture==2.14", "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.5", - "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/" From faa21d2fc8535a6b90223203aab46d008e9c6db3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:06:21 +0000 Subject: [PATCH 098/688] 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 60bd5dbae..a1fa17472 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,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", "pyyaml==6.0.2", "ruff==0.12.12", # We add shellcheck-py not only for shell scripts and shell code blocks, From e6edf35d8cf00ad835c61090f9be6ad4d0f71b21 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 10:07:17 +0000 Subject: [PATCH 099/688] 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 a1fa17472..88d6cdeba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ optional-dependencies.dev = [ "pytest==8.4.2", "pytest-cov==7.0.0", "pyyaml==6.0.2", - "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 c9b5da269b2e6af16ee5e1d4ce1be783fc1a6429 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Sep 2025 10:07:26 +0000 Subject: [PATCH 100/688] 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 88d6cdeba..30d6da20a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,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 91cccb574eebf16923c593adc3179ef1af2c898f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 14 Sep 2025 22:34:53 +0100 Subject: [PATCH 101/688] 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 30d6da20a..8155d78c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,6 @@ optional-dependencies.dev = [ "mypy-strict-kwargs==2025.4.3", "pre-commit==4.3.0", "pydocstyle==6.3", - "pyenchant==3.3.0rc1", "pygments==2.19.2", "pylint==3.3.8", "pylint-per-file-ignores==2.0.3", From c3c9801d05f04662ba004f7049de387a1aa09a1f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 15 Sep 2025 03:29:50 +0100 Subject: [PATCH 102/688] Make pylint spelling dependency explicit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8155d78c5..0f0a053a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ optional-dependencies.dev = [ "pre-commit==4.3.0", "pydocstyle==6.3", "pygments==2.19.2", - "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 3b6658fe8dc4f3e05fc466a1eab29fb2ece13adb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 10:35:09 +0000 Subject: [PATCH 103/688] 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 0f0a053a8..d3d24c834 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.6.6", "sphinxcontrib-spelling==8.0.1", "sybil==9.2.0", - "types-requests==2.32.4.20250809", + "types-requests==2.32.4.20250913", "vulture==2.14", "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.5", From 8b1cdcbc391a2bdce36bc3dc827ee8e540996fa3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:07:54 +0000 Subject: [PATCH 104/688] 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 d3d24c834..83e974706 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.50", "deptry==0.23.1", "doc8==2.0.0", - "doccmd==2025.4.8", + "doccmd==2025.9.19", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.7.19", From c7f89faa312257ca277b3bd1ccad4cbce90a8ae4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:36:18 +0000 Subject: [PATCH 105/688] 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 83e974706..2d53e4755 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pytest==8.4.2", "pytest-cov==7.0.0", "pyyaml==6.0.2", - "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 4b1dafd48ef33ebc55a459c3238da0c848f8b34a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:36:24 +0000 Subject: [PATCH 106/688] 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 83e974706..9f9c4d147 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,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 95dc03e8f8df892f064e1192e3eec9d32193097f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 10:07:07 +0000 Subject: [PATCH 107/688] 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 29e697a8f..7c6b82d34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,7 @@ optional-dependencies.dev = [ "pyroma==5.0", "pytest==8.4.2", "pytest-cov==7.0.0", - "pyyaml==6.0.2", + "pyyaml==6.0.3", "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 From 6885ac6ade117646c842399615bcdbf28a91d574 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 10:07:35 +0000 Subject: [PATCH 108/688] 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 29e697a8f..188de4b02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ optional-dependencies.dev = [ "doccmd==2025.9.19", "docformatter==1.7.7", "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 b2fc84bd89cec895d83320b961be38333d8a6366 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 10:11:42 +0000 Subject: [PATCH 109/688] 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 7c6b82d34..735892aa2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pytest==8.4.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 d8e1a6cca688d2d6fe32c1e0d1716d25ea3ef2ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:07:33 +0000 Subject: [PATCH 110/688] 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 81cf71311..8020ea126 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,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 6b54a1d39908fe85d54a6db29925d7230e665693 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:12:33 +0000 Subject: [PATCH 111/688] 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 8020ea126..85e1fc266 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "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 5e4a468ac77c7babc8063d224b317e0267bc082a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 10:06:19 +0000 Subject: [PATCH 112/688] 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 85e1fc266..020c8b7f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pytest==8.4.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 2c7878dd67c512db71c38f95f2a5075cb9d89558 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:51:43 +0000 Subject: [PATCH 113/688] 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 020c8b7f5..2eefdc34c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ optional-dependencies.dev = [ "pre-commit==4.3.0", "pydocstyle==6.3", "pygments==2.19.2", - "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 d21db3bbf61f79f88ed25218ca491a25bf43268a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 7 Oct 2025 07:32:36 +0100 Subject: [PATCH 114/688] Remove Codecov - it is flaky and doesn't provide much value --- .github/workflows/ci.yml | 8 +------- README.rst | 4 +--- codecov.yaml | 7 ------- pyproject.toml | 1 - 4 files changed, 2 insertions(+), 18 deletions(-) delete mode 100644 codecov.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8e3da5a0..92a2848b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,13 +35,7 @@ jobs: run: | # We run tests against "." and not the tests directory as we test the README # and documentation. - uv run --extra=dev --python=${{ matrix.python-version }} pytest -s -vvv --cov-fail-under 100 --cov=src/ --cov=tests/ . --cov-report=xml - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 - with: - fail_ci_if_error: true - token: ${{ secrets.CODECOV_TOKEN }} + uv run --extra=dev --python=${{ matrix.python-version }} pytest -s -vvv --cov-fail-under 100 --cov=src/ --cov=tests/ . completion-ci: needs: build diff --git a/README.rst b/README.rst index 3cd5bb22c..dea6bb93d 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -|Build Status| |codecov| |PyPI| +|Build Status| |PyPI| vws-python ========== @@ -71,8 +71,6 @@ See the `full documentation `__. .. |Build Status| image:: https://github.com/VWS-Python/vws-python/actions/workflows/ci.yml/badge.svg?branch=main :target: https://github.com/VWS-Python/vws-python/actions -.. |codecov| image:: https://codecov.io/gh/VWS-Python/vws-python/branch/main/graph/badge.svg - :target: https://codecov.io/gh/VWS-Python/vws-python .. |PyPI| image:: https://badge.fury.io/py/VWS-Python.svg :target: https://badge.fury.io/py/VWS-Python .. |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 2eefdc34c..aac9f9969 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -288,7 +288,6 @@ ignore = [ "Makefile", "ci", "ci/**", - "codecov.yaml", "doc8.ini", "docs", "docs/**", From 4dd75bbd990351a4b8b5a3cacdc9f60e8c4491bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 10:07:36 +0000 Subject: [PATCH 115/688] 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/ci.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92a2848b8..b71569197 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,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/lint.yml b/.github/workflows/lint.yml index 71774a6d8..d3bb9d802 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -26,7 +26,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 a19408730..8665829a5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,7 +34,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' From 3dd98d7c572fcc423ae991b46a5364d1cd0254b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 10:08:09 +0000 Subject: [PATCH 116/688] 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 aac9f9969..662d1c988 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pytest==8.4.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 86b2807633fd9db999a1cc5617ff7f4320b7162a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:07:14 +0000 Subject: [PATCH 117/688] 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 662d1c988..c733120c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "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 f62b24ca2058330d4104e2681ae133a20daedd6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 10:37:40 +0000 Subject: [PATCH 118/688] 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 c733120c5..8710c28af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ dependencies = [ "vws-auth-tools>=2024.7.12", ] optional-dependencies.dev = [ - "actionlint-py==1.7.7.23", + "actionlint-py==1.7.8.24", "check-manifest==0.50", "deptry==0.23.1", "doc8==2.0.0", From ce7d4972c75e7fb4a1e015c1d38220054dc35ac1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 10:45:51 +0000 Subject: [PATCH 119/688] Bump pylint-per-file-ignores from 2.0.3 to 3.1.0 Bumps [pylint-per-file-ignores](https://github.com/SAP/pylint-per-file-ignores) from 2.0.3 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/v2.0.3...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-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 c733120c5..101848b7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pygments==2.19.2", "pylint[spelling]==3.3.9", - "pylint-per-file-ignores==2.0.3", + "pylint-per-file-ignores==3.1.0", "pyproject-fmt==2.10.0", "pyright==1.1.406", "pyroma==5.0", From 66fa29cb29e876c3a30fa17ae27d9855ff5527f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 10:46:11 +0000 Subject: [PATCH 120/688] 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 c733120c5..dc4390829 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ optional-dependencies.dev = [ "vulture==2.14", "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.5", - "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/" From fde6d89b25ee3322da9830147a49eaac2a6e8e57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 10:54:41 +0000 Subject: [PATCH 121/688] 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 8665829a5..ca3d74491 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,7 +63,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 6110b1372df22d7e6c63d3c524fe737552eff435 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 10:57:05 +0000 Subject: [PATCH 122/688] 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 28931851c..75385c471 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ optional-dependencies.dev = [ "pre-commit==4.3.0", "pydocstyle==6.3", "pygments==2.19.2", - "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 7639d13bf94f9db9587202aea1d6ab43b589c06b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:09:00 +0000 Subject: [PATCH 123/688] 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 75385c471..8221ef96f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "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 158efeae3dafe8b5ffc8e82285faca0cb2e35d07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:09:16 +0000 Subject: [PATCH 124/688] 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 75385c471..22b645f2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ optional-dependencies.dev = [ "pre-commit==4.3.0", "pydocstyle==6.3", "pygments==2.19.2", - "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 1bcd0b372324e04017dcfc2935d812d2204dce8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 10:08:19 +0000 Subject: [PATCH 125/688] 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 f5a4a96fd..26443a6c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ ] optional-dependencies.dev = [ "actionlint-py==1.7.8.24", - "check-manifest==0.50", + "check-manifest==0.51", "deptry==0.23.1", "doc8==2.0.0", "doccmd==2025.9.19", From 03280e551448d19f380c7952822102ffcec12513 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 10:07:19 +0000 Subject: [PATCH 126/688] 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 26443a6c4..d3da05490 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pytest==8.4.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 32fcb153a5867b7e7958b69291af5d7b41093383 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:00:50 +0000 Subject: [PATCH 127/688] 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 d3da05490..37e7ed7b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.23.1", "doc8==2.0.0", - "doccmd==2025.9.19", + "doccmd==2025.10.18", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.9.25", From 8b1fd521869de717a6b4b7c6be9572874f186adb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 10:08:34 +0000 Subject: [PATCH 128/688] 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 37e7ed7b6..77c9ca89f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ optional-dependencies.dev = [ "pre-commit==4.3.0", "pydocstyle==6.3", "pygments==2.19.2", - "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 53cf3a0e2f8473c101cf1d1fc9dde27e4d4a3436 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:07:55 +0000 Subject: [PATCH 129/688] 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 77c9ca89f..0d72ee5a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ optional-dependencies.dev = [ "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.0", "sphinx-pyproject==0.3.0", - "sphinx-substitution-extensions==2025.6.6", + "sphinx-substitution-extensions==2025.10.24", "sphinxcontrib-spelling==8.0.1", "sybil==9.2.0", "types-requests==2.32.4.20250913", From aabdd340ce454dff1596fccda58ea9c466fa40a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:08:14 +0000 Subject: [PATCH 130/688] 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 77c9ca89f..ea4ce5631 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pytest==8.4.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 ee5594fa58c94fc0c27edca4fd36c78fc79ab0e7 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Oct 2025 02:08:39 +0000 Subject: [PATCH 131/688] Remove docs/Makefile and update pre-commit to use sphinx-build directly --- .pre-commit-config.yaml | 15 +++++++++------ docs/Makefile | 20 -------------------- 2 files changed, 9 insertions(+), 26 deletions(-) delete mode 100644 docs/Makefile diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5c164119e..adc80ff7e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -117,7 +117,8 @@ repos: - id: shellcheck-docs name: shellcheck-docs - 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" language: python types_or: [markdown, rst] @@ -134,7 +135,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] @@ -259,7 +261,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] @@ -301,7 +304,7 @@ repos: - 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] @@ -310,7 +313,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] @@ -319,7 +322,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 15e9c44b1..000000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXPROJ = VWSPYTHON -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 57db930a19e1a1a82db3723f58a6b8d9a2ea90e8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Oct 2025 02:12:14 +0000 Subject: [PATCH 132/688] Update base Makefile to use sphinx-build directly --- Makefile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 216a25740..b4612b464 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,7 @@ SHELL := /bin/bash -euxo pipefail - -# Treat Sphinx warnings as errors -SPHINXOPTS := -W - .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 82faaf5752dfe3cf76b4f860a3df04cedae388f0 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:14:08 +0000 Subject: [PATCH 133/688] [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 adc80ff7e..8a8e38b20 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -117,8 +117,7 @@ repos: - id: shellcheck-docs name: shellcheck-docs - 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" language: python types_or: [markdown, rst] @@ -135,8 +134,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] @@ -261,8 +259,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] @@ -304,7 +301,8 @@ repos: - 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] @@ -313,7 +311,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 eb9fb0ff6cdd0895c426be2fdc248fce87b9a5fc Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Oct 2025 02:22:55 +0000 Subject: [PATCH 134/688] 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 8a8e38b20..4236e1a68 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -321,7 +321,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 cc7bbe869276e7054a64ca551cf78ded97711723 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Oct 2025 02:34:43 +0000 Subject: [PATCH 135/688] 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 80ef1ad105705cd056b36839b1e09173b2fd5c00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:16:55 +0000 Subject: [PATCH 136/688] 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 b83b042ac..e5f693e12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.23.1", "doc8==2.0.0", - "doccmd==2025.10.18", + "doccmd==2025.10.25", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.9.25", From e725d98f9752834c2e59dd1b44fedc107b5ee2d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:19:22 +0000 Subject: [PATCH 137/688] 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 b83b042ac..80c0f3e90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,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-cov==7.0.0", From 3172d5e1d03178b6a76eadcbd0ff9ff5730554d3 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Oct 2025 12:23:10 +0000 Subject: [PATCH 138/688] 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 b83b042ac..ecc36d8eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -278,6 +278,7 @@ make-summary-multi-line = true ignore = [ ".checkmake-config.ini", + ".prettierrc", ".yamlfmt", "*.enc", ".pre-commit-config.yaml", From 82fdfe69f4d3ca3e292c76d170bc8216e7902745 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 27 Oct 2025 22:19:21 +0000 Subject: [PATCH 139/688] Remove Makefile and replace make commands with direct equivalents --- Makefile | 8 -------- docs/source/contributing.rst | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) delete mode 100644 Makefile diff --git a/Makefile b/Makefile deleted file mode 100644 index b4612b464..000000000 --- a/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -SHELL := /bin/bash -euxo pipefail -.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/docs/source/contributing.rst b/docs/source/contributing.rst index 21e5dbe82..0e531220d 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -64,8 +64,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 f4dc7328d8340caa110aa4087e0df9c799a5bda4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 10:08:38 +0000 Subject: [PATCH 140/688] 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 48816b12d..778c8f743 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.23.1", "doc8==2.0.0", - "doccmd==2025.10.25", + "doccmd==2025.10.27", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.9.25", From 5347522b7c5bb079f5b2f419d28e2599dcda9189 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 28 Oct 2025 10:54:06 +0000 Subject: [PATCH 141/688] Bump uv to 0.9.5 --- .pre-commit-config.yaml | 62 ++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4236e1a68..3a653b355 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -96,7 +96,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 @@ -104,7 +104,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 @@ -112,7 +112,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 @@ -121,7 +121,7 @@ repos: --shell=bash" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: shfmt @@ -129,7 +129,7 @@ repos: entry: uv run --extra=dev 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 @@ -138,7 +138,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 @@ -148,7 +148,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 @@ -156,7 +156,7 @@ repos: entry: uv run --extra=dev doccmd --language=python --command="mypy" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] - id: check-manifest name: check-manifest @@ -164,7 +164,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 @@ -173,7 +173,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 @@ -181,7 +181,7 @@ repos: entry: uv run --extra=dev doccmd --language=python --command="pyright" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] - id: vulture name: vulture @@ -189,7 +189,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 @@ -197,7 +197,7 @@ repos: entry: uv run --extra=dev doccmd --language=python --command="vulture" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: pyroma @@ -206,7 +206,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 @@ -214,7 +214,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 @@ -223,7 +223,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 @@ -231,14 +231,14 @@ repos: language: python stages: [manual] types_or: [markdown, rst] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] - id: ruff-check-fix name: Ruff check fix 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 @@ -246,7 +246,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 @@ -254,7 +254,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 @@ -263,7 +263,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 @@ -271,7 +271,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 @@ -279,7 +279,7 @@ repos: entry: uv run --extra=dev -m interrogate language: python types_or: [python] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: interrogate-docs @@ -287,7 +287,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 @@ -296,7 +296,7 @@ repos: language: python types_or: [toml] files: pyproject.toml - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] stages: [pre-commit] - id: linkcheck @@ -307,7 +307,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 @@ -317,7 +317,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 @@ -325,7 +325,7 @@ repos: language: python stages: [manual] pass_filenames: false - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] - id: pyright-verifytypes name: pyright-verifytypes @@ -334,14 +334,14 @@ repos: language: python pass_filenames: false types_or: [python] - additional_dependencies: [uv==0.6.3] + additional_dependencies: [uv==0.9.5] - id: yamlfix name: yamlfix 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 @@ -349,5 +349,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 a0231fd59cfc58a30bf5c4b2dd4715fb25a272b3 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 28 Oct 2025 12:21:53 +0000 Subject: [PATCH 142/688] 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 8c7e78f05..4bce31b24 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -60,6 +60,7 @@ html_show_sourcelink = False html_theme_options = { "sidebar_hide_name": False, + "top_of_page_buttons": ["view", "edit"], } # Output file base name for HTML help builder. From 2ed9e31ab765e7f4fd0d9a0fa233ffcb8d606325 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 28 Oct 2025 13:42:05 +0000 Subject: [PATCH 143/688] 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 4bce31b24..914bcb886 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -60,7 +60,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/", + "source_branch": "main", + "source_directory": "docs/source/", } # Output file base name for HTML help builder. From aefc8d7efd9c850bc4eee2c576eaa6e8acc2d30d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Oct 2025 10:08:16 +0000 Subject: [PATCH 144/688] 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 280218ea2..c05290470 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,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-pyproject==0.3.0", "sphinx-substitution-extensions==2025.10.24", "sphinxcontrib-spelling==8.0.1", From 9a94a5c41493e8efaa10d37a9a090e65a76dc977 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:08:28 +0000 Subject: [PATCH 145/688] 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 c05290470..1d11449d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pytest==8.4.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 ceb43b785390c9727aebc1dce84bb9712952a586 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:07:53 +0000 Subject: [PATCH 146/688] 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 1d11449d2..ffcb7f329 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "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 26b2a9c820a377f7354fb0abfac247ee5722866e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:07:56 +0000 Subject: [PATCH 147/688] 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 ffcb7f329..8fd215533 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pytest==8.4.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 e9831945a81fbb3a41d4a17c56b7994275746785 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 8 Nov 2025 16:22:49 +0000 Subject: [PATCH 148/688] Add --no-write-to-file flag for read-only doccmd commands --- .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 3a653b355..1f5f92b50 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -117,8 +117,8 @@ repos: - id: shellcheck-docs name: shellcheck-docs - entry: uv run --extra=dev doccmd --language=shell --language=console --command="shellcheck - --shell=bash" + entry: uv run --extra=dev doccmd --no-write-to-file --language=shell --language=console + --command="shellcheck --shell=bash" language: python types_or: [markdown, rst] additional_dependencies: [uv==0.9.5] @@ -153,7 +153,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] additional_dependencies: [uv==0.9.5] @@ -178,7 +178,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] additional_dependencies: [uv==0.9.5] @@ -194,7 +194,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: [markdown, rst] additional_dependencies: [uv==0.9.5] @@ -227,7 +227,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] @@ -284,7 +284,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 3ba7058114bf5b7cab3568faa1a2d0fce9c83035 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 8 Nov 2025 19:54:48 +0000 Subject: [PATCH 149/688] Bump doccmd to 2025.11.8.1 and sybil-extras to 2025.11.8 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8fd215533..3fe95ed38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.23.1", "doc8==2.0.0", - "doccmd==2025.10.27", + "doccmd==2025.11.8", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.9.25", From bfbfc7e0d98eb863de3ca683d0d15248011bdb8f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 8 Nov 2025 20:13:34 +0000 Subject: [PATCH 150/688] 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 1f5f92b50..fb99a708b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -117,8 +117,8 @@ repos: - id: shellcheck-docs name: shellcheck-docs - entry: uv run --extra=dev doccmd --no-write-to-file --language=shell --language=console - --command="shellcheck --shell=bash" + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=shell + --language=console --command="shellcheck --shell=bash" language: python types_or: [markdown, rst] additional_dependencies: [uv==0.9.5] @@ -153,7 +153,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] additional_dependencies: [uv==0.9.5] @@ -178,7 +179,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] additional_dependencies: [uv==0.9.5] @@ -194,7 +196,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: [markdown, rst] additional_dependencies: [uv==0.9.5] @@ -227,7 +230,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] @@ -284,7 +288,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 1b6dd2cdde873c11d397704ae5be915cd7659b5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 11:26:06 +0000 Subject: [PATCH 151/688] Bump doccmd from 2025.11.8 to 2025.11.8.1 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.11.8 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.11.08...2025.11.08.1) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.11.8.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 3fe95ed38..dc7683977 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.23.1", "doc8==2.0.0", - "doccmd==2025.11.8", + "doccmd==2025.11.8.1", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.9.25", From 1a537631547a8beb70329d46b227673eacd050a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 11:26:14 +0000 Subject: [PATCH 152/688] 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 3fe95ed38..b46b4a360 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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-cov==7.0.0", "pyyaml==6.0.3", "ruff==0.14.4", From 5b9a189e4154d88108440a5a31c61093d76b780b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 11:26:28 +0000 Subject: [PATCH 153/688] 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 3fe95ed38..c40855d60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,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", "pygments==2.19.2", "pylint[spelling]==4.0.2", From fac2dcda58555860d2099700c7d42dc2f59beb59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 11:26:35 +0000 Subject: [PATCH 154/688] 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 3fe95ed38..d52f07008 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ dependencies = [ optional-dependencies.dev = [ "actionlint-py==1.7.8.24", "check-manifest==0.51", - "deptry==0.23.1", + "deptry==0.24.0", "doc8==2.0.0", "doccmd==2025.11.8", "docformatter==1.7.7", From 0d1f110142d6fdce9934dc161da0ec3720e8277a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:08:25 +0000 Subject: [PATCH 155/688] 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 7b0e67fa0..1c33fd64c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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-cov==7.0.0", "pyyaml==6.0.3", "ruff==0.14.4", From 87b26bc582df6843d37ee7c1a4c155d1faebf4d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:07:07 +0000 Subject: [PATCH 156/688] 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 1c33fd64c..b3a393d15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ optional-dependencies.dev = [ "pre-commit==4.4.0", "pydocstyle==6.3", "pygments==2.19.2", - "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 56dffc291149b888d8f29e7c8b0c8761bae02ab5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:07:31 +0000 Subject: [PATCH 157/688] 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 1c33fd64c..3f1bd9df9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pytest==9.0.1", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 3b1bdeb4cfde26afae8d4aa5a321a130cc3e9958 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 10:10:29 +0000 Subject: [PATCH 158/688] 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 12b6f57c9..ad3977ba3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ optional-dependencies.dev = [ "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.1", "sphinx-pyproject==0.3.0", - "sphinx-substitution-extensions==2025.10.24", + "sphinx-substitution-extensions==2025.11.17", "sphinxcontrib-spelling==8.0.1", "sybil==9.2.0", "types-requests==2.32.4.20250913", From 59e86d3e789f04a48e0c3fe93d81db51388bdca5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 10:08:03 +0000 Subject: [PATCH 159/688] 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 ad3977ba3..bf32f1900 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,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-pyproject==0.3.0", "sphinx-substitution-extensions==2025.11.17", "sphinxcontrib-spelling==8.0.1", From bbe38af05e4fcf69b39da33fe7f1bde4dd3f668f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 10:08:20 +0000 Subject: [PATCH 160/688] 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 bf32f1900..1ab667665 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2025.11.8.1", + "doccmd==2025.11.20", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.9.25", From 8b0901ead24fba4db3ae894f391531f29466f184 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 10:06:36 +0000 Subject: [PATCH 161/688] 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/ci.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b71569197..73e6ce82c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,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/lint.yml b/.github/workflows/lint.yml index d3bb9d802..fbdc4c6ce 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -23,7 +23,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 ca3d74491..2ccae040a 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: # See # https://github.com/stefanzweifel/git-auto-commit-action?tab=readme-ov-file#push-to-protected-branches From b9a9e7fc3328f8db1a1e99e090637e9e429d16f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:00:48 +0000 Subject: [PATCH 162/688] Bump ruff from 0.14.5 to 0.14.6 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.5 to 0.14.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.14.5...0.14.6) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.14.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 1ab667665..e21e40d16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pytest==9.0.1", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "ruff==0.14.5", + "ruff==0.14.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 0b16d8afe0534f3b9536b1141c4998cdd590db00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:00:57 +0000 Subject: [PATCH 163/688] 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 1ab667665..e72620c23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,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", "pygments==2.19.2", "pylint[spelling]==4.0.3", From 853ddae258071d037f702bd3070c6d7bc90b29e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:01:03 +0000 Subject: [PATCH 164/688] 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 1ab667665..815ba085d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ dependencies = [ "vws-auth-tools>=2024.7.12", ] optional-dependencies.dev = [ - "actionlint-py==1.7.8.24", + "actionlint-py==1.7.9.24", "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", From 2e18e5431b27c089b21b7942bd04c4ecd37b1d5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 10:06:53 +0000 Subject: [PATCH 165/688] 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 b6c742df5..30c82e995 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pygments==2.19.2", "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 7631aae5f5bca321c70211ae01c5a17f5f56208e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:35:39 +0000 Subject: [PATCH 166/688] 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 30c82e995..e89705fa1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,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 8af37ccca7e839af52ae2c334526fcd3e1171991 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:35:48 +0000 Subject: [PATCH 167/688] 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 30c82e995..0905b4a76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ optional-dependencies.dev = [ "pre-commit==4.5.0", "pydocstyle==6.3", "pygments==2.19.2", - "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 8318d6b97d0ed7573d9c346fecd148c8c08cca1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:36:03 +0000 Subject: [PATCH 168/688] 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 30c82e995..e0f7b7705 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "sphinx-lint==1.0.2", "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2025.11.17", - "sphinxcontrib-spelling==8.0.1", + "sphinxcontrib-spelling==8.0.2", "sybil==9.2.0", "types-requests==2.32.4.20250913", "vulture==2.14", From 19936ad54596c7a66babbe067d6d79833a014962 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:36:13 +0000 Subject: [PATCH 169/688] Bump ruff from 0.14.6 to 0.14.7 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.6 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.6...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 30c82e995..dd38c1a33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pytest==9.0.1", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "ruff==0.14.6", + "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 3f68948742f8d2513452284b2b3dd902947f2e9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 10:07:49 +0000 Subject: [PATCH 170/688] 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 27218b970..8a4cf2e2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,7 @@ optional-dependencies.dev = [ "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2025.11.17", "sphinxcontrib-spelling==8.0.2", - "sybil==9.2.0", + "sybil==9.3.0", "types-requests==2.32.4.20250913", "vulture==2.14", "vws-python-mock==2025.3.10.1", From 61ead975199d709d9910ae9bb96a27914347ad50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 10:07:39 +0000 Subject: [PATCH 171/688] 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 8a4cf2e2d..22244360a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2025.11.20", + "doccmd==2025.12.3", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.9.25", From 80d4d1d4386fe0d1f0f13805f689e630223db648 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:06:57 +0000 Subject: [PATCH 172/688] 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 22244360a..35be4b8dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pytest==9.0.1", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 ba8e9c31e41f8c87aabcd50aba4f52010f37f1df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:07:04 +0000 Subject: [PATCH 173/688] Bump doccmd from 2025.12.3 to 2025.12.5 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.12.3 to 2025.12.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.05) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2025.12.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 22244360a..00784ecf5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2025.12.3", + "doccmd==2025.12.5", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.9.25", From af3d68217fb3c98fc1ea0c2c00be3d71da1df79a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 8 Dec 2025 07:22:26 +0000 Subject: [PATCH 174/688] Add validation for gha-find-replace actions 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 2ccae040a..60b4f9398 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,6 +55,7 @@ jobs: echo "underline=${underline}" >> "$GITHUB_OUTPUT" - name: Update changelog + id: update_changelog uses: jacobtomlinson/gha-find-replace@v3 with: find: "Next\n----" @@ -63,6 +64,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 92e7b03224bb43b46b55e0b424238d141147b382 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 10:09:00 +0000 Subject: [PATCH 175/688] 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 ef165a9f5..db2c59e9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "pyproject-fmt==2.11.1", "pyright==1.1.407", "pyroma==5.0", - "pytest==9.0.1", + "pytest==9.0.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", "ruff==0.14.8", From 27ec48f4068cc873013190326ff8c65603b1db7b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 10:09:06 +0000 Subject: [PATCH 176/688] Bump doccmd from 2025.12.5 to 2025.12.8.5 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2025.12.5 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.05...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 ef165a9f5..9157ef37a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2025.12.5", + "doccmd==2025.12.8.5", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.9.25", From dd3dfb2ddc739a3324eab9af956bb4da4df4d1c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:08:47 +0000 Subject: [PATCH 177/688] 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 e1b5405c2..6bfdb8b2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,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.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", From d8832258b02509285368ea9b81cf5e6508e4e19f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 10:06:54 +0000 Subject: [PATCH 178/688] 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 6bfdb8b2c..5c399ffce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2025.12.8.5", + "doccmd==2025.12.10", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.9.25", From c9adeaef2a8df61209a3a3d8dd6f14505dbdb870 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 10:06:23 +0000 Subject: [PATCH 179/688] 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 5c399ffce..aa8469e08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pytest==9.0.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 e91723bfb0438b957412d137e091e02c33d46b17 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 13 Dec 2025 07:47:46 +0000 Subject: [PATCH 180/688] 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 aa8469e08..63c580c12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ dynamic = [ "version", ] dependencies = [ - "beartype>=0.18.5", + "beartype>=0.22.9", "requests>=2.32.3", "urllib3>=2.2.3", "vws-auth-tools>=2024.7.12", From 394cc018b13c3a1bdafd296e816dde6919646c45 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 13 Dec 2025 08:46:40 +0000 Subject: [PATCH 181/688] 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 fb99a708b..869dd167b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,6 +25,8 @@ ci: - pyright - pyright-docs - pyright-verifytypes + - ty + - ty-docs - pyroma - ruff-check-fix - ruff-check-fix-docs @@ -341,6 +343,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: yamlfix name: yamlfix entry: uv run --extra=dev yamlfix diff --git a/pyproject.toml b/pyproject.toml index 63c580c12..c55ab29cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.11.17", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", + "ty==0.0.1a34", "types-requests==2.32.4.20250913", "vulture==2.14", "vws-python-mock==2025.3.10.1", From 39bd39bbf5cbbd54e15e1c93293ad4fe23ce22e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 10:07:12 +0000 Subject: [PATCH 182/688] 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 c55ab29cd..0c7d5b23d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,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 4c4bcd7e8a6938b4666b6426d1f461fda2325251 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 10:07:17 +0000 Subject: [PATCH 183/688] 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 c55ab29cd..e004a6c1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2025.12.10", + "doccmd==2025.12.13", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.9.25", From 66535b2d74b3cdda1113677b6a8e949719c18e9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 10:07:29 +0000 Subject: [PATCH 184/688] 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 c55ab29cd..db264822d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ optional-dependencies.dev = [ "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.2", "sphinx-pyproject==0.3.0", - "sphinx-substitution-extensions==2025.11.17", + "sphinx-substitution-extensions==2025.12.15", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "ty==0.0.1a34", From 6b3eddb13067d339f394928fe642461d6cf32967 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:07:37 +0000 Subject: [PATCH 185/688] Bump ty from 0.0.1a34 to 0.0.1a35 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.1a34 to 0.0.1a35. - [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.1-alpha.35) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.1a35 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 c78c199b2..fbfc2d380 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.12.15", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.1a34", + "ty==0.0.1a35", "types-requests==2.32.4.20250913", "vulture==2.14", "vws-python-mock==2025.3.10.1", From beaee0dd2d42b36956cbd988790f82cf893e374f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:07:12 +0000 Subject: [PATCH 186/688] 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 fbfc2d380..6cc88f6ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,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", "pygments==2.19.2", "pylint[spelling]==4.0.4", From 26fa111cbd1f1219241ac9c5a75b7eb986522228 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:07:28 +0000 Subject: [PATCH 187/688] Bump ty from 0.0.1a35 to 0.0.2 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.1a35 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.35...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 fbfc2d380..12cee6d12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.12.15", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.1a35", + "ty==0.0.2", "types-requests==2.32.4.20250913", "vulture==2.14", "vws-python-mock==2025.3.10.1", From a536d6257f57d2464da9f40efd17f758e5b7387c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Dec 2025 10:06:55 +0000 Subject: [PATCH 188/688] 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 2a2a07f0f..d624a7acc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ optional-dependencies.dev = [ "vulture==2.14", "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.5", - "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/" From 50dd267dc9f250847acaf2d8da3361554326e148 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Dec 2025 10:07:07 +0000 Subject: [PATCH 189/688] Bump ty from 0.0.2 to 0.0.3 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.2 to 0.0.3. - [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.3) --- updated-dependencies: - dependency-name: ty dependency-version: 0.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 2a2a07f0f..1f2180bc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.12.15", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.2", + "ty==0.0.3", "types-requests==2.32.4.20250913", "vulture==2.14", "vws-python-mock==2025.3.10.1", From 44ef2a77b8aa085c177c70b98f724fbd1b3880e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:06:31 +0000 Subject: [PATCH 190/688] 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 3bb297ad8..64b60edb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pytest==9.0.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 d6636bdad9cf3686abd731fb4b8feaf5bc8c052e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:06:37 +0000 Subject: [PATCH 191/688] Bump ty from 0.0.3 to 0.0.4 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.3 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.3...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 3bb297ad8..c04b49cb0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.12.15", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.3", + "ty==0.0.4", "types-requests==2.32.4.20250913", "vulture==2.14", "vws-python-mock==2025.3.10.1", From 1bd3d3788e29a5f3237f564566494adcef9836b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:11:26 +0000 Subject: [PATCH 192/688] Bump sphinx from 8.2.3 to 9.0.4 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 8.2.3 to 9.0.4. - [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.0.4) --- updated-dependencies: - dependency-name: sphinx dependency-version: 9.0.4 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 c04b49cb0..21f7d62fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,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.0.4", "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.2", "sphinx-pyproject==0.3.0", From 9723406fb1f2715b0be49193b486c659ed65b465 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 19 Dec 2025 20:00:44 +0000 Subject: [PATCH 193/688] Update furo package version to 2025.12.19 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 21f7d62fd..88123bf98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ optional-dependencies.dev = [ "doccmd==2025.12.13", "docformatter==1.7.7", "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 88c8a17a56f3ace8352dc675e089b8422f687d9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 10:06:54 +0000 Subject: [PATCH 194/688] 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 fbc8246f8..317f65c8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ optional-dependencies.dev = [ "doccmd==2025.12.13", "docformatter==1.7.7", "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 ac207c1a8d55cf624374dae8028f582ad4aaef8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 10:07:09 +0000 Subject: [PATCH 195/688] 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 fbc8246f8..5d847450f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.12.15", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.4", + "ty==0.0.5", "types-requests==2.32.4.20250913", "vulture==2.14", "vws-python-mock==2025.3.10.1", From e0a7faa6995fd78b42c83cef94d8a6276613ef2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Dec 2025 10:06:00 +0000 Subject: [PATCH 196/688] 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 53fa894f3..095963da1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.12.15", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.5", + "ty==0.0.6", "types-requests==2.32.4.20250913", "vulture==2.14", "vws-python-mock==2025.3.10.1", From 0e55cc744253a9bb880728f6bc9c3db130565083 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Dec 2025 10:06:37 +0000 Subject: [PATCH 197/688] 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 095963da1..0777cf4a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.12.15", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.6", + "ty==0.0.7", "types-requests==2.32.4.20250913", "vulture==2.14", "vws-python-mock==2025.3.10.1", From 235856347f8e614dbd4414f6d3e41cfefc568606 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 26 Dec 2025 14:24:52 +0000 Subject: [PATCH 198/688] 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 869dd167b..90934191b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,6 +40,8 @@ ci: - vulture - vulture-docs - yamlfix + - pyrefly + - pyrefly-docs default_install_hook_types: [pre-commit, pre-push, commit-msg] @@ -376,3 +378,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 53fa894f3..62a8733d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,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 47c76bda6095117739fb2eef4277c22f4b4828b8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 26 Dec 2025 15:17:08 +0000 Subject: [PATCH 199/688] Fix pyrefly --- src/vws/vws.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/vws/vws.py b/src/vws/vws.py index 5dd7503db..bb05233c3 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -352,10 +352,10 @@ def get_target_record(self, target_id: str) -> TargetStatusAndRecord: target_record_dict = dict(result_data["target_record"]) target_record = TargetRecord( target_id=target_record_dict["target_id"], - active_flag=target_record_dict["active_flag"], + active_flag=bool(target_record_dict["active_flag"]), name=target_record_dict["name"], - width=target_record_dict["width"], - tracking_rating=target_record_dict["tracking_rating"], + width=float(target_record_dict["width"]), + tracking_rating=int(target_record_dict["tracking_rating"]), reco_rating=target_record_dict["reco_rating"], ) return TargetStatusAndRecord( @@ -485,11 +485,11 @@ def get_target_summary_report(self, target_id: str) -> TargetSummaryReport: database_name=result_data["database_name"], target_name=result_data["target_name"], upload_date=date.fromisoformat(result_data["upload_date"]), - active_flag=result_data["active_flag"], - tracking_rating=result_data["tracking_rating"], - total_recos=result_data["total_recos"], - current_month_recos=result_data["current_month_recos"], - previous_month_recos=result_data["previous_month_recos"], + active_flag=bool(result_data["active_flag"]), + tracking_rating=int(result_data["tracking_rating"]), + total_recos=int(result_data["total_recos"]), + current_month_recos=int(result_data["current_month_recos"]), + previous_month_recos=int(result_data["previous_month_recos"]), ) def get_database_summary_report(self) -> DatabaseSummaryReport: @@ -524,18 +524,18 @@ def get_database_summary_report(self) -> DatabaseSummaryReport: response_data = dict(json.loads(s=response.text)) return DatabaseSummaryReport( - active_images=response_data["active_images"], - current_month_recos=response_data["current_month_recos"], - failed_images=response_data["failed_images"], - inactive_images=response_data["inactive_images"], - name=response_data["name"], - previous_month_recos=response_data["previous_month_recos"], - processing_images=response_data["processing_images"], - reco_threshold=response_data["reco_threshold"], - request_quota=response_data["request_quota"], - request_usage=response_data["request_usage"], - target_quota=response_data["target_quota"], - total_recos=response_data["total_recos"], + active_images=int(response_data["active_images"]), + current_month_recos=int(response_data["current_month_recos"]), + failed_images=int(response_data["failed_images"]), + inactive_images=int(response_data["inactive_images"]), + name=str(response_data["name"]), + previous_month_recos=int(response_data["previous_month_recos"]), + processing_images=int(response_data["processing_images"]), + reco_threshold=int(response_data["reco_threshold"]), + request_quota=int(response_data["request_quota"]), + request_usage=int(response_data["request_usage"]), + target_quota=int(response_data["target_quota"]), + total_recos=int(response_data["total_recos"]), ) def delete_target(self, target_id: str) -> None: From 495f0048c6d5e923f001d43b729a79fca5937b37 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 26 Dec 2025 15:21:38 +0000 Subject: [PATCH 200/688] Fix mypy --- src/vws/vws.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vws/vws.py b/src/vws/vws.py index bb05233c3..8def19ed7 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -528,7 +528,7 @@ def get_database_summary_report(self) -> DatabaseSummaryReport: current_month_recos=int(response_data["current_month_recos"]), failed_images=int(response_data["failed_images"]), inactive_images=int(response_data["inactive_images"]), - name=str(response_data["name"]), + name=str(object=response_data["name"]), previous_month_recos=int(response_data["previous_month_recos"]), processing_images=int(response_data["processing_images"]), reco_threshold=int(response_data["reco_threshold"]), From ab3c1cbc0ed74693bd2566a07a79aa9d65f4cab8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 29 Dec 2025 13:11:32 +0000 Subject: [PATCH 201/688] Add zizmor for GitHub Actions security linting --- .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 90934191b..f80c2aa4a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,6 +40,7 @@ ci: - vulture - vulture-docs - yamlfix + - zizmor - pyrefly - pyrefly-docs @@ -371,6 +372,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 fffc77ad5..c4fd55a7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,6 +79,7 @@ optional-dependencies.dev = [ "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.5", "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/" From 8e33271b8df067abfb6f40170d644c0a79dd805e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 29 Dec 2025 13:42:59 +0000 Subject: [PATCH 202/688] Add zizmor config --- zizmor.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 zizmor.yml diff --git a/zizmor.yml b/zizmor.yml new file mode 100644 index 000000000..fd4a958a9 --- /dev/null +++ b/zizmor.yml @@ -0,0 +1,4 @@ +--- +rules: + unpinned-uses: + disable: true From fb43e8b27a7f85b3250a39b7541aeed8d7d050c2 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 29 Dec 2025 14:36:32 +0000 Subject: [PATCH 203/688] Fix zizmor / ignore some errors --- .github/workflows/ci.yml | 6 ++++-- .github/workflows/dependabot-merge.yml | 1 - .github/workflows/lint.yml | 6 ++++-- .github/workflows/release.yml | 6 +++--- zizmor.yml | 8 ++++++++ 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73e6ce82c..ca86af4b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,4 @@ --- - name: Test on: @@ -12,9 +11,10 @@ on: # Run at 1:00 every day - cron: 0 1 * * * +permissions: {} + jobs: build: - strategy: matrix: python-version: ['3.13'] @@ -24,6 +24,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/dependabot-merge.yml b/.github/workflows/dependabot-merge.yml index 5238c9f68..4932493cf 100644 --- a/.github/workflows/dependabot-merge.yml +++ b/.github/workflows/dependabot-merge.yml @@ -1,5 +1,4 @@ --- - name: Dependabot auto-merge on: pull_request diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fbdc4c6ce..4c01f743c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,5 +1,4 @@ --- - name: Lint on: @@ -12,9 +11,10 @@ on: # Run at 1:00 every day - cron: 0 1 * * * +permissions: {} + jobs: build: - strategy: matrix: python-version: ['3.13'] @@ -24,6 +24,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 60b4f9398..305cecb7b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,4 @@ --- - name: Release on: workflow_dispatch @@ -32,6 +31,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 @@ -59,8 +59,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 }}" include: CHANGELOG.rst regex: false diff --git a/zizmor.yml b/zizmor.yml index fd4a958a9..f63e179d2 100644 --- a/zizmor.yml +++ b/zizmor.yml @@ -2,3 +2,11 @@ rules: unpinned-uses: disable: true + cache-poisoning: + disable: true + bot-conditions: + disable: true + dependabot-cooldown: + disable: true + template-injection: + disable: true From 502ec2e4c18d91ab4c4556bbe5354031f1d7fc17 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 29 Dec 2025 14:37:41 +0000 Subject: [PATCH 204/688] Ignore zizmor in check-manifest --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index c4fd55a7e..d1c81f7e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -302,6 +302,7 @@ ignore = [ "tests/**", "vuforia_secrets.env.example", "lint.mk", + "zizmor.yml", ] [tool.deptry] From 30aee41db18d59beb0864ddef49bf181947a4460 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 29 Dec 2025 16:11:22 +0000 Subject: [PATCH 205/688] Fix bugbot issues --- .github/workflows/release.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 305cecb7b..8bbb38a09 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: actions/checkout@v6 - with: + with: # zizmor: ignore[artipacked] git-auto-commit-action requires credentials # See # https://github.com/stefanzweifel/git-auto-commit-action?tab=readme-ov-file#push-to-protected-branches token: ${{ secrets.RELEASE_PAT }} @@ -31,7 +31,6 @@ 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 @@ -59,8 +58,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\ + \ }}" include: CHANGELOG.rst regex: false From ba43cc99746c69c87521fcf0305766a7690357b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 10:05:49 +0000 Subject: [PATCH 206/688] 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 d1c81f7e7..f6bce47c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.12.15", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.7", + "ty==0.0.8", "types-requests==2.32.4.20250913", "vulture==2.14", "vws-python-mock==2025.3.10.1", From 2781f1225cdcd1ce50dfc498c2917beda3ea187e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 10:05:56 +0000 Subject: [PATCH 207/688] 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 d1c81f7e7..daa3fedd5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,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 d40ba68247317001eb7a6c3cb79b4fcbd4728dba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Dec 2025 10:07:02 +0000 Subject: [PATCH 208/688] 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 2c15b49b0..51b8d55c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,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 ed0c648710c592668c3c896f6fd290eddf0d7c0d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 2 Jan 2026 06:50:58 +0000 Subject: [PATCH 209/688] Switch from pre-commit to prek - Replace pre-commit with prek in pyproject.toml dev dependencies - Update .pre-commit-config.yaml to use prek builtin hooks - Remove pre-commit CI configuration (ci: section) - Update GitHub Actions workflow to use prek commands - Update contributing documentation for prek --- .github/workflows/lint.yml | 8 ++--- .pre-commit-config.yaml | 59 ++++++------------------------------ docs/source/contributing.rst | 10 +++--- pyproject.toml | 2 +- 4 files changed, 19 insertions(+), 60 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4c01f743c..a2e604942 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -35,9 +35,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 }} @@ -47,7 +47,7 @@ jobs: completion-lint: needs: build runs-on: ubuntu-latest - if: always() # Run even if one matrix job fails + if: always() # Run even if one matrix job fails steps: - name: Check matrix job status run: |- diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f80c2aa4a..82eeb1116 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,58 +1,14 @@ --- fail_fast: true -# See https://pre-commit.com for more information -# See https://pre-commit.com/hooks.html for more hooks - -ci: - # We use system Python, with required dependencies specified in pyproject.toml. - # We therefore cannot use those dependencies in pre-commit CI. - skip: - - actionlint - - sphinx-lint - - check-manifest - - deptry - - doc8 - - docformatter - - docs - - interrogate - - interrogate-docs - - linkcheck - - mypy - - mypy-docs - - pylint - - pyproject-fmt-fix - - pyright - - pyright-docs - - pyright-verifytypes - - ty - - ty-docs - - pyroma - - ruff-check-fix - - ruff-check-fix-docs - - ruff-format-fix - - ruff-format-fix-docs - - shellcheck - - shellcheck-docs - - shfmt - - shfmt-docs - - spelling - - vulture - - vulture-docs - - yamlfix - - zizmor - - pyrefly - - pyrefly-docs +# See https://prek.j178.dev for more information + + default_install_hook_types: [pre-commit, pre-push, commit-msg] repos: - - repo: meta - hooks: - - id: check-useless-excludes - stages: [pre-commit] - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 + - repo: builtin hooks: - id: check-added-large-files stages: [pre-commit] @@ -76,11 +32,14 @@ repos: stages: [pre-commit] - id: end-of-file-fixer stages: [pre-commit] + - id: trailing-whitespace + stages: [pre-commit] + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: - 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: diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 0e531220d..55d902399 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -27,11 +27,11 @@ and on Ubuntu with ``apt``: $ apt-get install -y enchant -Install ``pre-commit`` hooks: +Install ``prek`` 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 51b8d55c1..2aa051145 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,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", "pygments==2.19.2", "pylint[spelling]==4.0.4", From 852127947726f91c4b4566c25bf3a26be3f2f38e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 2 Jan 2026 07:05:06 +0000 Subject: [PATCH 210/688] Undo some unnecessary changes --- .pre-commit-config.yaml | 45 ++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 82eeb1116..c18dc43f5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,8 +3,6 @@ fail_fast: true # See https://prek.j178.dev for more information - - default_install_hook_types: [pre-commit, pre-push, commit-msg] repos: @@ -32,14 +30,11 @@ repos: stages: [pre-commit] - id: end-of-file-fixer stages: [pre-commit] - - id: trailing-whitespace - stages: [pre-commit] - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 - hooks: - 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: @@ -81,7 +76,8 @@ repos: - id: shellcheck-docs name: shellcheck-docs - entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=shell + entry: + uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=shell --language=console --command="shellcheck --shell=bash" language: python types_or: [markdown, rst] @@ -98,7 +94,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] @@ -117,7 +114,8 @@ repos: - id: mypy-docs name: mypy-docs stages: [pre-push] - entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python + entry: + uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python --command="mypy" language: python types_or: [markdown, rst] @@ -143,7 +141,8 @@ 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 + entry: + uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python --command="pyright" language: python types_or: [markdown, rst] @@ -160,7 +159,8 @@ repos: - id: vulture-docs name: vulture docs - entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python + entry: + uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python --command="vulture" language: python types_or: [markdown, rst] @@ -194,7 +194,8 @@ repos: - id: pylint-docs name: pylint-docs - entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python + entry: + uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python --command="pylint" language: python stages: [manual] @@ -227,7 +228,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] @@ -252,7 +254,8 @@ repos: - id: interrogate-docs name: interrogate docs - entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python + entry: + uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python --command="interrogate" language: python types_or: [markdown, rst] @@ -270,7 +273,8 @@ repos: - id: linkcheck name: linkcheck - entry: uv run --extra=dev sphinx-build -M linkcheck docs/source docs/build + entry: + uv run --extra=dev sphinx-build -M linkcheck docs/source docs/build -W language: python types_or: [rst] @@ -280,7 +284,8 @@ repos: - id: spelling name: spelling - entry: uv run --extra=dev sphinx-build -M spelling docs/source docs/build + entry: + uv run --extra=dev sphinx-build -M spelling docs/source docs/build -W language: python types_or: [rst] @@ -317,7 +322,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 + entry: + uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python --command="ty check" language: python types_or: [markdown, rst] @@ -360,7 +366,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 + entry: + uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python --command="pyrefly check" language: python types_or: [markdown, rst] From e2c817a172fb46354c735495fde48b39f9e4e944 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 2 Jan 2026 07:10:53 +0000 Subject: [PATCH 211/688] Remove stage --- .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 c18dc43f5..3313df31a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ fail_fast: true # See https://prek.j178.dev for more information -default_install_hook_types: [pre-commit, pre-push, commit-msg] +default_install_hook_types: [pre-commit, pre-push] repos: - repo: builtin From 1a622a69c12d664cc3d46d0f0d130ba3dc9d4654 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 2 Jan 2026 07:12:37 +0000 Subject: [PATCH 212/688] Remove changes --- .pre-commit-config.yaml | 86 ++++++++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 26 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3313df31a..d9a702af3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,58 @@ --- fail_fast: true -# See https://prek.j178.dev for more information +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks + +ci: + # We use system Python, with required dependencies specified in pyproject.toml. + # We therefore cannot use those dependencies in pre-commit CI. + skip: + - actionlint + - sphinx-lint + - check-manifest + - deptry + - doc8 + - docformatter + - docs + - interrogate + - interrogate-docs + - linkcheck + - mypy + - mypy-docs + - pylint + - pyproject-fmt-fix + - pyright + - pyright-docs + - pyright-verifytypes + - ty + - ty-docs + - pyroma + - ruff-check-fix + - ruff-check-fix-docs + - ruff-format-fix + - ruff-format-fix-docs + - shellcheck + - shellcheck-docs + - shfmt + - shfmt-docs + - spelling + - vulture + - vulture-docs + - yamlfix + - zizmor + - pyrefly + - pyrefly-docs default_install_hook_types: [pre-commit, pre-push] repos: - - repo: builtin + - 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] @@ -76,8 +122,7 @@ repos: - id: shellcheck-docs name: shellcheck-docs - entry: - uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=shell + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=shell --language=console --command="shellcheck --shell=bash" language: python types_or: [markdown, rst] @@ -94,8 +139,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] @@ -114,8 +158,7 @@ repos: - id: mypy-docs name: mypy-docs stages: [pre-push] - entry: - uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python --command="mypy" language: python types_or: [markdown, rst] @@ -141,8 +184,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 + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python --command="pyright" language: python types_or: [markdown, rst] @@ -159,8 +201,7 @@ repos: - id: vulture-docs name: vulture docs - entry: - uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python --command="vulture" language: python types_or: [markdown, rst] @@ -194,8 +235,7 @@ repos: - id: pylint-docs name: pylint-docs - entry: - uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python --command="pylint" language: python stages: [manual] @@ -228,8 +268,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] @@ -254,8 +293,7 @@ repos: - id: interrogate-docs name: interrogate docs - entry: - uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python --command="interrogate" language: python types_or: [markdown, rst] @@ -273,8 +311,7 @@ repos: - id: linkcheck name: linkcheck - entry: - uv run --extra=dev sphinx-build -M linkcheck docs/source docs/build + entry: uv run --extra=dev sphinx-build -M linkcheck docs/source docs/build -W language: python types_or: [rst] @@ -284,8 +321,7 @@ repos: - id: spelling name: spelling - entry: - uv run --extra=dev sphinx-build -M spelling docs/source docs/build + entry: uv run --extra=dev sphinx-build -M spelling docs/source docs/build -W language: python types_or: [rst] @@ -322,8 +358,7 @@ 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 + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python --command="ty check" language: python types_or: [markdown, rst] @@ -366,8 +401,7 @@ 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 + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=python --command="pyrefly check" language: python types_or: [markdown, rst] From dee2727aa1c8f7462c99c17ca6acf01b2ad0b9c1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 2 Jan 2026 07:12:59 +0000 Subject: [PATCH 213/688] Test a change --- src/vws/exceptions/vws_exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index 482223766..e0e268e29 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -127,7 +127,7 @@ class ProjectInactiveError(VWSError): @beartype class MetadataTooLargeError(VWSError): """ - Exception raised when Vuforia returns a response with a result code + Exception raised when Vuforia returns responses with a result code 'MetadataTooLarge'. """ From bcb26d17a371a82c5a25754e9199ed2a80c0fe22 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 2 Jan 2026 07:13:08 +0000 Subject: [PATCH 214/688] Undo a change --- src/vws/exceptions/vws_exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index e0e268e29..482223766 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -127,7 +127,7 @@ class ProjectInactiveError(VWSError): @beartype class MetadataTooLargeError(VWSError): """ - Exception raised when Vuforia returns responses with a result code + Exception raised when Vuforia returns a response with a result code 'MetadataTooLarge'. """ From 833c51f2a8ac442a80dbd4d8189e6df605b74775 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 07:14:39 +0000 Subject: [PATCH 215/688] [pre-commit.ci lite] apply automatic fixes --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a2e604942..0a9661f41 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -47,7 +47,7 @@ jobs: completion-lint: needs: build runs-on: ubuntu-latest - if: always() # Run even if one matrix job fails + if: always() # Run even if one matrix job fails steps: - name: Check matrix job status run: |- From 86f85f2c052ab368e78a58bb31a5244f7eee94b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 10:06:16 +0000 Subject: [PATCH 216/688] 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 2aa051145..aa0509965 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ dependencies = [ "vws-auth-tools>=2024.7.12", ] optional-dependencies.dev = [ - "actionlint-py==1.7.9.24", + "actionlint-py==1.7.10.24", "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", From bca8178e917fc97c4c3930fd3686be3ab93591f5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 3 Jan 2026 07:26:15 +0000 Subject: [PATCH 217/688] Make prettier printWidth more compatible with yamlfix --- .prettierrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.prettierrc b/.prettierrc index 3ab9aa054..4a36aae89 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,7 +3,8 @@ { "files": ["*.yaml", "*.yml"], "options": { - "singleQuote": true + "singleQuote": true, + "printWidth": 100 } } ] From 2b14da712b5473f4ddb7795a79a5e051de813222 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 3 Jan 2026 07:37:14 +0000 Subject: [PATCH 218/688] Make prettier more compatible with yamlfix --- .prettierrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.prettierrc b/.prettierrc index 4a36aae89..e0af3cff2 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,6 +4,7 @@ "files": ["*.yaml", "*.yml"], "options": { "singleQuote": true, + "printWidth": 100", "printWidth": 100 } } From 63942af74d3513bc9c33ea54fb688fb10a052537 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 3 Jan 2026 07:46:50 +0000 Subject: [PATCH 219/688] Fix prettier config --- .prettierrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.prettierrc b/.prettierrc index e0af3cff2..a6c3428b7 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,7 +4,7 @@ "files": ["*.yaml", "*.yml"], "options": { "singleQuote": true, - "printWidth": 100", + "printWidth": 100, "printWidth": 100 } } From c0c781fb624d6ab0c811921945ca4193746fb328 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 3 Jan 2026 08:14:11 +0000 Subject: [PATCH 220/688] 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 d9a702af3..1ed243a7a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -374,7 +374,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 7de5dbbf4b415e06cc201aeea7d311709e3d847c Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 3 Jan 2026 08:15:10 +0000 Subject: [PATCH 221/688] Remove duplicate key --- .prettierrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.prettierrc b/.prettierrc index a6c3428b7..4a36aae89 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,7 +4,6 @@ "files": ["*.yaml", "*.yml"], "options": { "singleQuote": true, - "printWidth": 100, "printWidth": 100 } } From d06536eaa00a3ba326e326b55b405ba424b3f15c Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 3 Jan 2026 08:47:44 +0000 Subject: [PATCH 222/688] Fix zizmor warning in release workflow --- .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 8bbb38a09..1c092f518 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,8 +58,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 8d50b7c22d43cf7bdacd70141ea18ec7c86deb2d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 3 Jan 2026 09:05:54 +0000 Subject: [PATCH 223/688] Fix block scalar newline interpretation in release workflow --- .github/workflows/release.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1c092f518..685e0a9bb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,8 +58,7 @@ 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\n----\n\n${{ steps.calver.outputs.release }}\n${{ steps.changelog_underline.outputs.underline }}\n" include: CHANGELOG.rst regex: false From 345aef02c5d608bd6c7fb94410a19a266b497ce1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 3 Jan 2026 09:12:31 +0000 Subject: [PATCH 224/688] [pre-commit.ci lite] apply automatic fixes --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 685e0a9bb..8e655a8a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,7 +58,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 }}\n" + replace: "Next\n----\n\n${{ steps.calver.outputs.release }}\n${{ steps.changelog_underline.outputs.underline\ + \ }}\n" include: CHANGELOG.rst regex: false From 75b82221014b5f19bf172d3d8e9b6559c3343217 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:06:22 +0000 Subject: [PATCH 225/688] 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 aa0509965..69fc622d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2025.12.13", + "doccmd==2026.1.3.2", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.12.19", From af376fc361b240c36e98da5ca197a8ed8243fbc3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 10:08:51 +0000 Subject: [PATCH 226/688] 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 69fc622d7..e31b01609 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.12.15", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.8", + "ty==0.0.9", "types-requests==2.32.4.20250913", "vulture==2.14", "vws-python-mock==2025.3.10.1", From 1edb010db619b933d63371322c28a48c38bac4e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 10:08:56 +0000 Subject: [PATCH 227/688] 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 69fc622d7..13651363d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,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 e654679ecf7997c5edadc7321ece4141d795bee6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 10:09:18 +0000 Subject: [PATCH 228/688] 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 69fc622d7..e6e7250dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.5", "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/" From e66ebb4cf271489a7f1b3d456a1f846f0419d736 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 10:10:40 +0000 Subject: [PATCH 229/688] 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 779fc7e80..2c91b8116 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ optional-dependencies.dev = [ "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "ty==0.0.9", - "types-requests==2.32.4.20250913", + "types-requests==2.32.4.20260107", "vulture==2.14", "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.5", From c96a3da27556606ecfd3cbea41df72a3854e08ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 21:46:10 +0000 Subject: [PATCH 230/688] 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 2c91b8116..2516a3caa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,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", "pygments==2.19.2", "pylint[spelling]==4.0.4", From c6979f1ba097daa9b45acac3ecb0df02e6aaf86e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:31:59 +0000 Subject: [PATCH 231/688] 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 2516a3caa..1c4a71770 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,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-cov==7.0.0", From 3d89fa80ff2f3a40dafdf91187b2cb00b8e3c036 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:32:08 +0000 Subject: [PATCH 232/688] 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 2516a3caa..2cd83f5ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.12.15", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.9", + "ty==0.0.10", "types-requests==2.32.4.20260107", "vulture==2.14", "vws-python-mock==2025.3.10.1", From aa46135c99a55def1a1a6394a610555b5d1bbf1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 10:38:01 +0000 Subject: [PATCH 233/688] 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 37398cc75..e7202bd8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ optional-dependencies.dev = [ "pytest==9.0.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 7795bb4b0066777daabe200ddc80375928a05558 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:34:02 +0000 Subject: [PATCH 234/688] 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 e7202bd8c..cfeaf5906 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional-dependencies.dev = [ "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.2", "sphinx-pyproject==0.3.0", - "sphinx-substitution-extensions==2025.12.15", + "sphinx-substitution-extensions==2026.1.12", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", "ty==0.0.10", From 5352d8fa2df773b301f81e46d48529b86323efe9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:34:11 +0000 Subject: [PATCH 235/688] 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 e7202bd8c..30af2dcd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,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", "pygments==2.19.2", From 59071162cc24648136663e4e4eb1afb6e064270b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:34:20 +0000 Subject: [PATCH 236/688] 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 e7202bd8c..9c89f308d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2025.12.15", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.10", + "ty==0.0.11", "types-requests==2.32.4.20260107", "vulture==2.14", "vws-python-mock==2025.3.10.1", From c5f4eadbcd741164f39152960d3fba50ec2b3c13 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 12 Jan 2026 14:17:13 +0000 Subject: [PATCH 237/688] Switch from docformatter to pydocstringformatter --- .pre-commit-config.yaml | 8 ++++---- pyproject.toml | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1ed243a7a..6b48bc64c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ ci: - check-manifest - deptry - doc8 - - docformatter + - pydocstringformatter - docs - interrogate - interrogate-docs @@ -104,9 +104,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/pyproject.toml b/pyproject.toml index 3f7f5df0a..d4a6cbd29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,13 +42,13 @@ optional-dependencies.dev = [ "deptry==0.24.0", "doc8==2.0.0", "doccmd==2026.1.3.2", - "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2026.1.12", "prek==0.2.27", + "pydocstringformatter==0.7.3", "pydocstyle==6.3", "pygments==2.19.2", "pylint[spelling]==4.0.4", @@ -274,9 +274,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 = [ @@ -346,6 +343,10 @@ enableTypeIgnoreComments = false reportUnnecessaryTypeIgnoreComment = true typeCheckingMode = "strict" +[tool.pydocstringformatter] +write = true +split-summary-body = false + [tool.interrogate] fail-under = 100 omit-covered-files = true From e4126ee5441f4afdae41339aad26f77c04bc4638 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, 12 Jan 2026 14:18:45 +0000 Subject: [PATCH 238/688] [pre-commit.ci lite] apply automatic fixes --- conftest.py | 8 +- docs/source/__init__.py | 4 +- docs/source/conf.py | 4 +- src/vws/__init__.py | 4 +- src/vws/exceptions/__init__.py | 4 +- src/vws/exceptions/base_exceptions.py | 12 +-- src/vws/exceptions/cloud_reco_exceptions.py | 16 +--- src/vws/exceptions/custom_exceptions.py | 20 +--- src/vws/exceptions/vws_exceptions.py | 68 ++++--------- src/vws/include_target_data.py | 4 +- src/vws/query.py | 12 +-- src/vws/reports.py | 8 +- src/vws/response.py | 8 +- src/vws/vws.py | 12 +-- tests/__init__.py | 4 +- tests/conftest.py | 24 ++--- tests/test_cloud_reco_exceptions.py | 8 +- tests/test_query.py | 40 ++------ tests/test_vws.py | 100 +++++--------------- tests/test_vws_exceptions.py | 20 +--- 20 files changed, 100 insertions(+), 280 deletions(-) diff --git a/conftest.py b/conftest.py index 1d776f8db..0e579a4db 100644 --- a/conftest.py +++ b/conftest.py @@ -1,6 +1,4 @@ -""" -Setup for Sybil. -""" +"""Setup for Sybil.""" import io import uuid @@ -21,9 +19,7 @@ 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 914bcb886..920ba1a4b 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/src/vws/__init__.py b/src/vws/__init__.py index 42788b92d..bfd96d6c2 100644 --- a/src/vws/__init__.py +++ b/src/vws/__init__.py @@ -1,6 +1,4 @@ -""" -A library for Vuforia Web Services. -""" +"""A library for Vuforia Web Services.""" from .query import CloudRecoService from .vws import VWS diff --git a/src/vws/exceptions/__init__.py b/src/vws/exceptions/__init__.py index a1e3cd91c..7260ffdbb 100644 --- a/src/vws/exceptions/__init__.py +++ b/src/vws/exceptions/__init__.py @@ -1,3 +1 @@ -""" -Custom exceptions raised by this package. -""" +"""Custom exceptions raised by this package.""" diff --git a/src/vws/exceptions/base_exceptions.py b/src/vws/exceptions/base_exceptions.py index 943323bfd..c86080593 100644 --- a/src/vws/exceptions/base_exceptions.py +++ b/src/vws/exceptions/base_exceptions.py @@ -10,9 +10,7 @@ @beartype class CloudRecoError(Exception): - """ - Base class for Vuforia Cloud Recognition Web API exceptions. - """ + """Base class for Vuforia Cloud Recognition Web API exceptions.""" def __init__(self, response: Response) -> None: """ @@ -24,9 +22,7 @@ def __init__(self, response: Response) -> None: @property def response(self) -> Response: - """ - The response returned by Vuforia which included this error. - """ + """The response returned by Vuforia which included this error.""" return self._response @@ -48,7 +44,5 @@ def __init__(self, response: Response) -> None: @property def response(self) -> Response: - """ - The response returned by Vuforia which included this error. - """ + """The response returned by Vuforia which included this error.""" return self._response diff --git a/src/vws/exceptions/cloud_reco_exceptions.py b/src/vws/exceptions/cloud_reco_exceptions.py index ff5dde209..2b20a7220 100644 --- a/src/vws/exceptions/cloud_reco_exceptions.py +++ b/src/vws/exceptions/cloud_reco_exceptions.py @@ -1,6 +1,4 @@ -""" -Exceptions which match errors raised by the Vuforia Cloud Recognition Web APIs. -""" +"""Exceptions which match errors raised by the Vuforia Cloud Recognition Web APIs.""" from beartype import beartype @@ -17,31 +15,27 @@ class MaxNumResultsOutOfRangeError(CloudRecoError): @beartype class InactiveProjectError(CloudRecoError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'InactiveProject'. """ @beartype class BadImageError(CloudRecoError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'BadImage'. """ @beartype class AuthenticationFailureError(CloudRecoError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'AuthenticationFailure'. """ @beartype class RequestTimeTooSkewedError(CloudRecoError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'RequestTimeTooSkewed'. """ diff --git a/src/vws/exceptions/custom_exceptions.py b/src/vws/exceptions/custom_exceptions.py index 694e98c30..466dfd1cd 100644 --- a/src/vws/exceptions/custom_exceptions.py +++ b/src/vws/exceptions/custom_exceptions.py @@ -11,9 +11,7 @@ @beartype class RequestEntityTooLargeError(Exception): - """ - Exception raised when the given image is too large. - """ + """Exception raised when the given image is too large.""" def __init__(self, response: Response) -> None: """ @@ -25,24 +23,18 @@ def __init__(self, response: Response) -> None: @property def response(self) -> Response: - """ - The response returned by Vuforia which included this error. - """ + """The response returned by Vuforia which included this error.""" return self._response @beartype class TargetProcessingTimeoutError(Exception): - """ - Exception raised when waiting for a target to be processed times out. - """ + """Exception raised when waiting for a target to be processed times out.""" @beartype class ServerError(Exception): # pragma: no cover - """ - Exception raised when VWS returns a server error. - """ + """Exception raised when VWS returns a server error.""" def __init__(self, response: Response) -> None: """ @@ -54,7 +46,5 @@ def __init__(self, response: Response) -> None: @property def response(self) -> Response: - """ - The response returned by Vuforia which included this error. - """ + """The response returned by Vuforia which included this error.""" return self._response diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index 482223766..a25c2261b 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -14,16 +14,13 @@ @beartype class UnknownTargetError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'UnknownTarget'. """ @property def target_id(self) -> str: - """ - The unknown target ID. - """ + """The unknown target ID.""" path = urlparse(url=self.response.url).path # Every HTTP path which can raise this error is in the format # `/something/{target_id}`. @@ -32,23 +29,19 @@ def target_id(self) -> str: @beartype class FailError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code 'Fail'. - """ + """Exception raised when Vuforia returns a response with a result code 'Fail'.""" @beartype class BadImageError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'BadImage'. """ @beartype class AuthenticationFailureError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'AuthenticationFailure'. """ @@ -56,24 +49,20 @@ class AuthenticationFailureError(VWSError): # See https://github.com/VWS-Python/vws-python/issues/822. @beartype class RequestQuotaReachedError(VWSError): # pragma: no cover - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'RequestQuotaReached'. """ @beartype class TargetStatusProcessingError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'TargetStatusProcessing'. """ @property def target_id(self) -> str: - """ - The processing target ID. - """ + """The processing target ID.""" path = urlparse(url=self.response.url).path # Every HTTP path which can raise this error is in the format # `/something/{target_id}`. @@ -83,8 +72,7 @@ def target_id(self) -> str: # This is not simulated by the mock. @beartype class DateRangeError(VWSError): # pragma: no cover - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'DateRangeError'. """ @@ -92,8 +80,7 @@ class DateRangeError(VWSError): # pragma: no cover # This is not simulated by the mock. @beartype class TargetQuotaReachedError(VWSError): # pragma: no cover - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'TargetQuotaReached'. """ @@ -101,8 +88,7 @@ class TargetQuotaReachedError(VWSError): # pragma: no cover # This is not simulated by the mock. @beartype class ProjectSuspendedError(VWSError): # pragma: no cover - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'ProjectSuspended'. """ @@ -110,48 +96,41 @@ class ProjectSuspendedError(VWSError): # pragma: no cover # This is not simulated by the mock. @beartype class ProjectHasNoAPIAccessError(VWSError): # pragma: no cover - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'ProjectHasNoAPIAccess'. """ @beartype class ProjectInactiveError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'ProjectInactive'. """ @beartype class MetadataTooLargeError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'MetadataTooLarge'. """ @beartype class RequestTimeTooSkewedError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'RequestTimeTooSkewed'. """ @beartype class TargetNameExistError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'TargetNameExist'. """ @property def target_name(self) -> str: - """ - The target name which already exists. - """ + """The target name which already exists.""" response_body = self.response.request_body or b"" request_json = json.loads(s=response_body) return str(object=request_json["name"]) @@ -159,24 +138,20 @@ def target_name(self) -> str: @beartype class ImageTooLargeError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'ImageTooLarge'. """ @beartype class TargetStatusNotSuccessError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'TargetStatusNotSuccess'. """ @property def target_id(self) -> str: - """ - The unknown target ID. - """ + """The unknown target ID.""" path = urlparse(url=self.response.url).path # Every HTTP path which can raise this error is in the format # `/something/{target_id}`. @@ -185,7 +160,6 @@ def target_id(self) -> str: @beartype class TooManyRequestsError(VWSError): # pragma: no cover - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'TooManyRequests'. """ diff --git a/src/vws/include_target_data.py b/src/vws/include_target_data.py index 37682e08a..0692a1e40 100644 --- a/src/vws/include_target_data.py +++ b/src/vws/include_target_data.py @@ -1,6 +1,4 @@ -""" -Tools for managing ``CloudRecoService.query``'s ``include_target_data``. -""" +"""Tools for managing ``CloudRecoService.query``'s ``include_target_data``.""" from enum import StrEnum, auto, unique diff --git a/src/vws/query.py b/src/vws/query.py index 96122aa14..ab449c614 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -1,6 +1,4 @@ -""" -Tools for interacting with the Vuforia Cloud Recognition Web APIs. -""" +"""Tools for interacting with the Vuforia Cloud Recognition Web APIs.""" import datetime import io @@ -34,9 +32,7 @@ @beartype def _get_image_data(image: _ImageType) -> bytes: - """ - Get the data of an image file. - """ + """Get the data of an image file.""" original_tell = image.tell() image.seek(0) image_data = image.read() @@ -46,9 +42,7 @@ def _get_image_data(image: _ImageType) -> bytes: @beartype class CloudRecoService: - """ - An interface to the Vuforia Cloud Recognition Web APIs. - """ + """An interface to the Vuforia Cloud Recognition Web APIs.""" def __init__( self, diff --git a/src/vws/reports.py b/src/vws/reports.py index f6a77133c..e59408aad 100644 --- a/src/vws/reports.py +++ b/src/vws/reports.py @@ -1,6 +1,4 @@ -""" -Classes for representing Vuforia reports. -""" +"""Classes for representing Vuforia reports.""" import datetime from dataclasses import dataclass @@ -86,9 +84,7 @@ class TargetRecord: @beartype @dataclass(frozen=True) class TargetData: - """ - The target data optionally included with a query match. - """ + """The target data optionally included with a query match.""" name: str application_metadata: str | None diff --git a/src/vws/response.py b/src/vws/response.py index 269f2e23f..e2060cb9e 100644 --- a/src/vws/response.py +++ b/src/vws/response.py @@ -1,6 +1,4 @@ -""" -Responses for requests to VWS and VWQ. -""" +"""Responses for requests to VWS and VWQ.""" from dataclasses import dataclass @@ -10,9 +8,7 @@ @dataclass(frozen=True) @beartype class Response: - """ - A response from a request. - """ + """A response from a request.""" text: str url: str diff --git a/src/vws/vws.py b/src/vws/vws.py index 8def19ed7..1191ee7df 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -1,6 +1,4 @@ -""" -Tools for interacting with Vuforia APIs. -""" +"""Tools for interacting with Vuforia APIs.""" import base64 import io @@ -52,9 +50,7 @@ @beartype def _get_image_data(image: _ImageType) -> bytes: - """ - Get the data of an image file. - """ + """Get the data of an image file.""" original_tell = image.tell() image.seek(0) image_data = image.read() @@ -131,9 +127,7 @@ def _target_api_request( @beartype(conf=BeartypeConf(is_pep484_tower=True)) class VWS: - """ - An interface to Vuforia Web Services APIs. - """ + """An interface to Vuforia Web Services APIs.""" def __init__( self, 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 74c6fc56d..7c0647786 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 io from collections.abc import Generator @@ -16,9 +14,7 @@ @pytest.fixture(name="_mock_database") def fixture_mock_database() -> Generator[VuforiaDatabase]: - """ - Yield a mock ``VuforiaDatabase``. - """ + """Yield a mock ``VuforiaDatabase``.""" # We use a low processing time so that tests run quickly. with MockVWS(processing_time_seconds=0.2) as mock: database = VuforiaDatabase() @@ -28,9 +24,7 @@ def fixture_mock_database() -> Generator[VuforiaDatabase]: @pytest.fixture def vws_client(_mock_database: VuforiaDatabase) -> VWS: - """ - A VWS client which connects to a mock database. - """ + """A VWS client which connects to a mock database.""" return VWS( server_access_key=_mock_database.server_access_key, server_secret_key=_mock_database.server_secret_key, @@ -39,9 +33,7 @@ def vws_client(_mock_database: VuforiaDatabase) -> VWS: @pytest.fixture def cloud_reco_client(_mock_database: VuforiaDatabase) -> CloudRecoService: - """ - A ``CloudRecoService`` client which connects to a mock database. - """ + """A ``CloudRecoService`` client which connects to a mock database.""" return CloudRecoService( client_access_key=_mock_database.client_access_key, client_secret_key=_mock_database.client_secret_key, @@ -54,9 +46,7 @@ def fixture_image_file( tmp_path: Path, request: pytest.FixtureRequest, ) -> Generator[BinaryIO]: - """ - An image file object. - """ + """An image file object.""" file = tmp_path / "image.jpg" buffer = high_quality_image.getvalue() file.write_bytes(data=buffer) @@ -71,9 +61,7 @@ def image( high_quality_image: io.BytesIO, image_file: BinaryIO, ) -> io.BytesIO | BinaryIO: - """ - An image in any of the types that the API accepts. - """ + """An image in any of the types that the API accepts.""" if request.param == "high_quality_image": return high_quality_image return image_file diff --git a/tests/test_cloud_reco_exceptions.py b/tests/test_cloud_reco_exceptions.py index 3515b0d22..d4c8cb0f7 100644 --- a/tests/test_cloud_reco_exceptions.py +++ b/tests/test_cloud_reco_exceptions.py @@ -1,6 +1,4 @@ -""" -Tests for exceptions raised when using the CloudRecoService. -""" +"""Tests for exceptions raised when using the CloudRecoService.""" import io import uuid @@ -63,9 +61,7 @@ def test_image_too_large( def test_cloudrecoexception_inheritance() -> None: - """ - CloudRecoService-specific exceptions inherit from CloudRecoException. - """ + """CloudRecoService-specific exceptions inherit from CloudRecoException.""" subclasses = [ MaxNumResultsOutOfRangeError, InactiveProjectError, diff --git a/tests/test_query.py b/tests/test_query.py index e229cf95d..11b803eb5 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,6 +1,4 @@ -""" -Tests for the ``CloudRecoService`` querying functionality. -""" +"""Tests for the ``CloudRecoService`` querying functionality.""" import io import uuid @@ -14,18 +12,14 @@ class TestQuery: - """ - Tests for making image queries. - """ + """Tests for making image queries.""" @staticmethod def test_no_matches( cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: - """ - An empty list is returned if there are no matches. - """ + """An empty list is returned if there are no matches.""" result = cloud_reco_client.query(image=image) assert result == [] @@ -35,9 +29,7 @@ def test_match( cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: - """ - Details of matching targets are returned. - """ + """Details of matching targets are returned.""" target_id = vws_client.add_target( name="x", width=1, @@ -51,9 +43,7 @@ def test_match( class TestCustomBaseVWQURL: - """ - Tests for using a custom base VWQ URL. - """ + """Tests for using a custom base VWQ URL.""" @staticmethod def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: @@ -93,9 +83,7 @@ def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: class TestMaxNumResults: - """ - Tests for the ``max_num_results`` parameter of ``query``. - """ + """Tests for the ``max_num_results`` parameter of ``query``.""" @staticmethod def test_default( @@ -103,9 +91,7 @@ def test_default( cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: - """ - By default the maximum number of results is 1. - """ + """By default the maximum number of results is 1.""" target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, @@ -131,9 +117,7 @@ def test_custom( cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: - """ - It is possible to set a custom ``max_num_results``. - """ + """It is possible to set a custom ``max_num_results``.""" target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, @@ -167,9 +151,7 @@ def test_custom( class TestIncludeTargetData: - """ - Tests for the ``include_target_data`` parameter of ``query``. - """ + """Tests for the ``include_target_data`` parameter of ``query``.""" @staticmethod def test_default( @@ -177,9 +159,7 @@ def test_default( cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: - """ - By default, target data is only returned in the top match. - """ + """By default, target data is only returned in the top match.""" target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, diff --git a/tests/test_vws.py b/tests/test_vws.py index e16eff555..d427e1c9b 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -1,6 +1,4 @@ -""" -Tests for helper functions for managing a Vuforia database. -""" +"""Tests for helper functions for managing a Vuforia database.""" import base64 import datetime @@ -25,9 +23,7 @@ class TestAddTarget: - """ - Tests for adding a target. - """ + """Tests for adding a target.""" @staticmethod @pytest.mark.parametrize( @@ -43,9 +39,7 @@ def test_add_target( *, active_flag: bool, ) -> None: - """ - No exception is raised when adding one target. - """ + """No exception is raised when adding one target.""" name = "x" width = 1 if application_metadata is None: @@ -98,9 +92,7 @@ def test_add_two_targets( class TestCustomBaseVWSURL: - """ - Tests for using a custom base VWS URL. - """ + """Tests for using a custom base VWS URL.""" @staticmethod def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: @@ -128,18 +120,14 @@ def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: class TestListTargets: - """ - Tests for listing targets. - """ + """Tests for listing targets.""" @staticmethod def test_list_targets( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """ - It is possible to get a list of target IDs. - """ + """It is possible to get a list of target IDs.""" id_1 = vws_client.add_target( name="x", width=1, @@ -158,18 +146,14 @@ def test_list_targets( class TestDelete: - """ - Test for deleting a target. - """ + """Test for deleting a target.""" @staticmethod def test_delete_target( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """ - It is possible to delete a target. - """ + """It is possible to delete a target.""" target_id = vws_client.add_target( name="x", width=1, @@ -185,18 +169,14 @@ def test_delete_target( class TestGetTargetSummaryReport: - """ - Tests for getting a summary report for a target. - """ + """Tests for getting a summary report for a target.""" @staticmethod def test_get_target_summary_report( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """ - Details of a target are returned by ``get_target_summary_report``. - """ + """Details of a target are returned by ``get_target_summary_report``.""" date = "2018-04-25" target_name = uuid.uuid4().hex with freeze_time(time_to_freeze=date): @@ -240,15 +220,11 @@ def test_get_target_summary_report( class TestGetDatabaseSummaryReport: - """ - Tests for getting a summary report for a database. - """ + """Tests for getting a summary report for a database.""" @staticmethod def test_get_target(vws_client: VWS) -> None: - """ - Details of a database are returned by ``get_database_summary_report``. - """ + """Details of a database are returned by ``get_database_summary_report``.""" report = vws_client.get_database_summary_report() expected_report = DatabaseSummaryReport( @@ -287,18 +263,14 @@ def test_get_target(vws_client: VWS) -> None: class TestGetTargetRecord: - """ - Tests for getting a record of a target. - """ + """Tests for getting a record of a target.""" @staticmethod def test_get_target_record( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """ - Details of a target are returned by ``get_target_record``. - """ + """Details of a target are returned by ``get_target_record``.""" target_id = vws_client.add_target( name="x", width=1, @@ -344,9 +316,7 @@ def test_get_failed( vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: - """ - Check that the report works with a failed target. - """ + """Check that the report works with a failed target.""" target_id = vws_client.add_target( name="x", width=1, @@ -362,18 +332,14 @@ def test_get_failed( class TestWaitForTargetProcessed: - """ - Tests for waiting for a target to be processed. - """ + """Tests for waiting for a target to be processed.""" @staticmethod def test_wait_for_target_processed( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """ - It is possible to wait until a target is processed. - """ + """It is possible to wait until a target is processed.""" target_id = vws_client.add_target( name="x", width=1, @@ -391,9 +357,7 @@ def test_wait_for_target_processed( def test_default_seconds_between_requests( image: io.BytesIO | BinaryIO, ) -> None: - """ - By default, 0.2 seconds are waited between polling requests. - """ + """By default, 0.2 seconds are waited between polling requests.""" with MockVWS(processing_time_seconds=0.5) as mock: database = VuforiaDatabase() mock.add_database(database=database) @@ -443,9 +407,7 @@ def test_default_seconds_between_requests( def test_custom_seconds_between_requests( image: io.BytesIO | BinaryIO, ) -> None: - """ - It is possible to customize the time waited between polling requests. - """ + """It is possible to customize the time waited between polling requests.""" with MockVWS(processing_time_seconds=0.5) as mock: database = VuforiaDatabase() mock.add_database(database=database) @@ -493,9 +455,7 @@ def test_custom_seconds_between_requests( @staticmethod def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: - """ - It is possible to set a maximum timeout. - """ + """It is possible to set a maximum timeout.""" with MockVWS(processing_time_seconds=0.5) as mock: database = VuforiaDatabase() mock.add_database(database=database) @@ -531,18 +491,14 @@ def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: class TestGetDuplicateTargets: - """ - Tests for getting duplicate targets. - """ + """Tests for getting duplicate targets.""" @staticmethod def test_get_duplicate_targets( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """ - It is possible to get the IDs of similar targets. - """ + """It is possible to get the IDs of similar targets.""" target_id = vws_client.add_target( name="x", width=1, @@ -565,9 +521,7 @@ def test_get_duplicate_targets( class TestUpdateTarget: - """ - Tests for updating a target. - """ + """Tests for updating a target.""" @staticmethod def test_update_target( @@ -576,9 +530,7 @@ def test_update_target( different_high_quality_image: io.BytesIO, cloud_reco_client: CloudRecoService, ) -> None: - """ - It is possible to update a target. - """ + """It is possible to update a target.""" old_name = uuid.uuid4().hex old_width = secrets.choice(seq=range(1, 5000)) / 100 target_id = vws_client.add_target( @@ -635,9 +587,7 @@ def test_no_fields_given( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """ - It is possible to give no update fields. - """ + """It is possible to give no update fields.""" target_id = vws_client.add_target( name="x", width=1, diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 86da66040..eb5b12a6f 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -1,6 +1,4 @@ -""" -Tests for VWS exceptions. -""" +"""Tests for VWS exceptions.""" import io import uuid @@ -98,9 +96,7 @@ def test_request_quota_reached() -> None: def test_fail(high_quality_image: io.BytesIO) -> None: - """ - A ``Fail`` exception is raised when the server access key does not exist. - """ + """A ``Fail`` exception is raised when the server access key does not exist.""" with MockVWS(): vws_client = VWS( server_access_key=uuid.uuid4().hex, @@ -120,9 +116,7 @@ def test_fail(high_quality_image: io.BytesIO) -> None: def test_bad_image(vws_client: VWS) -> None: - """ - A ``BadImage`` exception is raised when a non-image is given. - """ + """A ``BadImage`` exception is raised when a non-image is given.""" not_an_image = io.BytesIO(initial_bytes=b"Not an image") with pytest.raises(expected_exception=BadImageError) as exc: vws_client.add_target( @@ -325,9 +319,7 @@ def test_target_status_not_success( def test_vwsexception_inheritance() -> None: - """ - VWS-related exceptions should inherit from VWSException. - """ + """VWS-related exceptions should inherit from VWSException.""" subclasses = [ AuthenticationFailureError, BadImageError, @@ -354,9 +346,7 @@ def test_base_exception( vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: - """ - ``VWSException``s has a response property. - """ + """``VWSException``s has a response property.""" with pytest.raises(expected_exception=VWSError) as exc: vws_client.get_target_record(target_id="a") From b57ba660dc68b9c917b91fe720d5bd223280745d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 12 Jan 2026 14:23:57 +0000 Subject: [PATCH 239/688] Configure pydocstringformatter to make minimal changes --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index d4a6cbd29..f83bea5bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -346,6 +346,8 @@ typeCheckingMode = "strict" [tool.pydocstringformatter] write = true split-summary-body = false +beginning-quotes = false +closing-quotes = false [tool.interrogate] fail-under = 100 From 6c82f4a9543f22170b369090744d872742ae8630 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 12 Jan 2026 14:24:43 +0000 Subject: [PATCH 240/688] Switch from pydocstringformatter --- conftest.py | 8 +- docs/source/__init__.py | 4 +- docs/source/conf.py | 4 +- pyproject.toml | 2 - src/vws/__init__.py | 4 +- src/vws/exceptions/__init__.py | 4 +- src/vws/exceptions/base_exceptions.py | 12 ++- src/vws/exceptions/cloud_reco_exceptions.py | 16 +++- src/vws/exceptions/custom_exceptions.py | 20 +++- src/vws/exceptions/vws_exceptions.py | 68 +++++++++---- src/vws/include_target_data.py | 4 +- src/vws/query.py | 12 ++- src/vws/reports.py | 8 +- src/vws/response.py | 8 +- src/vws/vws.py | 12 ++- tests/__init__.py | 4 +- tests/conftest.py | 24 +++-- tests/test_cloud_reco_exceptions.py | 8 +- tests/test_query.py | 40 ++++++-- tests/test_vws.py | 100 +++++++++++++++----- tests/test_vws_exceptions.py | 20 +++- 21 files changed, 280 insertions(+), 102 deletions(-) diff --git a/conftest.py b/conftest.py index 0e579a4db..1d776f8db 100644 --- a/conftest.py +++ b/conftest.py @@ -1,4 +1,6 @@ -"""Setup for Sybil.""" +""" +Setup for Sybil. +""" import io import uuid @@ -19,7 +21,9 @@ 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 535ceb2ec..b63eed5fb 100644 --- a/docs/source/__init__.py +++ b/docs/source/__init__.py @@ -1 +1,3 @@ -"""Documentation.""" +""" +Documentation. +""" diff --git a/docs/source/conf.py b/docs/source/conf.py index 920ba1a4b..914bcb886 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,5 +1,7 @@ #!/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 f83bea5bb..d4a6cbd29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -346,8 +346,6 @@ typeCheckingMode = "strict" [tool.pydocstringformatter] write = true split-summary-body = false -beginning-quotes = false -closing-quotes = false [tool.interrogate] fail-under = 100 diff --git a/src/vws/__init__.py b/src/vws/__init__.py index bfd96d6c2..42788b92d 100644 --- a/src/vws/__init__.py +++ b/src/vws/__init__.py @@ -1,4 +1,6 @@ -"""A library for Vuforia Web Services.""" +""" +A library for Vuforia Web Services. +""" from .query import CloudRecoService from .vws import VWS diff --git a/src/vws/exceptions/__init__.py b/src/vws/exceptions/__init__.py index 7260ffdbb..a1e3cd91c 100644 --- a/src/vws/exceptions/__init__.py +++ b/src/vws/exceptions/__init__.py @@ -1 +1,3 @@ -"""Custom exceptions raised by this package.""" +""" +Custom exceptions raised by this package. +""" diff --git a/src/vws/exceptions/base_exceptions.py b/src/vws/exceptions/base_exceptions.py index c86080593..943323bfd 100644 --- a/src/vws/exceptions/base_exceptions.py +++ b/src/vws/exceptions/base_exceptions.py @@ -10,7 +10,9 @@ @beartype class CloudRecoError(Exception): - """Base class for Vuforia Cloud Recognition Web API exceptions.""" + """ + Base class for Vuforia Cloud Recognition Web API exceptions. + """ def __init__(self, response: Response) -> None: """ @@ -22,7 +24,9 @@ def __init__(self, response: Response) -> None: @property def response(self) -> Response: - """The response returned by Vuforia which included this error.""" + """ + The response returned by Vuforia which included this error. + """ return self._response @@ -44,5 +48,7 @@ def __init__(self, response: Response) -> None: @property def response(self) -> Response: - """The response returned by Vuforia which included this error.""" + """ + The response returned by Vuforia which included this error. + """ return self._response diff --git a/src/vws/exceptions/cloud_reco_exceptions.py b/src/vws/exceptions/cloud_reco_exceptions.py index 2b20a7220..ff5dde209 100644 --- a/src/vws/exceptions/cloud_reco_exceptions.py +++ b/src/vws/exceptions/cloud_reco_exceptions.py @@ -1,4 +1,6 @@ -"""Exceptions which match errors raised by the Vuforia Cloud Recognition Web APIs.""" +""" +Exceptions which match errors raised by the Vuforia Cloud Recognition Web APIs. +""" from beartype import beartype @@ -15,27 +17,31 @@ class MaxNumResultsOutOfRangeError(CloudRecoError): @beartype class InactiveProjectError(CloudRecoError): - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'InactiveProject'. """ @beartype class BadImageError(CloudRecoError): - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'BadImage'. """ @beartype class AuthenticationFailureError(CloudRecoError): - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'AuthenticationFailure'. """ @beartype class RequestTimeTooSkewedError(CloudRecoError): - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'RequestTimeTooSkewed'. """ diff --git a/src/vws/exceptions/custom_exceptions.py b/src/vws/exceptions/custom_exceptions.py index 466dfd1cd..694e98c30 100644 --- a/src/vws/exceptions/custom_exceptions.py +++ b/src/vws/exceptions/custom_exceptions.py @@ -11,7 +11,9 @@ @beartype class RequestEntityTooLargeError(Exception): - """Exception raised when the given image is too large.""" + """ + Exception raised when the given image is too large. + """ def __init__(self, response: Response) -> None: """ @@ -23,18 +25,24 @@ def __init__(self, response: Response) -> None: @property def response(self) -> Response: - """The response returned by Vuforia which included this error.""" + """ + The response returned by Vuforia which included this error. + """ return self._response @beartype class TargetProcessingTimeoutError(Exception): - """Exception raised when waiting for a target to be processed times out.""" + """ + Exception raised when waiting for a target to be processed times out. + """ @beartype class ServerError(Exception): # pragma: no cover - """Exception raised when VWS returns a server error.""" + """ + Exception raised when VWS returns a server error. + """ def __init__(self, response: Response) -> None: """ @@ -46,5 +54,7 @@ def __init__(self, response: Response) -> None: @property def response(self) -> Response: - """The response returned by Vuforia which included this error.""" + """ + The response returned by Vuforia which included this error. + """ return self._response diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index a25c2261b..482223766 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -14,13 +14,16 @@ @beartype class UnknownTargetError(VWSError): - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'UnknownTarget'. """ @property def target_id(self) -> str: - """The unknown target ID.""" + """ + The unknown target ID. + """ path = urlparse(url=self.response.url).path # Every HTTP path which can raise this error is in the format # `/something/{target_id}`. @@ -29,19 +32,23 @@ def target_id(self) -> str: @beartype class FailError(VWSError): - """Exception raised when Vuforia returns a response with a result code 'Fail'.""" + """ + Exception raised when Vuforia returns a response with a result code 'Fail'. + """ @beartype class BadImageError(VWSError): - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'BadImage'. """ @beartype class AuthenticationFailureError(VWSError): - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'AuthenticationFailure'. """ @@ -49,20 +56,24 @@ class AuthenticationFailureError(VWSError): # See https://github.com/VWS-Python/vws-python/issues/822. @beartype class RequestQuotaReachedError(VWSError): # pragma: no cover - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'RequestQuotaReached'. """ @beartype class TargetStatusProcessingError(VWSError): - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'TargetStatusProcessing'. """ @property def target_id(self) -> str: - """The processing target ID.""" + """ + The processing target ID. + """ path = urlparse(url=self.response.url).path # Every HTTP path which can raise this error is in the format # `/something/{target_id}`. @@ -72,7 +83,8 @@ def target_id(self) -> str: # This is not simulated by the mock. @beartype class DateRangeError(VWSError): # pragma: no cover - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'DateRangeError'. """ @@ -80,7 +92,8 @@ class DateRangeError(VWSError): # pragma: no cover # This is not simulated by the mock. @beartype class TargetQuotaReachedError(VWSError): # pragma: no cover - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'TargetQuotaReached'. """ @@ -88,7 +101,8 @@ class TargetQuotaReachedError(VWSError): # pragma: no cover # This is not simulated by the mock. @beartype class ProjectSuspendedError(VWSError): # pragma: no cover - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'ProjectSuspended'. """ @@ -96,41 +110,48 @@ class ProjectSuspendedError(VWSError): # pragma: no cover # This is not simulated by the mock. @beartype class ProjectHasNoAPIAccessError(VWSError): # pragma: no cover - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'ProjectHasNoAPIAccess'. """ @beartype class ProjectInactiveError(VWSError): - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'ProjectInactive'. """ @beartype class MetadataTooLargeError(VWSError): - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'MetadataTooLarge'. """ @beartype class RequestTimeTooSkewedError(VWSError): - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'RequestTimeTooSkewed'. """ @beartype class TargetNameExistError(VWSError): - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'TargetNameExist'. """ @property def target_name(self) -> str: - """The target name which already exists.""" + """ + The target name which already exists. + """ response_body = self.response.request_body or b"" request_json = json.loads(s=response_body) return str(object=request_json["name"]) @@ -138,20 +159,24 @@ def target_name(self) -> str: @beartype class ImageTooLargeError(VWSError): - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'ImageTooLarge'. """ @beartype class TargetStatusNotSuccessError(VWSError): - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'TargetStatusNotSuccess'. """ @property def target_id(self) -> str: - """The unknown target ID.""" + """ + The unknown target ID. + """ path = urlparse(url=self.response.url).path # Every HTTP path which can raise this error is in the format # `/something/{target_id}`. @@ -160,6 +185,7 @@ def target_id(self) -> str: @beartype class TooManyRequestsError(VWSError): # pragma: no cover - """Exception raised when Vuforia returns a response with a result code + """ + Exception raised when Vuforia returns a response with a result code 'TooManyRequests'. """ diff --git a/src/vws/include_target_data.py b/src/vws/include_target_data.py index 0692a1e40..37682e08a 100644 --- a/src/vws/include_target_data.py +++ b/src/vws/include_target_data.py @@ -1,4 +1,6 @@ -"""Tools for managing ``CloudRecoService.query``'s ``include_target_data``.""" +""" +Tools for managing ``CloudRecoService.query``'s ``include_target_data``. +""" from enum import StrEnum, auto, unique diff --git a/src/vws/query.py b/src/vws/query.py index ab449c614..96122aa14 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -1,4 +1,6 @@ -"""Tools for interacting with the Vuforia Cloud Recognition Web APIs.""" +""" +Tools for interacting with the Vuforia Cloud Recognition Web APIs. +""" import datetime import io @@ -32,7 +34,9 @@ @beartype def _get_image_data(image: _ImageType) -> bytes: - """Get the data of an image file.""" + """ + Get the data of an image file. + """ original_tell = image.tell() image.seek(0) image_data = image.read() @@ -42,7 +46,9 @@ def _get_image_data(image: _ImageType) -> bytes: @beartype class CloudRecoService: - """An interface to the Vuforia Cloud Recognition Web APIs.""" + """ + An interface to the Vuforia Cloud Recognition Web APIs. + """ def __init__( self, diff --git a/src/vws/reports.py b/src/vws/reports.py index e59408aad..f6a77133c 100644 --- a/src/vws/reports.py +++ b/src/vws/reports.py @@ -1,4 +1,6 @@ -"""Classes for representing Vuforia reports.""" +""" +Classes for representing Vuforia reports. +""" import datetime from dataclasses import dataclass @@ -84,7 +86,9 @@ class TargetRecord: @beartype @dataclass(frozen=True) class TargetData: - """The target data optionally included with a query match.""" + """ + The target data optionally included with a query match. + """ name: str application_metadata: str | None diff --git a/src/vws/response.py b/src/vws/response.py index e2060cb9e..269f2e23f 100644 --- a/src/vws/response.py +++ b/src/vws/response.py @@ -1,4 +1,6 @@ -"""Responses for requests to VWS and VWQ.""" +""" +Responses for requests to VWS and VWQ. +""" from dataclasses import dataclass @@ -8,7 +10,9 @@ @dataclass(frozen=True) @beartype class Response: - """A response from a request.""" + """ + A response from a request. + """ text: str url: str diff --git a/src/vws/vws.py b/src/vws/vws.py index 1191ee7df..8def19ed7 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -1,4 +1,6 @@ -"""Tools for interacting with Vuforia APIs.""" +""" +Tools for interacting with Vuforia APIs. +""" import base64 import io @@ -50,7 +52,9 @@ @beartype def _get_image_data(image: _ImageType) -> bytes: - """Get the data of an image file.""" + """ + Get the data of an image file. + """ original_tell = image.tell() image.seek(0) image_data = image.read() @@ -127,7 +131,9 @@ def _target_api_request( @beartype(conf=BeartypeConf(is_pep484_tower=True)) class VWS: - """An interface to Vuforia Web Services APIs.""" + """ + An interface to Vuforia Web Services APIs. + """ def __init__( self, diff --git a/tests/__init__.py b/tests/__init__.py index 3502d86d5..c7e38a862 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +1,3 @@ -"""Tests for ``vws``.""" +""" +Tests for ``vws``. +""" diff --git a/tests/conftest.py b/tests/conftest.py index 7c0647786..74c6fc56d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,6 @@ -"""Configuration, plugins and fixtures for `pytest`.""" +""" +Configuration, plugins and fixtures for `pytest`. +""" import io from collections.abc import Generator @@ -14,7 +16,9 @@ @pytest.fixture(name="_mock_database") def fixture_mock_database() -> Generator[VuforiaDatabase]: - """Yield a mock ``VuforiaDatabase``.""" + """ + Yield a mock ``VuforiaDatabase``. + """ # We use a low processing time so that tests run quickly. with MockVWS(processing_time_seconds=0.2) as mock: database = VuforiaDatabase() @@ -24,7 +28,9 @@ def fixture_mock_database() -> Generator[VuforiaDatabase]: @pytest.fixture def vws_client(_mock_database: VuforiaDatabase) -> VWS: - """A VWS client which connects to a mock database.""" + """ + A VWS client which connects to a mock database. + """ return VWS( server_access_key=_mock_database.server_access_key, server_secret_key=_mock_database.server_secret_key, @@ -33,7 +39,9 @@ def vws_client(_mock_database: VuforiaDatabase) -> VWS: @pytest.fixture def cloud_reco_client(_mock_database: VuforiaDatabase) -> CloudRecoService: - """A ``CloudRecoService`` client which connects to a mock database.""" + """ + A ``CloudRecoService`` client which connects to a mock database. + """ return CloudRecoService( client_access_key=_mock_database.client_access_key, client_secret_key=_mock_database.client_secret_key, @@ -46,7 +54,9 @@ def fixture_image_file( tmp_path: Path, request: pytest.FixtureRequest, ) -> Generator[BinaryIO]: - """An image file object.""" + """ + An image file object. + """ file = tmp_path / "image.jpg" buffer = high_quality_image.getvalue() file.write_bytes(data=buffer) @@ -61,7 +71,9 @@ def image( high_quality_image: io.BytesIO, image_file: BinaryIO, ) -> io.BytesIO | BinaryIO: - """An image in any of the types that the API accepts.""" + """ + An image in any of the types that the API accepts. + """ if request.param == "high_quality_image": return high_quality_image return image_file diff --git a/tests/test_cloud_reco_exceptions.py b/tests/test_cloud_reco_exceptions.py index d4c8cb0f7..3515b0d22 100644 --- a/tests/test_cloud_reco_exceptions.py +++ b/tests/test_cloud_reco_exceptions.py @@ -1,4 +1,6 @@ -"""Tests for exceptions raised when using the CloudRecoService.""" +""" +Tests for exceptions raised when using the CloudRecoService. +""" import io import uuid @@ -61,7 +63,9 @@ def test_image_too_large( def test_cloudrecoexception_inheritance() -> None: - """CloudRecoService-specific exceptions inherit from CloudRecoException.""" + """ + CloudRecoService-specific exceptions inherit from CloudRecoException. + """ subclasses = [ MaxNumResultsOutOfRangeError, InactiveProjectError, diff --git a/tests/test_query.py b/tests/test_query.py index 11b803eb5..e229cf95d 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,4 +1,6 @@ -"""Tests for the ``CloudRecoService`` querying functionality.""" +""" +Tests for the ``CloudRecoService`` querying functionality. +""" import io import uuid @@ -12,14 +14,18 @@ class TestQuery: - """Tests for making image queries.""" + """ + Tests for making image queries. + """ @staticmethod def test_no_matches( cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: - """An empty list is returned if there are no matches.""" + """ + An empty list is returned if there are no matches. + """ result = cloud_reco_client.query(image=image) assert result == [] @@ -29,7 +35,9 @@ def test_match( cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: - """Details of matching targets are returned.""" + """ + Details of matching targets are returned. + """ target_id = vws_client.add_target( name="x", width=1, @@ -43,7 +51,9 @@ def test_match( class TestCustomBaseVWQURL: - """Tests for using a custom base VWQ URL.""" + """ + Tests for using a custom base VWQ URL. + """ @staticmethod def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: @@ -83,7 +93,9 @@ def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: class TestMaxNumResults: - """Tests for the ``max_num_results`` parameter of ``query``.""" + """ + Tests for the ``max_num_results`` parameter of ``query``. + """ @staticmethod def test_default( @@ -91,7 +103,9 @@ def test_default( cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: - """By default the maximum number of results is 1.""" + """ + By default the maximum number of results is 1. + """ target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, @@ -117,7 +131,9 @@ def test_custom( cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: - """It is possible to set a custom ``max_num_results``.""" + """ + It is possible to set a custom ``max_num_results``. + """ target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, @@ -151,7 +167,9 @@ def test_custom( class TestIncludeTargetData: - """Tests for the ``include_target_data`` parameter of ``query``.""" + """ + Tests for the ``include_target_data`` parameter of ``query``. + """ @staticmethod def test_default( @@ -159,7 +177,9 @@ def test_default( cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: - """By default, target data is only returned in the top match.""" + """ + By default, target data is only returned in the top match. + """ target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, diff --git a/tests/test_vws.py b/tests/test_vws.py index d427e1c9b..e16eff555 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -1,4 +1,6 @@ -"""Tests for helper functions for managing a Vuforia database.""" +""" +Tests for helper functions for managing a Vuforia database. +""" import base64 import datetime @@ -23,7 +25,9 @@ class TestAddTarget: - """Tests for adding a target.""" + """ + Tests for adding a target. + """ @staticmethod @pytest.mark.parametrize( @@ -39,7 +43,9 @@ def test_add_target( *, active_flag: bool, ) -> None: - """No exception is raised when adding one target.""" + """ + No exception is raised when adding one target. + """ name = "x" width = 1 if application_metadata is None: @@ -92,7 +98,9 @@ def test_add_two_targets( class TestCustomBaseVWSURL: - """Tests for using a custom base VWS URL.""" + """ + Tests for using a custom base VWS URL. + """ @staticmethod def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: @@ -120,14 +128,18 @@ def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: class TestListTargets: - """Tests for listing targets.""" + """ + Tests for listing targets. + """ @staticmethod def test_list_targets( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """It is possible to get a list of target IDs.""" + """ + It is possible to get a list of target IDs. + """ id_1 = vws_client.add_target( name="x", width=1, @@ -146,14 +158,18 @@ def test_list_targets( class TestDelete: - """Test for deleting a target.""" + """ + Test for deleting a target. + """ @staticmethod def test_delete_target( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """It is possible to delete a target.""" + """ + It is possible to delete a target. + """ target_id = vws_client.add_target( name="x", width=1, @@ -169,14 +185,18 @@ def test_delete_target( class TestGetTargetSummaryReport: - """Tests for getting a summary report for a target.""" + """ + Tests for getting a summary report for a target. + """ @staticmethod def test_get_target_summary_report( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """Details of a target are returned by ``get_target_summary_report``.""" + """ + Details of a target are returned by ``get_target_summary_report``. + """ date = "2018-04-25" target_name = uuid.uuid4().hex with freeze_time(time_to_freeze=date): @@ -220,11 +240,15 @@ def test_get_target_summary_report( class TestGetDatabaseSummaryReport: - """Tests for getting a summary report for a database.""" + """ + Tests for getting a summary report for a database. + """ @staticmethod def test_get_target(vws_client: VWS) -> None: - """Details of a database are returned by ``get_database_summary_report``.""" + """ + Details of a database are returned by ``get_database_summary_report``. + """ report = vws_client.get_database_summary_report() expected_report = DatabaseSummaryReport( @@ -263,14 +287,18 @@ def test_get_target(vws_client: VWS) -> None: class TestGetTargetRecord: - """Tests for getting a record of a target.""" + """ + Tests for getting a record of a target. + """ @staticmethod def test_get_target_record( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """Details of a target are returned by ``get_target_record``.""" + """ + Details of a target are returned by ``get_target_record``. + """ target_id = vws_client.add_target( name="x", width=1, @@ -316,7 +344,9 @@ def test_get_failed( vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: - """Check that the report works with a failed target.""" + """ + Check that the report works with a failed target. + """ target_id = vws_client.add_target( name="x", width=1, @@ -332,14 +362,18 @@ def test_get_failed( class TestWaitForTargetProcessed: - """Tests for waiting for a target to be processed.""" + """ + Tests for waiting for a target to be processed. + """ @staticmethod def test_wait_for_target_processed( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """It is possible to wait until a target is processed.""" + """ + It is possible to wait until a target is processed. + """ target_id = vws_client.add_target( name="x", width=1, @@ -357,7 +391,9 @@ def test_wait_for_target_processed( def test_default_seconds_between_requests( image: io.BytesIO | BinaryIO, ) -> None: - """By default, 0.2 seconds are waited between polling requests.""" + """ + By default, 0.2 seconds are waited between polling requests. + """ with MockVWS(processing_time_seconds=0.5) as mock: database = VuforiaDatabase() mock.add_database(database=database) @@ -407,7 +443,9 @@ def test_default_seconds_between_requests( def test_custom_seconds_between_requests( image: io.BytesIO | BinaryIO, ) -> None: - """It is possible to customize the time waited between polling requests.""" + """ + It is possible to customize the time waited between polling requests. + """ with MockVWS(processing_time_seconds=0.5) as mock: database = VuforiaDatabase() mock.add_database(database=database) @@ -455,7 +493,9 @@ def test_custom_seconds_between_requests( @staticmethod def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: - """It is possible to set a maximum timeout.""" + """ + It is possible to set a maximum timeout. + """ with MockVWS(processing_time_seconds=0.5) as mock: database = VuforiaDatabase() mock.add_database(database=database) @@ -491,14 +531,18 @@ def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: class TestGetDuplicateTargets: - """Tests for getting duplicate targets.""" + """ + Tests for getting duplicate targets. + """ @staticmethod def test_get_duplicate_targets( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """It is possible to get the IDs of similar targets.""" + """ + It is possible to get the IDs of similar targets. + """ target_id = vws_client.add_target( name="x", width=1, @@ -521,7 +565,9 @@ def test_get_duplicate_targets( class TestUpdateTarget: - """Tests for updating a target.""" + """ + Tests for updating a target. + """ @staticmethod def test_update_target( @@ -530,7 +576,9 @@ def test_update_target( different_high_quality_image: io.BytesIO, cloud_reco_client: CloudRecoService, ) -> None: - """It is possible to update a target.""" + """ + It is possible to update a target. + """ old_name = uuid.uuid4().hex old_width = secrets.choice(seq=range(1, 5000)) / 100 target_id = vws_client.add_target( @@ -587,7 +635,9 @@ def test_no_fields_given( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """It is possible to give no update fields.""" + """ + It is possible to give no update fields. + """ target_id = vws_client.add_target( name="x", width=1, diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index eb5b12a6f..86da66040 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -1,4 +1,6 @@ -"""Tests for VWS exceptions.""" +""" +Tests for VWS exceptions. +""" import io import uuid @@ -96,7 +98,9 @@ def test_request_quota_reached() -> None: def test_fail(high_quality_image: io.BytesIO) -> None: - """A ``Fail`` exception is raised when the server access key does not exist.""" + """ + A ``Fail`` exception is raised when the server access key does not exist. + """ with MockVWS(): vws_client = VWS( server_access_key=uuid.uuid4().hex, @@ -116,7 +120,9 @@ def test_fail(high_quality_image: io.BytesIO) -> None: def test_bad_image(vws_client: VWS) -> None: - """A ``BadImage`` exception is raised when a non-image is given.""" + """ + A ``BadImage`` exception is raised when a non-image is given. + """ not_an_image = io.BytesIO(initial_bytes=b"Not an image") with pytest.raises(expected_exception=BadImageError) as exc: vws_client.add_target( @@ -319,7 +325,9 @@ def test_target_status_not_success( def test_vwsexception_inheritance() -> None: - """VWS-related exceptions should inherit from VWSException.""" + """ + VWS-related exceptions should inherit from VWSException. + """ subclasses = [ AuthenticationFailureError, BadImageError, @@ -346,7 +354,9 @@ def test_base_exception( vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: - """``VWSException``s has a response property.""" + """ + ``VWSException``s has a response property. + """ with pytest.raises(expected_exception=VWSError) as exc: vws_client.get_target_record(target_id="a") From 848686798b4cd9b7617d973b77ef25fbdc69c10a 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, 12 Jan 2026 14:27:40 +0000 Subject: [PATCH 241/688] [pre-commit.ci lite] apply automatic fixes --- conftest.py | 8 +- docs/source/__init__.py | 4 +- docs/source/conf.py | 4 +- src/vws/__init__.py | 4 +- src/vws/exceptions/__init__.py | 4 +- src/vws/exceptions/base_exceptions.py | 12 +-- src/vws/exceptions/cloud_reco_exceptions.py | 16 +--- src/vws/exceptions/custom_exceptions.py | 20 +--- src/vws/exceptions/vws_exceptions.py | 68 ++++--------- src/vws/include_target_data.py | 4 +- src/vws/query.py | 12 +-- src/vws/reports.py | 8 +- src/vws/response.py | 8 +- src/vws/vws.py | 12 +-- tests/__init__.py | 4 +- tests/conftest.py | 24 ++--- tests/test_cloud_reco_exceptions.py | 8 +- tests/test_query.py | 40 ++------ tests/test_vws.py | 100 +++++--------------- tests/test_vws_exceptions.py | 20 +--- 20 files changed, 100 insertions(+), 280 deletions(-) diff --git a/conftest.py b/conftest.py index 1d776f8db..0e579a4db 100644 --- a/conftest.py +++ b/conftest.py @@ -1,6 +1,4 @@ -""" -Setup for Sybil. -""" +"""Setup for Sybil.""" import io import uuid @@ -21,9 +19,7 @@ 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 914bcb886..920ba1a4b 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/src/vws/__init__.py b/src/vws/__init__.py index 42788b92d..bfd96d6c2 100644 --- a/src/vws/__init__.py +++ b/src/vws/__init__.py @@ -1,6 +1,4 @@ -""" -A library for Vuforia Web Services. -""" +"""A library for Vuforia Web Services.""" from .query import CloudRecoService from .vws import VWS diff --git a/src/vws/exceptions/__init__.py b/src/vws/exceptions/__init__.py index a1e3cd91c..7260ffdbb 100644 --- a/src/vws/exceptions/__init__.py +++ b/src/vws/exceptions/__init__.py @@ -1,3 +1 @@ -""" -Custom exceptions raised by this package. -""" +"""Custom exceptions raised by this package.""" diff --git a/src/vws/exceptions/base_exceptions.py b/src/vws/exceptions/base_exceptions.py index 943323bfd..c86080593 100644 --- a/src/vws/exceptions/base_exceptions.py +++ b/src/vws/exceptions/base_exceptions.py @@ -10,9 +10,7 @@ @beartype class CloudRecoError(Exception): - """ - Base class for Vuforia Cloud Recognition Web API exceptions. - """ + """Base class for Vuforia Cloud Recognition Web API exceptions.""" def __init__(self, response: Response) -> None: """ @@ -24,9 +22,7 @@ def __init__(self, response: Response) -> None: @property def response(self) -> Response: - """ - The response returned by Vuforia which included this error. - """ + """The response returned by Vuforia which included this error.""" return self._response @@ -48,7 +44,5 @@ def __init__(self, response: Response) -> None: @property def response(self) -> Response: - """ - The response returned by Vuforia which included this error. - """ + """The response returned by Vuforia which included this error.""" return self._response diff --git a/src/vws/exceptions/cloud_reco_exceptions.py b/src/vws/exceptions/cloud_reco_exceptions.py index ff5dde209..2b20a7220 100644 --- a/src/vws/exceptions/cloud_reco_exceptions.py +++ b/src/vws/exceptions/cloud_reco_exceptions.py @@ -1,6 +1,4 @@ -""" -Exceptions which match errors raised by the Vuforia Cloud Recognition Web APIs. -""" +"""Exceptions which match errors raised by the Vuforia Cloud Recognition Web APIs.""" from beartype import beartype @@ -17,31 +15,27 @@ class MaxNumResultsOutOfRangeError(CloudRecoError): @beartype class InactiveProjectError(CloudRecoError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'InactiveProject'. """ @beartype class BadImageError(CloudRecoError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'BadImage'. """ @beartype class AuthenticationFailureError(CloudRecoError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'AuthenticationFailure'. """ @beartype class RequestTimeTooSkewedError(CloudRecoError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'RequestTimeTooSkewed'. """ diff --git a/src/vws/exceptions/custom_exceptions.py b/src/vws/exceptions/custom_exceptions.py index 694e98c30..466dfd1cd 100644 --- a/src/vws/exceptions/custom_exceptions.py +++ b/src/vws/exceptions/custom_exceptions.py @@ -11,9 +11,7 @@ @beartype class RequestEntityTooLargeError(Exception): - """ - Exception raised when the given image is too large. - """ + """Exception raised when the given image is too large.""" def __init__(self, response: Response) -> None: """ @@ -25,24 +23,18 @@ def __init__(self, response: Response) -> None: @property def response(self) -> Response: - """ - The response returned by Vuforia which included this error. - """ + """The response returned by Vuforia which included this error.""" return self._response @beartype class TargetProcessingTimeoutError(Exception): - """ - Exception raised when waiting for a target to be processed times out. - """ + """Exception raised when waiting for a target to be processed times out.""" @beartype class ServerError(Exception): # pragma: no cover - """ - Exception raised when VWS returns a server error. - """ + """Exception raised when VWS returns a server error.""" def __init__(self, response: Response) -> None: """ @@ -54,7 +46,5 @@ def __init__(self, response: Response) -> None: @property def response(self) -> Response: - """ - The response returned by Vuforia which included this error. - """ + """The response returned by Vuforia which included this error.""" return self._response diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index 482223766..a25c2261b 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -14,16 +14,13 @@ @beartype class UnknownTargetError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'UnknownTarget'. """ @property def target_id(self) -> str: - """ - The unknown target ID. - """ + """The unknown target ID.""" path = urlparse(url=self.response.url).path # Every HTTP path which can raise this error is in the format # `/something/{target_id}`. @@ -32,23 +29,19 @@ def target_id(self) -> str: @beartype class FailError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code 'Fail'. - """ + """Exception raised when Vuforia returns a response with a result code 'Fail'.""" @beartype class BadImageError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'BadImage'. """ @beartype class AuthenticationFailureError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'AuthenticationFailure'. """ @@ -56,24 +49,20 @@ class AuthenticationFailureError(VWSError): # See https://github.com/VWS-Python/vws-python/issues/822. @beartype class RequestQuotaReachedError(VWSError): # pragma: no cover - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'RequestQuotaReached'. """ @beartype class TargetStatusProcessingError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'TargetStatusProcessing'. """ @property def target_id(self) -> str: - """ - The processing target ID. - """ + """The processing target ID.""" path = urlparse(url=self.response.url).path # Every HTTP path which can raise this error is in the format # `/something/{target_id}`. @@ -83,8 +72,7 @@ def target_id(self) -> str: # This is not simulated by the mock. @beartype class DateRangeError(VWSError): # pragma: no cover - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'DateRangeError'. """ @@ -92,8 +80,7 @@ class DateRangeError(VWSError): # pragma: no cover # This is not simulated by the mock. @beartype class TargetQuotaReachedError(VWSError): # pragma: no cover - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'TargetQuotaReached'. """ @@ -101,8 +88,7 @@ class TargetQuotaReachedError(VWSError): # pragma: no cover # This is not simulated by the mock. @beartype class ProjectSuspendedError(VWSError): # pragma: no cover - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'ProjectSuspended'. """ @@ -110,48 +96,41 @@ class ProjectSuspendedError(VWSError): # pragma: no cover # This is not simulated by the mock. @beartype class ProjectHasNoAPIAccessError(VWSError): # pragma: no cover - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'ProjectHasNoAPIAccess'. """ @beartype class ProjectInactiveError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'ProjectInactive'. """ @beartype class MetadataTooLargeError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'MetadataTooLarge'. """ @beartype class RequestTimeTooSkewedError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'RequestTimeTooSkewed'. """ @beartype class TargetNameExistError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'TargetNameExist'. """ @property def target_name(self) -> str: - """ - The target name which already exists. - """ + """The target name which already exists.""" response_body = self.response.request_body or b"" request_json = json.loads(s=response_body) return str(object=request_json["name"]) @@ -159,24 +138,20 @@ def target_name(self) -> str: @beartype class ImageTooLargeError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'ImageTooLarge'. """ @beartype class TargetStatusNotSuccessError(VWSError): - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'TargetStatusNotSuccess'. """ @property def target_id(self) -> str: - """ - The unknown target ID. - """ + """The unknown target ID.""" path = urlparse(url=self.response.url).path # Every HTTP path which can raise this error is in the format # `/something/{target_id}`. @@ -185,7 +160,6 @@ def target_id(self) -> str: @beartype class TooManyRequestsError(VWSError): # pragma: no cover - """ - Exception raised when Vuforia returns a response with a result code + """Exception raised when Vuforia returns a response with a result code 'TooManyRequests'. """ diff --git a/src/vws/include_target_data.py b/src/vws/include_target_data.py index 37682e08a..0692a1e40 100644 --- a/src/vws/include_target_data.py +++ b/src/vws/include_target_data.py @@ -1,6 +1,4 @@ -""" -Tools for managing ``CloudRecoService.query``'s ``include_target_data``. -""" +"""Tools for managing ``CloudRecoService.query``'s ``include_target_data``.""" from enum import StrEnum, auto, unique diff --git a/src/vws/query.py b/src/vws/query.py index 96122aa14..ab449c614 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -1,6 +1,4 @@ -""" -Tools for interacting with the Vuforia Cloud Recognition Web APIs. -""" +"""Tools for interacting with the Vuforia Cloud Recognition Web APIs.""" import datetime import io @@ -34,9 +32,7 @@ @beartype def _get_image_data(image: _ImageType) -> bytes: - """ - Get the data of an image file. - """ + """Get the data of an image file.""" original_tell = image.tell() image.seek(0) image_data = image.read() @@ -46,9 +42,7 @@ def _get_image_data(image: _ImageType) -> bytes: @beartype class CloudRecoService: - """ - An interface to the Vuforia Cloud Recognition Web APIs. - """ + """An interface to the Vuforia Cloud Recognition Web APIs.""" def __init__( self, diff --git a/src/vws/reports.py b/src/vws/reports.py index f6a77133c..e59408aad 100644 --- a/src/vws/reports.py +++ b/src/vws/reports.py @@ -1,6 +1,4 @@ -""" -Classes for representing Vuforia reports. -""" +"""Classes for representing Vuforia reports.""" import datetime from dataclasses import dataclass @@ -86,9 +84,7 @@ class TargetRecord: @beartype @dataclass(frozen=True) class TargetData: - """ - The target data optionally included with a query match. - """ + """The target data optionally included with a query match.""" name: str application_metadata: str | None diff --git a/src/vws/response.py b/src/vws/response.py index 269f2e23f..e2060cb9e 100644 --- a/src/vws/response.py +++ b/src/vws/response.py @@ -1,6 +1,4 @@ -""" -Responses for requests to VWS and VWQ. -""" +"""Responses for requests to VWS and VWQ.""" from dataclasses import dataclass @@ -10,9 +8,7 @@ @dataclass(frozen=True) @beartype class Response: - """ - A response from a request. - """ + """A response from a request.""" text: str url: str diff --git a/src/vws/vws.py b/src/vws/vws.py index 8def19ed7..1191ee7df 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -1,6 +1,4 @@ -""" -Tools for interacting with Vuforia APIs. -""" +"""Tools for interacting with Vuforia APIs.""" import base64 import io @@ -52,9 +50,7 @@ @beartype def _get_image_data(image: _ImageType) -> bytes: - """ - Get the data of an image file. - """ + """Get the data of an image file.""" original_tell = image.tell() image.seek(0) image_data = image.read() @@ -131,9 +127,7 @@ def _target_api_request( @beartype(conf=BeartypeConf(is_pep484_tower=True)) class VWS: - """ - An interface to Vuforia Web Services APIs. - """ + """An interface to Vuforia Web Services APIs.""" def __init__( self, 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 74c6fc56d..7c0647786 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 io from collections.abc import Generator @@ -16,9 +14,7 @@ @pytest.fixture(name="_mock_database") def fixture_mock_database() -> Generator[VuforiaDatabase]: - """ - Yield a mock ``VuforiaDatabase``. - """ + """Yield a mock ``VuforiaDatabase``.""" # We use a low processing time so that tests run quickly. with MockVWS(processing_time_seconds=0.2) as mock: database = VuforiaDatabase() @@ -28,9 +24,7 @@ def fixture_mock_database() -> Generator[VuforiaDatabase]: @pytest.fixture def vws_client(_mock_database: VuforiaDatabase) -> VWS: - """ - A VWS client which connects to a mock database. - """ + """A VWS client which connects to a mock database.""" return VWS( server_access_key=_mock_database.server_access_key, server_secret_key=_mock_database.server_secret_key, @@ -39,9 +33,7 @@ def vws_client(_mock_database: VuforiaDatabase) -> VWS: @pytest.fixture def cloud_reco_client(_mock_database: VuforiaDatabase) -> CloudRecoService: - """ - A ``CloudRecoService`` client which connects to a mock database. - """ + """A ``CloudRecoService`` client which connects to a mock database.""" return CloudRecoService( client_access_key=_mock_database.client_access_key, client_secret_key=_mock_database.client_secret_key, @@ -54,9 +46,7 @@ def fixture_image_file( tmp_path: Path, request: pytest.FixtureRequest, ) -> Generator[BinaryIO]: - """ - An image file object. - """ + """An image file object.""" file = tmp_path / "image.jpg" buffer = high_quality_image.getvalue() file.write_bytes(data=buffer) @@ -71,9 +61,7 @@ def image( high_quality_image: io.BytesIO, image_file: BinaryIO, ) -> io.BytesIO | BinaryIO: - """ - An image in any of the types that the API accepts. - """ + """An image in any of the types that the API accepts.""" if request.param == "high_quality_image": return high_quality_image return image_file diff --git a/tests/test_cloud_reco_exceptions.py b/tests/test_cloud_reco_exceptions.py index 3515b0d22..d4c8cb0f7 100644 --- a/tests/test_cloud_reco_exceptions.py +++ b/tests/test_cloud_reco_exceptions.py @@ -1,6 +1,4 @@ -""" -Tests for exceptions raised when using the CloudRecoService. -""" +"""Tests for exceptions raised when using the CloudRecoService.""" import io import uuid @@ -63,9 +61,7 @@ def test_image_too_large( def test_cloudrecoexception_inheritance() -> None: - """ - CloudRecoService-specific exceptions inherit from CloudRecoException. - """ + """CloudRecoService-specific exceptions inherit from CloudRecoException.""" subclasses = [ MaxNumResultsOutOfRangeError, InactiveProjectError, diff --git a/tests/test_query.py b/tests/test_query.py index e229cf95d..11b803eb5 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,6 +1,4 @@ -""" -Tests for the ``CloudRecoService`` querying functionality. -""" +"""Tests for the ``CloudRecoService`` querying functionality.""" import io import uuid @@ -14,18 +12,14 @@ class TestQuery: - """ - Tests for making image queries. - """ + """Tests for making image queries.""" @staticmethod def test_no_matches( cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: - """ - An empty list is returned if there are no matches. - """ + """An empty list is returned if there are no matches.""" result = cloud_reco_client.query(image=image) assert result == [] @@ -35,9 +29,7 @@ def test_match( cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: - """ - Details of matching targets are returned. - """ + """Details of matching targets are returned.""" target_id = vws_client.add_target( name="x", width=1, @@ -51,9 +43,7 @@ def test_match( class TestCustomBaseVWQURL: - """ - Tests for using a custom base VWQ URL. - """ + """Tests for using a custom base VWQ URL.""" @staticmethod def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: @@ -93,9 +83,7 @@ def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: class TestMaxNumResults: - """ - Tests for the ``max_num_results`` parameter of ``query``. - """ + """Tests for the ``max_num_results`` parameter of ``query``.""" @staticmethod def test_default( @@ -103,9 +91,7 @@ def test_default( cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: - """ - By default the maximum number of results is 1. - """ + """By default the maximum number of results is 1.""" target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, @@ -131,9 +117,7 @@ def test_custom( cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: - """ - It is possible to set a custom ``max_num_results``. - """ + """It is possible to set a custom ``max_num_results``.""" target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, @@ -167,9 +151,7 @@ def test_custom( class TestIncludeTargetData: - """ - Tests for the ``include_target_data`` parameter of ``query``. - """ + """Tests for the ``include_target_data`` parameter of ``query``.""" @staticmethod def test_default( @@ -177,9 +159,7 @@ def test_default( cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: - """ - By default, target data is only returned in the top match. - """ + """By default, target data is only returned in the top match.""" target_id = vws_client.add_target( name=uuid.uuid4().hex, width=1, diff --git a/tests/test_vws.py b/tests/test_vws.py index e16eff555..d427e1c9b 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -1,6 +1,4 @@ -""" -Tests for helper functions for managing a Vuforia database. -""" +"""Tests for helper functions for managing a Vuforia database.""" import base64 import datetime @@ -25,9 +23,7 @@ class TestAddTarget: - """ - Tests for adding a target. - """ + """Tests for adding a target.""" @staticmethod @pytest.mark.parametrize( @@ -43,9 +39,7 @@ def test_add_target( *, active_flag: bool, ) -> None: - """ - No exception is raised when adding one target. - """ + """No exception is raised when adding one target.""" name = "x" width = 1 if application_metadata is None: @@ -98,9 +92,7 @@ def test_add_two_targets( class TestCustomBaseVWSURL: - """ - Tests for using a custom base VWS URL. - """ + """Tests for using a custom base VWS URL.""" @staticmethod def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: @@ -128,18 +120,14 @@ def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: class TestListTargets: - """ - Tests for listing targets. - """ + """Tests for listing targets.""" @staticmethod def test_list_targets( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """ - It is possible to get a list of target IDs. - """ + """It is possible to get a list of target IDs.""" id_1 = vws_client.add_target( name="x", width=1, @@ -158,18 +146,14 @@ def test_list_targets( class TestDelete: - """ - Test for deleting a target. - """ + """Test for deleting a target.""" @staticmethod def test_delete_target( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """ - It is possible to delete a target. - """ + """It is possible to delete a target.""" target_id = vws_client.add_target( name="x", width=1, @@ -185,18 +169,14 @@ def test_delete_target( class TestGetTargetSummaryReport: - """ - Tests for getting a summary report for a target. - """ + """Tests for getting a summary report for a target.""" @staticmethod def test_get_target_summary_report( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """ - Details of a target are returned by ``get_target_summary_report``. - """ + """Details of a target are returned by ``get_target_summary_report``.""" date = "2018-04-25" target_name = uuid.uuid4().hex with freeze_time(time_to_freeze=date): @@ -240,15 +220,11 @@ def test_get_target_summary_report( class TestGetDatabaseSummaryReport: - """ - Tests for getting a summary report for a database. - """ + """Tests for getting a summary report for a database.""" @staticmethod def test_get_target(vws_client: VWS) -> None: - """ - Details of a database are returned by ``get_database_summary_report``. - """ + """Details of a database are returned by ``get_database_summary_report``.""" report = vws_client.get_database_summary_report() expected_report = DatabaseSummaryReport( @@ -287,18 +263,14 @@ def test_get_target(vws_client: VWS) -> None: class TestGetTargetRecord: - """ - Tests for getting a record of a target. - """ + """Tests for getting a record of a target.""" @staticmethod def test_get_target_record( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """ - Details of a target are returned by ``get_target_record``. - """ + """Details of a target are returned by ``get_target_record``.""" target_id = vws_client.add_target( name="x", width=1, @@ -344,9 +316,7 @@ def test_get_failed( vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: - """ - Check that the report works with a failed target. - """ + """Check that the report works with a failed target.""" target_id = vws_client.add_target( name="x", width=1, @@ -362,18 +332,14 @@ def test_get_failed( class TestWaitForTargetProcessed: - """ - Tests for waiting for a target to be processed. - """ + """Tests for waiting for a target to be processed.""" @staticmethod def test_wait_for_target_processed( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """ - It is possible to wait until a target is processed. - """ + """It is possible to wait until a target is processed.""" target_id = vws_client.add_target( name="x", width=1, @@ -391,9 +357,7 @@ def test_wait_for_target_processed( def test_default_seconds_between_requests( image: io.BytesIO | BinaryIO, ) -> None: - """ - By default, 0.2 seconds are waited between polling requests. - """ + """By default, 0.2 seconds are waited between polling requests.""" with MockVWS(processing_time_seconds=0.5) as mock: database = VuforiaDatabase() mock.add_database(database=database) @@ -443,9 +407,7 @@ def test_default_seconds_between_requests( def test_custom_seconds_between_requests( image: io.BytesIO | BinaryIO, ) -> None: - """ - It is possible to customize the time waited between polling requests. - """ + """It is possible to customize the time waited between polling requests.""" with MockVWS(processing_time_seconds=0.5) as mock: database = VuforiaDatabase() mock.add_database(database=database) @@ -493,9 +455,7 @@ def test_custom_seconds_between_requests( @staticmethod def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: - """ - It is possible to set a maximum timeout. - """ + """It is possible to set a maximum timeout.""" with MockVWS(processing_time_seconds=0.5) as mock: database = VuforiaDatabase() mock.add_database(database=database) @@ -531,18 +491,14 @@ def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: class TestGetDuplicateTargets: - """ - Tests for getting duplicate targets. - """ + """Tests for getting duplicate targets.""" @staticmethod def test_get_duplicate_targets( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """ - It is possible to get the IDs of similar targets. - """ + """It is possible to get the IDs of similar targets.""" target_id = vws_client.add_target( name="x", width=1, @@ -565,9 +521,7 @@ def test_get_duplicate_targets( class TestUpdateTarget: - """ - Tests for updating a target. - """ + """Tests for updating a target.""" @staticmethod def test_update_target( @@ -576,9 +530,7 @@ def test_update_target( different_high_quality_image: io.BytesIO, cloud_reco_client: CloudRecoService, ) -> None: - """ - It is possible to update a target. - """ + """It is possible to update a target.""" old_name = uuid.uuid4().hex old_width = secrets.choice(seq=range(1, 5000)) / 100 target_id = vws_client.add_target( @@ -635,9 +587,7 @@ def test_no_fields_given( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """ - It is possible to give no update fields. - """ + """It is possible to give no update fields.""" target_id = vws_client.add_target( name="x", width=1, diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 86da66040..eb5b12a6f 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -1,6 +1,4 @@ -""" -Tests for VWS exceptions. -""" +"""Tests for VWS exceptions.""" import io import uuid @@ -98,9 +96,7 @@ def test_request_quota_reached() -> None: def test_fail(high_quality_image: io.BytesIO) -> None: - """ - A ``Fail`` exception is raised when the server access key does not exist. - """ + """A ``Fail`` exception is raised when the server access key does not exist.""" with MockVWS(): vws_client = VWS( server_access_key=uuid.uuid4().hex, @@ -120,9 +116,7 @@ def test_fail(high_quality_image: io.BytesIO) -> None: def test_bad_image(vws_client: VWS) -> None: - """ - A ``BadImage`` exception is raised when a non-image is given. - """ + """A ``BadImage`` exception is raised when a non-image is given.""" not_an_image = io.BytesIO(initial_bytes=b"Not an image") with pytest.raises(expected_exception=BadImageError) as exc: vws_client.add_target( @@ -325,9 +319,7 @@ def test_target_status_not_success( def test_vwsexception_inheritance() -> None: - """ - VWS-related exceptions should inherit from VWSException. - """ + """VWS-related exceptions should inherit from VWSException.""" subclasses = [ AuthenticationFailureError, BadImageError, @@ -354,9 +346,7 @@ def test_base_exception( vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: - """ - ``VWSException``s has a response property. - """ + """``VWSException``s has a response property.""" with pytest.raises(expected_exception=VWSError) as exc: vws_client.get_target_record(target_id="a") From 945db40a65fdb13147c726da0a2a087654b7ae9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:47:57 +0000 Subject: [PATCH 242/688] 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 3f7f5df0a..d874c44da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2026.1.3.2", + "doccmd==2026.1.12", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.12.19", From 7478c49044f21f350c1db4ca0a1863823ce78fab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:48:08 +0000 Subject: [PATCH 243/688] 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 3f7f5df0a..610bfaa4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,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 9039ba75145fd1d0f17a90de71f67d1dc5300c8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 10:33:29 +0000 Subject: [PATCH 244/688] 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 36d0acb22..0fbeab8f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,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", "pygments==2.19.2", "pylint[spelling]==4.0.4", From b17fdfd6c22125d139abc9154e9d8b490b106bd8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 10:33:39 +0000 Subject: [PATCH 245/688] 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 0fbeab8f2..2b9f2ef24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2026.1.12", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.11", + "ty==0.0.12", "types-requests==2.32.4.20260107", "vulture==2.14", "vws-python-mock==2025.3.10.1", From 2e312c4064e509a7dcd64ea00e70a8a6687e08d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:33:26 +0000 Subject: [PATCH 246/688] 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 2b9f2ef24..06a1d0d86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.5", "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/" From 6d3b483bd9bf2da8d6ff8ba2be1ef60f2c34f409 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:33:39 +0000 Subject: [PATCH 247/688] 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 2b9f2ef24..e2b49e137 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ optional-dependencies.dev = [ "pytest==9.0.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 fc0837e96106557f57bc41068fac7601936a353c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:33:50 +0000 Subject: [PATCH 248/688] 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 2b9f2ef24..8d922aa34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,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 23d947e07bfca1279087f06c81106a0dd9379f04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 12:03:18 +0000 Subject: [PATCH 249/688] Bump sphinx from 9.0.4 to 9.1.0 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 9.0.4 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/v9.0.4...v9.1.0) --- updated-dependencies: - dependency-name: sphinx dependency-version: 9.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 6c79db287..11864b58c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,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.0.4", + "sphinx==9.1.0", "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.2", "sphinx-pyproject==0.3.0", From 4e56a7d0e7c1a23fa8c93cf5587ada8704ce58e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 12:03:25 +0000 Subject: [PATCH 250/688] 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 6c79db287..e092671fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.5", "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/" From 35e943c0e7d1d4215dc64ce8c979ecca29f8120a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 12:03:32 +0000 Subject: [PATCH 251/688] 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 6c79db287..14fcad18f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,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", "pygments==2.19.2", "pylint[spelling]==4.0.4", From a3258f9fe66afc5c4e9dac1ef54be1638ffea95e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 12:03:39 +0000 Subject: [PATCH 252/688] 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 6c79db287..de7350677 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2026.1.12", + "doccmd==2026.1.18", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.12.19", From 6ea2afc8233a4c7263ec4eab5dbc2884a6b4ad82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 12:03:45 +0000 Subject: [PATCH 253/688] 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 6c79db287..a0c047b0c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,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 3f381a829f30a55ac2a598db8f976869db2b0b47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 10:33:13 +0000 Subject: [PATCH 254/688] 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 d4af4571f..dac0450a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,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 40b14bb7e46129662fdf68db6d7aaba68ede89bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 10:33:30 +0000 Subject: [PATCH 255/688] 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 dac0450a4..271d191a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,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", "pygments==2.19.2", "pylint[spelling]==4.0.4", From 8ec4f829753f3bf977a1f5a87f1c306f538c1ff9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 10:33:38 +0000 Subject: [PATCH 256/688] 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 dac0450a4..c9cf18b4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2026.1.12", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.12", + "ty==0.0.13", "types-requests==2.32.4.20260107", "vulture==2.14", "vws-python-mock==2025.3.10.1", From 46b0a4fc0263ebabd584e1daaa9d9a3281a46f76 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 10:33:50 +0000 Subject: [PATCH 257/688] 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 dac0450a4..0cedb1bf2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2026.1.18", + "doccmd==2026.1.21.2", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.12.19", From a130d1beed1315ced58d536005a8e93860102c99 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 22 Jan 2026 17:40:40 +0000 Subject: [PATCH 258/688] 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 0a9661f41..f4c94be57 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -34,6 +34,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 780a1b13ea766df310f037119c27237ebaaf50b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 10:33:14 +0000 Subject: [PATCH 259/688] 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 470b5cd13..1e9cd363e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2026.1.21.2", + "doccmd==2026.1.22.1", "docformatter==1.7.7", "freezegun==1.5.5", "furo==2025.12.19", From 89f628fc9d74cdb25e42e151a3ede8c53ca57823 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 10:33:30 +0000 Subject: [PATCH 260/688] 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 470b5cd13..34b9c6d09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ optional-dependencies.dev = [ "pytest==9.0.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 0ed9be617a157ece3114f783b3d9a5bbfbb9380b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 26 Jan 2026 09:26:34 +0000 Subject: [PATCH 261/688] Linewrap with pydocstringformatter --- pyproject.toml | 2 ++ src/vws/exceptions/cloud_reco_exceptions.py | 4 +++- src/vws/exceptions/custom_exceptions.py | 7 +++++-- src/vws/exceptions/vws_exceptions.py | 7 +++++-- tests/test_cloud_reco_exceptions.py | 7 +++++-- tests/test_query.py | 9 ++++++--- tests/test_vws.py | 12 +++++++++--- tests/test_vws_exceptions.py | 19 +++++++++++++------ 8 files changed, 48 insertions(+), 19 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d4a6cbd29..3998bc1a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -346,6 +346,8 @@ typeCheckingMode = "strict" [tool.pydocstringformatter] write = true split-summary-body = false +max-line-length = 79 +linewrap-full-docstring = true [tool.interrogate] fail-under = 100 diff --git a/src/vws/exceptions/cloud_reco_exceptions.py b/src/vws/exceptions/cloud_reco_exceptions.py index 2b20a7220..a59335f14 100644 --- a/src/vws/exceptions/cloud_reco_exceptions.py +++ b/src/vws/exceptions/cloud_reco_exceptions.py @@ -1,4 +1,6 @@ -"""Exceptions which match errors raised by the Vuforia Cloud Recognition Web APIs.""" +"""Exceptions which match errors raised by the Vuforia Cloud Recognition Web +APIs. +""" from beartype import beartype diff --git a/src/vws/exceptions/custom_exceptions.py b/src/vws/exceptions/custom_exceptions.py index 466dfd1cd..e06c20a19 100644 --- a/src/vws/exceptions/custom_exceptions.py +++ b/src/vws/exceptions/custom_exceptions.py @@ -1,6 +1,7 @@ """ Exceptions which do not map to errors at -https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#result-codes +https://developer.vuforia.com/library/web-api/cloud-targets-web-services- +api#result-codes or simple errors given by the cloud recognition service. """ @@ -29,7 +30,9 @@ def response(self) -> Response: @beartype class TargetProcessingTimeoutError(Exception): - """Exception raised when waiting for a target to be processed times out.""" + """Exception raised when waiting for a target to be processed times + out. + """ @beartype diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index a25c2261b..892f6e12f 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -1,7 +1,8 @@ """ Exception raised when Vuforia returns a response with a result code matching one of those documented at -https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#result-codes. +https://developer.vuforia.com/library/web-api/cloud-targets-web-services- +api#result-codes. """ import json @@ -29,7 +30,9 @@ def target_id(self) -> str: @beartype class FailError(VWSError): - """Exception raised when Vuforia returns a response with a result code 'Fail'.""" + """Exception raised when Vuforia returns a response with a result code + 'Fail'. + """ @beartype diff --git a/tests/test_cloud_reco_exceptions.py b/tests/test_cloud_reco_exceptions.py index d4c8cb0f7..a9e25338f 100644 --- a/tests/test_cloud_reco_exceptions.py +++ b/tests/test_cloud_reco_exceptions.py @@ -61,7 +61,9 @@ def test_image_too_large( def test_cloudrecoexception_inheritance() -> None: - """CloudRecoService-specific exceptions inherit from CloudRecoException.""" + """CloudRecoService-specific exceptions inherit from + CloudRecoException. + """ subclasses = [ MaxNumResultsOutOfRangeError, InactiveProjectError, @@ -77,7 +79,8 @@ def test_authentication_failure( high_quality_image: io.BytesIO, ) -> None: """ - An ``AuthenticationFailure`` exception is raised when the client access key + An ``AuthenticationFailure`` exception is raised when the client access + key exists but the client secret key is incorrect. """ database = VuforiaDatabase() diff --git a/tests/test_query.py b/tests/test_query.py index 11b803eb5..10fff3a8c 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -48,7 +48,8 @@ class TestCustomBaseVWQURL: @staticmethod def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: """ - It is possible to use query a target to a database under a custom VWQ + It is possible to use query a target to a database under a custom + VWQ URL. """ base_vwq_url = "http://example.com" @@ -190,7 +191,8 @@ def test_top( image: io.BytesIO | BinaryIO, ) -> None: """ - When ``CloudRecoIncludeTargetData.TOP`` is given, target data is only + When ``CloudRecoIncludeTargetData.TOP`` is given, target data is + only returned in the top match. """ target_id = vws_client.add_target( @@ -224,7 +226,8 @@ def test_none( image: io.BytesIO | BinaryIO, ) -> None: """ - When ``CloudRecoIncludeTargetData.NONE`` is given, target data is not + When ``CloudRecoIncludeTargetData.NONE`` is given, target data is + not returned in any match. """ target_id = vws_client.add_target( diff --git a/tests/test_vws.py b/tests/test_vws.py index d427e1c9b..73d8e1fdf 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -176,7 +176,9 @@ def test_get_target_summary_report( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """Details of a target are returned by ``get_target_summary_report``.""" + """Details of a target are returned by + ``get_target_summary_report``. + """ date = "2018-04-25" target_name = uuid.uuid4().hex with freeze_time(time_to_freeze=date): @@ -224,7 +226,9 @@ class TestGetDatabaseSummaryReport: @staticmethod def test_get_target(vws_client: VWS) -> None: - """Details of a database are returned by ``get_database_summary_report``.""" + """Details of a database are returned by + ``get_database_summary_report``. + """ report = vws_client.get_database_summary_report() expected_report = DatabaseSummaryReport( @@ -407,7 +411,9 @@ def test_default_seconds_between_requests( def test_custom_seconds_between_requests( image: io.BytesIO | BinaryIO, ) -> None: - """It is possible to customize the time waited between polling requests.""" + """It is possible to customize the time waited between polling + requests. + """ with MockVWS(processing_time_seconds=0.5) as mock: database = VuforiaDatabase() mock.add_database(database=database) diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index eb5b12a6f..496243274 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -40,7 +40,8 @@ def test_image_too_large( png_too_large: io.BytesIO | io.BufferedRandom, ) -> None: """ - When giving an image which is too large, an ``ImageTooLarge`` exception is + When giving an image which is too large, an ``ImageTooLarge`` exception + is raised. """ with pytest.raises(expected_exception=ImageTooLargeError) as exc: @@ -69,7 +70,8 @@ def test_invalid_given_id(vws_client: VWS) -> None: def test_add_bad_name(vws_client: VWS, high_quality_image: io.BytesIO) -> None: """ - When a name with a bad character is given, a ``ServerError`` exception is + When a name with a bad character is given, a ``ServerError`` exception + is raised. """ max_char_value = 65535 @@ -96,7 +98,9 @@ def test_request_quota_reached() -> None: def test_fail(high_quality_image: io.BytesIO) -> None: - """A ``Fail`` exception is raised when the server access key does not exist.""" + """A ``Fail`` exception is raised when the server access key does not + exist. + """ with MockVWS(): vws_client = VWS( server_access_key=uuid.uuid4().hex, @@ -135,7 +139,8 @@ def test_target_name_exist( high_quality_image: io.BytesIO, ) -> None: """ - A ``TargetNameExist`` exception is raised after adding two targets with the + A ``TargetNameExist`` exception is raised after adding two targets with + the same name. """ vws_client.add_target( @@ -162,7 +167,8 @@ def test_project_inactive( high_quality_image: io.BytesIO, ) -> None: """ - A ``ProjectInactive`` exception is raised if adding a target to an inactive + A ``ProjectInactive`` exception is raised if adding a target to an + inactive database. """ database = VuforiaDatabase(state=States.PROJECT_INACTIVE) @@ -267,7 +273,8 @@ def test_authentication_failure( high_quality_image: io.BytesIO, ) -> None: """ - An ``AuthenticationFailure`` exception is raised when the server access key + An ``AuthenticationFailure`` exception is raised when the server access + key exists but the server secret key is incorrect, or when a client key is incorrect. """ From e446e04b4352f3d4d1ed82994bec513d0d5cf64d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 26 Jan 2026 09:50:09 +0000 Subject: [PATCH 262/688] Un-break URLs --- pyproject.toml | 2 +- src/vws/exceptions/custom_exceptions.py | 3 +-- src/vws/exceptions/vws_exceptions.py | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3998bc1a9..a37ece588 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -344,7 +344,7 @@ reportUnnecessaryTypeIgnoreComment = true typeCheckingMode = "strict" [tool.pydocstringformatter] -write = true +write = false split-summary-body = false max-line-length = 79 linewrap-full-docstring = true diff --git a/src/vws/exceptions/custom_exceptions.py b/src/vws/exceptions/custom_exceptions.py index e06c20a19..ff20c1b6d 100644 --- a/src/vws/exceptions/custom_exceptions.py +++ b/src/vws/exceptions/custom_exceptions.py @@ -1,7 +1,6 @@ """ Exceptions which do not map to errors at -https://developer.vuforia.com/library/web-api/cloud-targets-web-services- -api#result-codes +https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#result-codes or simple errors given by the cloud recognition service. """ diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index 892f6e12f..c68107d00 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -1,8 +1,7 @@ """ Exception raised when Vuforia returns a response with a result code matching one of those documented at -https://developer.vuforia.com/library/web-api/cloud-targets-web-services- -api#result-codes. +https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#result-codes. """ import json From 06de28f492912c6c6cea349964fadc130429f89e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 26 Jan 2026 10:46:10 +0000 Subject: [PATCH 263/688] Work around pydocstringformatter issue --- src/vws/exceptions/custom_exceptions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vws/exceptions/custom_exceptions.py b/src/vws/exceptions/custom_exceptions.py index ff20c1b6d..cf3902264 100644 --- a/src/vws/exceptions/custom_exceptions.py +++ b/src/vws/exceptions/custom_exceptions.py @@ -1,7 +1,7 @@ -""" -Exceptions which do not map to errors at +"""Exceptions which do not map to errors at the following URL, or simple +errors given by the cloud recognition service. + https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#result-codes -or simple errors given by the cloud recognition service. """ from beartype import beartype From a820dbdcfabf5fc81a4580e8421dc47f4ea73b98 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 26 Jan 2026 10:50:31 +0000 Subject: [PATCH 264/688] Fix ruff ignore comment --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a37ece588..77640c0ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,8 +119,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", # Ruff warns that this conflicts with the formatter. From d4fe42e32a2351b9025f0ce53ec04a04dbd712ef Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 26 Jan 2026 10:52:41 +0000 Subject: [PATCH 265/688] Write fixes --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 77640c0ba..32edef2a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -344,7 +344,7 @@ reportUnnecessaryTypeIgnoreComment = true typeCheckingMode = "strict" [tool.pydocstringformatter] -write = false +write = true split-summary-body = false max-line-length = 79 linewrap-full-docstring = true From 31dfe8c1839343f38272d5b4f08fa67c3525bc45 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 26 Jan 2026 11:03:05 +0000 Subject: [PATCH 266/688] Re-add D200 to ruff ignores - conflicts with pydocstringformatter at line-length boundary --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 15fb0e3cf..9843058be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,7 +120,9 @@ 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). + # but may conflict with D200 at the line-length boundary, and doesn't enforce + # D205 (blank line after summary) or D212 (summary on first line). + "D200", "D205", "D212", # Ruff warns that this conflicts with the formatter. From 114e0835579deb7d0727a12bb158cefdaa3b235a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 26 Jan 2026 11:03:51 +0000 Subject: [PATCH 267/688] Remove D200 ignore by lowering pydocstringformatter line length to 75 --- bun.lock | 14 +++++++++++ example.txt | 0 issue.txt | 28 +++++++++++++++++++++ package.json | 6 +++++ pyproject.toml | 9 ++++--- src/vws/exceptions/base_exceptions.py | 3 ++- src/vws/exceptions/cloud_reco_exceptions.py | 3 ++- src/vws/exceptions/vws_exceptions.py | 6 +++-- src/vws/query.py | 3 ++- src/vws/vws.py | 3 ++- tests/test_cloud_reco_exceptions.py | 6 +++-- tests/test_query.py | 6 +++-- tests/test_vws.py | 6 +++-- tests/test_vws_exceptions.py | 27 +++++++++++++------- 14 files changed, 95 insertions(+), 25 deletions(-) create mode 100644 bun.lock create mode 100644 example.txt create mode 100644 issue.txt create mode 100644 package.json diff --git a/bun.lock b/bun.lock new file mode 100644 index 000000000..45f73b81b --- /dev/null +++ b/bun.lock @@ -0,0 +1,14 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "devDependencies": { + "prettier": "3.7.4", + }, + }, + }, + "packages": { + "prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="], + } +} diff --git a/example.txt b/example.txt new file mode 100644 index 000000000..e69de29bb diff --git a/issue.txt b/issue.txt new file mode 100644 index 000000000..abe88bb9b --- /dev/null +++ b/issue.txt @@ -0,0 +1,28 @@ +URLs are broken when reflowing paragraphs + +## Version + +0.7.3 + +## Example + +Input file: + +```python +"""For more information see https://example.com/documentation/api/v2/reference/getting-started#authentication or contact support.""" +``` + +Output after running `pydocstringformatter --linewrap-full-docstring`: + +```diff +--- issue.py ++++ issue.py +@@ -1,2 +1,5 @@ +-"""For more information see https://example.com/documentation/api/v2/reference/getting-started#authentication or contact support.""" ++"""For more information see https://example.com/documentation/api/v2/reference/getting-. ++ ++started#authentication or contact support. ++""" +``` + +The URL is broken across two lines, making it impossible to click. diff --git a/package.json b/package.json new file mode 100644 index 000000000..71b8ee1ef --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": {}, + "devDependencies": { + "prettier": "3.7.4" + } +} diff --git a/pyproject.toml b/pyproject.toml index 9843058be..55238906d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,9 +120,7 @@ lint.ignore = [ # Ruff warns that this conflicts with the formatter. "COM812", # Allow our chosen docstring line-style - pydocstringformatter handles formatting - # but may conflict with D200 at the line-length boundary, and doesn't enforce - # D205 (blank line after summary) or D212 (summary on first line). - "D200", + # but doesn't enforce D205 (blank line after summary) or D212 (summary on first line). "D205", "D212", # Ruff warns that this conflicts with the formatter. @@ -348,7 +346,10 @@ typeCheckingMode = "strict" [tool.pydocstringformatter] write = true split-summary-body = false -max-line-length = 79 +# Use a lower line length than ruff (79) to avoid conflicts with D200 - +# pydocstringformatter would otherwise split docstrings at exactly 79 chars +# which ruff considers should stay on one line. +max-line-length = 75 linewrap-full-docstring = true [tool.interrogate] diff --git a/src/vws/exceptions/base_exceptions.py b/src/vws/exceptions/base_exceptions.py index c86080593..fa031838a 100644 --- a/src/vws/exceptions/base_exceptions.py +++ b/src/vws/exceptions/base_exceptions.py @@ -1,5 +1,6 @@ """ -Base exceptions for errors returned by Vuforia Web Services or the Vuforia +Base exceptions for errors returned by Vuforia Web Services or the +Vuforia Cloud Recognition Web API. """ diff --git a/src/vws/exceptions/cloud_reco_exceptions.py b/src/vws/exceptions/cloud_reco_exceptions.py index a59335f14..b2e3ee67f 100644 --- a/src/vws/exceptions/cloud_reco_exceptions.py +++ b/src/vws/exceptions/cloud_reco_exceptions.py @@ -1,4 +1,5 @@ -"""Exceptions which match errors raised by the Vuforia Cloud Recognition Web +"""Exceptions which match errors raised by the Vuforia Cloud Recognition +Web APIs. """ diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index c68107d00..6b1447577 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -1,7 +1,9 @@ """ -Exception raised when Vuforia returns a response with a result code matching +Exception raised when Vuforia returns a response with a result code +matching one of those documented at -https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#result-codes. +https://developer.vuforia.com/library/web-api/cloud-targets-web-services- +api#result-codes. """ import json diff --git a/src/vws/query.py b/src/vws/query.py index ab449c614..6c2dc481e 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -68,7 +68,8 @@ def query( CloudRecoIncludeTargetData.TOP ), ) -> list[QueryResult]: - """Use the Vuforia Web Query API to make an Image Recognition Query. + """Use the Vuforia Web Query API to make an Image Recognition + Query. See https://developer.vuforia.com/library/web-api/vuforia-query-web-api diff --git a/src/vws/vws.py b/src/vws/vws.py index 1191ee7df..9b3d19b4e 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -567,7 +567,8 @@ def delete_target(self, target_id: str) -> None: ) def get_duplicate_targets(self, target_id: str) -> list[str]: - """Get targets which may be considered duplicates of a given target. + """Get targets which may be considered duplicates of a given + target. See https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#check. diff --git a/tests/test_cloud_reco_exceptions.py b/tests/test_cloud_reco_exceptions.py index a9e25338f..5576f4d40 100644 --- a/tests/test_cloud_reco_exceptions.py +++ b/tests/test_cloud_reco_exceptions.py @@ -49,7 +49,8 @@ def test_image_too_large( png_too_large: io.BytesIO | io.BufferedRandom, ) -> None: """ - A ``RequestEntityTooLarge`` exception is raised if an image which is too + A ``RequestEntityTooLarge`` exception is raised if an image which is + too large is given. """ with pytest.raises(expected_exception=RequestEntityTooLargeError) as exc: @@ -79,7 +80,8 @@ def test_authentication_failure( high_quality_image: io.BytesIO, ) -> None: """ - An ``AuthenticationFailure`` exception is raised when the client access + An ``AuthenticationFailure`` exception is raised when the client + access key exists but the client secret key is incorrect. """ diff --git a/tests/test_query.py b/tests/test_query.py index 10fff3a8c..a79a712c0 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -48,7 +48,8 @@ class TestCustomBaseVWQURL: @staticmethod def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: """ - It is possible to use query a target to a database under a custom + It is possible to use query a target to a database under a + custom VWQ URL. """ @@ -226,7 +227,8 @@ def test_none( image: io.BytesIO | BinaryIO, ) -> None: """ - When ``CloudRecoIncludeTargetData.NONE`` is given, target data is + When ``CloudRecoIncludeTargetData.NONE`` is given, target data + is not returned in any match. """ diff --git a/tests/test_vws.py b/tests/test_vws.py index 73d8e1fdf..00936bde8 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -77,7 +77,8 @@ def test_add_two_targets( vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: - """No exception is raised when adding two targets with different names. + """No exception is raised when adding two targets with different + names. This demonstrates that the image seek position is not changed. """ @@ -97,7 +98,8 @@ class TestCustomBaseVWSURL: @staticmethod def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: """ - It is possible to use add a target to a database under a custom VWS + It is possible to use add a target to a database under a custom + VWS URL. """ base_vws_url = "http://example.com" diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 496243274..4262b3ea6 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -40,7 +40,8 @@ def test_image_too_large( png_too_large: io.BytesIO | io.BufferedRandom, ) -> None: """ - When giving an image which is too large, an ``ImageTooLarge`` exception + When giving an image which is too large, an ``ImageTooLarge`` + exception is raised. """ @@ -58,7 +59,8 @@ def test_image_too_large( def test_invalid_given_id(vws_client: VWS) -> None: """ - Giving an invalid ID to a helper which requires a target ID to be given + Giving an invalid ID to a helper which requires a target ID to be + given causes an ``UnknownTarget`` exception to be raised. """ target_id = "12345abc" @@ -70,7 +72,8 @@ def test_invalid_given_id(vws_client: VWS) -> None: def test_add_bad_name(vws_client: VWS, high_quality_image: io.BytesIO) -> None: """ - When a name with a bad character is given, a ``ServerError`` exception + When a name with a bad character is given, a ``ServerError`` + exception is raised. """ @@ -139,7 +142,8 @@ def test_target_name_exist( high_quality_image: io.BytesIO, ) -> None: """ - A ``TargetNameExist`` exception is raised after adding two targets with + A ``TargetNameExist`` exception is raised after adding two targets + with the same name. """ @@ -196,7 +200,8 @@ def test_target_status_processing( high_quality_image: io.BytesIO, ) -> None: """ - A ``TargetStatusProcessing`` exception is raised if trying to delete a + A ``TargetStatusProcessing`` exception is raised if trying to delete + a target which is processing. """ target_id = vws_client.add_target( @@ -219,7 +224,8 @@ def test_metadata_too_large( high_quality_image: io.BytesIO, ) -> None: """ - A ``MetadataTooLarge`` exception is raised if the metadata given is too + A ``MetadataTooLarge`` exception is raised if the metadata given is + too large. """ with pytest.raises(expected_exception=MetadataTooLargeError) as exc: @@ -239,7 +245,8 @@ def test_request_time_too_skewed( high_quality_image: io.BytesIO, ) -> None: """ - A ``RequestTimeTooSkewed`` exception is raised when the request time is + A ``RequestTimeTooSkewed`` exception is raised when the request time + is more than five minutes different from the server time. """ target_id = vws_client.add_target( @@ -273,7 +280,8 @@ def test_authentication_failure( high_quality_image: io.BytesIO, ) -> None: """ - An ``AuthenticationFailure`` exception is raised when the server access + An ``AuthenticationFailure`` exception is raised when the server + access key exists but the server secret key is incorrect, or when a client key is incorrect. @@ -307,7 +315,8 @@ def test_target_status_not_success( high_quality_image: io.BytesIO, ) -> None: """ - A ``TargetStatusNotSuccess`` exception is raised when updating a target + A ``TargetStatusNotSuccess`` exception is raised when updating a + target which has a status which is not "Success". """ target_id = vws_client.add_target( From 7f330219e793b08895ff78a54052b950d3fc3254 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 26 Jan 2026 11:04:20 +0000 Subject: [PATCH 268/688] Remove unrelated test files --- bun.lock | 14 -------------- example.txt | 0 issue.txt | 28 ---------------------------- package.json | 6 ------ 4 files changed, 48 deletions(-) delete mode 100644 bun.lock delete mode 100644 example.txt delete mode 100644 issue.txt delete mode 100644 package.json diff --git a/bun.lock b/bun.lock deleted file mode 100644 index 45f73b81b..000000000 --- a/bun.lock +++ /dev/null @@ -1,14 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "devDependencies": { - "prettier": "3.7.4", - }, - }, - }, - "packages": { - "prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="], - } -} diff --git a/example.txt b/example.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/issue.txt b/issue.txt deleted file mode 100644 index abe88bb9b..000000000 --- a/issue.txt +++ /dev/null @@ -1,28 +0,0 @@ -URLs are broken when reflowing paragraphs - -## Version - -0.7.3 - -## Example - -Input file: - -```python -"""For more information see https://example.com/documentation/api/v2/reference/getting-started#authentication or contact support.""" -``` - -Output after running `pydocstringformatter --linewrap-full-docstring`: - -```diff ---- issue.py -+++ issue.py -@@ -1,2 +1,5 @@ --"""For more information see https://example.com/documentation/api/v2/reference/getting-started#authentication or contact support.""" -+"""For more information see https://example.com/documentation/api/v2/reference/getting-. -+ -+started#authentication or contact support. -+""" -``` - -The URL is broken across two lines, making it impossible to click. diff --git a/package.json b/package.json deleted file mode 100644 index 71b8ee1ef..000000000 --- a/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "dependencies": {}, - "devDependencies": { - "prettier": "3.7.4" - } -} From 98aae9197c8181b2f85e10501391cf6c6e00a8a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 11:37:16 +0000 Subject: [PATCH 269/688] 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 55238906d..c40ae9b46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2026.1.22.1", + "doccmd==2026.1.25", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From 1d9fb85fb1d106930476ce6d385f6878d748cd16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 11:37:24 +0000 Subject: [PATCH 270/688] 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 55238906d..8af403249 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,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", "pygments==2.19.2", "pylint[spelling]==4.0.4", From 099a85381d6547554c43b99c1cdfa2bb41b35c7a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 26 Jan 2026 12:23:38 +0000 Subject: [PATCH 271/688] Remove --example-workers 0 due to mypy bug See https://github.com/python/mypy/issues/18283 --- .pre-commit-config.yaml | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6b48bc64c..49e946718 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,8 @@ --- fail_fast: true +# We do not use --example-workers 0 due to https://github.com/python/mypy/issues/18283 + # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks @@ -122,8 +124,8 @@ repos: - id: shellcheck-docs name: shellcheck-docs - entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=shell - --language=console --command="shellcheck --shell=bash" + entry: uv run --extra=dev doccmd --no-write-to-file --language=shell --language=console + --command="shellcheck --shell=bash" language: python types_or: [markdown, rst] additional_dependencies: [uv==0.9.5] @@ -158,8 +160,7 @@ repos: - 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] additional_dependencies: [uv==0.9.5] @@ -184,8 +185,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] additional_dependencies: [uv==0.9.5] @@ -201,8 +201,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: [markdown, rst] additional_dependencies: [uv==0.9.5] @@ -235,8 +234,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] @@ -293,8 +291,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] @@ -358,8 +355,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] @@ -401,8 +398,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 fce3a97d981c50cb42a704de6f8b2f8d92aafb07 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 26 Jan 2026 12:40:25 +0000 Subject: [PATCH 272/688] Fix: Remove --example-workers 0 only from mypy-docs hook This reverts the previous incorrect change that removed --example-workers 0 from all hooks, and correctly removes it only from mypy-docs. See https://github.com/python/mypy/issues/18283 --- .pre-commit-config.yaml | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 49e946718..0126b809a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,8 +1,6 @@ --- fail_fast: true -# We do not use --example-workers 0 due to https://github.com/python/mypy/issues/18283 - # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks @@ -124,8 +122,8 @@ repos: - id: shellcheck-docs name: shellcheck-docs - entry: uv run --extra=dev doccmd --no-write-to-file --language=shell --language=console - --command="shellcheck --shell=bash" + entry: uv run --extra=dev doccmd --no-write-to-file --example-workers 0 --language=shell + --language=console --command="shellcheck --shell=bash" language: python types_or: [markdown, rst] additional_dependencies: [uv==0.9.5] @@ -160,7 +158,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 --language=python + --command="mypy" language: python types_or: [markdown, rst] additional_dependencies: [uv==0.9.5] @@ -185,7 +184,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] additional_dependencies: [uv==0.9.5] @@ -201,7 +201,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: [markdown, rst] additional_dependencies: [uv==0.9.5] @@ -234,7 +235,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] @@ -291,7 +293,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] @@ -355,8 +358,8 @@ repos: - id: ty-docs name: ty-docs stages: [pre-push] - entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="ty - check" + 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] @@ -398,8 +401,8 @@ repos: - id: pyrefly-docs name: pyrefly-docs stages: [pre-push] - entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="pyrefly - check" + 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] From 4a5ce8e7c7572ce6809784976f2cffdc3346aeef 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, 26 Jan 2026 12:42:29 +0000 Subject: [PATCH 273/688] [pre-commit.ci lite] apply automatic fixes --- .pre-commit-config.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0126b809a..9bb1ad683 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -158,8 +158,7 @@ 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 --language=python --command="mypy" language: python types_or: [markdown, rst] additional_dependencies: [uv==0.9.5] From 2a9c9f60296dc392004d5d8fdbee2f2ed6d2d094 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 26 Jan 2026 12:46:08 +0000 Subject: [PATCH 274/688] Add comment explaining why --example-workers 0 is not used for mypy-docs --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9bb1ad683..c64693b9d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -155,6 +155,7 @@ 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] From 90cd53bb3a690f83d6c1edc3e42db6b4dc3f61a1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 27 Jan 2026 04:31:17 +0000 Subject: [PATCH 275/688] Support Python 3.14 --- .github/workflows/ci.yml | 2 +- pyproject.toml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca86af4b1..2bec50283 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: build: strategy: matrix: - python-version: ['3.13'] + python-version: ['3.13', '3.14'] platform: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.platform }} diff --git a/pyproject.toml b/pyproject.toml index 3d1113d2c..3b6366fb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ classifiers = [ "Operating System :: POSIX", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] dynamic = [ "version", @@ -311,7 +312,7 @@ pep621_dev_dependency_groups = [ [tool.pyproject-fmt] indent = 4 keep_full_version = true -max_supported_python = "3.13" +max_supported_python = "3.14" [tool.pytest.ini_options] From 03aea77ffa3add893acce8b0152a3d5238742c98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:31:24 +0000 Subject: [PATCH 276/688] Bump doccmd from 2026.1.25 to 2026.1.27.2 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.1.25 to 2026.1.27.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.25...2026.01.27.2) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.1.27.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 3b6366fb6..1787e4339 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2026.1.25", + "doccmd==2026.1.27.2", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From 37d14fb090552481e9eceb495c972c35b8c16625 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:31:36 +0000 Subject: [PATCH 277/688] 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 3b6366fb6..6edd655cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2026.1.12", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.13", + "ty==0.0.14", "types-requests==2.32.4.20260107", "vulture==2.14", "vws-python-mock==2025.3.10.1", From ae28c2d82d69580211ab889d3b3cc78cceea10e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:31:41 +0000 Subject: [PATCH 278/688] 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 3b6366fb6..a9b0825ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,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 5945d15615a27ad04ae4267e19a16a19e188be7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:33:09 +0000 Subject: [PATCH 279/688] Bump doccmd from 2026.1.27.2 to 2026.1.28 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.1.27.2 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.2...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 d1971da3c..3d34fe0f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2026.1.27.2", + "doccmd==2026.1.28", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From e3f97fd96995842c48480fae8c124431b353b969 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:33:07 +0000 Subject: [PATCH 280/688] 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 3d34fe0f9..9f1747298 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,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 13376509e7e766b0953355279b65ddaefdfdf0f8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 2 Feb 2026 11:40:32 +0000 Subject: [PATCH 281/688] Add custom request timeout support for VWS and CloudRecoService Allow users to configure request timeouts instead of using a hardcoded 30-second default. Both VWS and CloudRecoService now accept an optional request_timeout_seconds parameter during initialization, with proper defaults and documentation. Co-Authored-By: Claude Haiku 4.5 --- src/vws/query.py | 7 +++++-- src/vws/vws.py | 10 ++++++++-- tests/test_query.py | 48 +++++++++++++++++++++++++++++++++++++++++++++ tests/test_vws.py | 41 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 4 deletions(-) diff --git a/src/vws/query.py b/src/vws/query.py index 6c2dc481e..b9d42d4c8 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -49,16 +49,20 @@ def __init__( client_access_key: str, client_secret_key: str, base_vwq_url: str = "https://cloudreco.vuforia.com", + request_timeout_seconds: float = 30.0, ) -> None: """ Args: client_access_key: A VWS client access key. client_secret_key: A VWS client secret key. base_vwq_url: The base URL for the VWQ API. + request_timeout_seconds: The timeout in seconds for each HTTP + request made to the Cloud Reco API. """ self._client_access_key = client_access_key self._client_secret_key = client_secret_key self._base_vwq_url = base_vwq_url + self.request_timeout_seconds = request_timeout_seconds def query( self, @@ -141,8 +145,7 @@ def query( url=urljoin(base=self._base_vwq_url, url=request_path), headers=headers, data=content, - # We should make the timeout customizable. - timeout=30, + timeout=self.request_timeout_seconds, ) response = Response( text=requests_response.text, diff --git a/src/vws/vws.py b/src/vws/vws.py index 9b3d19b4e..82eb7c5bc 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -68,6 +68,7 @@ def _target_api_request( data: bytes, request_path: str, base_vws_url: str, + request_timeout_seconds: float, ) -> Response: """Make a request to the Vuforia Target API. @@ -82,6 +83,7 @@ def _target_api_request( request_path: The path to the endpoint which will be used in the request. base_vws_url: The base URL for the VWS API. + request_timeout_seconds: The timeout in seconds for the request. Returns: The response to the request made by `requests`. @@ -111,8 +113,7 @@ def _target_api_request( url=url, headers=headers, data=data, - # We should make the timeout customizable. - timeout=30, + timeout=request_timeout_seconds, ) return Response( @@ -134,16 +135,20 @@ def __init__( server_access_key: str, server_secret_key: str, base_vws_url: str = "https://vws.vuforia.com", + request_timeout_seconds: float = 30.0, ) -> None: """ Args: server_access_key: A VWS server access key. server_secret_key: A VWS server secret key. base_vws_url: The base URL for the VWS API. + request_timeout_seconds: The timeout in seconds for each HTTP + request made to the VWS API. """ self._server_access_key = server_access_key self._server_secret_key = server_secret_key self._base_vws_url = base_vws_url + self.request_timeout_seconds = request_timeout_seconds def make_request( self, @@ -187,6 +192,7 @@ def make_request( data=data, request_path=request_path, base_vws_url=self._base_vws_url, + request_timeout_seconds=self.request_timeout_seconds, ) if ( diff --git a/tests/test_query.py b/tests/test_query.py index a79a712c0..af9d19799 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -42,6 +42,54 @@ def test_match( assert matching_target.target_id == target_id +class TestCustomRequestTimeout: + """Tests for using a custom request timeout.""" + + @staticmethod + def test_default_timeout() -> None: + """By default, the request timeout is 30 seconds.""" + default_timeout_seconds = 30.0 + with MockVWS() as mock: + database = VuforiaDatabase() + mock.add_database(database=database) + cloud_reco_client = CloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + ) + expected = default_timeout_seconds + assert cloud_reco_client.request_timeout_seconds == expected + + @staticmethod + def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: + """It is possible to set a custom request timeout.""" + with MockVWS() as mock: + database = VuforiaDatabase() + mock.add_database(database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + custom_timeout = 60.5 + cloud_reco_client = CloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + request_timeout_seconds=custom_timeout, + ) + assert cloud_reco_client.request_timeout_seconds == custom_timeout + + # Verify requests work with the custom timeout + target_id = vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + vws_client.wait_for_target_processed(target_id=target_id) + matches = cloud_reco_client.query(image=image) + assert len(matches) == 1 + + class TestCustomBaseVWQURL: """Tests for using a custom base VWQ URL.""" diff --git a/tests/test_vws.py b/tests/test_vws.py index 00936bde8..25fe7227e 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -92,6 +92,47 @@ def test_add_two_targets( ) +class TestCustomRequestTimeout: + """Tests for using a custom request timeout.""" + + @staticmethod + def test_default_timeout() -> None: + """By default, the request timeout is 30 seconds.""" + default_timeout_seconds = 30.0 + with MockVWS() as mock: + database = VuforiaDatabase() + mock.add_database(database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + expected = default_timeout_seconds + assert vws_client.request_timeout_seconds == expected + + @staticmethod + def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: + """It is possible to set a custom request timeout.""" + with MockVWS() as mock: + database = VuforiaDatabase() + mock.add_database(database=database) + custom_timeout = 60.5 + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + request_timeout_seconds=custom_timeout, + ) + assert vws_client.request_timeout_seconds == custom_timeout + + # Verify requests work with the custom timeout + vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + + class TestCustomBaseVWSURL: """Tests for using a custom base VWS URL.""" From 5e1f289b1b491558dc44298e6701d1f27e56d59b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:49:50 +0000 Subject: [PATCH 282/688] 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 9f1747298..c01f056ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,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", "pygments==2.19.2", From 2504ba117f6b5208e62a714c84e8ff2ba0db68d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:49:56 +0000 Subject: [PATCH 283/688] 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 9f1747298..9196881ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2026.1.28", + "doccmd==2026.1.31.3", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From 32c7e2bc767d735827b6a6ef15ed4634764470e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:50:08 +0000 Subject: [PATCH 284/688] Bump pyproject-fmt from 2.11.1 to 2.12.1 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.11.1 to 2.12.1. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.11.1...pyproject-fmt/2.12.1) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.12.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 9f1747298..b24fc8ef8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.11.1", + "pyproject-fmt==2.12.1", "pyrefly==0.50.1", "pyright==1.1.408", "pyroma==5.0.1", From 7f0990fc6cfabefd54e88a485dccf39d9b75ff1c Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 2 Feb 2026 11:54:49 +0000 Subject: [PATCH 285/688] Add tests that verify timeout is actually enforced Add test_timeout_raises_on_slow_response and test_longer_timeout_succeeds tests to both VWS and CloudRecoService. These tests simulate slow server responses and verify that short timeouts actually raise Timeout exceptions. Co-Authored-By: Claude Opus 4.5 --- tests/test_query.py | 104 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_vws.py | 89 +++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) diff --git a/tests/test_query.py b/tests/test_query.py index af9d19799..a3f4c0331 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,9 +1,13 @@ """Tests for the ``CloudRecoService`` querying functionality.""" import io +import time import uuid from typing import BinaryIO +from unittest.mock import patch +import pytest +import requests from mock_vws import MockVWS from mock_vws.database import VuforiaDatabase @@ -89,6 +93,106 @@ def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: matches = cloud_reco_client.query(image=image) assert len(matches) == 1 + @staticmethod + def test_timeout_raises_on_slow_response( + image: io.BytesIO | BinaryIO, + ) -> None: + """A short timeout raises an error when the server is slow.""" + with MockVWS() as mock: + database = VuforiaDatabase() + mock.add_database(database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + cloud_reco_client = CloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + request_timeout_seconds=0.1, + ) + + target_id = vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + vws_client.wait_for_target_processed(target_id=target_id) + + simulated_slow_threshold = 0.5 + original_request = requests.request + + def slow_request( + *args: object, + **kwargs: float | None, + ) -> requests.Response: + """Simulate a slow server response.""" + timeout = kwargs.get("timeout") + if timeout is not None and timeout < simulated_slow_threshold: + time.sleep(0.2) + raise requests.exceptions.Timeout + return original_request(*args, **kwargs) # type: ignore[arg-type] + + with ( + patch.object( + requests, + "request", + side_effect=slow_request, + ), + pytest.raises(requests.exceptions.Timeout), + ): + cloud_reco_client.query(image=image) + + @staticmethod + def test_longer_timeout_succeeds(image: io.BytesIO | BinaryIO) -> None: + """A longer timeout allows slow responses to complete.""" + simulated_slow_threshold = 0.5 + + with MockVWS() as mock: + database = VuforiaDatabase() + mock.add_database(database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + cloud_reco_client = CloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + request_timeout_seconds=1.0, + ) + + target_id = vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + vws_client.wait_for_target_processed(target_id=target_id) + + original_request = requests.request + + def slow_request( + *args: object, + **kwargs: float | None, + ) -> requests.Response: + """Simulate a slow server response.""" + timeout = kwargs.get("timeout") + if timeout is not None and timeout < simulated_slow_threshold: + time.sleep(0.2) + raise requests.exceptions.Timeout + return original_request(*args, **kwargs) # type: ignore[arg-type] + + with patch.object( + requests, + "request", + side_effect=slow_request, + ): + # This should succeed because timeout is 1.0 > 0.5 + matches = cloud_reco_client.query(image=image) + assert len(matches) == 1 + class TestCustomBaseVWQURL: """Tests for using a custom base VWQ URL.""" diff --git a/tests/test_vws.py b/tests/test_vws.py index 25fe7227e..03b5818b3 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -4,10 +4,13 @@ import datetime import io import secrets +import time import uuid from typing import BinaryIO +from unittest.mock import patch import pytest +import requests from freezegun import freeze_time from mock_vws import MockVWS from mock_vws.database import VuforiaDatabase @@ -132,6 +135,92 @@ def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: application_metadata=None, ) + @staticmethod + def test_timeout_raises_on_slow_response( + image: io.BytesIO | BinaryIO, + ) -> None: + """A short timeout raises an error when the server is slow.""" + simulated_slow_threshold = 0.5 + + with MockVWS() as mock: + database = VuforiaDatabase() + 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, + ) + + original_request = requests.request + + def slow_request( + *args: object, + **kwargs: float | None, + ) -> requests.Response: + """Simulate a slow server response.""" + timeout = kwargs.get("timeout") + if timeout is not None and timeout < simulated_slow_threshold: + time.sleep(0.2) + raise requests.exceptions.Timeout + return original_request(*args, **kwargs) # type: ignore[arg-type] + + with ( + patch.object( + requests, + "request", + side_effect=slow_request, + ), + pytest.raises(requests.exceptions.Timeout), + ): + vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + + @staticmethod + def test_longer_timeout_succeeds(image: io.BytesIO | BinaryIO) -> None: + """A longer timeout allows slow responses to complete.""" + simulated_slow_threshold = 0.5 + + with MockVWS() as mock: + database = VuforiaDatabase() + 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=1.0, + ) + + original_request = requests.request + + def slow_request( + *args: object, + **kwargs: float | None, + ) -> requests.Response: + """Simulate a slow server response.""" + timeout = kwargs.get("timeout") + if timeout is not None and timeout < simulated_slow_threshold: + time.sleep(0.2) + raise requests.exceptions.Timeout + return original_request(*args, **kwargs) # type: ignore[arg-type] + + with patch.object( + requests, + "request", + side_effect=slow_request, + ): + # This should succeed because timeout is 1.0 > 0.5 + vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + class TestCustomBaseVWSURL: """Tests for using a custom base VWS URL.""" From 1a930ea8c1c3ac4e1ef130a26592a75815af28ac Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 2 Feb 2026 11:56:10 +0000 Subject: [PATCH 286/688] Fix mypy errors in timeout tests Use keyword arguments for patch.object and pytest.raises to satisfy mypy's type checking. Co-Authored-By: Claude Opus 4.5 --- tests/test_query.py | 10 +++++----- tests/test_vws.py | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_query.py b/tests/test_query.py index a3f4c0331..6e8247ab1 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -136,11 +136,11 @@ def slow_request( with ( patch.object( - requests, - "request", + target=requests, + attribute="request", side_effect=slow_request, ), - pytest.raises(requests.exceptions.Timeout), + pytest.raises(expected_exception=requests.exceptions.Timeout), ): cloud_reco_client.query(image=image) @@ -185,8 +185,8 @@ def slow_request( return original_request(*args, **kwargs) # type: ignore[arg-type] with patch.object( - requests, - "request", + target=requests, + attribute="request", side_effect=slow_request, ): # This should succeed because timeout is 1.0 > 0.5 diff --git a/tests/test_vws.py b/tests/test_vws.py index 03b5818b3..0f03a1ccf 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -166,11 +166,11 @@ def slow_request( with ( patch.object( - requests, - "request", + target=requests, + attribute="request", side_effect=slow_request, ), - pytest.raises(requests.exceptions.Timeout), + pytest.raises(expected_exception=requests.exceptions.Timeout), ): vws_client.add_target( name="x", @@ -208,8 +208,8 @@ def slow_request( return original_request(*args, **kwargs) # type: ignore[arg-type] with patch.object( - requests, - "request", + target=requests, + attribute="request", side_effect=slow_request, ): # This should succeed because timeout is 1.0 > 0.5 From a0b073dc1f3e3c6a77be8425fa65cac5a49b43df Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 2 Feb 2026 11:56:45 +0000 Subject: [PATCH 287/688] Add pyright ignore comments for type checking Co-Authored-By: Claude Opus 4.5 --- tests/test_query.py | 4 ++-- tests/test_vws.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_query.py b/tests/test_query.py index 6e8247ab1..85b2d03ab 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -132,7 +132,7 @@ def slow_request( if timeout is not None and timeout < simulated_slow_threshold: time.sleep(0.2) raise requests.exceptions.Timeout - return original_request(*args, **kwargs) # type: ignore[arg-type] + return original_request(*args, **kwargs) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with ( patch.object( @@ -182,7 +182,7 @@ def slow_request( if timeout is not None and timeout < simulated_slow_threshold: time.sleep(0.2) raise requests.exceptions.Timeout - return original_request(*args, **kwargs) # type: ignore[arg-type] + return original_request(*args, **kwargs) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with patch.object( target=requests, diff --git a/tests/test_vws.py b/tests/test_vws.py index 0f03a1ccf..bbf157b4b 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -162,7 +162,7 @@ def slow_request( if timeout is not None and timeout < simulated_slow_threshold: time.sleep(0.2) raise requests.exceptions.Timeout - return original_request(*args, **kwargs) # type: ignore[arg-type] + return original_request(*args, **kwargs) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with ( patch.object( @@ -205,7 +205,7 @@ def slow_request( if timeout is not None and timeout < simulated_slow_threshold: time.sleep(0.2) raise requests.exceptions.Timeout - return original_request(*args, **kwargs) # type: ignore[arg-type] + return original_request(*args, **kwargs) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with patch.object( target=requests, From 06af721e61a9c9c287d86af354479cf546e6c481 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 3 Feb 2026 09:42:35 +0000 Subject: [PATCH 288/688] Apply pyproject-fmt formatting changes --- pyproject.toml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b24fc8ef8..706eec1bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,16 +89,16 @@ urls.Source = "https://github.com/VWS-Python/vws-python" [tool.setuptools] zip-safe = false -[tool.setuptools.packages.find] -where = [ - "src", -] - [tool.setuptools.package-data] vws = [ "py.typed", ] +[tool.setuptools.packages.find] +where = [ + "src", +] + [tool.distutils.bdist_wheel] universal = true @@ -156,6 +156,12 @@ lint.pydocstyle.convention = "google" [tool.pylint] +[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.'MASTER'] # Pickle collected data for later comparisons. @@ -256,12 +262,6 @@ per-file-ignores = [ "doccmd_README_rst_*.py:invalid-name", ] -[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 @@ -319,15 +319,15 @@ max_supported_python = "3.14" xfail_strict = true log_cli = true -[tool.coverage.run] - -branch = true - [tool.coverage.report] exclude_also = [ "if TYPE_CHECKING:", ] +[tool.coverage.run] + +branch = true + [tool.mypy] strict = true From a0091087ce92a6121f720380d92b13d19b820e03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:35:06 +0000 Subject: [PATCH 289/688] 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 e082d0dad..2b8127c7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.12.1", - "pyrefly==0.50.1", + "pyrefly==0.51.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 62c1d7b547e48efa401afd6ea13e5fa1c4aa4516 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:33:23 +0000 Subject: [PATCH 290/688] 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 2b8127c7a..86a1c0567 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==9.0.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 669eeb63db602acce9403e55bd6377df19a7e9a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:33:25 +0000 Subject: [PATCH 291/688] 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 86a1c0567..a72a70214 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2026.1.12", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.14", + "ty==0.0.15", "types-requests==2.32.4.20260107", "vulture==2.14", "vws-python-mock==2025.3.10.1", From 5de56fc8482bbbf5299bdfe4055d1d29b1871b66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 10:33:19 +0000 Subject: [PATCH 292/688] 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 a72a70214..c8d5296f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.12.1", - "pyrefly==0.51.0", + "pyrefly==0.51.1", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From cb4b09565ee4de781443466d12239317830091f0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 8 Feb 2026 13:26:26 +0000 Subject: [PATCH 293/688] 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 | 74 +++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c8d5296f0..2d1e49c43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,7 +126,7 @@ lint.ignore = [ "D212", # 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", ] @@ -181,21 +181,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", ] # Allow loading of arbitrary C extensions. Extensions are imported into the @@ -209,12 +209,12 @@ unsafe-load-any-extension = false # 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 @@ -228,28 +228,28 @@ enable = [ # --disable=W" disable = [ - 'too-few-public-methods', - 'too-many-locals', - 'too-many-arguments', - 'too-many-instance-attributes', - 'too-many-return-statements', - 'too-many-lines', - 'locally-disabled', + "too-few-public-methods", + "too-many-locals", + "too-many-arguments", + "too-many-instance-attributes", + "too-many-return-statements", + "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", # mypy does not want untyped parameters. - 'useless-type-doc', + "useless-type-doc", ] # We ignore invalid names because: From 7721f7c3699de06751116058bc0967b5181f9348 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 8 Feb 2026 13:26:26 +0000 Subject: [PATCH 294/688] Bump pyproject-fmt to 2.14.0 and apply formatting Co-Authored-By: Claude Opus 4.6 --- pyproject.toml | 101 +++++++++++++++---------------------------------- 1 file changed, 31 insertions(+), 70 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2d1e49c43..47c38d627 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.12.1", + "pyproject-fmt==2.14.0", "pyrefly==0.51.1", "pyright==1.1.408", "pyroma==5.0.1", @@ -62,10 +62,6 @@ optional-dependencies.dev = [ "pytest-cov==7.0.0", "pyyaml==6.0.3", "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==9.1.0", "sphinx-copybutton==0.5.2", @@ -79,6 +75,10 @@ optional-dependencies.dev = [ "vulture==2.14", "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.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. + "shellcheck-py==0.11.0.1", "yamlfix==1.19.1", "zizmor==1.22.0", ] @@ -88,22 +88,17 @@ urls.Source = "https://github.com/VWS-Python/vws-python" [tool.setuptools] zip-safe = false - -[tool.setuptools.package-data] -vws = [ +package-data.vws = [ "py.typed", ] - -[tool.setuptools.packages.find] -where = [ +packages.find.where = [ "src", ] -[tool.distutils.bdist_wheel] -universal = true +[tool.distutils] +bdist_wheel.universal = true [tool.setuptools_scm] - # 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. @@ -113,39 +108,34 @@ version_scheme = "post-release" [tool.ruff] line-length = 79 - 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", - # 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", ] - lint.per-file-ignores."doccmd_*.py" = [ # Allow asserts in docs. "S101", ] - lint.per-file-ignores."docs/source/*.py" = [ # Allow asserts in docs. "S101", ] - lint.per-file-ignores."tests/*.py" = [ # Allow asserts in tests. "S101", ] - # Do not automatically remove commented out code. # We comment out code during development, and with VSCode auto-save, this code # is sometimes annoyingly removed. @@ -155,21 +145,13 @@ lint.unfixable = [ lint.pydocstyle.convention = "google" [tool.pylint] - -[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.'MASTER'] - +"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. @@ -179,7 +161,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", @@ -197,18 +179,14 @@ load-plugins = [ "pylint.extensions.set_membership", "pylint.extensions.typing", ] - # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. -unsafe-load-any-extension = false - -[tool.pylint.'MESSAGES CONTROL'] - +"MASTER".unsafe-load-any-extension = false # 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", @@ -216,7 +194,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 @@ -226,8 +203,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 = [ "too-few-public-methods", "too-many-locals", "too-many-arguments", @@ -251,32 +227,25 @@ disable = [ # mypy does not want untyped parameters. "useless-type-doc", ] - # 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. # - conf.py is a Sphinx configuration file which requires lowercase global variable names. -per-file-ignores = [ +"MESSAGES CONTROL".per-file-ignores = [ "docs/source/conf.py:invalid-name", "docs/source/doccmd_*.py:invalid-name", "doccmd_README_rst_*.py:invalid-name", ] - -[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", @@ -314,22 +283,17 @@ indent = 4 keep_full_version = true max_supported_python = "3.14" -[tool.pytest.ini_options] - -xfail_strict = true -log_cli = true +[tool.pytest] +ini_options.xfail_strict = true +ini_options.log_cli = true -[tool.coverage.report] -exclude_also = [ +[tool.coverage] +report.exclude_also = [ "if TYPE_CHECKING:", ] - -[tool.coverage.run] - -branch = true +run.branch = true [tool.mypy] - strict = true files = [ "." ] exclude = [ "build" ] @@ -339,7 +303,6 @@ plugins = [ ] [tool.pyright] - enableTypeIgnoreComments = false reportUnnecessaryTypeIgnoreComment = true typeCheckingMode = "strict" @@ -359,7 +322,6 @@ omit-covered-files = true verbose = 2 [tool.doc8] - max_line_length = 2000 ignore_path = [ "./.eggs", @@ -408,7 +370,6 @@ ignore_names = [ "templates_path", "warning_is_error", ] - # Duplicate some of .gitignore exclude = [ ".venv" ] From 28780e38e30338b74a5bed80b1221ff6157420ac Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 9 Feb 2026 10:47:25 +0000 Subject: [PATCH 295/688] Bump pyproject-fmt to 2.14.2 --- pyproject.toml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 47c38d627..5dd6a685a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "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.1", "pyright==1.1.408", "pyroma==5.0.1", @@ -62,6 +62,10 @@ optional-dependencies.dev = [ "pytest-cov==7.0.0", "pyyaml==6.0.3", "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==9.1.0", "sphinx-copybutton==0.5.2", @@ -75,10 +79,6 @@ optional-dependencies.dev = [ "vulture==2.14", "vws-python-mock==2025.3.10.1", "vws-test-fixtures==2023.3.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. - "shellcheck-py==0.11.0.1", "yamlfix==1.19.1", "zizmor==1.22.0", ] @@ -112,17 +112,17 @@ 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", + # 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", ] lint.per-file-ignores."doccmd_*.py" = [ # Allow asserts in docs. From a4478f35a67cf32cc7faab486e28ea8ec46e190e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 11:42:27 +0000 Subject: [PATCH 296/688] 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 5dd6a685a..bc7a0ec98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.14.2", - "pyrefly==0.51.1", + "pyrefly==0.51.2", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 8cc2e58df6658fbfc28eae7d51a3ca99ec7dcbc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 11:42:39 +0000 Subject: [PATCH 297/688] 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 5dd6a685a..6ba414452 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,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", "pygments==2.19.2", From baa97f9e121d261444064ddff394417b893fabd7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 10:33:11 +0000 Subject: [PATCH 298/688] 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 b9db7f6b8..32c854a40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,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 17d7ac377ea891feceebc753dabb3277dc8b0f82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 10:33:16 +0000 Subject: [PATCH 299/688] 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 b9db7f6b8..22287017f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.14.2", + "pyproject-fmt==2.15.1", "pyrefly==0.51.2", "pyright==1.1.408", "pyroma==5.0.1", From b5087fbd447928b2a598e8233265d002fcb21afb Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 10 Feb 2026 10:39:19 +0000 Subject: [PATCH 300/688] Re-format pyproject.toml with pyproject-fmt 2.15.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 22287017f..4046c5f44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -288,10 +288,10 @@ ini_options.xfail_strict = true ini_options.log_cli = true [tool.coverage] +run.branch = true report.exclude_also = [ "if TYPE_CHECKING:", ] -run.branch = true [tool.mypy] strict = true From 3264eea681160f63114499ce70de382d767a8b56 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 11 Feb 2026 09:14:23 +0000 Subject: [PATCH 301/688] Update pyproject-fmt to 2.15.2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2919be653..9100eb182 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "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 72eaa04ad32c25aadb89d2ccd0fb2447c6577733 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 10:33:32 +0000 Subject: [PATCH 302/688] 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 9100eb182..126fb24d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2026.1.12", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.15", + "ty==0.0.16", "types-requests==2.32.4.20260107", "vulture==2.14", "vws-python-mock==2025.3.10.1", From 75edda24ae41cbd83f611545584c1aef72a2d7f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:33:20 +0000 Subject: [PATCH 303/688] 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 126fb24d2..c699fdc14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "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 f4e3dc1f7525ca75b47b31428bb47af24d9be950 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 10:33:20 +0000 Subject: [PATCH 304/688] 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 c699fdc14..54eb5f3fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "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 615d5798a840f19f968930f0651f32ea7f310f85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 10:33:40 +0000 Subject: [PATCH 305/688] 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 c699fdc14..1e7fdedf0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==9.0.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 8217323d13e44f8fa65f5a7ffe30288feb47a036 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 07:59:41 +0000 Subject: [PATCH 306/688] Accept tuple for request_timeout_seconds to match requests API The timeout parameter now accepts either a float (applied to both connect and read timeouts) or a (connect, read) tuple, matching the requests library's timeout interface. Updated docstrings to reference requests. Co-Authored-By: Claude Opus 4.6 --- src/vws/query.py | 8 +++++--- src/vws/vws.py | 14 +++++++++----- tests/test_query.py | 31 ++++++++++++++++++++++++++++++- tests/test_vws.py | 24 +++++++++++++++++++++++- 4 files changed, 67 insertions(+), 10 deletions(-) diff --git a/src/vws/query.py b/src/vws/query.py index b9d42d4c8..0e4e33d04 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -49,15 +49,17 @@ def __init__( client_access_key: str, client_secret_key: str, base_vwq_url: str = "https://cloudreco.vuforia.com", - request_timeout_seconds: float = 30.0, + request_timeout_seconds: float | tuple[float, float] = 30.0, ) -> None: """ Args: client_access_key: A VWS client access key. client_secret_key: A VWS client secret key. base_vwq_url: The base URL for the VWQ API. - request_timeout_seconds: The timeout in seconds for each HTTP - request made to the Cloud Reco API. + request_timeout_seconds: The timeout for each HTTP request, as + used by ``requests.request``. This can be a float to set + both the connect and read timeouts, or a (connect, read) + tuple. """ self._client_access_key = client_access_key self._client_secret_key = client_secret_key diff --git a/src/vws/vws.py b/src/vws/vws.py index 82eb7c5bc..dfb868cff 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -68,7 +68,7 @@ def _target_api_request( data: bytes, request_path: str, base_vws_url: str, - request_timeout_seconds: float, + request_timeout_seconds: float | tuple[float, float], ) -> Response: """Make a request to the Vuforia Target API. @@ -83,7 +83,9 @@ def _target_api_request( request_path: The path to the endpoint which will be used in the request. base_vws_url: The base URL for the VWS API. - request_timeout_seconds: The timeout in seconds for the request. + request_timeout_seconds: The timeout for the request, as used by + ``requests.request``. This can be a float to set both the + connect and read timeouts, or a (connect, read) tuple. Returns: The response to the request made by `requests`. @@ -135,15 +137,17 @@ def __init__( server_access_key: str, server_secret_key: str, base_vws_url: str = "https://vws.vuforia.com", - request_timeout_seconds: float = 30.0, + request_timeout_seconds: float | tuple[float, float] = 30.0, ) -> None: """ Args: server_access_key: A VWS server access key. server_secret_key: A VWS server secret key. base_vws_url: The base URL for the VWS API. - request_timeout_seconds: The timeout in seconds for each HTTP - request made to the VWS API. + request_timeout_seconds: The timeout for each HTTP request, as + used by ``requests.request``. This can be a float to set + both the connect and read timeouts, or a (connect, read) + tuple. """ self._server_access_key = server_access_key self._server_secret_key = server_secret_key diff --git a/tests/test_query.py b/tests/test_query.py index 85b2d03ab..d1e13f2cf 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -65,7 +65,7 @@ def test_default_timeout() -> None: @staticmethod def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: - """It is possible to set a custom request timeout.""" + """It is possible to set a custom request timeout as a float.""" with MockVWS() as mock: database = VuforiaDatabase() mock.add_database(database=database) @@ -93,6 +93,35 @@ def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: matches = cloud_reco_client.query(image=image) assert len(matches) == 1 + @staticmethod + def test_custom_timeout_tuple(image: io.BytesIO | BinaryIO) -> None: + """It is possible to set separate connect and read timeouts.""" + with MockVWS() as mock: + database = VuforiaDatabase() + mock.add_database(database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + custom_timeout = (5.0, 30.0) + cloud_reco_client = CloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + request_timeout_seconds=custom_timeout, + ) + assert cloud_reco_client.request_timeout_seconds == custom_timeout + + target_id = vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + vws_client.wait_for_target_processed(target_id=target_id) + matches = cloud_reco_client.query(image=image) + assert len(matches) == 1 + @staticmethod def test_timeout_raises_on_slow_response( image: io.BytesIO | BinaryIO, diff --git a/tests/test_vws.py b/tests/test_vws.py index bbf157b4b..2350ca365 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -114,7 +114,7 @@ def test_default_timeout() -> None: @staticmethod def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: - """It is possible to set a custom request timeout.""" + """It is possible to set a custom request timeout as a float.""" with MockVWS() as mock: database = VuforiaDatabase() mock.add_database(database=database) @@ -135,6 +135,28 @@ def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: application_metadata=None, ) + @staticmethod + def test_custom_timeout_tuple(image: io.BytesIO | BinaryIO) -> None: + """It is possible to set separate connect and read timeouts.""" + with MockVWS() as mock: + database = VuforiaDatabase() + mock.add_database(database=database) + custom_timeout = (5.0, 30.0) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + request_timeout_seconds=custom_timeout, + ) + assert vws_client.request_timeout_seconds == custom_timeout + + vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + @staticmethod def test_timeout_raises_on_slow_response( image: io.BytesIO | BinaryIO, From 1b2001ecdf413e236a9ab625df3f4a23324f7eee Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 08:05:28 +0000 Subject: [PATCH 307/688] Fix integer timeouts failing at request time _target_api_request used plain @beartype which rejects int for float, while VWS.__init__ used is_pep484_tower=True which accepts it. This caused integer timeouts to pass construction but fail on first request. Enable is_pep484_tower on _target_api_request and CloudRecoService too. Co-Authored-By: Claude Opus 4.6 --- src/vws/query.py | 4 ++-- src/vws/vws.py | 2 +- tests/test_query.py | 29 +++++++++++++++++++++++++++++ tests/test_vws.py | 22 ++++++++++++++++++++++ 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/vws/query.py b/src/vws/query.py index 0e4e33d04..db1945b11 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -8,7 +8,7 @@ from urllib.parse import urljoin import requests -from beartype import beartype +from beartype import BeartypeConf, beartype from urllib3.filepost import encode_multipart_formdata from vws_auth_tools import authorization_header, rfc_1123_date @@ -40,7 +40,7 @@ def _get_image_data(image: _ImageType) -> bytes: return image_data -@beartype +@beartype(conf=BeartypeConf(is_pep484_tower=True)) class CloudRecoService: """An interface to the Vuforia Cloud Recognition Web APIs.""" diff --git a/src/vws/vws.py b/src/vws/vws.py index dfb868cff..cdff3f906 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -58,7 +58,7 @@ def _get_image_data(image: _ImageType) -> bytes: return image_data -@beartype +@beartype(conf=BeartypeConf(is_pep484_tower=True)) def _target_api_request( *, content_type: str, diff --git a/tests/test_query.py b/tests/test_query.py index d1e13f2cf..5516a0ea4 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -93,6 +93,35 @@ def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: matches = cloud_reco_client.query(image=image) assert len(matches) == 1 + @staticmethod + def test_custom_timeout_int(image: io.BytesIO | BinaryIO) -> None: + """It is possible to set a custom request timeout as an int.""" + with MockVWS() as mock: + database = VuforiaDatabase() + mock.add_database(database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + custom_timeout = 60 + cloud_reco_client = CloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + request_timeout_seconds=custom_timeout, + ) + assert cloud_reco_client.request_timeout_seconds == custom_timeout + + target_id = vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + vws_client.wait_for_target_processed(target_id=target_id) + matches = cloud_reco_client.query(image=image) + assert len(matches) == 1 + @staticmethod def test_custom_timeout_tuple(image: io.BytesIO | BinaryIO) -> None: """It is possible to set separate connect and read timeouts.""" diff --git a/tests/test_vws.py b/tests/test_vws.py index 2350ca365..db3b88b5d 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -135,6 +135,28 @@ def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: application_metadata=None, ) + @staticmethod + def test_custom_timeout_int(image: io.BytesIO | BinaryIO) -> None: + """It is possible to set a custom request timeout as an int.""" + with MockVWS() as mock: + database = VuforiaDatabase() + mock.add_database(database=database) + custom_timeout = 60 + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + request_timeout_seconds=custom_timeout, + ) + assert vws_client.request_timeout_seconds == custom_timeout + + vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + @staticmethod def test_custom_timeout_tuple(image: io.BytesIO | BinaryIO) -> None: """It is possible to set separate connect and read timeouts.""" From 7123758816b61dce6b9f132d8625c8a9f31f0266 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 08:18:06 +0000 Subject: [PATCH 308/688] Fix CI failures in timeout tests - Remove branching and time.sleep from slow_request mocks, fixing coverage gaps (partial branches) and Windows CI timeouts - Remove test_longer_timeout_succeeds (redundant with test_custom_timeout) - Assert the timeout value is passed correctly instead of branching - Remove unused time import and unused *args parameter Co-Authored-By: Claude Opus 4.6 --- tests/test_query.py | 66 ++++----------------------------------------- tests/test_vws.py | 59 ++++------------------------------------ 2 files changed, 10 insertions(+), 115 deletions(-) diff --git a/tests/test_query.py b/tests/test_query.py index 5516a0ea4..43af56711 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,7 +1,6 @@ """Tests for the ``CloudRecoService`` querying functionality.""" import io -import time import uuid from typing import BinaryIO from unittest.mock import patch @@ -156,6 +155,7 @@ def test_timeout_raises_on_slow_response( image: io.BytesIO | BinaryIO, ) -> None: """A short timeout raises an error when the server is slow.""" + custom_timeout = 0.1 with MockVWS() as mock: database = VuforiaDatabase() mock.add_database(database=database) @@ -166,7 +166,7 @@ def test_timeout_raises_on_slow_response( cloud_reco_client = CloudRecoService( client_access_key=database.client_access_key, client_secret_key=database.client_secret_key, - request_timeout_seconds=0.1, + request_timeout_seconds=custom_timeout, ) target_id = vws_client.add_target( @@ -178,19 +178,12 @@ def test_timeout_raises_on_slow_response( ) vws_client.wait_for_target_processed(target_id=target_id) - simulated_slow_threshold = 0.5 - original_request = requests.request - def slow_request( - *args: object, **kwargs: float | None, ) -> requests.Response: - """Simulate a slow server response.""" - timeout = kwargs.get("timeout") - if timeout is not None and timeout < simulated_slow_threshold: - time.sleep(0.2) - raise requests.exceptions.Timeout - return original_request(*args, **kwargs) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + """Simulate a server that is too slow to respond.""" + assert kwargs["timeout"] == custom_timeout + raise requests.exceptions.Timeout with ( patch.object( @@ -202,55 +195,6 @@ def slow_request( ): cloud_reco_client.query(image=image) - @staticmethod - def test_longer_timeout_succeeds(image: io.BytesIO | BinaryIO) -> None: - """A longer timeout allows slow responses to complete.""" - simulated_slow_threshold = 0.5 - - with MockVWS() as mock: - database = VuforiaDatabase() - mock.add_database(database=database) - vws_client = VWS( - server_access_key=database.server_access_key, - server_secret_key=database.server_secret_key, - ) - cloud_reco_client = CloudRecoService( - client_access_key=database.client_access_key, - client_secret_key=database.client_secret_key, - request_timeout_seconds=1.0, - ) - - target_id = vws_client.add_target( - name="x", - width=1, - image=image, - active_flag=True, - application_metadata=None, - ) - vws_client.wait_for_target_processed(target_id=target_id) - - original_request = requests.request - - def slow_request( - *args: object, - **kwargs: float | None, - ) -> requests.Response: - """Simulate a slow server response.""" - timeout = kwargs.get("timeout") - if timeout is not None and timeout < simulated_slow_threshold: - time.sleep(0.2) - raise requests.exceptions.Timeout - return original_request(*args, **kwargs) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] - - with patch.object( - target=requests, - attribute="request", - side_effect=slow_request, - ): - # This should succeed because timeout is 1.0 > 0.5 - matches = cloud_reco_client.query(image=image) - assert len(matches) == 1 - class TestCustomBaseVWQURL: """Tests for using a custom base VWQ URL.""" diff --git a/tests/test_vws.py b/tests/test_vws.py index db3b88b5d..e141b041c 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -4,7 +4,6 @@ import datetime import io import secrets -import time import uuid from typing import BinaryIO from unittest.mock import patch @@ -184,29 +183,22 @@ def test_timeout_raises_on_slow_response( image: io.BytesIO | BinaryIO, ) -> None: """A short timeout raises an error when the server is slow.""" - simulated_slow_threshold = 0.5 - + custom_timeout = 0.1 with MockVWS() as mock: database = VuforiaDatabase() 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, + request_timeout_seconds=custom_timeout, ) - original_request = requests.request - def slow_request( - *args: object, **kwargs: float | None, ) -> requests.Response: - """Simulate a slow server response.""" - timeout = kwargs.get("timeout") - if timeout is not None and timeout < simulated_slow_threshold: - time.sleep(0.2) - raise requests.exceptions.Timeout - return original_request(*args, **kwargs) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + """Simulate a server that is too slow to respond.""" + assert kwargs["timeout"] == custom_timeout + raise requests.exceptions.Timeout with ( patch.object( @@ -224,47 +216,6 @@ def slow_request( application_metadata=None, ) - @staticmethod - def test_longer_timeout_succeeds(image: io.BytesIO | BinaryIO) -> None: - """A longer timeout allows slow responses to complete.""" - simulated_slow_threshold = 0.5 - - with MockVWS() as mock: - database = VuforiaDatabase() - 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=1.0, - ) - - original_request = requests.request - - def slow_request( - *args: object, - **kwargs: float | None, - ) -> requests.Response: - """Simulate a slow server response.""" - timeout = kwargs.get("timeout") - if timeout is not None and timeout < simulated_slow_threshold: - time.sleep(0.2) - raise requests.exceptions.Timeout - return original_request(*args, **kwargs) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] - - with patch.object( - target=requests, - attribute="request", - side_effect=slow_request, - ): - # This should succeed because timeout is 1.0 > 0.5 - vws_client.add_target( - name="x", - width=1, - image=image, - active_flag=True, - application_metadata=None, - ) - class TestCustomBaseVWSURL: """Tests for using a custom base VWS URL.""" From 74763f3b207b4e6c206b55bf56907d7eefcb2e43 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 08:25:35 +0000 Subject: [PATCH 309/688] Matrix lint workflow by hook stage --- .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 f4c94be57..c6f0c8074 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,6 +19,7 @@ jobs: matrix: python-version: ['3.13'] platform: [ubuntu-latest, windows-latest] + hook-stage: [pre-commit, pre-push, manual] runs-on: ${{ matrix.platform }} @@ -37,10 +38,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 16c4604eb208167feeffe1ebfdcc8dd30e2bc2b8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 08:55:52 +0000 Subject: [PATCH 310/688] Bump vws-python-mock from 2025.3.10.1 to 2026.2.15 Co-Authored-By: Claude Opus 4.6 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 15ca13f24..2b0fb465f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ optional-dependencies.dev = [ "ty==0.0.16", "types-requests==2.32.4.20260107", "vulture==2.14", - "vws-python-mock==2025.3.10.1", + "vws-python-mock==2026.2.15", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.22.0", From 5d0a72941fdde8e783e56d1ff9e3281ffa785c73 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 08:59:13 +0000 Subject: [PATCH 311/688] Use vws-python-mock response_delay_seconds for timeout tests Upgrade vws-python-mock to 2026.2.15 which adds response_delay_seconds parameter. Replace unittest.mock.patch-based timeout simulation with MockVWS(response_delay_seconds=0.5), testing the real timeout mechanism end-to-end. Co-Authored-By: Claude Opus 4.6 --- pyproject.toml | 2 +- tests/test_query.py | 22 +++------------------- tests/test_vws.py | 22 +++------------------- 3 files changed, 7 insertions(+), 39 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9f1747298..68f237322 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ optional-dependencies.dev = [ "ty==0.0.14", "types-requests==2.32.4.20260107", "vulture==2.14", - "vws-python-mock==2025.3.10.1", + "vws-python-mock==2026.2.15", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.22.0", diff --git a/tests/test_query.py b/tests/test_query.py index 43af56711..29a9b3b4e 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -3,7 +3,6 @@ import io import uuid from typing import BinaryIO -from unittest.mock import patch import pytest import requests @@ -155,8 +154,7 @@ def test_timeout_raises_on_slow_response( image: io.BytesIO | BinaryIO, ) -> None: """A short timeout raises an error when the server is slow.""" - custom_timeout = 0.1 - with MockVWS() as mock: + with MockVWS(response_delay_seconds=0.5) as mock: database = VuforiaDatabase() mock.add_database(database=database) vws_client = VWS( @@ -166,7 +164,7 @@ def test_timeout_raises_on_slow_response( cloud_reco_client = CloudRecoService( client_access_key=database.client_access_key, client_secret_key=database.client_secret_key, - request_timeout_seconds=custom_timeout, + request_timeout_seconds=0.1, ) target_id = vws_client.add_target( @@ -178,21 +176,7 @@ def test_timeout_raises_on_slow_response( ) vws_client.wait_for_target_processed(target_id=target_id) - def slow_request( - **kwargs: float | None, - ) -> requests.Response: - """Simulate a server that is too slow to respond.""" - assert kwargs["timeout"] == custom_timeout - raise requests.exceptions.Timeout - - with ( - patch.object( - target=requests, - attribute="request", - side_effect=slow_request, - ), - pytest.raises(expected_exception=requests.exceptions.Timeout), - ): + with pytest.raises(expected_exception=requests.exceptions.Timeout): cloud_reco_client.query(image=image) diff --git a/tests/test_vws.py b/tests/test_vws.py index e141b041c..5d2a58f89 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -6,7 +6,6 @@ import secrets import uuid from typing import BinaryIO -from unittest.mock import patch import pytest import requests @@ -183,31 +182,16 @@ def test_timeout_raises_on_slow_response( image: io.BytesIO | BinaryIO, ) -> None: """A short timeout raises an error when the server is slow.""" - custom_timeout = 0.1 - with MockVWS() as mock: + with MockVWS(response_delay_seconds=0.5) as mock: database = VuforiaDatabase() 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=custom_timeout, + request_timeout_seconds=0.1, ) - def slow_request( - **kwargs: float | None, - ) -> requests.Response: - """Simulate a server that is too slow to respond.""" - assert kwargs["timeout"] == custom_timeout - raise requests.exceptions.Timeout - - with ( - patch.object( - target=requests, - attribute="request", - side_effect=slow_request, - ), - pytest.raises(expected_exception=requests.exceptions.Timeout), - ): + with pytest.raises(expected_exception=requests.exceptions.Timeout): vws_client.add_target( name="x", width=1, From 90c8285c54c3f74bc161d2a46437b088fa529970 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 09:01:21 +0000 Subject: [PATCH 312/688] Add changelog entry for custom request timeout support Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5527f9da1..174256880 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,8 @@ Changelog Next ---- +* Add ``request_timeout_seconds`` parameter to ``VWS`` and ``CloudRecoService``, allowing customization of the request timeout. This accepts a float or a ``(connect, read)`` tuple, matching the ``requests`` library's timeout interface. The default remains 30 seconds. + 2025.03.10.1 ------------ From 88e1d775cf54e3d65280334bfbd4b3565de7b76e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 09:12:18 +0000 Subject: [PATCH 313/688] Parametrize custom timeout tests Co-Authored-By: Claude Opus 4.6 --- tests/test_query.py | 74 +++++++-------------------------------------- tests/test_vws.py | 60 +++++++----------------------------- 2 files changed, 22 insertions(+), 112 deletions(-) diff --git a/tests/test_query.py b/tests/test_query.py index 29a9b3b4e..dafe5a738 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -62,67 +62,15 @@ def test_default_timeout() -> None: assert cloud_reco_client.request_timeout_seconds == expected @staticmethod - def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: - """It is possible to set a custom request timeout as a float.""" - with MockVWS() as mock: - database = VuforiaDatabase() - mock.add_database(database=database) - vws_client = VWS( - server_access_key=database.server_access_key, - server_secret_key=database.server_secret_key, - ) - custom_timeout = 60.5 - cloud_reco_client = CloudRecoService( - client_access_key=database.client_access_key, - client_secret_key=database.client_secret_key, - request_timeout_seconds=custom_timeout, - ) - assert cloud_reco_client.request_timeout_seconds == custom_timeout - - # Verify requests work with the custom timeout - target_id = vws_client.add_target( - name="x", - width=1, - image=image, - active_flag=True, - application_metadata=None, - ) - vws_client.wait_for_target_processed(target_id=target_id) - matches = cloud_reco_client.query(image=image) - assert len(matches) == 1 - - @staticmethod - def test_custom_timeout_int(image: io.BytesIO | BinaryIO) -> None: - """It is possible to set a custom request timeout as an int.""" - with MockVWS() as mock: - database = VuforiaDatabase() - mock.add_database(database=database) - vws_client = VWS( - server_access_key=database.server_access_key, - server_secret_key=database.server_secret_key, - ) - custom_timeout = 60 - cloud_reco_client = CloudRecoService( - client_access_key=database.client_access_key, - client_secret_key=database.client_secret_key, - request_timeout_seconds=custom_timeout, - ) - assert cloud_reco_client.request_timeout_seconds == custom_timeout - - target_id = vws_client.add_target( - name="x", - width=1, - image=image, - active_flag=True, - application_metadata=None, - ) - vws_client.wait_for_target_processed(target_id=target_id) - matches = cloud_reco_client.query(image=image) - assert len(matches) == 1 - - @staticmethod - def test_custom_timeout_tuple(image: io.BytesIO | BinaryIO) -> None: - """It is possible to set separate connect and read timeouts.""" + @pytest.mark.parametrize( + argnames="custom_timeout", + argvalues=[60.5, 60, (5.0, 30.0)], + ) + def test_custom_timeout( + image: io.BytesIO | BinaryIO, + custom_timeout: int | float | tuple[float, float], # noqa: PYI041 + ) -> None: + """It is possible to set a custom request timeout.""" with MockVWS() as mock: database = VuforiaDatabase() mock.add_database(database=database) @@ -130,13 +78,13 @@ def test_custom_timeout_tuple(image: io.BytesIO | BinaryIO) -> None: server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, ) - custom_timeout = (5.0, 30.0) cloud_reco_client = CloudRecoService( client_access_key=database.client_access_key, client_secret_key=database.client_secret_key, request_timeout_seconds=custom_timeout, ) - assert cloud_reco_client.request_timeout_seconds == custom_timeout + expected = custom_timeout + assert cloud_reco_client.request_timeout_seconds == expected target_id = vws_client.add_target( name="x", diff --git a/tests/test_vws.py b/tests/test_vws.py index 5d2a58f89..9fe63dc21 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -111,63 +111,25 @@ def test_default_timeout() -> None: assert vws_client.request_timeout_seconds == expected @staticmethod - def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: - """It is possible to set a custom request timeout as a float.""" - with MockVWS() as mock: - database = VuforiaDatabase() - mock.add_database(database=database) - custom_timeout = 60.5 - vws_client = VWS( - server_access_key=database.server_access_key, - server_secret_key=database.server_secret_key, - request_timeout_seconds=custom_timeout, - ) - assert vws_client.request_timeout_seconds == custom_timeout - - # Verify requests work with the custom timeout - vws_client.add_target( - name="x", - width=1, - image=image, - active_flag=True, - application_metadata=None, - ) - - @staticmethod - def test_custom_timeout_int(image: io.BytesIO | BinaryIO) -> None: - """It is possible to set a custom request timeout as an int.""" - with MockVWS() as mock: - database = VuforiaDatabase() - mock.add_database(database=database) - custom_timeout = 60 - vws_client = VWS( - server_access_key=database.server_access_key, - server_secret_key=database.server_secret_key, - request_timeout_seconds=custom_timeout, - ) - assert vws_client.request_timeout_seconds == custom_timeout - - vws_client.add_target( - name="x", - width=1, - image=image, - active_flag=True, - application_metadata=None, - ) - - @staticmethod - def test_custom_timeout_tuple(image: io.BytesIO | BinaryIO) -> None: - """It is possible to set separate connect and read timeouts.""" + @pytest.mark.parametrize( + argnames="custom_timeout", + argvalues=[60.5, 60, (5.0, 30.0)], + ) + def test_custom_timeout( + image: io.BytesIO | BinaryIO, + custom_timeout: int | float | tuple[float, float], # noqa: PYI041 + ) -> None: + """It is possible to set a custom request timeout.""" with MockVWS() as mock: database = VuforiaDatabase() mock.add_database(database=database) - custom_timeout = (5.0, 30.0) vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, request_timeout_seconds=custom_timeout, ) - assert vws_client.request_timeout_seconds == custom_timeout + expected = custom_timeout + assert vws_client.request_timeout_seconds == expected vws_client.add_target( name="x", From 605d27c4c81ca670e23b25ff2bcdded8cf20db14 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 10:42:29 +0000 Subject: [PATCH 314/688] Update timeout behavior and default-timeout tests --- src/vws/query.py | 4 ++-- src/vws/vws.py | 4 ++-- tests/test_query.py | 33 +++++++++++++++++++++++++++------ tests/test_vws.py | 43 +++++++++++++++++++++++++++++++++++++------ 4 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/vws/query.py b/src/vws/query.py index db1945b11..dd36e8f21 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -64,7 +64,7 @@ def __init__( self._client_access_key = client_access_key self._client_secret_key = client_secret_key self._base_vwq_url = base_vwq_url - self.request_timeout_seconds = request_timeout_seconds + self._request_timeout_seconds = request_timeout_seconds def query( self, @@ -147,7 +147,7 @@ def query( url=urljoin(base=self._base_vwq_url, url=request_path), headers=headers, data=content, - timeout=self.request_timeout_seconds, + timeout=self._request_timeout_seconds, ) response = Response( text=requests_response.text, diff --git a/src/vws/vws.py b/src/vws/vws.py index cdff3f906..4cb75ca32 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -152,7 +152,7 @@ def __init__( self._server_access_key = server_access_key self._server_secret_key = server_secret_key self._base_vws_url = base_vws_url - self.request_timeout_seconds = request_timeout_seconds + self._request_timeout_seconds = request_timeout_seconds def make_request( self, @@ -196,7 +196,7 @@ def make_request( data=data, request_path=request_path, base_vws_url=self._base_vws_url, - request_timeout_seconds=self.request_timeout_seconds, + request_timeout_seconds=self._request_timeout_seconds, ) if ( diff --git a/tests/test_query.py b/tests/test_query.py index dafe5a738..fd014cadf 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -6,6 +6,7 @@ import pytest import requests +from freezegun import freeze_time from mock_vws import MockVWS from mock_vws.database import VuforiaDatabase @@ -48,18 +49,38 @@ class TestCustomRequestTimeout: """Tests for using a custom request timeout.""" @staticmethod - def test_default_timeout() -> None: - """By default, the request timeout is 30 seconds.""" - default_timeout_seconds = 30.0 - with MockVWS() as mock: + @pytest.mark.parametrize( + argnames=("response_delay_seconds", "expect_timeout"), + argvalues=[(29, False), (31, True)], + ) + def test_default_timeout( + image: io.BytesIO | BinaryIO, + *, + response_delay_seconds: int, + expect_timeout: bool, + ) -> None: + """At 29 seconds there is no error; at 31 seconds there is a + timeout. + """ + with ( + freeze_time(auto_tick_seconds=1), + MockVWS(response_delay_seconds=response_delay_seconds) as mock, + ): database = VuforiaDatabase() mock.add_database(database=database) cloud_reco_client = CloudRecoService( client_access_key=database.client_access_key, client_secret_key=database.client_secret_key, ) - expected = default_timeout_seconds - assert cloud_reco_client.request_timeout_seconds == expected + + if expect_timeout: + with pytest.raises( + expected_exception=requests.exceptions.Timeout, + ): + cloud_reco_client.query(image=image) + else: + matches = cloud_reco_client.query(image=image) + assert matches == [] @staticmethod @pytest.mark.parametrize( diff --git a/tests/test_vws.py b/tests/test_vws.py index 9fe63dc21..03dc796c9 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -97,18 +97,49 @@ class TestCustomRequestTimeout: """Tests for using a custom request timeout.""" @staticmethod - def test_default_timeout() -> None: - """By default, the request timeout is 30 seconds.""" - default_timeout_seconds = 30.0 - with MockVWS() as mock: + @pytest.mark.parametrize( + argnames=("response_delay_seconds", "expect_timeout"), + argvalues=[(29, False), (31, True)], + ) + def test_default_timeout( + image: io.BytesIO | BinaryIO, + *, + response_delay_seconds: int, + expect_timeout: bool, + ) -> None: + """At 29 seconds there is no error; at 31 seconds there is a + timeout. + """ + with ( + freeze_time(auto_tick_seconds=1), + MockVWS(response_delay_seconds=response_delay_seconds) as mock, + ): database = VuforiaDatabase() mock.add_database(database=database) vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, ) - expected = default_timeout_seconds - assert vws_client.request_timeout_seconds == expected + + if expect_timeout: + with pytest.raises( + expected_exception=requests.exceptions.Timeout, + ): + vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + else: + vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) @staticmethod @pytest.mark.parametrize( From 2af9315334564b8685a13d3180bd67682ed258dd Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 10:43:05 +0000 Subject: [PATCH 315/688] Remove mypy-invalid timeout attribute assertions --- tests/test_query.py | 2 -- tests/test_vws.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/tests/test_query.py b/tests/test_query.py index fd014cadf..32c07989b 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -104,8 +104,6 @@ def test_custom_timeout( client_secret_key=database.client_secret_key, request_timeout_seconds=custom_timeout, ) - expected = custom_timeout - assert cloud_reco_client.request_timeout_seconds == expected target_id = vws_client.add_target( name="x", diff --git a/tests/test_vws.py b/tests/test_vws.py index 03dc796c9..bb71a0236 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -159,8 +159,6 @@ def test_custom_timeout( server_secret_key=database.server_secret_key, request_timeout_seconds=custom_timeout, ) - expected = custom_timeout - assert vws_client.request_timeout_seconds == expected vws_client.add_target( name="x", From 1c987da7c138b886d92fd49a50173338cc3eb422 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 10:48:13 +0000 Subject: [PATCH 316/688] Fix pylint empty-list comparison in timeout test --- tests/test_query.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_query.py b/tests/test_query.py index 32c07989b..3c78e7f77 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -80,16 +80,16 @@ def test_default_timeout( cloud_reco_client.query(image=image) else: matches = cloud_reco_client.query(image=image) - assert matches == [] + assert not matches @staticmethod @pytest.mark.parametrize( argnames="custom_timeout", - argvalues=[60.5, 60, (5.0, 30.0)], + argvalues=[60.5, 60.0, (5.0, 30.0)], ) def test_custom_timeout( image: io.BytesIO | BinaryIO, - custom_timeout: int | float | tuple[float, float], # noqa: PYI041 + custom_timeout: float | tuple[float, float], ) -> None: """It is possible to set a custom request timeout.""" with MockVWS() as mock: From 80c214b77f2ebaee417c45b63ef974ce420cdf95 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 10:51:47 +0000 Subject: [PATCH 317/688] Align timeout test parameter types --- tests/test_vws.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_vws.py b/tests/test_vws.py index bb71a0236..be4dd7a7e 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -144,11 +144,11 @@ def test_default_timeout( @staticmethod @pytest.mark.parametrize( argnames="custom_timeout", - argvalues=[60.5, 60, (5.0, 30.0)], + argvalues=[60.5, 60.0, (5.0, 30.0)], ) def test_custom_timeout( image: io.BytesIO | BinaryIO, - custom_timeout: int | float | tuple[float, float], # noqa: PYI041 + custom_timeout: float | tuple[float, float], ) -> None: """It is possible to set a custom request timeout.""" with MockVWS() as mock: From bdaacd891b766e5a5fafd3cef2c9e4b22e4f2ce8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 11:59:12 +0000 Subject: [PATCH 318/688] Bump mock-vws and use sleep_fn in timeout tests --- pyproject.toml | 2 +- tests/test_query.py | 44 ++++++++------------------------------------ tests/test_vws.py | 36 +++++++----------------------------- 3 files changed, 16 insertions(+), 66 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2b0fb465f..2f5a29b78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ optional-dependencies.dev = [ "ty==0.0.16", "types-requests==2.32.4.20260107", "vulture==2.14", - "vws-python-mock==2026.2.15", + "vws-python-mock==2026.2.15.4", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.22.0", diff --git a/tests/test_query.py b/tests/test_query.py index 3c78e7f77..4e7ccb86d 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,5 +1,6 @@ """Tests for the ``CloudRecoService`` querying functionality.""" +import datetime import io import uuid from typing import BinaryIO @@ -63,8 +64,13 @@ def test_default_timeout( timeout. """ with ( - freeze_time(auto_tick_seconds=1), - MockVWS(response_delay_seconds=response_delay_seconds) as mock, + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=response_delay_seconds, + sleep_fn=lambda seconds: frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + ) as mock, ): database = VuforiaDatabase() mock.add_database(database=database) @@ -82,40 +88,6 @@ def test_default_timeout( matches = cloud_reco_client.query(image=image) assert not matches - @staticmethod - @pytest.mark.parametrize( - argnames="custom_timeout", - argvalues=[60.5, 60.0, (5.0, 30.0)], - ) - def test_custom_timeout( - image: io.BytesIO | BinaryIO, - custom_timeout: float | tuple[float, float], - ) -> None: - """It is possible to set a custom request timeout.""" - with MockVWS() as mock: - database = VuforiaDatabase() - mock.add_database(database=database) - vws_client = VWS( - server_access_key=database.server_access_key, - server_secret_key=database.server_secret_key, - ) - cloud_reco_client = CloudRecoService( - client_access_key=database.client_access_key, - client_secret_key=database.client_secret_key, - request_timeout_seconds=custom_timeout, - ) - - target_id = vws_client.add_target( - name="x", - width=1, - image=image, - active_flag=True, - application_metadata=None, - ) - vws_client.wait_for_target_processed(target_id=target_id) - matches = cloud_reco_client.query(image=image) - assert len(matches) == 1 - @staticmethod def test_timeout_raises_on_slow_response( image: io.BytesIO | BinaryIO, diff --git a/tests/test_vws.py b/tests/test_vws.py index be4dd7a7e..e93c35063 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -111,8 +111,13 @@ def test_default_timeout( timeout. """ with ( - freeze_time(auto_tick_seconds=1), - MockVWS(response_delay_seconds=response_delay_seconds) as mock, + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=response_delay_seconds, + sleep_fn=lambda seconds: frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + ) as mock, ): database = VuforiaDatabase() mock.add_database(database=database) @@ -141,33 +146,6 @@ def test_default_timeout( application_metadata=None, ) - @staticmethod - @pytest.mark.parametrize( - argnames="custom_timeout", - argvalues=[60.5, 60.0, (5.0, 30.0)], - ) - def test_custom_timeout( - image: io.BytesIO | BinaryIO, - custom_timeout: float | tuple[float, float], - ) -> None: - """It is possible to set a custom request timeout.""" - with MockVWS() as mock: - database = VuforiaDatabase() - 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=custom_timeout, - ) - - vws_client.add_target( - name="x", - width=1, - image=image, - active_flag=True, - application_metadata=None, - ) - @staticmethod def test_timeout_raises_on_slow_response( image: io.BytesIO | BinaryIO, From ff977797212068c55040d02ae4326992eba2d1b1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 11:59:50 +0000 Subject: [PATCH 319/688] Fix sleep_fn return type for mypy --- tests/test_query.py | 9 ++++++--- tests/test_vws.py | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/test_query.py b/tests/test_query.py index 4e7ccb86d..8c6a73769 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -67,9 +67,12 @@ def test_default_timeout( freeze_time() as frozen_datetime, MockVWS( response_delay_seconds=response_delay_seconds, - sleep_fn=lambda seconds: frozen_datetime.tick( - delta=datetime.timedelta(seconds=seconds), - ), + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], ) as mock, ): database = VuforiaDatabase() diff --git a/tests/test_vws.py b/tests/test_vws.py index e93c35063..6bb771f14 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -114,9 +114,12 @@ def test_default_timeout( freeze_time() as frozen_datetime, MockVWS( response_delay_seconds=response_delay_seconds, - sleep_fn=lambda seconds: frozen_datetime.tick( - delta=datetime.timedelta(seconds=seconds), - ), + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], ) as mock, ): database = VuforiaDatabase() From a8b8800f4c19b1d7c2d10063bd2e398154b296ec Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 12:03:14 +0000 Subject: [PATCH 320/688] Bump vws-python-mock to 2026.2.15.4 Co-Authored-By: Claude Haiku 4.5 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2b0fb465f..2f5a29b78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ optional-dependencies.dev = [ "ty==0.0.16", "types-requests==2.32.4.20260107", "vulture==2.14", - "vws-python-mock==2026.2.15", + "vws-python-mock==2026.2.15.4", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.22.0", From 2d64771065a113a59c3dd65fa44f50577fe8b4b5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 12:07:32 +0000 Subject: [PATCH 321/688] Add tests for the default 30-second request timeout Test that VWS and CloudRecoService requests succeed at 29 seconds and time out at 31 seconds, verifying the existing default timeout behavior. Co-Authored-By: Claude Opus 4.6 --- tests/test_query.py | 50 ++++++++++++++++++++++++++++++++++++++ tests/test_vws.py | 58 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/tests/test_query.py b/tests/test_query.py index a79a712c0..31284325d 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,9 +1,13 @@ """Tests for the ``CloudRecoService`` querying functionality.""" +import datetime import io import uuid from typing import BinaryIO +import pytest +import requests +from freezegun import freeze_time from mock_vws import MockVWS from mock_vws.database import VuforiaDatabase @@ -42,6 +46,52 @@ def test_match( assert matching_target.target_id == target_id +class TestDefaultRequestTimeout: + """Tests for the default request timeout.""" + + @staticmethod + @pytest.mark.parametrize( + argnames=("response_delay_seconds", "expect_timeout"), + argvalues=[(29, False), (31, True)], + ) + def test_default_timeout( + image: io.BytesIO | BinaryIO, + *, + response_delay_seconds: int, + expect_timeout: bool, + ) -> None: + """At 29 seconds there is no error; at 31 seconds there is a + timeout. + """ + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=response_delay_seconds, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = VuforiaDatabase() + mock.add_database(database=database) + cloud_reco_client = CloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + ) + + if expect_timeout: + with pytest.raises( + expected_exception=requests.exceptions.Timeout, + ): + cloud_reco_client.query(image=image) + else: + matches = cloud_reco_client.query(image=image) + assert not matches + + class TestCustomBaseVWQURL: """Tests for using a custom base VWQ URL.""" diff --git a/tests/test_vws.py b/tests/test_vws.py index 00936bde8..9a1c8ded9 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -8,6 +8,7 @@ from typing import BinaryIO import pytest +import requests from freezegun import freeze_time from mock_vws import MockVWS from mock_vws.database import VuforiaDatabase @@ -92,6 +93,63 @@ def test_add_two_targets( ) +class TestDefaultRequestTimeout: + """Tests for the default request timeout.""" + + @staticmethod + @pytest.mark.parametrize( + argnames=("response_delay_seconds", "expect_timeout"), + argvalues=[(29, False), (31, True)], + ) + def test_default_timeout( + image: io.BytesIO | BinaryIO, + *, + response_delay_seconds: int, + expect_timeout: bool, + ) -> None: + """At 29 seconds there is no error; at 31 seconds there is a + timeout. + """ + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=response_delay_seconds, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = VuforiaDatabase() + mock.add_database(database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + + if expect_timeout: + with pytest.raises( + expected_exception=requests.exceptions.Timeout, + ): + vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + else: + vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + + class TestCustomBaseVWSURL: """Tests for using a custom base VWS URL.""" From 26b9256dcaca967567f75b28b0720ed22a03cbde Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 12:16:17 +0000 Subject: [PATCH 322/688] Add pyrefly non-exhaustive match as error --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 2f5a29b78..2dadc4af3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -376,3 +376,6 @@ exclude = [ ".venv" ] [tool.yamlfix] section_whitelines = 1 whitelines = 1 + +[tool.pyrefly] +errors.non-exhaustive-match = "error" From 495fad3cd48a71698a9e0f3518727682fd4505bd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sun, 15 Feb 2026 12:20:06 +0000 Subject: [PATCH 323/688] [pre-commit.ci lite] apply automatic fixes --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2dadc4af3..ed1e46588 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -302,6 +302,9 @@ plugins = [ "mypy_strict_kwargs", ] +[tool.pyrefly] +errors.non-exhaustive-match = "error" + [tool.pyright] enableTypeIgnoreComments = false reportUnnecessaryTypeIgnoreComment = true @@ -376,6 +379,3 @@ exclude = [ ".venv" ] [tool.yamlfix] section_whitelines = 1 whitelines = 1 - -[tool.pyrefly] -errors.non-exhaustive-match = "error" From a69677c294e8fd5ce4db907594cf17cba163b034 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 12:32:59 +0000 Subject: [PATCH 324/688] Add fast tests for custom request timeouts --- tests/test_query.py | 55 +++++++++++++++++++++++++++++++++++++ tests/test_vws.py | 66 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/tests/test_query.py b/tests/test_query.py index 31284325d..d7b64aff2 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -92,6 +92,61 @@ def test_default_timeout( assert not matches +class TestCustomRequestTimeout: + """Tests for custom request timeout values.""" + + @staticmethod + @pytest.mark.parametrize( + argnames=( + "custom_timeout", + "response_delay_seconds", + "expect_timeout", + ), + argvalues=[ + (0.1, 0.09, False), + (0.1, 0.11, True), + ((5.0, 0.1), 0.09, False), + ((5.0, 0.1), 0.11, True), + ], + ) + def test_custom_timeout( + image: io.BytesIO | BinaryIO, + *, + custom_timeout: float | tuple[float, float], + response_delay_seconds: float, + expect_timeout: bool, + ) -> None: + """Custom timeouts are honored for both float and tuple forms.""" + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=response_delay_seconds, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = VuforiaDatabase() + mock.add_database(database=database) + cloud_reco_client = CloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + request_timeout_seconds=custom_timeout, + ) + + if expect_timeout: + with pytest.raises( + expected_exception=requests.exceptions.Timeout, + ): + cloud_reco_client.query(image=image) + else: + matches = cloud_reco_client.query(image=image) + assert not matches + + class TestCustomBaseVWQURL: """Tests for using a custom base VWQ URL.""" diff --git a/tests/test_vws.py b/tests/test_vws.py index 9a1c8ded9..f09906ee5 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -150,6 +150,72 @@ def test_default_timeout( ) +class TestCustomRequestTimeout: + """Tests for custom request timeout values.""" + + @staticmethod + @pytest.mark.parametrize( + argnames=( + "custom_timeout", + "response_delay_seconds", + "expect_timeout", + ), + argvalues=[ + (0.1, 0.09, False), + (0.1, 0.11, True), + ((5.0, 0.1), 0.09, False), + ((5.0, 0.1), 0.11, True), + ], + ) + def test_custom_timeout( + image: io.BytesIO | BinaryIO, + *, + custom_timeout: float | tuple[float, float], + response_delay_seconds: float, + expect_timeout: bool, + ) -> None: + """Custom timeouts are honored for both float and tuple forms.""" + with ( + freeze_time() as frozen_datetime, + MockVWS( + response_delay_seconds=response_delay_seconds, + sleep_fn=lambda seconds: ( + frozen_datetime.tick( + delta=datetime.timedelta(seconds=seconds), + ), + None, + )[1], + ) as mock, + ): + database = VuforiaDatabase() + 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=custom_timeout, + ) + + if expect_timeout: + with pytest.raises( + expected_exception=requests.exceptions.Timeout, + ): + vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + else: + vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + + class TestCustomBaseVWSURL: """Tests for using a custom base VWS URL.""" From 9654d576889369d483ceb1cac4881fb19ee36df5 Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sun, 15 Feb 2026 12:36:58 +0000 Subject: [PATCH 325/688] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 174256880..20a40ddb9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.15 +---------- + + * Add ``request_timeout_seconds`` parameter to ``VWS`` and ``CloudRecoService``, allowing customization of the request timeout. This accepts a float or a ``(connect, read)`` tuple, matching the ``requests`` library's timeout interface. The default remains 30 seconds. 2025.03.10.1 From 1d1890940b37d3f0cd926d90608ab1fd29ee1af4 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 15 Feb 2026 21:49:48 +0000 Subject: [PATCH 326/688] Enable coverage report.show_missing --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 2dadc4af3..0da02ac7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -293,6 +293,7 @@ report.exclude_also = [ "if TYPE_CHECKING:", ] +report.show_missing = true [tool.mypy] strict = true files = [ "." ] From afa54a8546aa455f8a8c4ae633a4e1bc239c52bc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sun, 15 Feb 2026 21:50:55 +0000 Subject: [PATCH 327/688] [pre-commit.ci lite] apply automatic fixes --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0da02ac7e..931426792 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -292,8 +292,8 @@ run.branch = true report.exclude_also = [ "if TYPE_CHECKING:", ] - report.show_missing = true + [tool.mypy] strict = true files = [ "." ] From 9b9855a0b836115f14a54b7d004a27570cdf8502 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 11:29:09 +0000 Subject: [PATCH 328/688] 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 43defd463..458aea327 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2026.1.12", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", - "ty==0.0.16", + "ty==0.0.17", "types-requests==2.32.4.20260107", "vulture==2.14", "vws-python-mock==2026.2.15.4", From 1f228869cc8631068cfef17d3f2f0abdc4645892 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 11:29:21 +0000 Subject: [PATCH 329/688] 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 43defd463..71a44c2da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ "vws-auth-tools>=2024.7.12", ] optional-dependencies.dev = [ - "actionlint-py==1.7.10.24", + "actionlint-py==1.7.11.24", "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", From 0b2673e6c03eab8ff5d101622248ea13401a1e0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 11:29:27 +0000 Subject: [PATCH 330/688] 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 43defd463..1d5189397 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2026.1.31.3", + "doccmd==2026.2.15", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From d3f91187ab612dfc214890650dc6b32a15f5d5b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 11:29:38 +0000 Subject: [PATCH 331/688] 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 43defd463..5676f951e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,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", "pygments==2.19.2", From b179aa8f4762ed0dd782c28f64ef06c14beaf73f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 11:38:48 +0000 Subject: [PATCH 332/688] Bump vws-python-mock from 2026.2.15.4 to 2026.2.15.5 Bumps [vws-python-mock](https://github.com/VWS-Python/vws-python-mock) from 2026.2.15.4 to 2026.2.15.5. - [Release notes](https://github.com/VWS-Python/vws-python-mock/releases) - [Changelog](https://github.com/VWS-Python/vws-python-mock/blob/main/CHANGELOG.rst) - [Commits](https://github.com/VWS-Python/vws-python-mock/compare/2026.02.15.4...2026.02.15.5) --- updated-dependencies: - dependency-name: vws-python-mock dependency-version: 2026.2.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 c61d5e9e5..8aa97ac42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ optional-dependencies.dev = [ "ty==0.0.17", "types-requests==2.32.4.20260107", "vulture==2.14", - "vws-python-mock==2026.2.15.4", + "vws-python-mock==2026.2.15.5", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.22.0", From 8ebd2af35f65e9aad49011ad8684fc9021d20f8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 10:33:41 +0000 Subject: [PATCH 333/688] 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 c4cdc33c0..f73e2c9e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.16.0", - "pyrefly==0.52.0", + "pyrefly==0.53.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From d0c90e256d180fc1a7ca494bbad4c52d61f0e84f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 10:33:56 +0000 Subject: [PATCH 334/688] Bump vws-python-mock from 2026.2.15.5 to 2026.2.18 Bumps [vws-python-mock](https://github.com/VWS-Python/vws-python-mock) from 2026.2.15.5 to 2026.2.18. - [Release notes](https://github.com/VWS-Python/vws-python-mock/releases) - [Changelog](https://github.com/VWS-Python/vws-python-mock/blob/main/CHANGELOG.rst) - [Commits](https://github.com/VWS-Python/vws-python-mock/compare/2026.02.15.5...2026.02.18) --- updated-dependencies: - dependency-name: vws-python-mock dependency-version: 2026.2.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 c4cdc33c0..61d1d0081 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ optional-dependencies.dev = [ "ty==0.0.17", "types-requests==2.32.4.20260107", "vulture==2.14", - "vws-python-mock==2026.2.15.5", + "vws-python-mock==2026.2.18", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.22.0", From 1b4e9fa9403db4ac8f51111f25306860ec99a01e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 10:37:18 +0000 Subject: [PATCH 335/688] 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 f73e2c9e9..262882a72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "pylint[spelling]==4.0.4", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.16.0", + "pyproject-fmt==2.16.1", "pyrefly==0.53.0", "pyright==1.1.408", "pyroma==5.0.1", From f24db3bd1bb425e228c649a17f72580507285a8d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 12:34:40 +0000 Subject: [PATCH 336/688] Use CPU-only PyTorch index to speed up dev dependency installation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit torch and torchvision are transitive dependencies of vws-python-mock. By default uv resolves them from PyPI, which provides CUDA-enabled wheels on Linux (~916 MB for torch). Redirecting to the pytorch CPU-only index reduces torch to ~189 MB — a 727 MB saving per cold-cache install. Following: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation Co-Authored-By: Claude Sonnet 4.6 --- pyproject.toml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index fb0a98d48..28a4997a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,11 @@ optional-dependencies.dev = [ "sphinx-substitution-extensions==2026.1.12", "sphinxcontrib-spelling==8.0.2", "sybil==9.3.0", + # Listed explicitly (despite being transitive via vws-python-mock) so that + # [tool.uv.sources] can redirect to the CPU-only PyTorch index. + # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation + "torch>=2.5.1", + "torchvision>=0.20.1", "ty==0.0.17", "types-requests==2.32.4.20260107", "vulture==2.14", @@ -106,6 +111,11 @@ bdist_wheel.universal = true # Code to match this is in ``conf.py``. 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] line-length = 79 lint.select = [ @@ -277,6 +287,13 @@ pep621_dev_dependency_groups = [ "dev", "release", ] +# torch and torchvision are listed explicitly in dev deps to allow +# [tool.uv.sources] to redirect them to the CPU-only PyTorch index, +# but they are not directly imported in the vws-python source code. +per_rule_ignores.DEP002 = [ + "torch", + "torchvision", +] [tool.pyproject-fmt] indent = 4 From b46173516abbe846d8f80472fbca928a463d8b5b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 19 Feb 2026 09:31:54 +0000 Subject: [PATCH 337/688] Bump vws-python-mock to 2026.2.18.2 Co-Authored-By: Claude Haiku 4.5 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 28a4997a8..e6d42b7d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ "ty==0.0.17", "types-requests==2.32.4.20260107", "vulture==2.14", - "vws-python-mock==2026.2.18", + "vws-python-mock==2026.2.18.2", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.22.0", From abe06b7bcd75830402f939581fdfe621091ed8c7 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 00:31:41 +0000 Subject: [PATCH 338/688] Bump vws-python-mock to 2026.2.21 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update VuforiaDatabase → CloudDatabase and add_database → add_cloud_database(cloud_database=) to match new API. Co-Authored-By: Claude Sonnet 4.6 --- conftest.py | 6 +++--- docs/source/index.rst | 6 +++--- pyproject.toml | 2 +- tests/conftest.py | 14 +++++++------- tests/test_cloud_reco_exceptions.py | 10 +++++----- tests/test_query.py | 14 +++++++------- tests/test_vws.py | 26 +++++++++++++------------- tests/test_vws_exceptions.py | 10 +++++----- 8 files changed, 44 insertions(+), 44 deletions(-) diff --git a/conftest.py b/conftest.py index 0e579a4db..89548f800 100644 --- a/conftest.py +++ b/conftest.py @@ -9,7 +9,7 @@ import pytest from beartype import beartype from mock_vws import MockVWS -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from sybil import Sybil from sybil.parsers.rest import ( ClearNamespaceParser, @@ -53,7 +53,7 @@ def fixture_mock_vws( client_access_key = uuid.uuid4().hex client_secret_key = uuid.uuid4().hex - database = VuforiaDatabase( + database = CloudDatabase( server_access_key=server_access_key, server_secret_key=server_secret_key, client_access_key=client_access_key, @@ -66,7 +66,7 @@ def fixture_mock_vws( monkeypatch.setenv(name="VWS_CLIENT_SECRET_KEY", value=client_secret_key) # We use a low processing time so that tests run quickly. with MockVWS(processing_time_seconds=0.2) as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) yield diff --git a/docs/source/index.rst b/docs/source/index.rst index 818b0698d..c61c2a302 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -78,13 +78,13 @@ To write unit tests for code which uses this library, without using your Vuforia import pathlib from mock_vws import MockVWS - from mock_vws.database import VuforiaDatabase + from mock_vws.database import CloudDatabase from vws import VWS, CloudRecoService with MockVWS() as mock: - database = VuforiaDatabase() - mock.add_database(database=database) + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, diff --git a/pyproject.toml b/pyproject.toml index e6d42b7d0..510f2c80b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ "ty==0.0.17", "types-requests==2.32.4.20260107", "vulture==2.14", - "vws-python-mock==2026.2.18.2", + "vws-python-mock==2026.2.21", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.22.0", diff --git a/tests/conftest.py b/tests/conftest.py index 7c0647786..9073a8c03 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,23 +7,23 @@ import pytest from mock_vws import MockVWS -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from vws import VWS, CloudRecoService @pytest.fixture(name="_mock_database") -def fixture_mock_database() -> Generator[VuforiaDatabase]: - """Yield a mock ``VuforiaDatabase``.""" +def fixture_mock_database() -> Generator[CloudDatabase]: + """Yield a mock ``CloudDatabase``.""" # We use a low processing time so that tests run quickly. with MockVWS(processing_time_seconds=0.2) as mock: - database = VuforiaDatabase() - mock.add_database(database=database) + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) yield database @pytest.fixture -def vws_client(_mock_database: VuforiaDatabase) -> VWS: +def vws_client(_mock_database: CloudDatabase) -> VWS: """A VWS client which connects to a mock database.""" return VWS( server_access_key=_mock_database.server_access_key, @@ -32,7 +32,7 @@ def vws_client(_mock_database: VuforiaDatabase) -> VWS: @pytest.fixture -def cloud_reco_client(_mock_database: VuforiaDatabase) -> CloudRecoService: +def cloud_reco_client(_mock_database: CloudDatabase) -> CloudRecoService: """A ``CloudRecoService`` client which connects to a mock database.""" return CloudRecoService( client_access_key=_mock_database.client_access_key, diff --git a/tests/test_cloud_reco_exceptions.py b/tests/test_cloud_reco_exceptions.py index 5576f4d40..3b2922fd9 100644 --- a/tests/test_cloud_reco_exceptions.py +++ b/tests/test_cloud_reco_exceptions.py @@ -6,7 +6,7 @@ import pytest from mock_vws import MockVWS -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from mock_vws.states import States from vws import CloudRecoService @@ -85,13 +85,13 @@ def test_authentication_failure( key exists but the client secret key is incorrect. """ - database = VuforiaDatabase() + database = CloudDatabase() cloud_reco_client = CloudRecoService( client_access_key=database.client_access_key, client_secret_key=uuid.uuid4().hex, ) with MockVWS() as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) with pytest.raises( expected_exception=AuthenticationFailureError @@ -108,9 +108,9 @@ def test_inactive_project( An ``InactiveProject`` exception is raised when querying an inactive database. """ - database = VuforiaDatabase(state=States.PROJECT_INACTIVE) + database = CloudDatabase(state=States.PROJECT_INACTIVE) with MockVWS() as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) cloud_reco_client = CloudRecoService( client_access_key=database.client_access_key, client_secret_key=database.client_secret_key, diff --git a/tests/test_query.py b/tests/test_query.py index d7b64aff2..b5ced6dde 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -9,7 +9,7 @@ import requests from freezegun import freeze_time from mock_vws import MockVWS -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from vws import VWS, CloudRecoService from vws.include_target_data import CloudRecoIncludeTargetData @@ -75,8 +75,8 @@ def test_default_timeout( )[1], ) as mock, ): - database = VuforiaDatabase() - mock.add_database(database=database) + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) cloud_reco_client = CloudRecoService( client_access_key=database.client_access_key, client_secret_key=database.client_secret_key, @@ -129,8 +129,8 @@ def test_custom_timeout( )[1], ) as mock, ): - database = VuforiaDatabase() - mock.add_database(database=database) + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) cloud_reco_client = CloudRecoService( client_access_key=database.client_access_key, client_secret_key=database.client_secret_key, @@ -160,8 +160,8 @@ def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: """ base_vwq_url = "http://example.com" with MockVWS(base_vwq_url=base_vwq_url) as mock: - database = VuforiaDatabase() - mock.add_database(database=database) + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, diff --git a/tests/test_vws.py b/tests/test_vws.py index f09906ee5..87cdbf8f0 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -11,7 +11,7 @@ import requests from freezegun import freeze_time from mock_vws import MockVWS -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from vws import VWS, CloudRecoService from vws.exceptions.custom_exceptions import TargetProcessingTimeoutError @@ -122,8 +122,8 @@ def test_default_timeout( )[1], ) as mock, ): - database = VuforiaDatabase() - mock.add_database(database=database) + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, @@ -187,8 +187,8 @@ def test_custom_timeout( )[1], ) as mock, ): - database = VuforiaDatabase() - mock.add_database(database=database) + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, @@ -228,8 +228,8 @@ def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: """ base_vws_url = "http://example.com" with MockVWS(base_vws_url=base_vws_url) as mock: - database = VuforiaDatabase() - mock.add_database(database=database) + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, @@ -489,8 +489,8 @@ def test_default_seconds_between_requests( ) -> None: """By default, 0.2 seconds are waited between polling requests.""" with MockVWS(processing_time_seconds=0.5) as mock: - database = VuforiaDatabase() - mock.add_database(database=database) + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, @@ -541,8 +541,8 @@ def test_custom_seconds_between_requests( requests. """ with MockVWS(processing_time_seconds=0.5) as mock: - database = VuforiaDatabase() - mock.add_database(database=database) + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, @@ -589,8 +589,8 @@ def test_custom_seconds_between_requests( def test_custom_timeout(image: io.BytesIO | BinaryIO) -> None: """It is possible to set a maximum timeout.""" with MockVWS(processing_time_seconds=0.5) as mock: - database = VuforiaDatabase() - mock.add_database(database=database) + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 4262b3ea6..74374e3d0 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -7,7 +7,7 @@ import pytest from freezegun import freeze_time from mock_vws import MockVWS -from mock_vws.database import VuforiaDatabase +from mock_vws.database import CloudDatabase from mock_vws.states import States from vws import VWS @@ -175,9 +175,9 @@ def test_project_inactive( inactive database. """ - database = VuforiaDatabase(state=States.PROJECT_INACTIVE) + database = CloudDatabase(state=States.PROJECT_INACTIVE) with MockVWS() as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, @@ -286,7 +286,7 @@ def test_authentication_failure( exists but the server secret key is incorrect, or when a client key is incorrect. """ - database = VuforiaDatabase() + database = CloudDatabase() vws_client = VWS( server_access_key=database.server_access_key, @@ -294,7 +294,7 @@ def test_authentication_failure( ) with MockVWS() as mock: - mock.add_database(database=database) + mock.add_cloud_database(cloud_database=database) with pytest.raises( expected_exception=AuthenticationFailureError From 1100913e2b1d0bf9a7efef1db2082788c6c1d84f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 23:27:43 +0000 Subject: [PATCH 339/688] Add VuMark generation support Implement VuMark instance generation API with support for multiple image formats (PNG, SVG, PDF). Add InvalidAcceptHeaderError and InvalidInstanceIdError exceptions for proper error handling. Includes comprehensive tests for all VuMark formats. Co-Authored-By: Claude Haiku 4.5 --- docs/source/api-reference.rst | 4 + pyproject.toml | 3 + src/vws/exceptions/vws_exceptions.py | 16 ++++ src/vws/vumark_accept.py | 18 +++++ src/vws/vws.py | 116 +++++++++++++++++++++++++++ tests/test_vws.py | 53 ++++++++++++ tests/test_vws_exceptions.py | 29 +++++++ 7 files changed, 239 insertions(+) create mode 100644 src/vws/vumark_accept.py diff --git a/docs/source/api-reference.rst b/docs/source/api-reference.rst index 01bba09b2..c94d810e1 100644 --- a/docs/source/api-reference.rst +++ b/docs/source/api-reference.rst @@ -13,6 +13,10 @@ API Reference :undoc-members: :members: +.. automodule:: vws.vumark_accept + :undoc-members: + :members: + .. automodule:: vws.response :undoc-members: :members: diff --git a/pyproject.toml b/pyproject.toml index 510f2c80b..a00c706fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -363,6 +363,9 @@ ignore_names = [ "pytest_plugins", # pytest fixtures - we name fixtures like this for this purpose "fixture_*", + # Enum members used dynamically + "PDF", + "SVG", # Sphinx "autoclass_content", "autoclass_content", diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index 6b1447577..6143f7397 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -167,3 +167,19 @@ class TooManyRequestsError(VWSError): # pragma: no cover """Exception raised when Vuforia returns a response with a result code 'TooManyRequests'. """ + + +# This is not simulated by client code because the accept parameter uses +# the VuMarkAccept enum, which only allows valid values. +@beartype +class InvalidAcceptHeaderError(VWSError): # pragma: no cover + """Exception raised when Vuforia returns a response with a result code + 'InvalidAcceptHeader'. + """ + + +@beartype +class InvalidInstanceIdError(VWSError): + """Exception raised when Vuforia returns a response with a result code + 'InvalidInstanceId'. + """ diff --git a/src/vws/vumark_accept.py b/src/vws/vumark_accept.py new file mode 100644 index 000000000..5dd21ab08 --- /dev/null +++ b/src/vws/vumark_accept.py @@ -0,0 +1,18 @@ +"""Tools for managing ``VWS.generate_vumark_instance``'s ``accept``.""" + +from enum import StrEnum, unique + +from beartype import beartype + + +@beartype +@unique +class VuMarkAccept(StrEnum): + """ + Options for the ``accept`` parameter of + ``VWS.generate_vumark_instance``. + """ + + PNG = "image/png" + SVG = "image/svg+xml" + PDF = "application/pdf" diff --git a/src/vws/vws.py b/src/vws/vws.py index 4cb75ca32..7b026a3c3 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -23,6 +23,8 @@ DateRangeError, FailError, ImageTooLargeError, + InvalidAcceptHeaderError, + InvalidInstanceIdError, MetadataTooLargeError, ProjectHasNoAPIAccessError, ProjectInactiveError, @@ -44,6 +46,7 @@ TargetSummaryReport, ) from vws.response import Response +from vws.vumark_accept import VuMarkAccept _ImageType = io.BytesIO | BinaryIO @@ -700,3 +703,116 @@ def update_target( expected_result_code="Success", content_type="application/json", ) + + def generate_vumark_instance( + self, + *, + target_id: str, + instance_id: str, + accept: VuMarkAccept = VuMarkAccept.PNG, + ) -> bytes: + """Generate a VuMark instance image. + + See + https://developer.vuforia.com/library/vuforia-engine/web-api/vumark-generation-web-api/ + for parameter details. + + Args: + target_id: The ID of the VuMark target. + instance_id: The instance ID to encode in the VuMark. + accept: The image format to return. + + Returns: + The VuMark instance image bytes. + + Raises: + ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The + secret key is not correct. + ~vws.exceptions.vws_exceptions.FailError: There was an error with + the request. For example, the given access key does not match a + known database. + ~vws.exceptions.vws_exceptions.InvalidAcceptHeaderError: The + Accept header value is not supported. + ~vws.exceptions.vws_exceptions.InvalidInstanceIdError: The + instance ID is invalid. For example, it may be empty. + ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is + an error with the time sent to Vuforia. + ~vws.exceptions.vws_exceptions.TargetStatusNotSuccessError: The + target is not in the success state. + ~vws.exceptions.vws_exceptions.UnknownTargetError: The given target + ID does not match a target in the database. + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. + """ + request_path = f"/targets/{target_id}/instances" + content_type = "application/json" + request_data = json.dumps(obj={"instance_id": instance_id}).encode( + encoding="utf-8", + ) + date_string = rfc_1123_date() + + signature_string = authorization_header( + access_key=self._server_access_key, + secret_key=self._server_secret_key, + method=HTTPMethod.POST, + content=request_data, + content_type=content_type, + date=date_string, + request_path=request_path, + ) + + headers = { + "Authorization": signature_string, + "Date": date_string, + "Content-Type": content_type, + "Accept": accept, + } + + url = urljoin(base=self._base_vws_url, url=request_path) + + requests_response = requests.request( + method=HTTPMethod.POST, + url=url, + headers=headers, + data=request_data, + timeout=self._request_timeout_seconds, + ) + + if requests_response.status_code == HTTPStatus.OK: + return bytes(requests_response.content) + + response = Response( + text=requests_response.text, + url=requests_response.url, + status_code=requests_response.status_code, + headers=dict(requests_response.headers), + request_body=requests_response.request.body, + tell_position=requests_response.raw.tell(), + ) + + if ( + requests_response.status_code == HTTPStatus.TOO_MANY_REQUESTS + ): # pragma: no cover + raise TooManyRequestsError(response=response) + + if ( + requests_response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR + ): # pragma: no cover + raise ServerError(response=response) + + result_code = json.loads(s=response.text)["result_code"] + + exception = { + "AuthenticationFailure": AuthenticationFailureError, + "DateRangeError": DateRangeError, + "Fail": FailError, + "InvalidAcceptHeader": InvalidAcceptHeaderError, + "InvalidInstanceId": InvalidInstanceIdError, + "RequestTimeTooSkewed": RequestTimeTooSkewedError, + "TargetStatusNotSuccess": TargetStatusNotSuccessError, + "UnknownTarget": UnknownTargetError, + }[result_code] + + raise exception(response=response) diff --git a/tests/test_vws.py b/tests/test_vws.py index 87cdbf8f0..1e077e16a 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -21,6 +21,7 @@ TargetStatuses, TargetSummaryReport, ) +from vws.vumark_accept import VuMarkAccept class TestAddTarget: @@ -729,3 +730,55 @@ def test_no_fields_given( ) vws_client.wait_for_target_processed(target_id=target_id) vws_client.update_target(target_id=target_id) + + +class TestGenerateVumarkInstance: + """Tests for generating VuMark instances.""" + + @staticmethod + @pytest.mark.parametrize( + argnames="accept", + argvalues=list(VuMarkAccept), + ) + def test_generate_vumark_instance( + vws_client: VWS, + high_quality_image: io.BytesIO, + accept: VuMarkAccept, + ) -> None: + """Bytes are returned when generating a VuMark instance.""" + target_id = vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + vws_client.wait_for_target_processed(target_id=target_id) + result = vws_client.generate_vumark_instance( + target_id=target_id, + instance_id="12345", + accept=accept, + ) + assert isinstance(result, bytes) + assert len(result) > 0 + + @staticmethod + def test_generate_vumark_default_accept( + vws_client: VWS, + high_quality_image: io.BytesIO, + ) -> None: + """By default, PNG is returned.""" + target_id = vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + vws_client.wait_for_target_processed(target_id=target_id) + result = vws_client.generate_vumark_instance( + target_id=target_id, + instance_id="12345", + ) + assert isinstance(result, bytes) + assert len(result) > 0 diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 74374e3d0..4a9f012de 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -21,6 +21,8 @@ DateRangeError, FailError, ImageTooLargeError, + InvalidAcceptHeaderError, + InvalidInstanceIdError, MetadataTooLargeError, ProjectHasNoAPIAccessError, ProjectInactiveError, @@ -342,6 +344,8 @@ def test_vwsexception_inheritance() -> None: DateRangeError, FailError, ImageTooLargeError, + InvalidAcceptHeaderError, + InvalidInstanceIdError, MetadataTooLargeError, ProjectInactiveError, ProjectHasNoAPIAccessError, @@ -358,6 +362,31 @@ def test_vwsexception_inheritance() -> None: assert issubclass(subclass, VWSError) +def test_invalid_instance_id( + vws_client: VWS, + high_quality_image: io.BytesIO, +) -> None: + """ + An ``InvalidInstanceId`` exception is raised when an empty instance + ID is given. + """ + target_id = vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + vws_client.wait_for_target_processed(target_id=target_id) + with pytest.raises(expected_exception=InvalidInstanceIdError) as exc: + vws_client.generate_vumark_instance( + target_id=target_id, + instance_id="", + ) + + assert exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + + def test_base_exception( vws_client: VWS, high_quality_image: io.BytesIO, From f5437a99a6948e1e96f8de7b141bc16c10deb5f5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 23:34:16 +0000 Subject: [PATCH 340/688] Add BadRequestError and bump vws-python-mock to 2026.2.18.2 Add BadRequestError exception for the BadRequest result code returned by the VuMark endpoint when invalid JSON is sent. Map it in generate_vumark_instance and include it in the exception inheritance test. Bump vws-python-mock to 2026.2.18.2 which adds full VuMark auth endpoint testing support. Co-Authored-By: Claude Haiku 4.5 --- src/vws/exceptions/vws_exceptions.py | 9 +++++++++ src/vws/vws.py | 2 ++ tests/test_vws_exceptions.py | 2 ++ 3 files changed, 13 insertions(+) diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index 6143f7397..6baac80c6 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -183,3 +183,12 @@ class InvalidInstanceIdError(VWSError): """Exception raised when Vuforia returns a response with a result code 'InvalidInstanceId'. """ + + +# This is not simulated by client code because the request body +# is always valid JSON when using this client. +@beartype +class BadRequestError(VWSError): # pragma: no cover + """Exception raised when Vuforia returns a response with a result code + 'BadRequest'. + """ diff --git a/src/vws/vws.py b/src/vws/vws.py index 7b026a3c3..ccc5f0056 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -20,6 +20,7 @@ from vws.exceptions.vws_exceptions import ( AuthenticationFailureError, BadImageError, + BadRequestError, DateRangeError, FailError, ImageTooLargeError, @@ -806,6 +807,7 @@ def generate_vumark_instance( exception = { "AuthenticationFailure": AuthenticationFailureError, + "BadRequest": BadRequestError, "DateRangeError": DateRangeError, "Fail": FailError, "InvalidAcceptHeader": InvalidAcceptHeaderError, diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 4a9f012de..4f5c3eded 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -18,6 +18,7 @@ from vws.exceptions.vws_exceptions import ( AuthenticationFailureError, BadImageError, + BadRequestError, DateRangeError, FailError, ImageTooLargeError, @@ -341,6 +342,7 @@ def test_vwsexception_inheritance() -> None: subclasses = [ AuthenticationFailureError, BadImageError, + BadRequestError, DateRangeError, FailError, ImageTooLargeError, From 1d8053baad08e87f99ed5fa6489839b4b9e6735e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 19 Feb 2026 09:03:20 +0000 Subject: [PATCH 341/688] Add 'enum' to spelling private dict Fix pylint wrong-spelling-in-comment (C0401) for the word 'enum' in the VuMark exception comments. Co-Authored-By: Claude Haiku 4.5 --- spelling_private_dict.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index ce782a4af..d5d5d3e99 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -42,6 +42,7 @@ decodable dev dict docstring +enum filename foo formdata From 8f0d330d7e22cc9adc09eefe38dbef1e122365e2 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 19 Feb 2026 09:25:22 +0000 Subject: [PATCH 342/688] Assert format-specific content in VuMark tests Check magic bytes/prefix for each format (PNG, SVG, PDF) rather than just asserting non-empty bytes are returned. Co-Authored-By: Claude Sonnet 4.6 --- tests/test_vws.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/test_vws.py b/tests/test_vws.py index 1e077e16a..575612ca8 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -737,15 +737,20 @@ class TestGenerateVumarkInstance: @staticmethod @pytest.mark.parametrize( - argnames="accept", - argvalues=list(VuMarkAccept), + argnames=("accept", "expected_prefix"), + argvalues=[ + pytest.param(VuMarkAccept.PNG, b"\x89PNG\r\n\x1a\n", id="png"), + pytest.param(VuMarkAccept.SVG, b"<", id="svg"), + pytest.param(VuMarkAccept.PDF, b"%PDF", id="pdf"), + ], ) def test_generate_vumark_instance( vws_client: VWS, high_quality_image: io.BytesIO, accept: VuMarkAccept, + expected_prefix: bytes, ) -> None: - """Bytes are returned when generating a VuMark instance.""" + """The returned bytes match the requested format.""" target_id = vws_client.add_target( name="x", width=1, @@ -759,8 +764,7 @@ def test_generate_vumark_instance( instance_id="12345", accept=accept, ) - assert isinstance(result, bytes) - assert len(result) > 0 + assert result.startswith(expected_prefix) @staticmethod def test_generate_vumark_default_accept( @@ -780,5 +784,4 @@ def test_generate_vumark_default_accept( target_id=target_id, instance_id="12345", ) - assert isinstance(result, bytes) - assert len(result) > 0 + assert result.startswith(b"\x89PNG\r\n\x1a\n") From 9d37d6e796b5c2fcd75929c5da8501b9f8be994d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 19 Feb 2026 09:26:48 +0000 Subject: [PATCH 343/688] Remove default for accept parameter in generate_vumark_instance Co-Authored-By: Claude Sonnet 4.6 --- src/vws/vws.py | 2 +- tests/test_vws.py | 20 -------------------- tests/test_vws_exceptions.py | 2 ++ 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/src/vws/vws.py b/src/vws/vws.py index ccc5f0056..e0035ffcc 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -710,7 +710,7 @@ def generate_vumark_instance( *, target_id: str, instance_id: str, - accept: VuMarkAccept = VuMarkAccept.PNG, + accept: VuMarkAccept, ) -> bytes: """Generate a VuMark instance image. diff --git a/tests/test_vws.py b/tests/test_vws.py index 575612ca8..8ac09a3e1 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -765,23 +765,3 @@ def test_generate_vumark_instance( accept=accept, ) assert result.startswith(expected_prefix) - - @staticmethod - def test_generate_vumark_default_accept( - vws_client: VWS, - high_quality_image: io.BytesIO, - ) -> None: - """By default, PNG is returned.""" - target_id = vws_client.add_target( - name="x", - width=1, - image=high_quality_image, - active_flag=True, - application_metadata=None, - ) - vws_client.wait_for_target_processed(target_id=target_id) - result = vws_client.generate_vumark_instance( - target_id=target_id, - instance_id="12345", - ) - assert result.startswith(b"\x89PNG\r\n\x1a\n") diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 4f5c3eded..b55f35fed 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -36,6 +36,7 @@ TargetStatusProcessingError, UnknownTargetError, ) +from vws.vumark_accept import VuMarkAccept def test_image_too_large( @@ -384,6 +385,7 @@ def test_invalid_instance_id( vws_client.generate_vumark_instance( target_id=target_id, instance_id="", + accept=VuMarkAccept.PNG, ) assert exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY From 628100943cc8856a7a0d53c37ef5b77ed6337698 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 19 Feb 2026 09:27:57 +0000 Subject: [PATCH 344/688] Remove now-unnecessary vulture ignore for SVG and PDF enum members They are now referenced explicitly in the parametrized test. Co-Authored-By: Claude Sonnet 4.6 --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a00c706fb..510f2c80b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -363,9 +363,6 @@ ignore_names = [ "pytest_plugins", # pytest fixtures - we name fixtures like this for this purpose "fixture_*", - # Enum members used dynamically - "PDF", - "SVG", # Sphinx "autoclass_content", "autoclass_content", From d09630d330f962eca86aab15dc24f7c1f4610082 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 19 Feb 2026 09:29:34 +0000 Subject: [PATCH 345/688] Add BadRequest, InvalidAcceptHeader, InvalidInstanceId to spelling dict Fix Sphinx spell checker failures for the result code names used in the new exception docstrings. Co-Authored-By: Claude Sonnet 4.6 --- spelling_private_dict.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index d5d5d3e99..5d095eabd 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -1,9 +1,12 @@ AuthenticationFailure BadImage +BadRequest ConnectionErrorPossiblyImageTooLarge DateRangeError ImageTooLarge InactiveProject +InvalidAcceptHeader +InvalidInstanceId JSONDecodeError MatchProcessing MaxNumResultsOutOfRange From dc7208b859cef76f2baf2115086b7f704d845e42 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 19 Feb 2026 09:31:17 +0000 Subject: [PATCH 346/688] Use double backticks for result codes in exception docstrings Replace single-quoted result code names with double backticks so the Sphinx spell checker treats them as inline code rather than words. Co-Authored-By: Claude Sonnet 4.6 --- spelling_private_dict.txt | 3 --- src/vws/exceptions/vws_exceptions.py | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index 5d095eabd..d5d5d3e99 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -1,12 +1,9 @@ AuthenticationFailure BadImage -BadRequest ConnectionErrorPossiblyImageTooLarge DateRangeError ImageTooLarge InactiveProject -InvalidAcceptHeader -InvalidInstanceId JSONDecodeError MatchProcessing MaxNumResultsOutOfRange diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index 6baac80c6..e2d44ec35 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -174,14 +174,14 @@ class TooManyRequestsError(VWSError): # pragma: no cover @beartype class InvalidAcceptHeaderError(VWSError): # pragma: no cover """Exception raised when Vuforia returns a response with a result code - 'InvalidAcceptHeader'. + ``InvalidAcceptHeader``. """ @beartype class InvalidInstanceIdError(VWSError): """Exception raised when Vuforia returns a response with a result code - 'InvalidInstanceId'. + ``InvalidInstanceId``. """ @@ -190,5 +190,5 @@ class InvalidInstanceIdError(VWSError): @beartype class BadRequestError(VWSError): # pragma: no cover """Exception raised when Vuforia returns a response with a result code - 'BadRequest'. + ``BadRequest``. """ From 604f9262c5d9c2e7e1f77280b71a4e6d163eeefc Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 19 Feb 2026 09:36:57 +0000 Subject: [PATCH 347/688] Refactor generate_vumark_instance to use _target_api_request Adds extra_headers parameter to _target_api_request and content field to Response, allowing generate_vumark_instance to reuse shared auth/signing logic instead of duplicating it. Co-Authored-By: Claude Sonnet 4.6 --- src/vws/query.py | 1 + src/vws/response.py | 1 + src/vws/vws.py | 51 +++++++++++++-------------------------------- 3 files changed, 17 insertions(+), 36 deletions(-) diff --git a/src/vws/query.py b/src/vws/query.py index dd36e8f21..ad55d8a47 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -156,6 +156,7 @@ def query( headers=dict(requests_response.headers), request_body=requests_response.request.body, tell_position=requests_response.raw.tell(), + content=bytes(requests_response.content), ) if response.status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE: diff --git a/src/vws/response.py b/src/vws/response.py index e2060cb9e..71d1b9a48 100644 --- a/src/vws/response.py +++ b/src/vws/response.py @@ -16,3 +16,4 @@ class Response: headers: dict[str, str] request_body: bytes | str | None tell_position: int + content: bytes diff --git a/src/vws/vws.py b/src/vws/vws.py index e0035ffcc..f3455b809 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -73,6 +73,7 @@ def _target_api_request( request_path: str, base_vws_url: str, request_timeout_seconds: float | tuple[float, float], + extra_headers: dict[str, str] | None = None, ) -> Response: """Make a request to the Vuforia Target API. @@ -90,6 +91,7 @@ def _target_api_request( request_timeout_seconds: The timeout for the request, as used by ``requests.request``. This can be a float to set both the connect and read timeouts, or a (connect, read) tuple. + extra_headers: Additional headers to include in the request. Returns: The response to the request made by `requests`. @@ -110,6 +112,7 @@ def _target_api_request( "Authorization": signature_string, "Date": date_string, "Content-Type": content_type, + **(extra_headers or {}), } url = urljoin(base=base_vws_url, url=request_path) @@ -129,6 +132,7 @@ def _target_api_request( headers=dict(requests_response.headers), request_body=requests_response.request.body, tell_position=requests_response.raw.tell(), + content=bytes(requests_response.content), ) @@ -752,54 +756,29 @@ def generate_vumark_instance( request_data = json.dumps(obj={"instance_id": instance_id}).encode( encoding="utf-8", ) - date_string = rfc_1123_date() - signature_string = authorization_header( - access_key=self._server_access_key, - secret_key=self._server_secret_key, - method=HTTPMethod.POST, - content=request_data, + response = _target_api_request( content_type=content_type, - date=date_string, - request_path=request_path, - ) - - headers = { - "Authorization": signature_string, - "Date": date_string, - "Content-Type": content_type, - "Accept": accept, - } - - url = urljoin(base=self._base_vws_url, url=request_path) - - requests_response = requests.request( + server_access_key=self._server_access_key, + server_secret_key=self._server_secret_key, method=HTTPMethod.POST, - url=url, - headers=headers, data=request_data, - timeout=self._request_timeout_seconds, + request_path=request_path, + base_vws_url=self._base_vws_url, + request_timeout_seconds=self._request_timeout_seconds, + extra_headers={"Accept": accept}, ) - if requests_response.status_code == HTTPStatus.OK: - return bytes(requests_response.content) - - response = Response( - text=requests_response.text, - url=requests_response.url, - status_code=requests_response.status_code, - headers=dict(requests_response.headers), - request_body=requests_response.request.body, - tell_position=requests_response.raw.tell(), - ) + if response.status_code == HTTPStatus.OK: + return response.content if ( - requests_response.status_code == HTTPStatus.TOO_MANY_REQUESTS + response.status_code == HTTPStatus.TOO_MANY_REQUESTS ): # pragma: no cover raise TooManyRequestsError(response=response) if ( - requests_response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR + response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR ): # pragma: no cover raise ServerError(response=response) From eb6f371af75431616a4bbdae6b4ce79dd6d9cd4b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 19 Feb 2026 09:39:29 +0000 Subject: [PATCH 348/688] Move 429/5xx handling into _target_api_request Centralises TooManyRequestsError and ServerError raising so callers (make_request, generate_vumark_instance) no longer duplicate the logic. Co-Authored-By: Claude Sonnet 4.6 --- src/vws/vws.py | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/vws/vws.py b/src/vws/vws.py index f3455b809..e1d2eea46 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -95,6 +95,12 @@ def _target_api_request( Returns: The response to the request made by `requests`. + + Raises: + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. """ date_string = rfc_1123_date() @@ -125,7 +131,7 @@ def _target_api_request( timeout=request_timeout_seconds, ) - return Response( + response = Response( text=requests_response.text, url=requests_response.url, status_code=requests_response.status_code, @@ -135,6 +141,19 @@ def _target_api_request( content=bytes(requests_response.content), ) + if ( + response.status_code == HTTPStatus.TOO_MANY_REQUESTS + ): # pragma: no cover + # The Vuforia API returns a 429 response with no JSON body. + raise TooManyRequestsError(response=response) + + if ( + response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR + ): # pragma: no cover + raise ServerError(response=response) + + return response + @beartype(conf=BeartypeConf(is_pep484_tower=True)) class VWS: @@ -207,17 +226,6 @@ def make_request( request_timeout_seconds=self._request_timeout_seconds, ) - if ( - response.status_code == HTTPStatus.TOO_MANY_REQUESTS - ): # pragma: no cover - # The Vuforia API returns a 429 response with no JSON body. - raise TooManyRequestsError(response=response) - - if ( - response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR - ): # pragma: no cover - raise ServerError(response=response) - result_code = json.loads(s=response.text)["result_code"] if result_code == expected_result_code: @@ -772,16 +780,6 @@ def generate_vumark_instance( if response.status_code == HTTPStatus.OK: return response.content - if ( - response.status_code == HTTPStatus.TOO_MANY_REQUESTS - ): # pragma: no cover - raise TooManyRequestsError(response=response) - - if ( - response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR - ): # pragma: no cover - raise ServerError(response=response) - result_code = json.loads(s=response.text)["result_code"] exception = { From 81a0741287f8609616a7483383254398cb2a686d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 19 Feb 2026 09:45:47 +0000 Subject: [PATCH 349/688] Use make_request in generate_vumark_instance Adds extra_headers and optional expected_result_code (None = binary/HTTP-200 success) to make_request, and adds VuMark-specific result codes to its error dispatch dict, so generate_vumark_instance no longer needs to call _target_api_request directly. Co-Authored-By: Claude Sonnet 4.6 --- src/vws/vws.py | 45 ++++++++++++++++++++------------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/src/vws/vws.py b/src/vws/vws.py index e1d2eea46..c121e081d 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -187,8 +187,9 @@ def make_request( method: str, data: bytes, request_path: str, - expected_result_code: str, + expected_result_code: str | None, content_type: str, + extra_headers: dict[str, str] | None = None, ) -> Response: """Make a request to the Vuforia Target API. @@ -201,7 +202,11 @@ def make_request( request. expected_result_code: See "VWS API Result Codes" on https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api. + Pass ``None`` for endpoints that return a non-JSON success + response (e.g. binary data); success is then determined by an + HTTP 200 status code. content_type: The content type of the request. + extra_headers: Additional headers to include in the request. Returns: The response to the request made by `requests`. @@ -224,8 +229,15 @@ def make_request( request_path=request_path, base_vws_url=self._base_vws_url, request_timeout_seconds=self._request_timeout_seconds, + extra_headers=extra_headers, ) + if ( + response.status_code == HTTPStatus.OK + and expected_result_code is None + ): + return response + result_code = json.loads(s=response.text)["result_code"] if result_code == expected_result_code: @@ -234,9 +246,12 @@ def make_request( exception = { "AuthenticationFailure": AuthenticationFailureError, "BadImage": BadImageError, + "BadRequest": BadRequestError, "DateRangeError": DateRangeError, "Fail": FailError, "ImageTooLarge": ImageTooLargeError, + "InvalidAcceptHeader": InvalidAcceptHeaderError, + "InvalidInstanceId": InvalidInstanceIdError, "MetadataTooLarge": MetadataTooLargeError, "ProjectHasNoAPIAccess": ProjectHasNoAPIAccessError, "ProjectInactive": ProjectInactiveError, @@ -765,33 +780,13 @@ def generate_vumark_instance( encoding="utf-8", ) - response = _target_api_request( - content_type=content_type, - server_access_key=self._server_access_key, - server_secret_key=self._server_secret_key, + response = self.make_request( method=HTTPMethod.POST, data=request_data, request_path=request_path, - base_vws_url=self._base_vws_url, - request_timeout_seconds=self._request_timeout_seconds, + expected_result_code=None, + content_type=content_type, extra_headers={"Accept": accept}, ) - if response.status_code == HTTPStatus.OK: - return response.content - - result_code = json.loads(s=response.text)["result_code"] - - exception = { - "AuthenticationFailure": AuthenticationFailureError, - "BadRequest": BadRequestError, - "DateRangeError": DateRangeError, - "Fail": FailError, - "InvalidAcceptHeader": InvalidAcceptHeaderError, - "InvalidInstanceId": InvalidInstanceIdError, - "RequestTimeTooSkewed": RequestTimeTooSkewedError, - "TargetStatusNotSuccess": TargetStatusNotSuccessError, - "UnknownTarget": UnknownTargetError, - }[result_code] - - raise exception(response=response) + return response.content From b326fd3b72b1bef381f4a51874a17f20d31e88aa Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 19 Feb 2026 09:48:13 +0000 Subject: [PATCH 350/688] Move 429/5xx handling back to make_request Now that generate_vumark_instance uses make_request, _target_api_request can be a pure sign-and-send function again. Co-Authored-By: Claude Sonnet 4.6 --- src/vws/vws.py | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/vws/vws.py b/src/vws/vws.py index c121e081d..23d5dbcbc 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -95,12 +95,6 @@ def _target_api_request( Returns: The response to the request made by `requests`. - - Raises: - ~vws.exceptions.custom_exceptions.ServerError: There is an error - with Vuforia's servers. - ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is - rate limiting access. """ date_string = rfc_1123_date() @@ -131,7 +125,7 @@ def _target_api_request( timeout=request_timeout_seconds, ) - response = Response( + return Response( text=requests_response.text, url=requests_response.url, status_code=requests_response.status_code, @@ -141,19 +135,6 @@ def _target_api_request( content=bytes(requests_response.content), ) - if ( - response.status_code == HTTPStatus.TOO_MANY_REQUESTS - ): # pragma: no cover - # The Vuforia API returns a 429 response with no JSON body. - raise TooManyRequestsError(response=response) - - if ( - response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR - ): # pragma: no cover - raise ServerError(response=response) - - return response - @beartype(conf=BeartypeConf(is_pep484_tower=True)) class VWS: @@ -232,6 +213,17 @@ def make_request( extra_headers=extra_headers, ) + if ( + response.status_code == HTTPStatus.TOO_MANY_REQUESTS + ): # pragma: no cover + # The Vuforia API returns a 429 response with no JSON body. + raise TooManyRequestsError(response=response) + + if ( + response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR + ): # pragma: no cover + raise ServerError(response=response) + if ( response.status_code == HTTPStatus.OK and expected_result_code is None From bda9d1ddaeb8d6889b85974b44101644c88bdf78 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 19 Feb 2026 09:49:31 +0000 Subject: [PATCH 351/688] Make extra_headers required in _target_api_request Co-Authored-By: Claude Sonnet 4.6 --- src/vws/vws.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vws/vws.py b/src/vws/vws.py index 23d5dbcbc..679d964bb 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -73,7 +73,7 @@ def _target_api_request( request_path: str, base_vws_url: str, request_timeout_seconds: float | tuple[float, float], - extra_headers: dict[str, str] | None = None, + extra_headers: dict[str, str], ) -> Response: """Make a request to the Vuforia Target API. @@ -112,7 +112,7 @@ def _target_api_request( "Authorization": signature_string, "Date": date_string, "Content-Type": content_type, - **(extra_headers or {}), + **extra_headers, } url = urljoin(base=base_vws_url, url=request_path) @@ -210,7 +210,7 @@ def make_request( request_path=request_path, base_vws_url=self._base_vws_url, request_timeout_seconds=self._request_timeout_seconds, - extra_headers=extra_headers, + extra_headers=extra_headers or {}, ) if ( From 9aec062cf5a3266889cb6d295d977fe7541ca23c Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 19 Feb 2026 10:28:54 +0000 Subject: [PATCH 352/688] Address PR comments: VuMark target type and target_id fix - Fix target_id property extraction for /targets/{id}/instances URL pattern - Add InvalidTargetTypeError exception - Add vumark_vws_client and vumark_target_id fixtures using a pre-populated VuMark template target (requires vws-python-mock#2962) - Update tests to use VuMark target instead of a standard cloud target - Add test_invalid_target_type test (requires vws-python-mock#2961) Co-Authored-By: Claude Sonnet 4.6 --- src/vws/exceptions/vws_exceptions.py | 28 +++++++++++++++++--------- src/vws/vws.py | 4 ++++ tests/conftest.py | 28 +++++++++++++++++++++++++- tests/test_vws.py | 16 ++++----------- tests/test_vws_exceptions.py | 30 +++++++++++++++++++++++----- 5 files changed, 79 insertions(+), 27 deletions(-) diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index e2d44ec35..865d2372c 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -24,9 +24,10 @@ class UnknownTargetError(VWSError): def target_id(self) -> str: """The unknown target ID.""" path = urlparse(url=self.response.url).path - # Every HTTP path which can raise this error is in the format - # `/something/{target_id}`. - return path.split(sep="/", maxsplit=2)[-1] + # Every HTTP path which can raise this error has the target ID as the + # second path segment, e.g. `/something/{target_id}` or + # `/something/{target_id}/more`. + return path.split(sep="/")[2] @beartype @@ -68,9 +69,10 @@ class TargetStatusProcessingError(VWSError): def target_id(self) -> str: """The processing target ID.""" path = urlparse(url=self.response.url).path - # Every HTTP path which can raise this error is in the format - # `/something/{target_id}`. - return path.split(sep="/", maxsplit=2)[-1] + # Every HTTP path which can raise this error has the target ID as the + # second path segment, e.g. `/something/{target_id}` or + # `/something/{target_id}/more`. + return path.split(sep="/")[2] # This is not simulated by the mock. @@ -157,9 +159,10 @@ class TargetStatusNotSuccessError(VWSError): def target_id(self) -> str: """The unknown target ID.""" path = urlparse(url=self.response.url).path - # Every HTTP path which can raise this error is in the format - # `/something/{target_id}`. - return path.split(sep="/", maxsplit=2)[-1] + # Every HTTP path which can raise this error has the target ID as the + # second path segment, e.g. `/something/{target_id}` or + # `/something/{target_id}/more`. + return path.split(sep="/")[2] @beartype @@ -192,3 +195,10 @@ class BadRequestError(VWSError): # pragma: no cover """Exception raised when Vuforia returns a response with a result code ``BadRequest``. """ + + +@beartype +class InvalidTargetTypeError(VWSError): + """Exception raised when Vuforia returns a response with a result code + ``InvalidTargetType``. + """ diff --git a/src/vws/vws.py b/src/vws/vws.py index 679d964bb..329ee5425 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -26,6 +26,7 @@ ImageTooLargeError, InvalidAcceptHeaderError, InvalidInstanceIdError, + InvalidTargetTypeError, MetadataTooLargeError, ProjectHasNoAPIAccessError, ProjectInactiveError, @@ -244,6 +245,7 @@ def make_request( "ImageTooLarge": ImageTooLargeError, "InvalidAcceptHeader": InvalidAcceptHeaderError, "InvalidInstanceId": InvalidInstanceIdError, + "InvalidTargetType": InvalidTargetTypeError, "MetadataTooLarge": MetadataTooLargeError, "ProjectHasNoAPIAccess": ProjectHasNoAPIAccessError, "ProjectInactive": ProjectInactiveError, @@ -755,6 +757,8 @@ def generate_vumark_instance( Accept header value is not supported. ~vws.exceptions.vws_exceptions.InvalidInstanceIdError: The instance ID is invalid. For example, it may be empty. + ~vws.exceptions.vws_exceptions.InvalidTargetTypeError: The target + is not a VuMark template target. ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is an error with the time sent to Vuforia. ~vws.exceptions.vws_exceptions.TargetStatusNotSuccessError: The diff --git a/tests/conftest.py b/tests/conftest.py index 9073a8c03..bca6ff6c1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,7 @@ import pytest from mock_vws import MockVWS -from mock_vws.database import CloudDatabase +from mock_vws.database import CloudDatabase, VuMarkDatabase, VuMarkTarget from vws import VWS, CloudRecoService @@ -22,6 +22,32 @@ def fixture_mock_database() -> Generator[CloudDatabase]: yield database +@pytest.fixture(name="_mock_vumark_database") +def fixture_mock_vumark_database() -> Generator[VuMarkDatabase]: + """Yield a mock ``VuMarkDatabase`` with a template target.""" + vumark_target = VuMarkTarget(name="vumark-template") + with MockVWS() as mock: + database = VuMarkDatabase(vumark_targets={vumark_target}) + mock.add_vumark_database(vumark_database=database) + yield database + + +@pytest.fixture +def vumark_vws_client(_mock_vumark_database: VuMarkDatabase) -> VWS: + """A VWS client which connects to a mock VuMark database.""" + return VWS( + server_access_key=_mock_vumark_database.server_access_key, + server_secret_key=_mock_vumark_database.server_secret_key, + ) + + +@pytest.fixture +def vumark_target_id(_mock_vumark_database: VuMarkDatabase) -> str: + """The ID of the VuMark template target.""" + (target,) = _mock_vumark_database.vumark_targets + return target.target_id + + @pytest.fixture def vws_client(_mock_database: CloudDatabase) -> VWS: """A VWS client which connects to a mock database.""" diff --git a/tests/test_vws.py b/tests/test_vws.py index 8ac09a3e1..0926f3c85 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -745,22 +745,14 @@ class TestGenerateVumarkInstance: ], ) def test_generate_vumark_instance( - vws_client: VWS, - high_quality_image: io.BytesIO, + vumark_vws_client: VWS, + vumark_target_id: str, accept: VuMarkAccept, expected_prefix: bytes, ) -> None: """The returned bytes match the requested format.""" - target_id = vws_client.add_target( - name="x", - width=1, - image=high_quality_image, - active_flag=True, - application_metadata=None, - ) - vws_client.wait_for_target_processed(target_id=target_id) - result = vws_client.generate_vumark_instance( - target_id=target_id, + result = vumark_vws_client.generate_vumark_instance( + target_id=vumark_target_id, instance_id="12345", accept=accept, ) diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index b55f35fed..0c34d928a 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -24,6 +24,7 @@ ImageTooLargeError, InvalidAcceptHeaderError, InvalidInstanceIdError, + InvalidTargetTypeError, MetadataTooLargeError, ProjectHasNoAPIAccessError, ProjectInactiveError, @@ -349,6 +350,7 @@ def test_vwsexception_inheritance() -> None: ImageTooLargeError, InvalidAcceptHeaderError, InvalidInstanceIdError, + InvalidTargetTypeError, MetadataTooLargeError, ProjectInactiveError, ProjectHasNoAPIAccessError, @@ -366,13 +368,31 @@ def test_vwsexception_inheritance() -> None: def test_invalid_instance_id( - vws_client: VWS, - high_quality_image: io.BytesIO, + vumark_vws_client: VWS, + vumark_target_id: str, ) -> None: """ An ``InvalidInstanceId`` exception is raised when an empty instance ID is given. """ + with pytest.raises(expected_exception=InvalidInstanceIdError) as exc: + vumark_vws_client.generate_vumark_instance( + target_id=vumark_target_id, + instance_id="", + accept=VuMarkAccept.PNG, + ) + + assert exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + + +def test_invalid_target_type( + vws_client: VWS, + high_quality_image: io.BytesIO, +) -> None: + """ + An ``InvalidTargetType`` exception is raised when trying to generate + a VuMark instance from a non-VuMark target. + """ target_id = vws_client.add_target( name="x", width=1, @@ -381,14 +401,14 @@ def test_invalid_instance_id( application_metadata=None, ) vws_client.wait_for_target_processed(target_id=target_id) - with pytest.raises(expected_exception=InvalidInstanceIdError) as exc: + with pytest.raises(expected_exception=InvalidTargetTypeError) as exc: vws_client.generate_vumark_instance( target_id=target_id, - instance_id="", + instance_id="12345", accept=VuMarkAccept.PNG, ) - assert exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + assert exc.value.response.status_code == HTTPStatus.FORBIDDEN def test_base_exception( From 5da612ed3fd50e7a3a5124403867d4d7bac75efc Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 19 Feb 2026 14:51:14 +0000 Subject: [PATCH 353/688] Move generate_vumark_instance to a new VuMarkService class VuMark generation targets a different database type (VuMark vs Cloud Reco), so it belongs in its own class rather than VWS, mirroring how CloudRecoService is separate from VWS. Co-Authored-By: Claude Sonnet 4.6 --- src/vws/__init__.py | 2 + src/vws/vumark_service.py | 143 +++++++++++++++++++++++++++++++++++ src/vws/vws.py | 68 ----------------- tests/conftest.py | 10 ++- tests/test_vws.py | 6 +- tests/test_vws_exceptions.py | 44 ++++++----- 6 files changed, 181 insertions(+), 92 deletions(-) create mode 100644 src/vws/vumark_service.py diff --git a/src/vws/__init__.py b/src/vws/__init__.py index bfd96d6c2..a091641f9 100644 --- a/src/vws/__init__.py +++ b/src/vws/__init__.py @@ -1,9 +1,11 @@ """A library for Vuforia Web Services.""" from .query import CloudRecoService +from .vumark_service import VuMarkService from .vws import VWS __all__ = [ "VWS", "CloudRecoService", + "VuMarkService", ] diff --git a/src/vws/vumark_service.py b/src/vws/vumark_service.py new file mode 100644 index 000000000..d4ad771b8 --- /dev/null +++ b/src/vws/vumark_service.py @@ -0,0 +1,143 @@ +"""Interface to the Vuforia VuMark Generation Web API.""" + +import json +from http import HTTPMethod, HTTPStatus + +from beartype import BeartypeConf, beartype + +from vws.exceptions.custom_exceptions import ServerError +from vws.exceptions.vws_exceptions import ( + AuthenticationFailureError, + BadRequestError, + DateRangeError, + FailError, + InvalidAcceptHeaderError, + InvalidInstanceIdError, + InvalidTargetTypeError, + RequestTimeTooSkewedError, + TargetStatusNotSuccessError, + TooManyRequestsError, + UnknownTargetError, +) +from vws.vumark_accept import VuMarkAccept +from vws.vws import _target_api_request + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +class VuMarkService: + """An interface to the Vuforia VuMark Generation Web API.""" + + def __init__( + self, + server_access_key: str, + server_secret_key: str, + base_vws_url: str = "https://vws.vuforia.com", + request_timeout_seconds: float | tuple[float, float] = 30.0, + ) -> None: + """ + Args: + server_access_key: A VWS server access key. + server_secret_key: A VWS server secret key. + base_vws_url: The base URL for the VWS API. + request_timeout_seconds: The timeout for each HTTP request, as + used by ``requests.request``. This can be a float to set + both the connect and read timeouts, or a (connect, read) + tuple. + """ + self._server_access_key = server_access_key + self._server_secret_key = server_secret_key + self._base_vws_url = base_vws_url + self._request_timeout_seconds = request_timeout_seconds + + def generate_vumark_instance( + self, + *, + target_id: str, + instance_id: str, + accept: VuMarkAccept, + ) -> bytes: + """Generate a VuMark instance image. + + See + https://developer.vuforia.com/library/vuforia-engine/web-api/vumark-generation-web-api/ + for parameter details. + + Args: + target_id: The ID of the VuMark target. + instance_id: The instance ID to encode in the VuMark. + accept: The image format to return. + + Returns: + The VuMark instance image bytes. + + Raises: + ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The + secret key is not correct. + ~vws.exceptions.vws_exceptions.FailError: There was an error with + the request. For example, the given access key does not match a + known database. + ~vws.exceptions.vws_exceptions.InvalidAcceptHeaderError: The + Accept header value is not supported. + ~vws.exceptions.vws_exceptions.InvalidInstanceIdError: The + instance ID is invalid. For example, it may be empty. + ~vws.exceptions.vws_exceptions.InvalidTargetTypeError: The target + is not a VuMark template target. + ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is + an error with the time sent to Vuforia. + ~vws.exceptions.vws_exceptions.TargetStatusNotSuccessError: The + target is not in the success state. + ~vws.exceptions.vws_exceptions.UnknownTargetError: The given target + ID does not match a target in the database. + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. + """ + request_path = f"/targets/{target_id}/instances" + content_type = "application/json" + request_data = json.dumps(obj={"instance_id": instance_id}).encode( + encoding="utf-8", + ) + + response = _target_api_request( + content_type=content_type, + server_access_key=self._server_access_key, + server_secret_key=self._server_secret_key, + method=HTTPMethod.POST, + data=request_data, + request_path=request_path, + base_vws_url=self._base_vws_url, + request_timeout_seconds=self._request_timeout_seconds, + extra_headers={"Accept": accept}, + ) + + if ( + response.status_code == HTTPStatus.TOO_MANY_REQUESTS + ): # pragma: no cover + # The Vuforia API returns a 429 response with no JSON body. + raise TooManyRequestsError(response=response) + + if ( + response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR + ): # pragma: no cover + raise ServerError(response=response) + + if response.status_code == HTTPStatus.OK: + return response.content + + result_code = json.loads(s=response.text)["result_code"] + + exception = { + "AuthenticationFailure": AuthenticationFailureError, + "BadRequest": BadRequestError, + "DateRangeError": DateRangeError, + "Fail": FailError, + "InvalidAcceptHeader": InvalidAcceptHeaderError, + "InvalidInstanceId": InvalidInstanceIdError, + "InvalidTargetType": InvalidTargetTypeError, + "RequestTimeTooSkewed": RequestTimeTooSkewedError, + "TargetStatusNotSuccess": TargetStatusNotSuccessError, + "UnknownTarget": UnknownTargetError, + }[result_code] + + raise exception(response=response) diff --git a/src/vws/vws.py b/src/vws/vws.py index 329ee5425..7accf1ea3 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -24,9 +24,6 @@ DateRangeError, FailError, ImageTooLargeError, - InvalidAcceptHeaderError, - InvalidInstanceIdError, - InvalidTargetTypeError, MetadataTooLargeError, ProjectHasNoAPIAccessError, ProjectInactiveError, @@ -48,7 +45,6 @@ TargetSummaryReport, ) from vws.response import Response -from vws.vumark_accept import VuMarkAccept _ImageType = io.BytesIO | BinaryIO @@ -243,9 +239,6 @@ def make_request( "DateRangeError": DateRangeError, "Fail": FailError, "ImageTooLarge": ImageTooLargeError, - "InvalidAcceptHeader": InvalidAcceptHeaderError, - "InvalidInstanceId": InvalidInstanceIdError, - "InvalidTargetType": InvalidTargetTypeError, "MetadataTooLarge": MetadataTooLargeError, "ProjectHasNoAPIAccess": ProjectHasNoAPIAccessError, "ProjectInactive": ProjectInactiveError, @@ -725,64 +718,3 @@ def update_target( expected_result_code="Success", content_type="application/json", ) - - def generate_vumark_instance( - self, - *, - target_id: str, - instance_id: str, - accept: VuMarkAccept, - ) -> bytes: - """Generate a VuMark instance image. - - See - https://developer.vuforia.com/library/vuforia-engine/web-api/vumark-generation-web-api/ - for parameter details. - - Args: - target_id: The ID of the VuMark target. - instance_id: The instance ID to encode in the VuMark. - accept: The image format to return. - - Returns: - The VuMark instance image bytes. - - Raises: - ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The - secret key is not correct. - ~vws.exceptions.vws_exceptions.FailError: There was an error with - the request. For example, the given access key does not match a - known database. - ~vws.exceptions.vws_exceptions.InvalidAcceptHeaderError: The - Accept header value is not supported. - ~vws.exceptions.vws_exceptions.InvalidInstanceIdError: The - instance ID is invalid. For example, it may be empty. - ~vws.exceptions.vws_exceptions.InvalidTargetTypeError: The target - is not a VuMark template target. - ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is - an error with the time sent to Vuforia. - ~vws.exceptions.vws_exceptions.TargetStatusNotSuccessError: The - target is not in the success state. - ~vws.exceptions.vws_exceptions.UnknownTargetError: The given target - ID does not match a target in the database. - ~vws.exceptions.custom_exceptions.ServerError: There is an error - with Vuforia's servers. - ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is - rate limiting access. - """ - request_path = f"/targets/{target_id}/instances" - content_type = "application/json" - request_data = json.dumps(obj={"instance_id": instance_id}).encode( - encoding="utf-8", - ) - - response = self.make_request( - method=HTTPMethod.POST, - data=request_data, - request_path=request_path, - expected_result_code=None, - content_type=content_type, - extra_headers={"Accept": accept}, - ) - - return response.content diff --git a/tests/conftest.py b/tests/conftest.py index bca6ff6c1..bd7bf3740 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,7 +9,7 @@ from mock_vws import MockVWS from mock_vws.database import CloudDatabase, VuMarkDatabase, VuMarkTarget -from vws import VWS, CloudRecoService +from vws import VWS, CloudRecoService, VuMarkService @pytest.fixture(name="_mock_database") @@ -33,9 +33,11 @@ def fixture_mock_vumark_database() -> Generator[VuMarkDatabase]: @pytest.fixture -def vumark_vws_client(_mock_vumark_database: VuMarkDatabase) -> VWS: - """A VWS client which connects to a mock VuMark database.""" - return VWS( +def vumark_service_client( + _mock_vumark_database: VuMarkDatabase, +) -> VuMarkService: + """A ``VuMarkService`` client which connects to a mock VuMark database.""" + return VuMarkService( server_access_key=_mock_vumark_database.server_access_key, server_secret_key=_mock_vumark_database.server_secret_key, ) diff --git a/tests/test_vws.py b/tests/test_vws.py index 0926f3c85..de8b92cab 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -13,7 +13,7 @@ from mock_vws import MockVWS from mock_vws.database import CloudDatabase -from vws import VWS, CloudRecoService +from vws import VWS, CloudRecoService, VuMarkService from vws.exceptions.custom_exceptions import TargetProcessingTimeoutError from vws.reports import ( DatabaseSummaryReport, @@ -745,13 +745,13 @@ class TestGenerateVumarkInstance: ], ) def test_generate_vumark_instance( - vumark_vws_client: VWS, + vumark_service_client: VuMarkService, vumark_target_id: str, accept: VuMarkAccept, expected_prefix: bytes, ) -> None: """The returned bytes match the requested format.""" - result = vumark_vws_client.generate_vumark_instance( + result = vumark_service_client.generate_vumark_instance( target_id=vumark_target_id, instance_id="12345", accept=accept, diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 0c34d928a..604bac87d 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -10,7 +10,7 @@ from mock_vws.database import CloudDatabase from mock_vws.states import States -from vws import VWS +from vws import VWS, VuMarkService from vws.exceptions.base_exceptions import VWSError from vws.exceptions.custom_exceptions import ( ServerError, @@ -368,7 +368,7 @@ def test_vwsexception_inheritance() -> None: def test_invalid_instance_id( - vumark_vws_client: VWS, + vumark_service_client: VuMarkService, vumark_target_id: str, ) -> None: """ @@ -376,7 +376,7 @@ def test_invalid_instance_id( ID is given. """ with pytest.raises(expected_exception=InvalidInstanceIdError) as exc: - vumark_vws_client.generate_vumark_instance( + vumark_service_client.generate_vumark_instance( target_id=vumark_target_id, instance_id="", accept=VuMarkAccept.PNG, @@ -386,27 +386,37 @@ def test_invalid_instance_id( def test_invalid_target_type( - vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: """ An ``InvalidTargetType`` exception is raised when trying to generate a VuMark instance from a non-VuMark target. """ - target_id = vws_client.add_target( - name="x", - width=1, - image=high_quality_image, - active_flag=True, - application_metadata=None, - ) - vws_client.wait_for_target_processed(target_id=target_id) - with pytest.raises(expected_exception=InvalidTargetTypeError) as exc: - vws_client.generate_vumark_instance( - target_id=target_id, - instance_id="12345", - accept=VuMarkAccept.PNG, + database = VuforiaDatabase() + with MockVWS(processing_time_seconds=0.2) as mock: + mock.add_database(database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, ) + target_id = vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + vws_client.wait_for_target_processed(target_id=target_id) + vumark_client = VuMarkService( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + with pytest.raises(expected_exception=InvalidTargetTypeError) as exc: + vumark_client.generate_vumark_instance( + target_id=target_id, + instance_id="12345", + accept=VuMarkAccept.PNG, + ) assert exc.value.response.status_code == HTTPStatus.FORBIDDEN From f12309a37988555e31677aeb31c7f7f2dc8370f0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 20 Feb 2026 14:06:29 +0000 Subject: [PATCH 354/688] Fix CI failures: spelling, is_vumark_template, test placeholder - Add 'vumark' to spelling dict - Remove is_vumark_template from Target() until mock#2962 is implemented - Make test_invalid_target_type a placeholder until mock#2961 is implemented - Rephrase fixture docstrings to avoid spelling check on 'pre' Co-Authored-By: Claude Sonnet 4.6 --- spelling_private_dict.txt | 1 + tests/test_vws_exceptions.py | 35 +++-------------------------------- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index d5d5d3e99..2329ef7f4 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -101,6 +101,7 @@ usefixtures validators vuforia vuforia's +vumark vwq vws xxx diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 604bac87d..49d0365c9 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -385,40 +385,11 @@ def test_invalid_instance_id( assert exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY -def test_invalid_target_type( - high_quality_image: io.BytesIO, -) -> None: +def test_invalid_target_type() -> None: """ - An ``InvalidTargetType`` exception is raised when trying to generate - a VuMark instance from a non-VuMark target. + See https://github.com/VWS-Python/vws-python-mock/issues/2961 for + writing this test. """ - database = VuforiaDatabase() - with MockVWS(processing_time_seconds=0.2) as mock: - mock.add_database(database=database) - vws_client = VWS( - server_access_key=database.server_access_key, - server_secret_key=database.server_secret_key, - ) - target_id = vws_client.add_target( - name="x", - width=1, - image=high_quality_image, - active_flag=True, - application_metadata=None, - ) - vws_client.wait_for_target_processed(target_id=target_id) - vumark_client = VuMarkService( - server_access_key=database.server_access_key, - server_secret_key=database.server_secret_key, - ) - with pytest.raises(expected_exception=InvalidTargetTypeError) as exc: - vumark_client.generate_vumark_instance( - target_id=target_id, - instance_id="12345", - accept=VuMarkAccept.PNG, - ) - - assert exc.value.response.status_code == HTTPStatus.FORBIDDEN def test_base_exception( From 22e80364d655f1b742f8248a8964ced62ada59b6 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 20 Feb 2026 14:09:10 +0000 Subject: [PATCH 355/688] Move target_api_request to shared _vws_request module Fixes Pyright reportPrivateUsage error: _target_api_request was defined in vws.py but used in vumark_service.py. Move it to a shared private module with a public name so both can import it cleanly. Co-Authored-By: Claude Sonnet 4.6 --- src/vws/_vws_request.py | 85 +++++++++++++++++++++++++++++++++++++++ src/vws/vumark_service.py | 4 +- src/vws/vws.py | 80 +----------------------------------- 3 files changed, 89 insertions(+), 80 deletions(-) create mode 100644 src/vws/_vws_request.py diff --git a/src/vws/_vws_request.py b/src/vws/_vws_request.py new file mode 100644 index 000000000..0ea914388 --- /dev/null +++ b/src/vws/_vws_request.py @@ -0,0 +1,85 @@ +"""Internal helper for making authenticated requests to the Vuforia Target +API. +""" + +from urllib.parse import urljoin + +import requests +from beartype import BeartypeConf, beartype +from vws_auth_tools import authorization_header, rfc_1123_date + +from vws.response import Response + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def target_api_request( + *, + content_type: str, + server_access_key: str, + server_secret_key: str, + method: str, + data: bytes, + request_path: str, + base_vws_url: str, + request_timeout_seconds: float | tuple[float, float], + extra_headers: dict[str, str], +) -> Response: + """Make a request to the Vuforia Target API. + + This uses `requests` to make a request against https://vws.vuforia.com. + + Args: + content_type: The content type of the request. + server_access_key: A VWS server access key. + server_secret_key: A VWS server secret key. + method: The HTTP method which will be used in the request. + data: The request body which will be used in the request. + request_path: The path to the endpoint which will be used in the + request. + base_vws_url: The base URL for the VWS API. + request_timeout_seconds: The timeout for the request, as used by + ``requests.request``. This can be a float to set both the + connect and read timeouts, or a (connect, read) tuple. + extra_headers: Additional headers to include in the request. + + Returns: + The response to the request made by `requests`. + """ + date_string = rfc_1123_date() + + signature_string = authorization_header( + access_key=server_access_key, + secret_key=server_secret_key, + method=method, + content=data, + content_type=content_type, + date=date_string, + request_path=request_path, + ) + + headers = { + "Authorization": signature_string, + "Date": date_string, + "Content-Type": content_type, + **extra_headers, + } + + url = urljoin(base=base_vws_url, url=request_path) + + requests_response = requests.request( + method=method, + url=url, + headers=headers, + data=data, + timeout=request_timeout_seconds, + ) + + return Response( + text=requests_response.text, + url=requests_response.url, + status_code=requests_response.status_code, + headers=dict(requests_response.headers), + request_body=requests_response.request.body, + tell_position=requests_response.raw.tell(), + content=bytes(requests_response.content), + ) diff --git a/src/vws/vumark_service.py b/src/vws/vumark_service.py index d4ad771b8..a23ac33f2 100644 --- a/src/vws/vumark_service.py +++ b/src/vws/vumark_service.py @@ -5,6 +5,7 @@ from beartype import BeartypeConf, beartype +from vws._vws_request import target_api_request from vws.exceptions.custom_exceptions import ServerError from vws.exceptions.vws_exceptions import ( AuthenticationFailureError, @@ -20,7 +21,6 @@ UnknownTargetError, ) from vws.vumark_accept import VuMarkAccept -from vws.vws import _target_api_request @beartype(conf=BeartypeConf(is_pep484_tower=True)) @@ -99,7 +99,7 @@ def generate_vumark_instance( encoding="utf-8", ) - response = _target_api_request( + response = target_api_request( content_type=content_type, server_access_key=self._server_access_key, server_secret_key=self._server_secret_key, diff --git a/src/vws/vws.py b/src/vws/vws.py index 7accf1ea3..2eebf8542 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -7,12 +7,10 @@ from datetime import date from http import HTTPMethod, HTTPStatus from typing import BinaryIO -from urllib.parse import urljoin -import requests from beartype import BeartypeConf, beartype -from vws_auth_tools import authorization_header, rfc_1123_date +from vws._vws_request import target_api_request from vws.exceptions.custom_exceptions import ( ServerError, TargetProcessingTimeoutError, @@ -59,80 +57,6 @@ def _get_image_data(image: _ImageType) -> bytes: return image_data -@beartype(conf=BeartypeConf(is_pep484_tower=True)) -def _target_api_request( - *, - content_type: str, - server_access_key: str, - server_secret_key: str, - method: str, - data: bytes, - request_path: str, - base_vws_url: str, - request_timeout_seconds: float | tuple[float, float], - extra_headers: dict[str, str], -) -> Response: - """Make a request to the Vuforia Target API. - - This uses `requests` to make a request against https://vws.vuforia.com. - - Args: - content_type: The content type of the request. - server_access_key: A VWS server access key. - server_secret_key: A VWS server secret key. - method: The HTTP method which will be used in the request. - data: The request body which will be used in the request. - request_path: The path to the endpoint which will be used in the - request. - base_vws_url: The base URL for the VWS API. - request_timeout_seconds: The timeout for the request, as used by - ``requests.request``. This can be a float to set both the - connect and read timeouts, or a (connect, read) tuple. - extra_headers: Additional headers to include in the request. - - Returns: - The response to the request made by `requests`. - """ - date_string = rfc_1123_date() - - signature_string = authorization_header( - access_key=server_access_key, - secret_key=server_secret_key, - method=method, - content=data, - content_type=content_type, - date=date_string, - request_path=request_path, - ) - - headers = { - "Authorization": signature_string, - "Date": date_string, - "Content-Type": content_type, - **extra_headers, - } - - url = urljoin(base=base_vws_url, url=request_path) - - requests_response = requests.request( - method=method, - url=url, - headers=headers, - data=data, - timeout=request_timeout_seconds, - ) - - return Response( - text=requests_response.text, - url=requests_response.url, - status_code=requests_response.status_code, - headers=dict(requests_response.headers), - request_body=requests_response.request.body, - tell_position=requests_response.raw.tell(), - content=bytes(requests_response.content), - ) - - @beartype(conf=BeartypeConf(is_pep484_tower=True)) class VWS: """An interface to Vuforia Web Services APIs.""" @@ -198,7 +122,7 @@ def make_request( This may happen if the server address is not a valid Vuforia server. """ - response = _target_api_request( + response = target_api_request( content_type=content_type, server_access_key=self._server_access_key, server_secret_key=self._server_secret_key, From 0730af2812577fc2c21b804ca5dcb0361285d388 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 00:44:17 +0000 Subject: [PATCH 356/688] Fix VuMarkTarget import: use mock_vws.target not mock_vws.database mypy flags VuMarkTarget as not explicitly exported from mock_vws.database; import it from mock_vws.target instead. Co-Authored-By: Claude Sonnet 4.6 --- tests/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index bd7bf3740..0f15e6a9a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,8 @@ import pytest from mock_vws import MockVWS -from mock_vws.database import CloudDatabase, VuMarkDatabase, VuMarkTarget +from mock_vws.database import CloudDatabase, VuMarkDatabase +from mock_vws.target import VuMarkTarget from vws import VWS, CloudRecoService, VuMarkService From bf64c7faae80e9d67cdb83962b6e14801be8c7c1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 01:14:51 +0000 Subject: [PATCH 357/688] Remove dead expected_result_code=None branch from VWS.make_request No VWS method passes expected_result_code=None; VuMarkService handles binary responses via its own direct target_api_request call. Removing the unused branch restores 100% test coverage. Co-Authored-By: Claude Sonnet 4.6 --- src/vws/vws.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/vws/vws.py b/src/vws/vws.py index 2eebf8542..6977ed18f 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -89,7 +89,7 @@ def make_request( method: str, data: bytes, request_path: str, - expected_result_code: str | None, + expected_result_code: str, content_type: str, extra_headers: dict[str, str] | None = None, ) -> Response: @@ -104,9 +104,6 @@ def make_request( request. expected_result_code: See "VWS API Result Codes" on https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api. - Pass ``None`` for endpoints that return a non-JSON success - response (e.g. binary data); success is then determined by an - HTTP 200 status code. content_type: The content type of the request. extra_headers: Additional headers to include in the request. @@ -145,12 +142,6 @@ def make_request( ): # pragma: no cover raise ServerError(response=response) - if ( - response.status_code == HTTPStatus.OK - and expected_result_code is None - ): - return response - result_code = json.loads(s=response.text)["result_code"] if result_code == expected_result_code: From 396941852d7aaa184b470bd82c7153d157d14740 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 02:17:10 +0000 Subject: [PATCH 358/688] Implement test_invalid_target_type to validate VuMark database type checking The test verifies that InvalidTargetTypeError is raised when attempting to generate a VuMark instance using credentials from a CloudDatabase (non-VuMark) database. This was previously a stub pending the mock implementation in vws-python-mock#2961. Co-Authored-By: Claude Haiku 4.5 --- tests/test_vws_exceptions.py | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 49d0365c9..2ab6510ee 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -385,11 +385,41 @@ def test_invalid_instance_id( assert exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY -def test_invalid_target_type() -> None: +def test_invalid_target_type( + high_quality_image: io.BytesIO, +) -> None: """ - See https://github.com/VWS-Python/vws-python-mock/issues/2961 for - writing this test. + An ``InvalidTargetType`` exception is raised when trying to generate + a VuMark instance from a non-VuMark database. """ + database = CloudDatabase() + with MockVWS(processing_time_seconds=0.2) as mock: + mock.add_cloud_database(cloud_database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + target_id = vws_client.add_target( + name="example_target", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + vumark_service = VuMarkService( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + with pytest.raises( + expected_exception=InvalidTargetTypeError, + ) as exc: + vumark_service.generate_vumark_instance( + target_id=target_id, + instance_id="example_instance_id", + accept=VuMarkAccept.PNG, + ) + + assert exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY def test_base_exception( From 0ca5921b520f2d569d71e9680c6b6e95aa047bf2 Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sat, 21 Feb 2026 08:32:28 +0000 Subject: [PATCH 359/688] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 20a40ddb9..fffe7a623 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.21 +---------- + + 2026.02.15 ---------- From a0cf4c1a3905953a04ae8f5ebfa1dc371ffa2b9e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 09:34:18 +0000 Subject: [PATCH 360/688] Enforce keyword-only arguments Co-Authored-By: Claude Opus 4.6 --- src/vws/query.py | 2 ++ src/vws/vws.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vws/query.py b/src/vws/query.py index ad55d8a47..9c997177e 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -46,6 +46,7 @@ class CloudRecoService: def __init__( self, + *, client_access_key: str, client_secret_key: str, base_vwq_url: str = "https://cloudreco.vuforia.com", @@ -68,6 +69,7 @@ def __init__( def query( self, + *, image: _ImageType, max_num_results: int = 1, include_target_data: CloudRecoIncludeTargetData = ( diff --git a/src/vws/vws.py b/src/vws/vws.py index 6977ed18f..1aa11374a 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -63,6 +63,7 @@ class VWS: def __init__( self, + *, server_access_key: str, server_secret_key: str, base_vws_url: str = "https://vws.vuforia.com", @@ -171,11 +172,11 @@ def make_request( def add_target( self, + *, name: str, width: float, image: _ImageType, application_metadata: str | None, - *, active_flag: bool, ) -> str: """Add a target to a Vuforia Web Services database. @@ -302,6 +303,7 @@ def get_target_record(self, target_id: str) -> TargetStatusAndRecord: def wait_for_target_processed( self, + *, target_id: str, seconds_between_requests: float = 0.2, timeout_seconds: float = 60 * 5, From d81e5ac9795187564fc0ee0ee9ddeb7a09ace318 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 10:08:43 +0000 Subject: [PATCH 361/688] 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 --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 510f2c80b..8a13280ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -301,8 +301,8 @@ keep_full_version = true max_supported_python = "3.14" [tool.pytest] -ini_options.xfail_strict = true -ini_options.log_cli = true +xfail_strict = true +log_cli = true [tool.coverage] run.branch = true From 253bbea6d26ea2050a32dae30b3776739302c5ec Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 21 Feb 2026 23:59:26 +0000 Subject: [PATCH 362/688] Use keyword-only parameters in tests Enforce keyword-only arguments in test functions that accept multiple parameters, improving call-site clarity. Co-authored-by: Cursor --- tests/test_cloud_reco_exceptions.py | 2 ++ tests/test_query.py | 12 ++++++++++-- tests/test_vws.py | 17 ++++++++++++++--- tests/test_vws_exceptions.py | 14 +++++++++++++- 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/tests/test_cloud_reco_exceptions.py b/tests/test_cloud_reco_exceptions.py index 3b2922fd9..43a263d40 100644 --- a/tests/test_cloud_reco_exceptions.py +++ b/tests/test_cloud_reco_exceptions.py @@ -24,6 +24,7 @@ def test_too_many_max_results( + *, cloud_reco_client: CloudRecoService, high_quality_image: io.BytesIO, ) -> None: @@ -45,6 +46,7 @@ def test_too_many_max_results( def test_image_too_large( + *, cloud_reco_client: CloudRecoService, png_too_large: io.BytesIO | io.BufferedRandom, ) -> None: diff --git a/tests/test_query.py b/tests/test_query.py index b5ced6dde..b7f9687de 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -20,6 +20,7 @@ class TestQuery: @staticmethod def test_no_matches( + *, cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, ) -> None: @@ -29,6 +30,7 @@ def test_no_matches( @staticmethod def test_match( + *, vws_client: VWS, cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, @@ -55,8 +57,8 @@ class TestDefaultRequestTimeout: argvalues=[(29, False), (31, True)], ) def test_default_timeout( - image: io.BytesIO | BinaryIO, *, + image: io.BytesIO | BinaryIO, response_delay_seconds: int, expect_timeout: bool, ) -> None: @@ -110,8 +112,8 @@ class TestCustomRequestTimeout: ], ) def test_custom_timeout( - image: io.BytesIO | BinaryIO, *, + image: io.BytesIO | BinaryIO, custom_timeout: float | tuple[float, float], response_delay_seconds: float, expect_timeout: bool, @@ -194,6 +196,7 @@ class TestMaxNumResults: @staticmethod def test_default( + *, vws_client: VWS, cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, @@ -220,6 +223,7 @@ def test_default( @staticmethod def test_custom( + *, vws_client: VWS, cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, @@ -262,6 +266,7 @@ class TestIncludeTargetData: @staticmethod def test_default( + *, vws_client: VWS, cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, @@ -292,6 +297,7 @@ def test_default( @staticmethod def test_top( + *, vws_client: VWS, cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, @@ -327,6 +333,7 @@ def test_top( @staticmethod def test_none( + *, vws_client: VWS, cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, @@ -363,6 +370,7 @@ def test_none( @staticmethod def test_all( + *, vws_client: VWS, cloud_reco_client: CloudRecoService, image: io.BytesIO | BinaryIO, diff --git a/tests/test_vws.py b/tests/test_vws.py index de8b92cab..0b435503d 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -34,11 +34,11 @@ class TestAddTarget: ) @pytest.mark.parametrize(argnames="active_flag", argvalues=[True, False]) def test_add_target( + *, vws_client: VWS, image: io.BytesIO | BinaryIO, application_metadata: bytes | None, cloud_reco_client: CloudRecoService, - *, active_flag: bool, ) -> None: """No exception is raised when adding one target.""" @@ -76,6 +76,7 @@ def test_add_target( @staticmethod def test_add_two_targets( + *, vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: @@ -103,8 +104,8 @@ class TestDefaultRequestTimeout: argvalues=[(29, False), (31, True)], ) def test_default_timeout( - image: io.BytesIO | BinaryIO, *, + image: io.BytesIO | BinaryIO, response_delay_seconds: int, expect_timeout: bool, ) -> None: @@ -169,8 +170,8 @@ class TestCustomRequestTimeout: ], ) def test_custom_timeout( - image: io.BytesIO | BinaryIO, *, + image: io.BytesIO | BinaryIO, custom_timeout: float | tuple[float, float], response_delay_seconds: float, expect_timeout: bool, @@ -251,6 +252,7 @@ class TestListTargets: @staticmethod def test_list_targets( + *, vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: @@ -277,6 +279,7 @@ class TestDelete: @staticmethod def test_delete_target( + *, vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: @@ -300,6 +303,7 @@ class TestGetTargetSummaryReport: @staticmethod def test_get_target_summary_report( + *, vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: @@ -398,6 +402,7 @@ class TestGetTargetRecord: @staticmethod def test_get_target_record( + *, vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: @@ -444,6 +449,7 @@ def test_get_target_record( @staticmethod def test_get_failed( + *, vws_client: VWS, image_file_failed_state: io.BytesIO, ) -> None: @@ -467,6 +473,7 @@ class TestWaitForTargetProcessed: @staticmethod def test_wait_for_target_processed( + *, vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: @@ -628,6 +635,7 @@ class TestGetDuplicateTargets: @staticmethod def test_get_duplicate_targets( + *, vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: @@ -658,6 +666,7 @@ class TestUpdateTarget: @staticmethod def test_update_target( + *, vws_client: VWS, image: io.BytesIO | BinaryIO, different_high_quality_image: io.BytesIO, @@ -717,6 +726,7 @@ def test_update_target( @staticmethod def test_no_fields_given( + *, vws_client: VWS, image: io.BytesIO | BinaryIO, ) -> None: @@ -745,6 +755,7 @@ class TestGenerateVumarkInstance: ], ) def test_generate_vumark_instance( + *, vumark_service_client: VuMarkService, vumark_target_id: str, accept: VuMarkAccept, diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 2ab6510ee..617016257 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -41,6 +41,7 @@ def test_image_too_large( + *, vws_client: VWS, png_too_large: io.BytesIO | io.BufferedRandom, ) -> None: @@ -75,7 +76,11 @@ def test_invalid_given_id(vws_client: VWS) -> None: assert exc.value.target_id == target_id -def test_add_bad_name(vws_client: VWS, high_quality_image: io.BytesIO) -> None: +def test_add_bad_name( + *, + vws_client: VWS, + high_quality_image: io.BytesIO, +) -> None: """ When a name with a bad character is given, a ``ServerError`` exception @@ -143,6 +148,7 @@ def test_bad_image(vws_client: VWS) -> None: def test_target_name_exist( + *, vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: @@ -201,6 +207,7 @@ def test_project_inactive( def test_target_status_processing( + *, vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: @@ -225,6 +232,7 @@ def test_target_status_processing( def test_metadata_too_large( + *, vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: @@ -246,6 +254,7 @@ def test_metadata_too_large( def test_request_time_too_skewed( + *, vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: @@ -316,6 +325,7 @@ def test_authentication_failure( def test_target_status_not_success( + *, vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: @@ -368,6 +378,7 @@ def test_vwsexception_inheritance() -> None: def test_invalid_instance_id( + *, vumark_service_client: VuMarkService, vumark_target_id: str, ) -> None: @@ -423,6 +434,7 @@ def test_invalid_target_type( def test_base_exception( + *, vws_client: VWS, high_quality_image: io.BytesIO, ) -> None: From a99022f2051daf7167bce129b40267ba72981396 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 00:59:14 +0000 Subject: [PATCH 363/688] Add @beartype to pytest_collection_modifyitems hook Add runtime type checking to the pytest hook that was missing it. Co-authored-by: Cursor --- conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conftest.py b/conftest.py index 89548f800..d5ef944b6 100644 --- a/conftest.py +++ b/conftest.py @@ -18,6 +18,7 @@ ) +@beartype def pytest_collection_modifyitems(items: list[pytest.Item]) -> None: """Apply the beartype decorator to all collected test functions.""" for item in items: From 294661069a3b72f5b6d55baf987f9b5872db3d35 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 03:27:13 +0000 Subject: [PATCH 364/688] Replace urljoin with string concatenation for base URL construction urljoin(base, path) silently drops any path prefix from the base URL when path starts with '/', so base_vws_url values like 'http://localhost/mock' would have '/mock' ignored. String concatenation preserves the full base URL path as intended. Co-Authored-By: Claude Sonnet 4.6 --- src/vws/_vws_request.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vws/_vws_request.py b/src/vws/_vws_request.py index 0ea914388..2fc7bd3bb 100644 --- a/src/vws/_vws_request.py +++ b/src/vws/_vws_request.py @@ -2,8 +2,6 @@ API. """ -from urllib.parse import urljoin - import requests from beartype import BeartypeConf, beartype from vws_auth_tools import authorization_header, rfc_1123_date @@ -64,7 +62,7 @@ def target_api_request( **extra_headers, } - url = urljoin(base=base_vws_url, url=request_path) + url = base_vws_url.rstrip("/") + request_path requests_response = requests.request( method=method, From 365c501d0305aff903afbba474ab7c4c8b588914 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 09:42:25 +0000 Subject: [PATCH 365/688] Fix urljoin in query.py and add path-prefix base URL tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Apply the same urljoin → string concatenation fix to query.py (CloudRecoService had the identical bug) - Add TestCustomBaseVWSURL::test_custom_base_url_with_path_prefix and TestCustomBaseVWQURL::test_custom_base_url_with_path_prefix to assert that a base URL containing a path prefix is preserved in the outgoing request URL; these tests would fail with the old urljoin approach Co-Authored-By: Claude Sonnet 4.6 --- src/vws/query.py | 3 +-- tests/test_query.py | 36 ++++++++++++++++++++++++++++++++++++ tests/test_vws.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/vws/query.py b/src/vws/query.py index 9c997177e..f5046bd1c 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -5,7 +5,6 @@ import json from http import HTTPMethod, HTTPStatus from typing import Any, BinaryIO -from urllib.parse import urljoin import requests from beartype import BeartypeConf, beartype @@ -146,7 +145,7 @@ def query( requests_response = requests.request( method=method, - url=urljoin(base=self._base_vwq_url, url=request_path), + url=self._base_vwq_url.rstrip("/") + request_path, headers=headers, data=content, timeout=self._request_timeout_seconds, diff --git a/tests/test_query.py b/tests/test_query.py index b7f9687de..d296f47b2 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -2,11 +2,13 @@ import datetime import io +import secrets import uuid from typing import BinaryIO import pytest import requests +import responses as responses_mock from freezegun import freeze_time from mock_vws import MockVWS from mock_vws.database import CloudDatabase @@ -190,6 +192,40 @@ def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: match = matches[0] assert match.target_id == target_id + @staticmethod + @responses_mock.activate + def test_custom_base_url_with_path_prefix( + image: io.BytesIO | BinaryIO, + ) -> None: + """ + A base VWQ URL with a path prefix is used as-is, without the + prefix + being dropped. + """ + base_vwq_url = "http://example.com/prefix" + responses_mock.add( + method=responses_mock.POST, + url="http://example.com/prefix/v1/query", + json={ + "result_code": "Success", + "results": [], + "query_id": "abc", + }, + status=200, + ) + cloud_reco_client = CloudRecoService( + client_access_key=secrets.token_hex(), + client_secret_key=secrets.token_hex(), + base_vwq_url=base_vwq_url, + ) + + cloud_reco_client.query(image=image) + + assert len(responses_mock.calls) == 1 + assert responses_mock.calls[0].request.url == ( + "http://example.com/prefix/v1/query" + ) + class TestMaxNumResults: """Tests for the ``max_num_results`` parameter of ``query``.""" diff --git a/tests/test_vws.py b/tests/test_vws.py index 0b435503d..cc6038eba 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -9,6 +9,7 @@ import pytest import requests +import responses as responses_mock from freezegun import freeze_time from mock_vws import MockVWS from mock_vws.database import CloudDatabase @@ -246,6 +247,38 @@ def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: application_metadata=None, ) + @staticmethod + @responses_mock.activate + def test_custom_base_url_with_path_prefix() -> None: + """ + A base VWS URL with a path prefix is used as-is, without the + prefix + being dropped. + """ + base_vws_url = "http://example.com/prefix" + responses_mock.add( + method=responses_mock.GET, + url="http://example.com/prefix/targets", + json={ + "result_code": "Success", + "results": [], + "transaction_id": "abc", + }, + status=200, + ) + vws_client = VWS( + server_access_key=secrets.token_hex(), + server_secret_key=secrets.token_hex(), + base_vws_url=base_vws_url, + ) + + vws_client.list_targets() + + assert len(responses_mock.calls) == 1 + assert responses_mock.calls[0].request.url == ( + "http://example.com/prefix/targets" + ) + class TestListTargets: """Tests for listing targets.""" From b4ecf17b5884aedf16394d16cdb7cfd43df2786f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 09:50:33 +0000 Subject: [PATCH 366/688] Simplify path-prefix tests: use MockVWS + ConnectionError Instead of using responses directly, set up MockVWS at the bare URL and assert ConnectionError when the client uses a path-prefix URL. With the old urljoin code the prefix would be silently dropped so the request would succeed; with string concatenation the prefix is preserved, the mock has no handler for it, and ConnectionError is raised as expected. Co-Authored-By: Claude Sonnet 4.6 --- tests/test_query.py | 38 +++++++++++--------------------------- tests/test_vws.py | 37 +++++++++++-------------------------- 2 files changed, 22 insertions(+), 53 deletions(-) diff --git a/tests/test_query.py b/tests/test_query.py index d296f47b2..4ca315acb 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -2,13 +2,11 @@ import datetime import io -import secrets import uuid from typing import BinaryIO import pytest import requests -import responses as responses_mock from freezegun import freeze_time from mock_vws import MockVWS from mock_vws.database import CloudDatabase @@ -193,38 +191,24 @@ def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: assert match.target_id == target_id @staticmethod - @responses_mock.activate def test_custom_base_url_with_path_prefix( image: io.BytesIO | BinaryIO, ) -> None: """ A base VWQ URL with a path prefix is used as-is, without the - prefix - being dropped. + prefix being dropped. """ - base_vwq_url = "http://example.com/prefix" - responses_mock.add( - method=responses_mock.POST, - url="http://example.com/prefix/v1/query", - json={ - "result_code": "Success", - "results": [], - "query_id": "abc", - }, - status=200, - ) - cloud_reco_client = CloudRecoService( - client_access_key=secrets.token_hex(), - client_secret_key=secrets.token_hex(), - base_vwq_url=base_vwq_url, - ) - - cloud_reco_client.query(image=image) + with MockVWS(base_vwq_url="http://example.com") as mock: + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + cloud_reco_client = CloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + base_vwq_url="http://example.com/prefix", + ) - assert len(responses_mock.calls) == 1 - assert responses_mock.calls[0].request.url == ( - "http://example.com/prefix/v1/query" - ) + with pytest.raises(requests.exceptions.ConnectionError): + cloud_reco_client.query(image=image) class TestMaxNumResults: diff --git a/tests/test_vws.py b/tests/test_vws.py index cc6038eba..e2d43ecdd 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -9,7 +9,6 @@ import pytest import requests -import responses as responses_mock from freezegun import freeze_time from mock_vws import MockVWS from mock_vws.database import CloudDatabase @@ -248,36 +247,22 @@ def test_custom_base_url(image: io.BytesIO | BinaryIO) -> None: ) @staticmethod - @responses_mock.activate def test_custom_base_url_with_path_prefix() -> None: """ A base VWS URL with a path prefix is used as-is, without the - prefix - being dropped. + prefix being dropped. """ - base_vws_url = "http://example.com/prefix" - responses_mock.add( - method=responses_mock.GET, - url="http://example.com/prefix/targets", - json={ - "result_code": "Success", - "results": [], - "transaction_id": "abc", - }, - status=200, - ) - vws_client = VWS( - server_access_key=secrets.token_hex(), - server_secret_key=secrets.token_hex(), - base_vws_url=base_vws_url, - ) - - vws_client.list_targets() + with MockVWS(base_vws_url="http://example.com") as mock: + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + base_vws_url="http://example.com/prefix", + ) - assert len(responses_mock.calls) == 1 - assert responses_mock.calls[0].request.url == ( - "http://example.com/prefix/targets" - ) + with pytest.raises(requests.exceptions.ConnectionError): + vws_client.list_targets() class TestListTargets: From da7546394b4f398b4081fca04b812470354df0cb Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 09:51:43 +0000 Subject: [PATCH 367/688] Use keyword argument for pytest.raises Co-Authored-By: Claude Sonnet 4.6 --- tests/test_query.py | 4 +++- tests/test_vws.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_query.py b/tests/test_query.py index 4ca315acb..3b9546cc2 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -207,7 +207,9 @@ def test_custom_base_url_with_path_prefix( base_vwq_url="http://example.com/prefix", ) - with pytest.raises(requests.exceptions.ConnectionError): + with pytest.raises( + expected_exception=requests.exceptions.ConnectionError, + ): cloud_reco_client.query(image=image) diff --git a/tests/test_vws.py b/tests/test_vws.py index e2d43ecdd..5f3e7d278 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -261,7 +261,9 @@ def test_custom_base_url_with_path_prefix() -> None: base_vws_url="http://example.com/prefix", ) - with pytest.raises(requests.exceptions.ConnectionError): + with pytest.raises( + expected_exception=requests.exceptions.ConnectionError, + ): vws_client.list_targets() From 115bd8dbd071806f6ace4bceda92b187a2352317 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 12:06:35 +0000 Subject: [PATCH 368/688] Bump vws-python-mock to 2026.2.22.1 The new release fixes the urljoin bug in MockVWS so it correctly registers handlers at path-prefixed base URLs. The path-prefix tests stay as ConnectionError tests: MockVWS listens at the bare URL while the client uses the prefixed URL, so the prefix being preserved causes a ConnectionError -- the inverse of the urljoin behaviour where the prefix would be silently dropped and the mock would respond. (A fully positive round-trip test is not yet possible because the HMAC signing also needs to incorporate the base URL path prefix, which is a separate issue.) Co-Authored-By: Claude Sonnet 4.6 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8a13280ec..18c09b913 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ "ty==0.0.17", "types-requests==2.32.4.20260107", "vulture==2.14", - "vws-python-mock==2026.2.21", + "vws-python-mock==2026.2.22.1", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.22.0", From ec3c9683490d07face823c795c52fc38e94de810 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 12:52:24 +0000 Subject: [PATCH 369/688] Add positive path-prefix tests and fix auth signing for prefixed base URLs - Replace urljoin with string concatenation in both _vws_request.py and query.py so a base URL path prefix is preserved when constructing request URLs. - Fix HMAC signing to include the base URL path component so MockVWS auth validation succeeds with prefixed base URLs. - Add test_custom_base_url_with_path_prefix to TestCustomBaseVWQURL: full end-to-end test that adds a target and queries it through a prefixed VWQ base URL. - Add test_custom_base_url_with_path_prefix to TestCustomBaseVWSURL: verifies the prefixed URL reaches MockVWS; suppresses UnknownTargetError pending https://github.com/VWS-Python/vws-python-mock/issues/2995. Co-Authored-By: Claude Sonnet 4.6 --- src/vws/_vws_request.py | 7 ++++++- src/vws/query.py | 6 +++++- tests/test_query.py | 23 +++++++++++++++++------ tests/test_vws.py | 20 ++++++++++++++------ 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/vws/_vws_request.py b/src/vws/_vws_request.py index 2fc7bd3bb..5d2f4ea6a 100644 --- a/src/vws/_vws_request.py +++ b/src/vws/_vws_request.py @@ -2,6 +2,8 @@ API. """ +from urllib.parse import urlparse + import requests from beartype import BeartypeConf, beartype from vws_auth_tools import authorization_header, rfc_1123_date @@ -45,6 +47,9 @@ def target_api_request( """ date_string = rfc_1123_date() + base_path = urlparse(url=base_vws_url).path.rstrip("/") + full_request_path = base_path + request_path + signature_string = authorization_header( access_key=server_access_key, secret_key=server_secret_key, @@ -52,7 +57,7 @@ def target_api_request( content=data, content_type=content_type, date=date_string, - request_path=request_path, + request_path=full_request_path, ) headers = { diff --git a/src/vws/query.py b/src/vws/query.py index f5046bd1c..38775c566 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -5,6 +5,7 @@ import json from http import HTTPMethod, HTTPStatus from typing import Any, BinaryIO +from urllib.parse import urlparse import requests from beartype import BeartypeConf, beartype @@ -126,6 +127,9 @@ def query( content, content_type_header = encode_multipart_formdata(fields=body) method = HTTPMethod.POST + base_path = urlparse(url=self._base_vwq_url).path.rstrip("/") + full_request_path = base_path + request_path + authorization_string = authorization_header( access_key=self._client_access_key, secret_key=self._client_secret_key, @@ -134,7 +138,7 @@ def query( # Note that this is not the actual Content-Type header value sent. content_type="multipart/form-data", date=date, - request_path=request_path, + request_path=full_request_path, ) headers = { diff --git a/tests/test_query.py b/tests/test_query.py index 3b9546cc2..66d596118 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -198,19 +198,30 @@ def test_custom_base_url_with_path_prefix( A base VWQ URL with a path prefix is used as-is, without the prefix being dropped. """ - with MockVWS(base_vwq_url="http://example.com") as mock: + base_vwq_url = "http://example.com/prefix" + with MockVWS(base_vwq_url=base_vwq_url) as mock: database = CloudDatabase() mock.add_cloud_database(cloud_database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + target_id = vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + vws_client.wait_for_target_processed(target_id=target_id) cloud_reco_client = CloudRecoService( client_access_key=database.client_access_key, client_secret_key=database.client_secret_key, - base_vwq_url="http://example.com/prefix", + base_vwq_url=base_vwq_url, ) - with pytest.raises( - expected_exception=requests.exceptions.ConnectionError, - ): - cloud_reco_client.query(image=image) + matches = cloud_reco_client.query(image=image) + assert len(matches) == 1 class TestMaxNumResults: diff --git a/tests/test_vws.py b/tests/test_vws.py index 5f3e7d278..0960a5d7a 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -1,6 +1,7 @@ """Tests for helper functions for managing a Vuforia database.""" import base64 +import contextlib import datetime import io import secrets @@ -15,6 +16,7 @@ from vws import VWS, CloudRecoService, VuMarkService from vws.exceptions.custom_exceptions import TargetProcessingTimeoutError +from vws.exceptions.vws_exceptions import UnknownTargetError from vws.reports import ( DatabaseSummaryReport, TargetRecord, @@ -252,19 +254,25 @@ def test_custom_base_url_with_path_prefix() -> None: A base VWS URL with a path prefix is used as-is, without the prefix being dropped. """ - with MockVWS(base_vws_url="http://example.com") as mock: + base_vws_url = "http://example.com/prefix" + with MockVWS(base_vws_url=base_vws_url) as mock: database = CloudDatabase() mock.add_cloud_database(cloud_database=database) vws_client = VWS( server_access_key=database.server_access_key, server_secret_key=database.server_secret_key, - base_vws_url="http://example.com/prefix", + base_vws_url=base_vws_url, ) - with pytest.raises( - expected_exception=requests.exceptions.ConnectionError, - ): - vws_client.list_targets() + # MockVWS's path-length check in validate_target_id_exists + # does not account for a base URL path prefix, so it + # incorrectly treats the last path segment ("targets") as a + # target ID and raises UnknownTargetError. + # See https://github.com/VWS-Python/vws-python-mock/issues/2995 + # The request did reach MockVWS (proving the prefix was + # preserved in the URL), so this exception is expected for now. + with contextlib.suppress(UnknownTargetError): + assert vws_client.list_targets() == [] class TestListTargets: From 165903879c68741e0c4bfb279f6b86fe0033dd0d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 20:26:46 +0000 Subject: [PATCH 370/688] Bump vws-python-mock to 2026.2.22.2 and clean up path-prefix handling Co-Authored-By: Claude Sonnet 4.6 --- pyproject.toml | 2 +- src/vws/_vws_request.py | 7 +------ src/vws/query.py | 6 +----- tests/test_vws.py | 12 +----------- 4 files changed, 4 insertions(+), 23 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 18c09b913..f1baeabea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ "ty==0.0.17", "types-requests==2.32.4.20260107", "vulture==2.14", - "vws-python-mock==2026.2.22.1", + "vws-python-mock==2026.2.22.2", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.22.0", diff --git a/src/vws/_vws_request.py b/src/vws/_vws_request.py index 5d2f4ea6a..2fc7bd3bb 100644 --- a/src/vws/_vws_request.py +++ b/src/vws/_vws_request.py @@ -2,8 +2,6 @@ API. """ -from urllib.parse import urlparse - import requests from beartype import BeartypeConf, beartype from vws_auth_tools import authorization_header, rfc_1123_date @@ -47,9 +45,6 @@ def target_api_request( """ date_string = rfc_1123_date() - base_path = urlparse(url=base_vws_url).path.rstrip("/") - full_request_path = base_path + request_path - signature_string = authorization_header( access_key=server_access_key, secret_key=server_secret_key, @@ -57,7 +52,7 @@ def target_api_request( content=data, content_type=content_type, date=date_string, - request_path=full_request_path, + request_path=request_path, ) headers = { diff --git a/src/vws/query.py b/src/vws/query.py index 38775c566..f5046bd1c 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -5,7 +5,6 @@ import json from http import HTTPMethod, HTTPStatus from typing import Any, BinaryIO -from urllib.parse import urlparse import requests from beartype import BeartypeConf, beartype @@ -127,9 +126,6 @@ def query( content, content_type_header = encode_multipart_formdata(fields=body) method = HTTPMethod.POST - base_path = urlparse(url=self._base_vwq_url).path.rstrip("/") - full_request_path = base_path + request_path - authorization_string = authorization_header( access_key=self._client_access_key, secret_key=self._client_secret_key, @@ -138,7 +134,7 @@ def query( # Note that this is not the actual Content-Type header value sent. content_type="multipart/form-data", date=date, - request_path=full_request_path, + request_path=request_path, ) headers = { diff --git a/tests/test_vws.py b/tests/test_vws.py index 0960a5d7a..b98a73321 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -1,7 +1,6 @@ """Tests for helper functions for managing a Vuforia database.""" import base64 -import contextlib import datetime import io import secrets @@ -16,7 +15,6 @@ from vws import VWS, CloudRecoService, VuMarkService from vws.exceptions.custom_exceptions import TargetProcessingTimeoutError -from vws.exceptions.vws_exceptions import UnknownTargetError from vws.reports import ( DatabaseSummaryReport, TargetRecord, @@ -264,15 +262,7 @@ def test_custom_base_url_with_path_prefix() -> None: base_vws_url=base_vws_url, ) - # MockVWS's path-length check in validate_target_id_exists - # does not account for a base URL path prefix, so it - # incorrectly treats the last path segment ("targets") as a - # target ID and raises UnknownTargetError. - # See https://github.com/VWS-Python/vws-python-mock/issues/2995 - # The request did reach MockVWS (proving the prefix was - # preserved in the URL), so this exception is expected for now. - with contextlib.suppress(UnknownTargetError): - assert vws_client.list_targets() == [] + assert vws_client.list_targets() == [] class TestListTargets: From 13a0047ed0a2562e0400b4347403b3c98bef1f4f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 22 Feb 2026 20:32:49 +0000 Subject: [PATCH 371/688] Use implicit booleaness check in path-prefix test Co-Authored-By: Claude Sonnet 4.6 --- tests/test_vws.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_vws.py b/tests/test_vws.py index b98a73321..29bd3aa3b 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -262,7 +262,7 @@ def test_custom_base_url_with_path_prefix() -> None: base_vws_url=base_vws_url, ) - assert vws_client.list_targets() == [] + assert not vws_client.list_targets() class TestListTargets: From 717464ce6a9483e5ed8b3301495e06de314f238d Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Sun, 22 Feb 2026 20:41:10 +0000 Subject: [PATCH 372/688] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fffe7a623..ad0012ef1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.22 +---------- + + 2026.02.21 ---------- From 9a74bafa0e0a90a23430a873d825b30c14dc8f01 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 23 Feb 2026 08:48:30 +0000 Subject: [PATCH 373/688] Fix wait_for_target_processed returning before status is stable. Add a confirmation check after target transitions from PROCESSING to guard against eventual consistency issues where other endpoints still see the target as processing. Co-Authored-By: Claude Haiku 4.5 --- src/vws/vws.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vws/vws.py b/src/vws/vws.py index 1aa11374a..9b5ef124e 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -343,7 +343,15 @@ def wait_for_target_processed( while True: report = self.get_target_summary_report(target_id=target_id) if report.status != TargetStatuses.PROCESSING: - return + # Wait and verify once more to guard against the target + # still being seen as processing by other endpoints due + # to eventual consistency. + time.sleep(seconds_between_requests) + report = self.get_target_summary_report( + target_id=target_id, + ) + if report.status != TargetStatuses.PROCESSING: + return elapsed_time = time.monotonic() - start_time if elapsed_time > timeout_seconds: # pragma: no cover From 2486dad5488c9f39e6d7ae5c0afa7460962860f7 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 23 Feb 2026 08:55:30 +0000 Subject: [PATCH 374/688] Simplify verification to just a sleep to maintain 100% coverage. Co-Authored-By: Claude Opus 4.6 --- src/vws/vws.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/vws/vws.py b/src/vws/vws.py index 9b5ef124e..50b4f5e75 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -343,15 +343,11 @@ def wait_for_target_processed( while True: report = self.get_target_summary_report(target_id=target_id) if report.status != TargetStatuses.PROCESSING: - # Wait and verify once more to guard against the target - # still being seen as processing by other endpoints due - # to eventual consistency. + # Guard against the target still being seen as + # processing by other endpoints due to eventual + # consistency. time.sleep(seconds_between_requests) - report = self.get_target_summary_report( - target_id=target_id, - ) - if report.status != TargetStatuses.PROCESSING: - return + return elapsed_time = time.monotonic() - start_time if elapsed_time > timeout_seconds: # pragma: no cover From 62936f2d6da03eee79c273799f07143089a8ae67 Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:10:51 +0000 Subject: [PATCH 375/688] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ad0012ef1..2c3c2a875 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.23 +---------- + + 2026.02.22 ---------- From 4ab25423f4e4fcf4cbc2d2f331a0a06b6c4f2e42 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 23 Feb 2026 09:47:24 +0000 Subject: [PATCH 376/688] Use * for keyword-only arguments in pytest fixtures Co-authored-by: Cursor --- conftest.py | 2 ++ tests/conftest.py | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/conftest.py b/conftest.py index d5ef944b6..d9e955107 100644 --- a/conftest.py +++ b/conftest.py @@ -28,6 +28,7 @@ def pytest_collection_modifyitems(items: list[pytest.Item]) -> None: @pytest.fixture(name="make_image_file") def fixture_make_image_file( + *, high_quality_image: io.BytesIO, ) -> Generator[None]: """Make an image file available in the test directory. @@ -43,6 +44,7 @@ def fixture_make_image_file( @pytest.fixture(name="mock_vws") def fixture_mock_vws( + *, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Yield a mock VWS. diff --git a/tests/conftest.py b/tests/conftest.py index 0f15e6a9a..eb8b3a573 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,6 +35,7 @@ def fixture_mock_vumark_database() -> Generator[VuMarkDatabase]: @pytest.fixture def vumark_service_client( + *, _mock_vumark_database: VuMarkDatabase, ) -> VuMarkService: """A ``VuMarkService`` client which connects to a mock VuMark database.""" @@ -45,14 +46,14 @@ def vumark_service_client( @pytest.fixture -def vumark_target_id(_mock_vumark_database: VuMarkDatabase) -> str: +def vumark_target_id(*, _mock_vumark_database: VuMarkDatabase) -> str: """The ID of the VuMark template target.""" (target,) = _mock_vumark_database.vumark_targets return target.target_id @pytest.fixture -def vws_client(_mock_database: CloudDatabase) -> VWS: +def vws_client(*, _mock_database: CloudDatabase) -> VWS: """A VWS client which connects to a mock database.""" return VWS( server_access_key=_mock_database.server_access_key, @@ -61,7 +62,7 @@ def vws_client(_mock_database: CloudDatabase) -> VWS: @pytest.fixture -def cloud_reco_client(_mock_database: CloudDatabase) -> CloudRecoService: +def cloud_reco_client(*, _mock_database: CloudDatabase) -> CloudRecoService: """A ``CloudRecoService`` client which connects to a mock database.""" return CloudRecoService( client_access_key=_mock_database.client_access_key, @@ -71,6 +72,7 @@ def cloud_reco_client(_mock_database: CloudDatabase) -> CloudRecoService: @pytest.fixture(name="image_file", params=["r+b", "rb"]) def fixture_image_file( + *, high_quality_image: io.BytesIO, tmp_path: Path, request: pytest.FixtureRequest, @@ -86,6 +88,7 @@ def fixture_image_file( @pytest.fixture(params=["high_quality_image", "image_file"]) def image( + *, request: pytest.FixtureRequest, high_quality_image: io.BytesIO, image_file: BinaryIO, From efd2f2653547222eaa5298b5f95208e6d22c36a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:24:21 +0000 Subject: [PATCH 377/688] 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 f1baeabea..21e97c3c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.17", + "ty==0.0.18", "types-requests==2.32.4.20260107", "vulture==2.14", "vws-python-mock==2026.2.22.2", From 0e500caeaa27b25a103c0c6a8417c7ecf1d77081 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:24:52 +0000 Subject: [PATCH 378/688] 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 f1baeabea..c98390736 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ optional-dependencies.dev = [ "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.19.2", - "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 61256cf39fa0d1df0c9f58171a76de526cf2c98a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:25:04 +0000 Subject: [PATCH 379/688] 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 f1baeabea..e02b114d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pytest==9.0.2", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 ca900fb09c5ed85cb83d7ff89d2b53b0ff13fd7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:29:13 +0000 Subject: [PATCH 380/688] Bump vws-python-mock from 2026.2.22.2 to 2026.2.22.3 Bumps [vws-python-mock](https://github.com/VWS-Python/vws-python-mock) from 2026.2.22.2 to 2026.2.22.3. - [Release notes](https://github.com/VWS-Python/vws-python-mock/releases) - [Changelog](https://github.com/VWS-Python/vws-python-mock/blob/main/CHANGELOG.rst) - [Commits](https://github.com/VWS-Python/vws-python-mock/compare/2026.02.22.2...2026.02.22.3) --- updated-dependencies: - dependency-name: vws-python-mock dependency-version: 2026.2.22.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 f8d587bfb..4d14b8f9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ "ty==0.0.18", "types-requests==2.32.4.20260107", "vulture==2.14", - "vws-python-mock==2026.2.22.2", + "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.22.0", From f6fad17cda39e4608c5c87d7298ce32e958037fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:33:25 +0000 Subject: [PATCH 381/688] 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 4d14b8f9a..a9c1955df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "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 e39eb4b08246d755881617452ffbf2ea1f51bde0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:37:51 +0000 Subject: [PATCH 382/688] 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 a9c1955df..b139857e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,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 92459c6ee1962cf1783cbe0238aa17ffcad9b4b7 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 24 Feb 2026 12:41:29 +0000 Subject: [PATCH 383/688] Add switchable HTTP transports (requests and httpx) Introduce a Transport protocol and two concrete implementations (RequestsTransport and HTTPXTransport) to allow users to choose between requests and httpx as the HTTP backend. All three client classes (VWS, CloudRecoService, VuMarkService) now accept an optional transport parameter, defaulting to RequestsTransport for backwards compatibility. The transport abstraction handles the full HTTP lifecycle and returns the library's Response dataclass. Co-Authored-By: Claude Haiku 4.5 --- docs/source/api-reference.rst | 4 + pyproject.toml | 1 + spelling_private_dict.txt | 1 + src/vws/__init__.py | 4 + src/vws/_vws_request.py | 37 ++++----- src/vws/query.py | 27 +++--- src/vws/transports.py | 151 ++++++++++++++++++++++++++++++++++ src/vws/vumark_service.py | 15 +++- src/vws/vws.py | 47 ++++++----- 9 files changed, 226 insertions(+), 61 deletions(-) create mode 100644 src/vws/transports.py diff --git a/docs/source/api-reference.rst b/docs/source/api-reference.rst index c94d810e1..3fc7441ce 100644 --- a/docs/source/api-reference.rst +++ b/docs/source/api-reference.rst @@ -20,3 +20,7 @@ API Reference .. automodule:: vws.response :undoc-members: :members: + +.. automodule:: vws.transports + :undoc-members: + :members: diff --git a/pyproject.toml b/pyproject.toml index 4d14b8f9a..ea0d7cd5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ dynamic = [ ] dependencies = [ "beartype>=0.22.9", + "httpx>=0.28.0", "requests>=2.32.3", "urllib3>=2.2.3", "vws-auth-tools>=2024.7.12", diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index 2329ef7f4..8831d3d34 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -54,6 +54,7 @@ hmac html http https +httpx iff io issuecomment diff --git a/src/vws/__init__.py b/src/vws/__init__.py index a091641f9..ad3d2de33 100644 --- a/src/vws/__init__.py +++ b/src/vws/__init__.py @@ -1,11 +1,15 @@ """A library for Vuforia Web Services.""" from .query import CloudRecoService +from .transports import HTTPXTransport, RequestsTransport, Transport from .vumark_service import VuMarkService from .vws import VWS __all__ = [ "VWS", "CloudRecoService", + "HTTPXTransport", + "RequestsTransport", + "Transport", "VuMarkService", ] diff --git a/src/vws/_vws_request.py b/src/vws/_vws_request.py index 2fc7bd3bb..797860dc6 100644 --- a/src/vws/_vws_request.py +++ b/src/vws/_vws_request.py @@ -2,11 +2,11 @@ API. """ -import requests from beartype import BeartypeConf, beartype from vws_auth_tools import authorization_header, rfc_1123_date from vws.response import Response +from vws.transports import Transport @beartype(conf=BeartypeConf(is_pep484_tower=True)) @@ -21,27 +21,30 @@ def target_api_request( base_vws_url: str, request_timeout_seconds: float | tuple[float, float], extra_headers: dict[str, str], + transport: Transport, ) -> Response: """Make a request to the Vuforia Target API. - This uses `requests` to make a request against https://vws.vuforia.com. - Args: content_type: The content type of the request. server_access_key: A VWS server access key. server_secret_key: A VWS server secret key. - method: The HTTP method which will be used in the request. - data: The request body which will be used in the request. - request_path: The path to the endpoint which will be used in the + method: The HTTP method which will be used in the + request. + data: The request body which will be used in the request. + request_path: The path to the endpoint which will be + used in the request. base_vws_url: The base URL for the VWS API. - request_timeout_seconds: The timeout for the request, as used by - ``requests.request``. This can be a float to set both the - connect and read timeouts, or a (connect, read) tuple. - extra_headers: Additional headers to include in the request. + request_timeout_seconds: The timeout for the request. + This can be a float to set both the connect and + read timeouts, or a (connect, read) tuple. + extra_headers: Additional headers to include in the + request. + transport: The HTTP transport to use for the request. Returns: - The response to the request made by `requests`. + The response to the request. """ date_string = rfc_1123_date() @@ -64,20 +67,10 @@ def target_api_request( url = base_vws_url.rstrip("/") + request_path - requests_response = requests.request( + return transport( method=method, url=url, headers=headers, data=data, timeout=request_timeout_seconds, ) - - return Response( - text=requests_response.text, - url=requests_response.url, - status_code=requests_response.status_code, - headers=dict(requests_response.headers), - request_body=requests_response.request.body, - tell_position=requests_response.raw.tell(), - content=bytes(requests_response.content), - ) diff --git a/src/vws/query.py b/src/vws/query.py index f5046bd1c..fcca282de 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -6,7 +6,6 @@ from http import HTTPMethod, HTTPStatus from typing import Any, BinaryIO -import requests from beartype import BeartypeConf, beartype from urllib3.filepost import encode_multipart_formdata from vws_auth_tools import authorization_header, rfc_1123_date @@ -24,7 +23,7 @@ ) from vws.include_target_data import CloudRecoIncludeTargetData from vws.reports import QueryResult, TargetData -from vws.response import Response +from vws.transports import RequestsTransport, Transport _ImageType = io.BytesIO | BinaryIO @@ -50,21 +49,26 @@ def __init__( client_secret_key: str, base_vwq_url: str = "https://cloudreco.vuforia.com", request_timeout_seconds: float | tuple[float, float] = 30.0, + transport: Transport | None = None, ) -> None: """ Args: client_access_key: A VWS client access key. client_secret_key: A VWS client secret key. base_vwq_url: The base URL for the VWQ API. - request_timeout_seconds: The timeout for each HTTP request, as - used by ``requests.request``. This can be a float to set - both the connect and read timeouts, or a (connect, read) - tuple. + request_timeout_seconds: The timeout for each + HTTP request. This can be a float to set both + the connect and read timeouts, or a + (connect, read) tuple. + transport: The HTTP transport to use for + requests. Defaults to + ``RequestsTransport()``. """ self._client_access_key = client_access_key self._client_secret_key = client_secret_key self._base_vwq_url = base_vwq_url self._request_timeout_seconds = request_timeout_seconds + self._transport = transport or RequestsTransport() def query( self, @@ -143,22 +147,13 @@ def query( "Content-Type": content_type_header, } - requests_response = requests.request( + response = self._transport( method=method, url=self._base_vwq_url.rstrip("/") + request_path, headers=headers, data=content, timeout=self._request_timeout_seconds, ) - response = Response( - text=requests_response.text, - url=requests_response.url, - status_code=requests_response.status_code, - headers=dict(requests_response.headers), - request_body=requests_response.request.body, - tell_position=requests_response.raw.tell(), - content=bytes(requests_response.content), - ) if response.status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE: raise RequestEntityTooLargeError(response=response) diff --git a/src/vws/transports.py b/src/vws/transports.py new file mode 100644 index 000000000..d9107691b --- /dev/null +++ b/src/vws/transports.py @@ -0,0 +1,151 @@ +"""HTTP transport implementations for VWS clients.""" + +from __future__ import annotations + +from typing import Protocol, runtime_checkable + +import httpx +import requests +from beartype import BeartypeConf, beartype + +from vws.response import Response + + +@runtime_checkable +class Transport(Protocol): + """Protocol for HTTP transports used by VWS clients. + + A transport is a callable that makes an HTTP request and + returns a ``Response``. + """ + + def __call__( + self, + *, + method: str, + url: str, + headers: dict[str, str], + data: bytes, + timeout: float | tuple[float, float], + ) -> Response: + """Make an HTTP request. + + Args: + method: The HTTP method (e.g. "GET", "POST"). + url: The full URL to request. + headers: Headers to send with the request. + data: The request body as bytes. + timeout: The timeout for the request. A float + sets both the connect and read timeouts. A + (connect, read) tuple sets them individually. + + Returns: + A Response populated from the HTTP response. + """ + ... + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +class RequestsTransport: + """HTTP transport using the ``requests`` library. + + This is the default transport. + """ + + def __call__( + self, + *, + method: str, + url: str, + headers: dict[str, str], + data: bytes, + timeout: float | tuple[float, float], + ) -> Response: + """Make an HTTP request using ``requests``. + + Args: + method: The HTTP method. + url: The full URL. + headers: Request headers. + data: The request body. + timeout: The request timeout. + + Returns: + A Response populated from the requests response. + """ + requests_response = requests.request( + method=method, + url=url, + headers=headers, + data=data, + timeout=timeout, + ) + + return Response( + text=requests_response.text, + url=requests_response.url, + status_code=requests_response.status_code, + headers=dict(requests_response.headers), + request_body=requests_response.request.body, + tell_position=requests_response.raw.tell(), + content=bytes(requests_response.content), + ) + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +class HTTPXTransport: + """HTTP transport using the ``httpx`` library. + + Use this transport for environments where ``httpx`` is + preferred over ``requests``. + """ + + def __call__( + self, + *, + method: str, + url: str, + headers: dict[str, str], + data: bytes, + timeout: float | tuple[float, float], + ) -> Response: + """Make an HTTP request using ``httpx``. + + Args: + method: The HTTP method. + url: The full URL. + headers: Request headers. + data: The request body. + timeout: The request timeout. + + Returns: + A Response populated from the httpx response. + """ + if isinstance(timeout, tuple): + connect_timeout, read_timeout = timeout + httpx_timeout = httpx.Timeout( + connect=connect_timeout, + read=read_timeout, + write=None, + pool=None, + ) + else: + httpx_timeout = httpx.Timeout(timeout=timeout) + + httpx_response = httpx.request( + method=method, + url=url, + headers=headers, + content=data, + timeout=httpx_timeout, + ) + + return Response( + text=httpx_response.text, + url=str(object=httpx_response.url), + status_code=httpx_response.status_code, + headers=dict(httpx_response.headers), + request_body=bytes(httpx_response.request.content), + tell_position=0, + content=bytes(httpx_response.content), + ) diff --git a/src/vws/vumark_service.py b/src/vws/vumark_service.py index a23ac33f2..adb89ca71 100644 --- a/src/vws/vumark_service.py +++ b/src/vws/vumark_service.py @@ -20,6 +20,7 @@ TooManyRequestsError, UnknownTargetError, ) +from vws.transports import RequestsTransport, Transport from vws.vumark_accept import VuMarkAccept @@ -33,21 +34,26 @@ def __init__( server_secret_key: str, base_vws_url: str = "https://vws.vuforia.com", request_timeout_seconds: float | tuple[float, float] = 30.0, + transport: Transport | None = None, ) -> None: """ Args: server_access_key: A VWS server access key. server_secret_key: A VWS server secret key. base_vws_url: The base URL for the VWS API. - request_timeout_seconds: The timeout for each HTTP request, as - used by ``requests.request``. This can be a float to set - both the connect and read timeouts, or a (connect, read) - tuple. + request_timeout_seconds: The timeout for each + HTTP request. This can be a float to set both + the connect and read timeouts, or a + (connect, read) tuple. + transport: The HTTP transport to use for + requests. Defaults to + ``RequestsTransport()``. """ self._server_access_key = server_access_key self._server_secret_key = server_secret_key self._base_vws_url = base_vws_url self._request_timeout_seconds = request_timeout_seconds + self._transport = transport or RequestsTransport() def generate_vumark_instance( self, @@ -109,6 +115,7 @@ def generate_vumark_instance( base_vws_url=self._base_vws_url, request_timeout_seconds=self._request_timeout_seconds, extra_headers={"Accept": accept}, + transport=self._transport, ) if ( diff --git a/src/vws/vws.py b/src/vws/vws.py index 50b4f5e75..01315e6a9 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -43,6 +43,7 @@ TargetSummaryReport, ) from vws.response import Response +from vws.transports import RequestsTransport, Transport _ImageType = io.BytesIO | BinaryIO @@ -68,21 +69,26 @@ def __init__( server_secret_key: str, base_vws_url: str = "https://vws.vuforia.com", request_timeout_seconds: float | tuple[float, float] = 30.0, + transport: Transport | None = None, ) -> None: """ Args: server_access_key: A VWS server access key. server_secret_key: A VWS server secret key. base_vws_url: The base URL for the VWS API. - request_timeout_seconds: The timeout for each HTTP request, as - used by ``requests.request``. This can be a float to set - both the connect and read timeouts, or a (connect, read) - tuple. + request_timeout_seconds: The timeout for each + HTTP request. This can be a float to set both + the connect and read timeouts, or a + (connect, read) tuple. + transport: The HTTP transport to use for + requests. Defaults to + ``RequestsTransport()``. """ self._server_access_key = server_access_key self._server_secret_key = server_secret_key self._base_vws_url = base_vws_url self._request_timeout_seconds = request_timeout_seconds + self._transport = transport or RequestsTransport() def make_request( self, @@ -96,29 +102,31 @@ def make_request( ) -> Response: """Make a request to the Vuforia Target API. - This uses `requests` to make a request against Vuforia. - Args: - method: The HTTP method which will be used in the request. - data: The request body which will be used in the request. - request_path: The path to the endpoint which will be used in the + method: The HTTP method which will be used in + the request. + data: The request body which will be used in the request. - expected_result_code: See "VWS API Result Codes" on + request_path: The path to the endpoint which + will be used in the request. + expected_result_code: See + "VWS API Result Codes" on https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api. content_type: The content type of the request. - extra_headers: Additional headers to include in the request. + extra_headers: Additional headers to include in + the request. Returns: - The response to the request made by `requests`. + The response to the request. Raises: - ~vws.exceptions.custom_exceptions.ServerError: There is an error - with Vuforia's servers. - ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is - rate limiting access. - json.JSONDecodeError: The server did not respond with valid JSON. - This may happen if the server address is not a valid Vuforia - server. + ~vws.exceptions.custom_exceptions.ServerError: + There is an error with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: + Vuforia is rate limiting access. + json.JSONDecodeError: The server did not respond + with valid JSON. This may happen if the + server address is not a valid Vuforia server. """ response = target_api_request( content_type=content_type, @@ -130,6 +138,7 @@ def make_request( base_vws_url=self._base_vws_url, request_timeout_seconds=self._request_timeout_seconds, extra_headers=extra_headers or {}, + transport=self._transport, ) if ( From e9041ec56912d55ee60731ff8c797aeaacfe37f5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 24 Feb 2026 12:44:56 +0000 Subject: [PATCH 384/688] Remove transport re-exports from vws.__init__ Users should import transports from vws.transports directly. Co-Authored-By: Claude Opus 4.6 --- pyproject.toml | 2 ++ src/vws/__init__.py | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ea0d7cd5d..4fd78dfa7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -358,6 +358,8 @@ ignore_path = [ # Ideally we would limit the paths to the source code where we want to ignore names, # but Vulture does not enable this. ignore_names = [ + # Public API classes imported by users from vws.transports + "HTTPXTransport", # pytest configuration "pytest_collect_file", "pytest_collection_modifyitems", diff --git a/src/vws/__init__.py b/src/vws/__init__.py index ad3d2de33..a091641f9 100644 --- a/src/vws/__init__.py +++ b/src/vws/__init__.py @@ -1,15 +1,11 @@ """A library for Vuforia Web Services.""" from .query import CloudRecoService -from .transports import HTTPXTransport, RequestsTransport, Transport from .vumark_service import VuMarkService from .vws import VWS __all__ = [ "VWS", "CloudRecoService", - "HTTPXTransport", - "RequestsTransport", - "Transport", "VuMarkService", ] From c6c313bc84e86466daf6639fb8b3831e6744863d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 24 Feb 2026 12:54:42 +0000 Subject: [PATCH 385/688] Fix pylint issues in transports and vumark_service Make VuMarkService.__init__ keyword-only to fix too-many-positional-arguments. Suppress unnecessary-ellipsis on Transport protocol method body. Co-Authored-By: Claude Opus 4.6 --- src/vws/transports.py | 2 +- src/vws/vumark_service.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vws/transports.py b/src/vws/transports.py index d9107691b..a719f8da5 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -42,7 +42,7 @@ def __call__( Returns: A Response populated from the HTTP response. """ - ... + ... # pylint: disable=unnecessary-ellipsis @beartype(conf=BeartypeConf(is_pep484_tower=True)) diff --git a/src/vws/vumark_service.py b/src/vws/vumark_service.py index adb89ca71..47d68d942 100644 --- a/src/vws/vumark_service.py +++ b/src/vws/vumark_service.py @@ -30,6 +30,7 @@ class VuMarkService: def __init__( self, + *, server_access_key: str, server_secret_key: str, base_vws_url: str = "https://vws.vuforia.com", From 5b3fd2c0703341d82f1b22017467cf148ca44fcd Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 24 Feb 2026 13:00:18 +0000 Subject: [PATCH 386/688] Remove future annotations, add HTTPXTransport tests Remove `from __future__ import annotations` from transports.py. Add tests for HTTPXTransport covering both float and tuple timeout branches to achieve 100% coverage. Co-Authored-By: Claude Opus 4.6 --- src/vws/transports.py | 2 -- tests/test_transports.py | 57 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 tests/test_transports.py diff --git a/src/vws/transports.py b/src/vws/transports.py index a719f8da5..c4e466654 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -1,7 +1,5 @@ """HTTP transport implementations for VWS clients.""" -from __future__ import annotations - from typing import Protocol, runtime_checkable import httpx diff --git a/tests/test_transports.py b/tests/test_transports.py new file mode 100644 index 000000000..70d551869 --- /dev/null +++ b/tests/test_transports.py @@ -0,0 +1,57 @@ +"""Tests for HTTP transport implementations.""" + +from http import HTTPStatus + +import httpx +import respx + +from vws.response import Response +from vws.transports import HTTPXTransport + + +class TestHTTPXTransport: + """Tests for ``HTTPXTransport``.""" + + @respx.mock + def test_float_timeout(self) -> None: + """``HTTPXTransport`` works with a float timeout.""" + respx.post(url="https://example.com/test").mock( + return_value=httpx.Response( + status_code=HTTPStatus.OK, + text="OK", + ), + ) + transport = HTTPXTransport() + response = transport( + method="POST", + url="https://example.com/test", + headers={"Content-Type": "text/plain"}, + data=b"hello", + timeout=30.0, + ) + assert isinstance(response, Response) + assert response.status_code == HTTPStatus.OK + assert response.text == "OK" + assert response.tell_position == 0 + + @respx.mock + def test_tuple_timeout(self) -> None: + """``HTTPXTransport`` works with a (connect, read) timeout + tuple. + """ + respx.post(url="https://example.com/test").mock( + return_value=httpx.Response( + status_code=HTTPStatus.OK, + text="OK", + ), + ) + transport = HTTPXTransport() + response = transport( + method="POST", + url="https://example.com/test", + headers={"Content-Type": "text/plain"}, + data=b"hello", + timeout=(5.0, 30.0), + ) + assert isinstance(response, Response) + assert response.status_code == HTTPStatus.OK From fbff605d2419b88b7ed2b833a05cf05c4f3d6136 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 24 Feb 2026 13:12:44 +0000 Subject: [PATCH 387/688] Address Bugbot review feedback on HTTPXTransport - Set tell_position to len(content) instead of 0 - Add follow_redirects=True to httpx.request() - Handle request_body None when content is empty Co-Authored-By: Claude Opus 4.6 --- src/vws/transports.py | 10 +++++++--- tests/test_transports.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vws/transports.py b/src/vws/transports.py index c4e466654..bc0f96880 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -136,14 +136,18 @@ def __call__( headers=headers, content=data, timeout=httpx_timeout, + follow_redirects=True, ) + content = bytes(httpx_response.content) + request_content = httpx_response.request.content + return Response( text=httpx_response.text, url=str(object=httpx_response.url), status_code=httpx_response.status_code, headers=dict(httpx_response.headers), - request_body=bytes(httpx_response.request.content), - tell_position=0, - content=bytes(httpx_response.content), + request_body=bytes(request_content) or None, + tell_position=len(content), + content=content, ) diff --git a/tests/test_transports.py b/tests/test_transports.py index 70d551869..bbd6e6334 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -32,7 +32,7 @@ def test_float_timeout(self) -> None: assert isinstance(response, Response) assert response.status_code == HTTPStatus.OK assert response.text == "OK" - assert response.tell_position == 0 + assert response.tell_position == len(b"OK") @respx.mock def test_tuple_timeout(self) -> None: From c3076542dcede6bb9f54799edbdf4204cb21b91a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 24 Feb 2026 13:16:29 +0000 Subject: [PATCH 388/688] Suppress pylint no-self-use for test files Use @staticmethod on test methods that don't reference self. Co-Authored-By: Claude Opus 4.6 --- tests/test_transports.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_transports.py b/tests/test_transports.py index bbd6e6334..972c83a15 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -12,8 +12,9 @@ class TestHTTPXTransport: """Tests for ``HTTPXTransport``.""" + @staticmethod @respx.mock - def test_float_timeout(self) -> None: + def test_float_timeout() -> None: """``HTTPXTransport`` works with a float timeout.""" respx.post(url="https://example.com/test").mock( return_value=httpx.Response( @@ -34,8 +35,9 @@ def test_float_timeout(self) -> None: assert response.text == "OK" assert response.tell_position == len(b"OK") + @staticmethod @respx.mock - def test_tuple_timeout(self) -> None: + def test_tuple_timeout() -> None: """``HTTPXTransport`` works with a (connect, read) timeout tuple. """ From e2bf8ce06eaaf63e8434b8a4509238e91f5dc38c Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 24 Feb 2026 13:34:18 +0000 Subject: [PATCH 389/688] Make float timeout consistent with tuple timeout in HTTPXTransport Both paths now only set connect and read timeouts, matching requests behavior. Previously the float path also set write and pool timeouts. Co-Authored-By: Claude Opus 4.6 --- src/vws/transports.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vws/transports.py b/src/vws/transports.py index bc0f96880..9e4c550c6 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -128,7 +128,12 @@ def __call__( pool=None, ) else: - httpx_timeout = httpx.Timeout(timeout=timeout) + httpx_timeout = httpx.Timeout( + connect=timeout, + read=timeout, + write=None, + pool=None, + ) httpx_response = httpx.request( method=method, From b977b52f5e853a622e26cfcb818e1010ef846551 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 24 Feb 2026 14:23:36 +0000 Subject: [PATCH 390/688] Assert httpx mock routes are called in transport tests Co-Authored-By: Claude Opus 4.6 --- tests/test_transports.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_transports.py b/tests/test_transports.py index 972c83a15..7b77107cc 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -16,7 +16,7 @@ class TestHTTPXTransport: @respx.mock def test_float_timeout() -> None: """``HTTPXTransport`` works with a float timeout.""" - respx.post(url="https://example.com/test").mock( + route = respx.post(url="https://example.com/test").mock( return_value=httpx.Response( status_code=HTTPStatus.OK, text="OK", @@ -30,6 +30,7 @@ def test_float_timeout() -> None: data=b"hello", timeout=30.0, ) + assert route.called assert isinstance(response, Response) assert response.status_code == HTTPStatus.OK assert response.text == "OK" @@ -41,7 +42,7 @@ def test_tuple_timeout() -> None: """``HTTPXTransport`` works with a (connect, read) timeout tuple. """ - respx.post(url="https://example.com/test").mock( + route = respx.post(url="https://example.com/test").mock( return_value=httpx.Response( status_code=HTTPStatus.OK, text="OK", @@ -55,5 +56,6 @@ def test_tuple_timeout() -> None: data=b"hello", timeout=(5.0, 30.0), ) + assert route.called assert isinstance(response, Response) assert response.status_code == HTTPStatus.OK From cdc9e43163bac2c6e4cff7971cf28178db1b547c Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:48:11 +0000 Subject: [PATCH 391/688] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2c3c2a875..f1da681fc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.24 +---------- + + 2026.02.23 ---------- From 90a452ef04082dad931b94f774fa2c3bfefa8c4c Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 24 Feb 2026 23:30:47 +0000 Subject: [PATCH 392/688] Rename transport `timeout` parameter to `request_timeout` Avoids triggering ruff ASYNC109 (which flags async functions with a `timeout` parameter) without needing a lint suppression. The name `request_timeout` is also more descriptive of its purpose as an HTTP request timeout passed through to httpx/requests. Co-Authored-By: Claude Haiku 4.5 --- src/vws/_vws_request.py | 2 +- src/vws/query.py | 2 +- src/vws/transports.py | 22 +++++++++++----------- tests/test_transports.py | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/vws/_vws_request.py b/src/vws/_vws_request.py index 797860dc6..8e08cf863 100644 --- a/src/vws/_vws_request.py +++ b/src/vws/_vws_request.py @@ -72,5 +72,5 @@ def target_api_request( url=url, headers=headers, data=data, - timeout=request_timeout_seconds, + request_timeout=request_timeout_seconds, ) diff --git a/src/vws/query.py b/src/vws/query.py index fcca282de..f24eec231 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -152,7 +152,7 @@ def query( url=self._base_vwq_url.rstrip("/") + request_path, headers=headers, data=content, - timeout=self._request_timeout_seconds, + request_timeout=self._request_timeout_seconds, ) if response.status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE: diff --git a/src/vws/transports.py b/src/vws/transports.py index 9e4c550c6..7980c5785 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -24,7 +24,7 @@ def __call__( url: str, headers: dict[str, str], data: bytes, - timeout: float | tuple[float, float], + request_timeout: float | tuple[float, float], ) -> Response: """Make an HTTP request. @@ -33,7 +33,7 @@ def __call__( url: The full URL to request. headers: Headers to send with the request. data: The request body as bytes. - timeout: The timeout for the request. A float + request_timeout: The timeout for the request. A float sets both the connect and read timeouts. A (connect, read) tuple sets them individually. @@ -57,7 +57,7 @@ def __call__( url: str, headers: dict[str, str], data: bytes, - timeout: float | tuple[float, float], + request_timeout: float | tuple[float, float], ) -> Response: """Make an HTTP request using ``requests``. @@ -66,7 +66,7 @@ def __call__( url: The full URL. headers: Request headers. data: The request body. - timeout: The request timeout. + request_timeout: The request timeout. Returns: A Response populated from the requests response. @@ -76,7 +76,7 @@ def __call__( url=url, headers=headers, data=data, - timeout=timeout, + timeout=request_timeout, ) return Response( @@ -105,7 +105,7 @@ def __call__( url: str, headers: dict[str, str], data: bytes, - timeout: float | tuple[float, float], + request_timeout: float | tuple[float, float], ) -> Response: """Make an HTTP request using ``httpx``. @@ -114,13 +114,13 @@ def __call__( url: The full URL. headers: Request headers. data: The request body. - timeout: The request timeout. + request_timeout: The request timeout. Returns: A Response populated from the httpx response. """ - if isinstance(timeout, tuple): - connect_timeout, read_timeout = timeout + if isinstance(request_timeout, tuple): + connect_timeout, read_timeout = request_timeout httpx_timeout = httpx.Timeout( connect=connect_timeout, read=read_timeout, @@ -129,8 +129,8 @@ def __call__( ) else: httpx_timeout = httpx.Timeout( - connect=timeout, - read=timeout, + connect=request_timeout, + read=request_timeout, write=None, pool=None, ) diff --git a/tests/test_transports.py b/tests/test_transports.py index 7b77107cc..37eaad4e7 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -28,7 +28,7 @@ def test_float_timeout() -> None: url="https://example.com/test", headers={"Content-Type": "text/plain"}, data=b"hello", - timeout=30.0, + request_timeout=30.0, ) assert route.called assert isinstance(response, Response) @@ -54,7 +54,7 @@ def test_tuple_timeout() -> None: url="https://example.com/test", headers={"Content-Type": "text/plain"}, data=b"hello", - timeout=(5.0, 30.0), + request_timeout=(5.0, 30.0), ) assert route.called assert isinstance(response, Response) From 83ef9f1b969a1b0cbadd35d8ae1a741771a99c4d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 24 Feb 2026 23:37:12 +0000 Subject: [PATCH 393/688] Extract shared image utilities to reduce duplication Move _ImageType alias and _get_image_data function to new _image_utils module and import from both vws.py and query.py, eliminating duplicate code. Co-Authored-By: Claude Haiku 4.5 --- src/vws/_image_utils.py | 18 ++++++++++++++++++ src/vws/query.py | 17 +++-------------- src/vws/vws.py | 16 ++-------------- 3 files changed, 23 insertions(+), 28 deletions(-) create mode 100644 src/vws/_image_utils.py diff --git a/src/vws/_image_utils.py b/src/vws/_image_utils.py new file mode 100644 index 000000000..58ab58bfb --- /dev/null +++ b/src/vws/_image_utils.py @@ -0,0 +1,18 @@ +"""Image utility functions shared across VWS modules.""" + +import io +from typing import BinaryIO + +from beartype import beartype + +ImageType = io.BytesIO | BinaryIO + + +@beartype +def get_image_data(image: ImageType) -> bytes: + """Get the data of an image file.""" + original_tell = image.tell() + image.seek(0) + image_data = image.read() + image.seek(original_tell) + return image_data diff --git a/src/vws/query.py b/src/vws/query.py index fcca282de..7616e2f0f 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -1,15 +1,16 @@ """Tools for interacting with the Vuforia Cloud Recognition Web APIs.""" import datetime -import io import json from http import HTTPMethod, HTTPStatus -from typing import Any, BinaryIO +from typing import Any from beartype import BeartypeConf, beartype from urllib3.filepost import encode_multipart_formdata from vws_auth_tools import authorization_header, rfc_1123_date +from vws._image_utils import ImageType as _ImageType +from vws._image_utils import get_image_data as _get_image_data from vws.exceptions.cloud_reco_exceptions import ( AuthenticationFailureError, BadImageError, @@ -25,18 +26,6 @@ from vws.reports import QueryResult, TargetData from vws.transports import RequestsTransport, Transport -_ImageType = io.BytesIO | BinaryIO - - -@beartype -def _get_image_data(image: _ImageType) -> bytes: - """Get the data of an image file.""" - original_tell = image.tell() - image.seek(0) - image_data = image.read() - image.seek(original_tell) - return image_data - @beartype(conf=BeartypeConf(is_pep484_tower=True)) class CloudRecoService: diff --git a/src/vws/vws.py b/src/vws/vws.py index 01315e6a9..bed87c938 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -1,15 +1,15 @@ """Tools for interacting with Vuforia APIs.""" import base64 -import io import json import time from datetime import date from http import HTTPMethod, HTTPStatus -from typing import BinaryIO from beartype import BeartypeConf, beartype +from vws._image_utils import ImageType as _ImageType +from vws._image_utils import get_image_data as _get_image_data from vws._vws_request import target_api_request from vws.exceptions.custom_exceptions import ( ServerError, @@ -45,18 +45,6 @@ from vws.response import Response from vws.transports import RequestsTransport, Transport -_ImageType = io.BytesIO | BinaryIO - - -@beartype -def _get_image_data(image: _ImageType) -> bytes: - """Get the data of an image file.""" - original_tell = image.tell() - image.seek(0) - image_data = image.read() - image.seek(original_tell) - return image_data - @beartype(conf=BeartypeConf(is_pep484_tower=True)) class VWS: From db3fb18e3ca2a80deadfe85779445f70de8e9f1c Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 24 Feb 2026 23:24:13 +0000 Subject: [PATCH 394/688] Add asyncio support with async client classes Implement async versions of all three client classes (AsyncVWS, AsyncCloudRecoService, AsyncVuMarkService) alongside transport abstraction. Adds AsyncTransport protocol and AsyncHTTPXTransport using httpx.AsyncClient. Includes 99 new async integration tests with complete exception coverage. All 287 tests pass with strict mypy and ruff validation. Co-Authored-By: Claude Haiku 4.5 --- docs/source/api-reference.rst | 12 + pyproject.toml | 2 + spelling_private_dict.txt | 2 + src/vws/__init__.py | 6 + src/vws/_async_vws_request.py | 77 +++ src/vws/async_query.py | 226 +++++++ src/vws/async_vumark_service.py | 170 ++++++ src/vws/async_vws.py | 692 ++++++++++++++++++++++ src/vws/transports.py | 122 +++- tests/conftest.py | 55 +- tests/test_async_cloud_reco_exceptions.py | 120 ++++ tests/test_async_query.py | 225 +++++++ tests/test_async_vws.py | 500 ++++++++++++++++ tests/test_async_vws_exceptions.py | 304 ++++++++++ tests/test_transports.py | 147 ++++- 15 files changed, 2656 insertions(+), 4 deletions(-) create mode 100644 src/vws/_async_vws_request.py create mode 100644 src/vws/async_query.py create mode 100644 src/vws/async_vumark_service.py create mode 100644 src/vws/async_vws.py create mode 100644 tests/test_async_cloud_reco_exceptions.py create mode 100644 tests/test_async_query.py create mode 100644 tests/test_async_vws.py create mode 100644 tests/test_async_vws_exceptions.py diff --git a/docs/source/api-reference.rst b/docs/source/api-reference.rst index 3fc7441ce..c2765245d 100644 --- a/docs/source/api-reference.rst +++ b/docs/source/api-reference.rst @@ -5,6 +5,18 @@ API Reference :undoc-members: :members: +.. automodule:: vws.async_vws + :undoc-members: + :members: + +.. automodule:: vws.async_query + :undoc-members: + :members: + +.. automodule:: vws.async_vumark_service + :undoc-members: + :members: + .. automodule:: vws.reports :undoc-members: :members: diff --git a/pyproject.toml b/pyproject.toml index 88d229a1d..b4fbfeea3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,6 +60,7 @@ optional-dependencies.dev = [ "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", + "pytest-asyncio==1.3.0", "pytest-cov==7.0.0", "pyyaml==6.0.3", "ruff==0.15.2", @@ -359,6 +360,7 @@ ignore_path = [ # but Vulture does not enable this. ignore_names = [ # Public API classes imported by users from vws.transports + "AsyncHTTPXTransport", "HTTPXTransport", # pytest configuration "pytest_collect_file", diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index 8831d3d34..73d358a00 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -27,6 +27,8 @@ admin api args ascii +async +asyncio beartype bool boolean diff --git a/src/vws/__init__.py b/src/vws/__init__.py index a091641f9..a181cb818 100644 --- a/src/vws/__init__.py +++ b/src/vws/__init__.py @@ -1,11 +1,17 @@ """A library for Vuforia Web Services.""" +from .async_query import AsyncCloudRecoService +from .async_vumark_service import AsyncVuMarkService +from .async_vws import AsyncVWS from .query import CloudRecoService from .vumark_service import VuMarkService from .vws import VWS __all__ = [ "VWS", + "AsyncCloudRecoService", + "AsyncVWS", + "AsyncVuMarkService", "CloudRecoService", "VuMarkService", ] diff --git a/src/vws/_async_vws_request.py b/src/vws/_async_vws_request.py new file mode 100644 index 000000000..4ebd50c86 --- /dev/null +++ b/src/vws/_async_vws_request.py @@ -0,0 +1,77 @@ +"""Internal helper for making authenticated async requests to the +Vuforia Target API. +""" + +from beartype import BeartypeConf, beartype +from vws_auth_tools import authorization_header, rfc_1123_date + +from vws.response import Response +from vws.transports import AsyncTransport + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +async def async_target_api_request( + *, + content_type: str, + server_access_key: str, + server_secret_key: str, + method: str, + data: bytes, + request_path: str, + base_vws_url: str, + request_timeout_seconds: float | tuple[float, float], + extra_headers: dict[str, str], + transport: AsyncTransport, +) -> Response: + """Make an async request to the Vuforia Target API. + + Args: + content_type: The content type of the request. + server_access_key: A VWS server access key. + server_secret_key: A VWS server secret key. + method: The HTTP method which will be used in the + request. + data: The request body which will be used in the + request. + request_path: The path to the endpoint which will be + used in the request. + base_vws_url: The base URL for the VWS API. + request_timeout_seconds: The timeout for the request. + This can be a float to set both the connect and + read timeouts, or a (connect, read) tuple. + extra_headers: Additional headers to include in the + request. + transport: The async HTTP transport to use for the + request. + + Returns: + The response to the request. + """ + date_string = rfc_1123_date() + + signature_string = authorization_header( + access_key=server_access_key, + secret_key=server_secret_key, + method=method, + content=data, + content_type=content_type, + date=date_string, + request_path=request_path, + ) + + headers = { + "Authorization": signature_string, + "Date": date_string, + "Content-Type": content_type, + **extra_headers, + } + + url = base_vws_url.rstrip("/") + request_path + + return await transport( + method=method, + url=url, + headers=headers, + data=data, + request_timeout=request_timeout_seconds, + ) diff --git a/src/vws/async_query.py b/src/vws/async_query.py new file mode 100644 index 000000000..56981d4e1 --- /dev/null +++ b/src/vws/async_query.py @@ -0,0 +1,226 @@ +"""Async tools for interacting with the Vuforia Cloud Recognition +Web APIs. +""" + +import datetime +import json +from http import HTTPMethod, HTTPStatus +from typing import Any, Self + +from beartype import BeartypeConf, beartype +from urllib3.filepost import encode_multipart_formdata +from vws_auth_tools import authorization_header, rfc_1123_date + +from vws._image_utils import ImageType as _ImageType +from vws._image_utils import get_image_data as _get_image_data +from vws.exceptions.cloud_reco_exceptions import ( + AuthenticationFailureError, + BadImageError, + InactiveProjectError, + MaxNumResultsOutOfRangeError, + RequestTimeTooSkewedError, +) +from vws.exceptions.custom_exceptions import ( + RequestEntityTooLargeError, + ServerError, +) +from vws.include_target_data import CloudRecoIncludeTargetData +from vws.reports import QueryResult, TargetData +from vws.transports import AsyncHTTPXTransport, AsyncTransport + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +class AsyncCloudRecoService: + """An async interface to the Vuforia Cloud Recognition Web + APIs. + """ + + def __init__( + self, + *, + client_access_key: str, + client_secret_key: str, + base_vwq_url: str = "https://cloudreco.vuforia.com", + request_timeout_seconds: float | tuple[float, float] = 30.0, + transport: AsyncTransport | None = None, + ) -> None: + """ + Args: + client_access_key: A VWS client access key. + client_secret_key: A VWS client secret key. + base_vwq_url: The base URL for the VWQ API. + request_timeout_seconds: The timeout for each + HTTP request. This can be a float to set both + the connect and read timeouts, or a + (connect, read) tuple. + transport: The async HTTP transport to use for + requests. Defaults to + ``AsyncHTTPXTransport()``. + """ + self._client_access_key = client_access_key + self._client_secret_key = client_secret_key + self._base_vwq_url = base_vwq_url + self._request_timeout_seconds = request_timeout_seconds + self._transport = transport or AsyncHTTPXTransport() + + async def aclose(self) -> None: + """Close the underlying transport if it supports closing.""" + close = getattr(self._transport, "aclose", None) + if close is not None: + await close() + + async def __aenter__(self) -> Self: + """Enter the async context manager.""" + return self + + async def __aexit__(self, *_args: object) -> None: + """Exit the async context manager and close the transport.""" + await self.aclose() + + async def query( + self, + *, + image: _ImageType, + max_num_results: int = 1, + include_target_data: CloudRecoIncludeTargetData = ( + CloudRecoIncludeTargetData.TOP + ), + ) -> list[QueryResult]: + """Use the Vuforia Web Query API to make an Image + Recognition Query. + + See + https://developer.vuforia.com/library/web-api/vuforia-query-web-api + for parameter details. + + Args: + image: The image to make a query against. + max_num_results: The maximum number of matching + targets to be returned. + include_target_data: Indicates if target_data + records shall be returned for the matched + targets. Accepted values are top (default + value, only return target_data for top ranked + match), none (return no target_data), all + (for all matched targets). + + Raises: + ~vws.exceptions.cloud_reco_exceptions.AuthenticationFailureError: + The client access key pair is not correct. + ~vws.exceptions.cloud_reco_exceptions.MaxNumResultsOutOfRangeError: + ``max_num_results`` is not within the range (1, 50). + ~vws.exceptions.cloud_reco_exceptions.InactiveProjectError: The + project is inactive. + ~vws.exceptions.cloud_reco_exceptions.RequestTimeTooSkewedError: + There is an error with the time sent to Vuforia. + ~vws.exceptions.cloud_reco_exceptions.BadImageError: There is a + problem with the given image. For example, it must be a JPEG or + PNG file in the grayscale or RGB color space. + ~vws.exceptions.custom_exceptions.RequestEntityTooLargeError: The + given image is too large. + ~vws.exceptions.custom_exceptions.ServerError: There is an + error with Vuforia's servers. + + Returns: + An ordered list of target details of matching + targets. + """ + image_content = _get_image_data(image=image) + body: dict[str, Any] = { + "image": ( + "image.jpeg", + image_content, + "image/jpeg", + ), + "max_num_results": ( + None, + int(max_num_results), + "text/plain", + ), + "include_target_data": ( + None, + include_target_data.value, + "text/plain", + ), + } + date = rfc_1123_date() + request_path = "/v1/query" + content, content_type_header = encode_multipart_formdata(fields=body) + method = HTTPMethod.POST + + authorization_string = authorization_header( + access_key=self._client_access_key, + secret_key=self._client_secret_key, + method=method, + content=content, + # Note that this is not the actual Content-Type + # header value sent. + content_type="multipart/form-data", + date=date, + request_path=request_path, + ) + + headers = { + "Authorization": authorization_string, + "Date": date, + "Content-Type": content_type_header, + } + + response = await self._transport( + method=method, + url=self._base_vwq_url.rstrip("/") + request_path, + headers=headers, + data=content, + request_timeout=self._request_timeout_seconds, + ) + + if response.status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE: + raise RequestEntityTooLargeError(response=response) + + if "Integer out of range" in response.text: + raise MaxNumResultsOutOfRangeError( + response=response, + ) + + if ( + response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR + ): # pragma: no cover + raise ServerError(response=response) + + result_code = json.loads(s=response.text)["result_code"] + if result_code != "Success": + exception = { + "AuthenticationFailure": (AuthenticationFailureError), + "BadImage": BadImageError, + "InactiveProject": InactiveProjectError, + "RequestTimeTooSkewed": (RequestTimeTooSkewedError), + }[result_code] + raise exception(response=response) + + result: list[QueryResult] = [] + result_list = list( + json.loads(s=response.text)["results"], + ) + for item in result_list: + target_data: TargetData | None = None + if "target_data" in item: + target_data_dict = item["target_data"] + metadata = target_data_dict["application_metadata"] + timestamp_string = target_data_dict["target_timestamp"] + target_timestamp = datetime.datetime.fromtimestamp( + timestamp=timestamp_string, + tz=datetime.UTC, + ) + target_data = TargetData( + name=target_data_dict["name"], + application_metadata=metadata, + target_timestamp=target_timestamp, + ) + + query_result = QueryResult( + target_id=item["target_id"], + target_data=target_data, + ) + + result.append(query_result) + return result diff --git a/src/vws/async_vumark_service.py b/src/vws/async_vumark_service.py new file mode 100644 index 000000000..c1e189eeb --- /dev/null +++ b/src/vws/async_vumark_service.py @@ -0,0 +1,170 @@ +"""Async interface to the Vuforia VuMark Generation Web API.""" + +import json +from http import HTTPMethod, HTTPStatus +from typing import Self + +from beartype import BeartypeConf, beartype + +from vws._async_vws_request import async_target_api_request +from vws.exceptions.custom_exceptions import ServerError +from vws.exceptions.vws_exceptions import ( + AuthenticationFailureError, + BadRequestError, + DateRangeError, + FailError, + InvalidAcceptHeaderError, + InvalidInstanceIdError, + InvalidTargetTypeError, + RequestTimeTooSkewedError, + TargetStatusNotSuccessError, + TooManyRequestsError, + UnknownTargetError, +) +from vws.transports import AsyncHTTPXTransport, AsyncTransport +from vws.vumark_accept import VuMarkAccept + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +class AsyncVuMarkService: + """An async interface to the Vuforia VuMark Generation Web + API. + """ + + def __init__( + self, + *, + server_access_key: str, + server_secret_key: str, + base_vws_url: str = "https://vws.vuforia.com", + request_timeout_seconds: float | tuple[float, float] = 30.0, + transport: AsyncTransport | None = None, + ) -> None: + """ + Args: + server_access_key: A VWS server access key. + server_secret_key: A VWS server secret key. + base_vws_url: The base URL for the VWS API. + request_timeout_seconds: The timeout for each + HTTP request. This can be a float to set both + the connect and read timeouts, or a + (connect, read) tuple. + transport: The async HTTP transport to use for + requests. Defaults to + ``AsyncHTTPXTransport()``. + """ + self._server_access_key = server_access_key + self._server_secret_key = server_secret_key + self._base_vws_url = base_vws_url + self._request_timeout_seconds = request_timeout_seconds + self._transport = transport or AsyncHTTPXTransport() + + async def aclose(self) -> None: + """Close the underlying transport if it supports closing.""" + close = getattr(self._transport, "aclose", None) + if close is not None: + await close() + + async def __aenter__(self) -> Self: + """Enter the async context manager.""" + return self + + async def __aexit__(self, *_args: object) -> None: + """Exit the async context manager and close the transport.""" + await self.aclose() + + async def generate_vumark_instance( + self, + *, + target_id: str, + instance_id: str, + accept: VuMarkAccept, + ) -> bytes: + """Generate a VuMark instance image. + + See + https://developer.vuforia.com/library/vuforia-engine/web-api/vumark-generation-web-api/ + for parameter details. + + Args: + target_id: The ID of the VuMark target. + instance_id: The instance ID to encode in the + VuMark. + accept: The image format to return. + + Returns: + The VuMark instance image bytes. + + Raises: + ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The + secret key is not correct. + ~vws.exceptions.vws_exceptions.FailError: There was an error with + the request. For example, the given access key does not match a + known database. + ~vws.exceptions.vws_exceptions.InvalidAcceptHeaderError: The + Accept header value is not supported. + ~vws.exceptions.vws_exceptions.InvalidInstanceIdError: The + instance ID is invalid. For example, it may be empty. + ~vws.exceptions.vws_exceptions.InvalidTargetTypeError: The target + is not a VuMark template target. + ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is + an error with the time sent to Vuforia. + ~vws.exceptions.vws_exceptions.TargetStatusNotSuccessError: The + target is not in the success state. + ~vws.exceptions.vws_exceptions.UnknownTargetError: The given target + ID does not match a target in the database. + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. + """ + request_path = f"/targets/{target_id}/instances" + content_type = "application/json" + request_data = json.dumps( + obj={"instance_id": instance_id}, + ).encode(encoding="utf-8") + + response = await async_target_api_request( + content_type=content_type, + server_access_key=self._server_access_key, + server_secret_key=self._server_secret_key, + method=HTTPMethod.POST, + data=request_data, + request_path=request_path, + base_vws_url=self._base_vws_url, + request_timeout_seconds=(self._request_timeout_seconds), + extra_headers={"Accept": accept}, + transport=self._transport, + ) + + if ( + response.status_code == HTTPStatus.TOO_MANY_REQUESTS + ): # pragma: no cover + # The Vuforia API returns a 429 response with no + # JSON body. + raise TooManyRequestsError(response=response) + + if ( + response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR + ): # pragma: no cover + raise ServerError(response=response) + + if response.status_code == HTTPStatus.OK: + return response.content + + result_code = json.loads(s=response.text)["result_code"] + + exception = { + "AuthenticationFailure": (AuthenticationFailureError), + "BadRequest": BadRequestError, + "DateRangeError": DateRangeError, + "Fail": FailError, + "InvalidAcceptHeader": InvalidAcceptHeaderError, + "InvalidInstanceId": InvalidInstanceIdError, + "InvalidTargetType": InvalidTargetTypeError, + "RequestTimeTooSkewed": RequestTimeTooSkewedError, + "TargetStatusNotSuccess": (TargetStatusNotSuccessError), + "UnknownTarget": UnknownTargetError, + }[result_code] + + raise exception(response=response) diff --git a/src/vws/async_vws.py b/src/vws/async_vws.py new file mode 100644 index 000000000..f0f9005a1 --- /dev/null +++ b/src/vws/async_vws.py @@ -0,0 +1,692 @@ +"""Async tools for interacting with Vuforia APIs.""" + +import asyncio +import base64 +import json +from datetime import date +from http import HTTPMethod, HTTPStatus +from typing import Self + +from beartype import BeartypeConf, beartype + +from vws._async_vws_request import async_target_api_request +from vws._image_utils import ImageType as _ImageType +from vws._image_utils import get_image_data as _get_image_data +from vws.exceptions.custom_exceptions import ( + ServerError, + TargetProcessingTimeoutError, +) +from vws.exceptions.vws_exceptions import ( + AuthenticationFailureError, + BadImageError, + BadRequestError, + DateRangeError, + FailError, + ImageTooLargeError, + MetadataTooLargeError, + ProjectHasNoAPIAccessError, + ProjectInactiveError, + ProjectSuspendedError, + RequestQuotaReachedError, + RequestTimeTooSkewedError, + TargetNameExistError, + TargetQuotaReachedError, + TargetStatusNotSuccessError, + TargetStatusProcessingError, + TooManyRequestsError, + UnknownTargetError, +) +from vws.reports import ( + DatabaseSummaryReport, + TargetRecord, + TargetStatusAndRecord, + TargetStatuses, + TargetSummaryReport, +) +from vws.response import Response +from vws.transports import AsyncHTTPXTransport, AsyncTransport + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +class AsyncVWS: + """An async interface to Vuforia Web Services APIs.""" + + def __init__( + self, + *, + server_access_key: str, + server_secret_key: str, + base_vws_url: str = "https://vws.vuforia.com", + request_timeout_seconds: float | tuple[float, float] = 30.0, + transport: AsyncTransport | None = None, + ) -> None: + """ + Args: + server_access_key: A VWS server access key. + server_secret_key: A VWS server secret key. + base_vws_url: The base URL for the VWS API. + request_timeout_seconds: The timeout for each + HTTP request. This can be a float to set both + the connect and read timeouts, or a + (connect, read) tuple. + transport: The async HTTP transport to use for + requests. Defaults to + ``AsyncHTTPXTransport()``. + """ + self._server_access_key = server_access_key + self._server_secret_key = server_secret_key + self._base_vws_url = base_vws_url + self._request_timeout_seconds = request_timeout_seconds + self._transport = transport or AsyncHTTPXTransport() + + async def aclose(self) -> None: + """Close the underlying transport if it supports closing.""" + close = getattr(self._transport, "aclose", None) + if close is not None: + await close() + + async def __aenter__(self) -> Self: + """Enter the async context manager.""" + return self + + async def __aexit__(self, *_args: object) -> None: + """Exit the async context manager and close the transport.""" + await self.aclose() + + async def make_request( + self, + *, + method: str, + data: bytes, + request_path: str, + expected_result_code: str, + content_type: str, + extra_headers: dict[str, str] | None = None, + ) -> Response: + """Make an async request to the Vuforia Target API. + + Args: + method: The HTTP method which will be used in + the request. + data: The request body which will be used in the + request. + request_path: The path to the endpoint which + will be used in the request. + expected_result_code: See + "VWS API Result Codes" on + https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api. + content_type: The content type of the request. + extra_headers: Additional headers to include in + the request. + + Returns: + The response to the request. + + Raises: + ~vws.exceptions.custom_exceptions.ServerError: + There is an error with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: + Vuforia is rate limiting access. + json.JSONDecodeError: The server did not respond + with valid JSON. This may happen if the + server address is not a valid Vuforia server. + """ + response = await async_target_api_request( + content_type=content_type, + server_access_key=self._server_access_key, + server_secret_key=self._server_secret_key, + method=method, + data=data, + request_path=request_path, + base_vws_url=self._base_vws_url, + request_timeout_seconds=self._request_timeout_seconds, + extra_headers=extra_headers or {}, + transport=self._transport, + ) + + if ( + response.status_code == HTTPStatus.TOO_MANY_REQUESTS + ): # pragma: no cover + # The Vuforia API returns a 429 response with no JSON body. + raise TooManyRequestsError(response=response) + + if ( + response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR + ): # pragma: no cover + raise ServerError(response=response) + + result_code = json.loads(s=response.text)["result_code"] + + if result_code == expected_result_code: + return response + + exception = { + "AuthenticationFailure": AuthenticationFailureError, + "BadImage": BadImageError, + "BadRequest": BadRequestError, + "DateRangeError": DateRangeError, + "Fail": FailError, + "ImageTooLarge": ImageTooLargeError, + "MetadataTooLarge": MetadataTooLargeError, + "ProjectHasNoAPIAccess": ProjectHasNoAPIAccessError, + "ProjectInactive": ProjectInactiveError, + "ProjectSuspended": ProjectSuspendedError, + "RequestQuotaReached": RequestQuotaReachedError, + "RequestTimeTooSkewed": RequestTimeTooSkewedError, + "TargetNameExist": TargetNameExistError, + "TargetQuotaReached": TargetQuotaReachedError, + "TargetStatusNotSuccess": TargetStatusNotSuccessError, + "TargetStatusProcessing": TargetStatusProcessingError, + "UnknownTarget": UnknownTargetError, + }[result_code] + + raise exception(response=response) + + async def add_target( + self, + *, + name: str, + width: float, + image: _ImageType, + application_metadata: str | None, + active_flag: bool, + ) -> str: + """Add a target to a Vuforia Web Services database. + + See + https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#add + for parameter details. + + Args: + name: The name of the target. + width: The width of the target. + image: The image of the target. + active_flag: Whether or not the target is active for query. + application_metadata: The application metadata of the target. + This must be base64 encoded, for example by using:: + + base64.b64encode('input_string').decode('ascii') + + Returns: + The target ID of the new target. + + Raises: + ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The + secret key is not correct. + ~vws.exceptions.vws_exceptions.BadImageError: There is a problem + with the given image. For example, it must be a JPEG or PNG + file in the grayscale or RGB color space. + ~vws.exceptions.vws_exceptions.FailError: There was an error with + the request. For example, the given access key does not match a + known database. + ~vws.exceptions.vws_exceptions.MetadataTooLargeError: The given + metadata is too large. The maximum size is 1 MB of data when + Base64 encoded. + ~vws.exceptions.vws_exceptions.ImageTooLargeError: The given image + is too large. + ~vws.exceptions.vws_exceptions.TargetNameExistError: A target with + the given ``name`` already exists. + ~vws.exceptions.vws_exceptions.ProjectInactiveError: The project is + inactive. + ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is + an error with the time sent to Vuforia. + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. This has been seen to happen when the + given name includes a bad character. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. + """ + image_data = _get_image_data(image=image) + image_data_encoded = base64.b64encode(s=image_data).decode( + encoding="ascii", + ) + + data = { + "name": name, + "width": width, + "image": image_data_encoded, + "active_flag": active_flag, + "application_metadata": application_metadata, + } + + content = json.dumps(obj=data).encode(encoding="utf-8") + + response = await self.make_request( + method=HTTPMethod.POST, + data=content, + request_path="/targets", + expected_result_code="TargetCreated", + content_type="application/json", + ) + + return str(object=json.loads(s=response.text)["target_id"]) + + async def get_target_record(self, target_id: str) -> TargetStatusAndRecord: + """Get a given target's target record from the Target + Management System. + + See + https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#target-record. + + Args: + target_id: The ID of the target to get details of. + + Returns: + Response details of a target from Vuforia. + + Raises: + ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The + secret key is not correct. + ~vws.exceptions.vws_exceptions.FailError: There was an error with + the request. For example, the given access key does not match a + known database. + ~vws.exceptions.vws_exceptions.UnknownTargetError: The given target + ID does not match a target in the database. + ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is + an error with the time sent to Vuforia. + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. + """ + response = await self.make_request( + method=HTTPMethod.GET, + data=b"", + request_path=f"/targets/{target_id}", + expected_result_code="Success", + content_type="application/json", + ) + + result_data = json.loads(s=response.text) + status = TargetStatuses(value=result_data["status"]) + target_record_dict = dict(result_data["target_record"]) + target_record = TargetRecord( + target_id=target_record_dict["target_id"], + active_flag=bool(target_record_dict["active_flag"]), + name=target_record_dict["name"], + width=float(target_record_dict["width"]), + tracking_rating=int(target_record_dict["tracking_rating"]), + reco_rating=target_record_dict["reco_rating"], + ) + return TargetStatusAndRecord( + status=status, + target_record=target_record, + ) + + async def wait_for_target_processed( + self, + *, + target_id: str, + seconds_between_requests: float = 0.2, + timeout_seconds: float = 60 * 5, + ) -> None: + """Wait up to five minutes (arbitrary) for a target to + get past the processing stage. + + Args: + target_id: The ID of the target to wait for. + seconds_between_requests: The number of seconds to + wait between requests made while polling the + target status. + We wait 0.2 seconds by default, rather than + less, than that to decrease the number of calls + made to the API, to decrease the likelihood of + hitting the request quota. + timeout_seconds: The maximum number of seconds to + wait for the target to be processed. + + Raises: + ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The + secret key is not correct. + ~vws.exceptions.vws_exceptions.FailError: There was an error with + the request. For example, the given access key does not match a + known database. + ~vws.exceptions.custom_exceptions.TargetProcessingTimeoutError: The + target remained in the processing stage for more than + ``timeout_seconds`` seconds. + ~vws.exceptions.vws_exceptions.UnknownTargetError: The given target + ID does not match a target in the database. + ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is + an error with the time sent to Vuforia. + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. + """ + start_time = asyncio.get_event_loop().time() + while True: + report = await self.get_target_summary_report( + target_id=target_id, + ) + if report.status != TargetStatuses.PROCESSING: + # Guard against the target still being seen as + # processing by other endpoints due to eventual + # consistency. + await asyncio.sleep( + delay=seconds_between_requests, + ) + return + + elapsed_time = asyncio.get_event_loop().time() - start_time + if elapsed_time > timeout_seconds: # pragma: no cover + raise TargetProcessingTimeoutError + + await asyncio.sleep( + delay=seconds_between_requests, + ) + + async def list_targets(self) -> list[str]: + """List target IDs. + + See + https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#details-list. + + Returns: + The IDs of all targets in the database. + + Raises: + ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The + secret key is not correct. + ~vws.exceptions.vws_exceptions.FailError: There was an error with + the request. For example, the given access key does not match a + known database. + ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is + an error with the time sent to Vuforia. + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. + """ + response = await self.make_request( + method=HTTPMethod.GET, + data=b"", + request_path="/targets", + expected_result_code="Success", + content_type="application/json", + ) + + return list(json.loads(s=response.text)["results"]) + + async def get_target_summary_report( + self, target_id: str + ) -> TargetSummaryReport: + """Get a summary report for a target. + + See + https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#summary-report. + + Args: + target_id: The ID of the target to get a summary + report for. + + Returns: + Details of the target. + + Raises: + ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The + secret key is not correct. + ~vws.exceptions.vws_exceptions.FailError: There was an error with + the request. For example, the given access key does not match a + known database. + ~vws.exceptions.vws_exceptions.UnknownTargetError: The given target + ID does not match a target in the database. + ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is + an error with the time sent to Vuforia. + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. + """ + response = await self.make_request( + method=HTTPMethod.GET, + data=b"", + request_path=f"/summary/{target_id}", + expected_result_code="Success", + content_type="application/json", + ) + + result_data = dict(json.loads(s=response.text)) + return TargetSummaryReport( + status=TargetStatuses( + value=result_data["status"], + ), + database_name=result_data["database_name"], + target_name=result_data["target_name"], + upload_date=date.fromisoformat( + result_data["upload_date"], + ), + active_flag=bool(result_data["active_flag"]), + tracking_rating=int( + result_data["tracking_rating"], + ), + total_recos=int(result_data["total_recos"]), + current_month_recos=int( + result_data["current_month_recos"], + ), + previous_month_recos=int( + result_data["previous_month_recos"], + ), + ) + + async def get_database_summary_report( + self, + ) -> DatabaseSummaryReport: + """Get a summary report for the database. + + See + https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#summary-report. + + Returns: + Details of the database. + + Raises: + ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The + secret key is not correct. + ~vws.exceptions.vws_exceptions.FailError: There was an error with + the request. For example, the given access key does not match a + known database. + ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is + an error with the time sent to Vuforia. + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. + """ + response = await self.make_request( + method=HTTPMethod.GET, + data=b"", + request_path="/summary", + expected_result_code="Success", + content_type="application/json", + ) + + response_data = dict(json.loads(s=response.text)) + return DatabaseSummaryReport( + active_images=int(response_data["active_images"]), + current_month_recos=int( + response_data["current_month_recos"], + ), + failed_images=int(response_data["failed_images"]), + inactive_images=int( + response_data["inactive_images"], + ), + name=str(object=response_data["name"]), + previous_month_recos=int( + response_data["previous_month_recos"], + ), + processing_images=int( + response_data["processing_images"], + ), + reco_threshold=int( + response_data["reco_threshold"], + ), + request_quota=int(response_data["request_quota"]), + request_usage=int(response_data["request_usage"]), + target_quota=int(response_data["target_quota"]), + total_recos=int(response_data["total_recos"]), + ) + + async def delete_target(self, target_id: str) -> None: + """Delete a given target. + + See + https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#delete. + + Args: + target_id: The ID of the target to delete. + + Raises: + ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The + secret key is not correct. + ~vws.exceptions.vws_exceptions.FailError: There was an error with + the request. For example, the given access key does not match a + known database. + ~vws.exceptions.vws_exceptions.UnknownTargetError: The given target + ID does not match a target in the database. + ~vws.exceptions.vws_exceptions.TargetStatusProcessingError: The + given target is in the processing state. + ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is + an error with the time sent to Vuforia. + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. + """ + await self.make_request( + method=HTTPMethod.DELETE, + data=b"", + request_path=f"/targets/{target_id}", + expected_result_code="Success", + content_type="application/json", + ) + + async def get_duplicate_targets(self, target_id: str) -> list[str]: + """Get targets which may be considered duplicates of a + given target. + + See + https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#check. + + Args: + target_id: The ID of the target to delete. + + Returns: + The target IDs of duplicate targets. + + Raises: + ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The + secret key is not correct. + ~vws.exceptions.vws_exceptions.FailError: There was an error with + the request. For example, the given access key does not match a + known database. + ~vws.exceptions.vws_exceptions.UnknownTargetError: The given target + ID does not match a target in the database. + ~vws.exceptions.vws_exceptions.ProjectInactiveError: The project is + inactive. + ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is + an error with the time sent to Vuforia. + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. + """ + response = await self.make_request( + method=HTTPMethod.GET, + data=b"", + request_path=f"/duplicates/{target_id}", + expected_result_code="Success", + content_type="application/json", + ) + + return list( + json.loads(s=response.text)["similar_targets"], + ) + + async def update_target( + self, + *, + target_id: str, + name: str | None = None, + width: float | None = None, + image: _ImageType | None = None, + active_flag: bool | None = None, + application_metadata: str | None = None, + ) -> None: + """Update a target in a Vuforia Web Services database. + + See + https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#update + for parameter details. + + Args: + target_id: The ID of the target to update. + name: The name of the target. + width: The width of the target. + image: The image of the target. + active_flag: Whether or not the target is active + for query. + application_metadata: The application metadata of + the target. + This must be base64 encoded, for example by + using:: + + base64.b64encode('input_string').decode('ascii') + + Giving ``None`` will not change the application + metadata. + + Raises: + ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The + secret key is not correct. + ~vws.exceptions.vws_exceptions.BadImageError: There is a problem + with the given image. For example, it must be a JPEG or PNG + file in the grayscale or RGB color space. + ~vws.exceptions.vws_exceptions.FailError: There was an error with + the request. For example, the given access key does not match a + known database. + ~vws.exceptions.vws_exceptions.MetadataTooLargeError: The given + metadata is too large. The maximum size is 1 MB of data when + Base64 encoded. + ~vws.exceptions.vws_exceptions.ImageTooLargeError: The given image + is too large. + ~vws.exceptions.vws_exceptions.TargetNameExistError: A target with + the given ``name`` already exists. + ~vws.exceptions.vws_exceptions.ProjectInactiveError: The project is + inactive. + ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is + an error with the time sent to Vuforia. + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. + """ + data: dict[str, str | bool | float | int] = {} + + if name is not None: + data["name"] = name + + if width is not None: + data["width"] = width + + if image is not None: + image_data = _get_image_data(image=image) + image_data_encoded = base64.b64encode( + s=image_data, + ).decode(encoding="ascii") + data["image"] = image_data_encoded + + if active_flag is not None: + data["active_flag"] = active_flag + + if application_metadata is not None: + data["application_metadata"] = application_metadata + + content = json.dumps(obj=data).encode(encoding="utf-8") + + await self.make_request( + method=HTTPMethod.PUT, + data=content, + request_path=f"/targets/{target_id}", + expected_result_code="Success", + content_type="application/json", + ) diff --git a/src/vws/transports.py b/src/vws/transports.py index 7980c5785..6bf9ceb9e 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -1,6 +1,7 @@ """HTTP transport implementations for VWS clients.""" -from typing import Protocol, runtime_checkable +from collections.abc import Awaitable +from typing import Protocol, Self, runtime_checkable import httpx import requests @@ -156,3 +157,122 @@ def __call__( tell_position=len(content), content=content, ) + + +@runtime_checkable +class AsyncTransport(Protocol): + """Protocol for async HTTP transports used by VWS clients. + + An async transport is a callable that makes an HTTP request + and returns a ``Response``. + """ + + def __call__( + self, + *, + method: str, + url: str, + headers: dict[str, str], + data: bytes, + request_timeout: float | tuple[float, float], + ) -> Awaitable[Response]: + """Make an async HTTP request. + + Args: + method: The HTTP method (e.g. "GET", "POST"). + url: The full URL to request. + headers: Headers to send with the request. + data: The request body as bytes. + request_timeout: The timeout for the request. A float + sets both the connect and read timeouts. A + (connect, read) tuple sets them individually. + + Returns: + A Response populated from the HTTP response. + """ + ... # pylint: disable=unnecessary-ellipsis + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +class AsyncHTTPXTransport: + """Async HTTP transport using the ``httpx`` library. + + This is the default transport for async VWS clients. + A single ``httpx.AsyncClient`` is reused across requests + for connection pooling. + """ + + def __init__(self) -> None: + """Create an ``AsyncHTTPXTransport``.""" + self._client = httpx.AsyncClient() + + async def aclose(self) -> None: + """Close the underlying ``httpx.AsyncClient``.""" + await self._client.aclose() + + async def __aenter__(self) -> Self: + """Enter the async context manager.""" + return self + + async def __aexit__(self, *_args: object) -> None: + """Exit the async context manager and close the client.""" + await self.aclose() + + async def __call__( + self, + *, + method: str, + url: str, + headers: dict[str, str], + data: bytes, + request_timeout: float | tuple[float, float], + ) -> Response: + """Make an async HTTP request using ``httpx``. + + Args: + method: The HTTP method. + url: The full URL. + headers: Request headers. + data: The request body. + request_timeout: The request timeout. + + Returns: + A Response populated from the httpx response. + """ + if isinstance(request_timeout, tuple): + connect_timeout, read_timeout = request_timeout + httpx_timeout = httpx.Timeout( + connect=connect_timeout, + read=read_timeout, + write=None, + pool=None, + ) + else: + httpx_timeout = httpx.Timeout( + connect=request_timeout, + read=request_timeout, + write=None, + pool=None, + ) + + httpx_response = await self._client.request( + method=method, + url=url, + headers=headers, + content=data, + timeout=httpx_timeout, + follow_redirects=True, + ) + + content = bytes(httpx_response.content) + request_content = httpx_response.request.content + + return Response( + text=httpx_response.text, + url=str(object=httpx_response.url), + status_code=httpx_response.status_code, + headers=dict(httpx_response.headers), + request_body=bytes(request_content) or None, + tell_position=len(content), + content=content, + ) diff --git a/tests/conftest.py b/tests/conftest.py index eb8b3a573..820908ba9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,16 +1,24 @@ """Configuration, plugins and fixtures for `pytest`.""" import io -from collections.abc import Generator +from collections.abc import AsyncGenerator, Generator from pathlib import Path from typing import BinaryIO, Literal import pytest +import pytest_asyncio from mock_vws import MockVWS from mock_vws.database import CloudDatabase, VuMarkDatabase from mock_vws.target import VuMarkTarget -from vws import VWS, CloudRecoService, VuMarkService +from vws import ( + VWS, + AsyncCloudRecoService, + AsyncVuMarkService, + AsyncVWS, + CloudRecoService, + VuMarkService, +) @pytest.fixture(name="_mock_database") @@ -70,6 +78,49 @@ def cloud_reco_client(*, _mock_database: CloudDatabase) -> CloudRecoService: ) +@pytest_asyncio.fixture +async def async_vws_client( + *, + _mock_database: CloudDatabase, +) -> AsyncGenerator[AsyncVWS]: + """An async VWS client which connects to a mock database.""" + async with AsyncVWS( + server_access_key=_mock_database.server_access_key, + server_secret_key=_mock_database.server_secret_key, + ) as client: + yield client + + +@pytest_asyncio.fixture +async def async_cloud_reco_client( + *, + _mock_database: CloudDatabase, +) -> AsyncGenerator[AsyncCloudRecoService]: + """An async ``CloudRecoService`` client which connects to a mock + database. + """ + async with AsyncCloudRecoService( + client_access_key=_mock_database.client_access_key, + client_secret_key=_mock_database.client_secret_key, + ) as client: + yield client + + +@pytest_asyncio.fixture +async def async_vumark_service_client( + *, + _mock_vumark_database: VuMarkDatabase, +) -> AsyncGenerator[AsyncVuMarkService]: + """An async ``VuMarkService`` client which connects to a mock VuMark + database. + """ + async with AsyncVuMarkService( + server_access_key=_mock_vumark_database.server_access_key, + server_secret_key=_mock_vumark_database.server_secret_key, + ) as client: + yield client + + @pytest.fixture(name="image_file", params=["r+b", "rb"]) def fixture_image_file( *, diff --git a/tests/test_async_cloud_reco_exceptions.py b/tests/test_async_cloud_reco_exceptions.py new file mode 100644 index 000000000..cbe2b22cc --- /dev/null +++ b/tests/test_async_cloud_reco_exceptions.py @@ -0,0 +1,120 @@ +"""Tests for exceptions raised when using the +AsyncCloudRecoService. +""" + +import io +import uuid +from http import HTTPStatus + +import pytest +from mock_vws import MockVWS +from mock_vws.database import CloudDatabase +from mock_vws.states import States + +from vws import AsyncCloudRecoService +from vws.exceptions.cloud_reco_exceptions import ( + AuthenticationFailureError, + InactiveProjectError, + MaxNumResultsOutOfRangeError, +) +from vws.exceptions.custom_exceptions import ( + RequestEntityTooLargeError, +) + + +@pytest.mark.asyncio +async def test_too_many_max_results( + *, + async_cloud_reco_client: AsyncCloudRecoService, + high_quality_image: io.BytesIO, +) -> None: + """A ``MaxNumResultsOutOfRange`` error is raised if the given + ``max_num_results`` is out of range. + """ + with pytest.raises( + expected_exception=MaxNumResultsOutOfRangeError, + ) as exc: + await async_cloud_reco_client.query( + image=high_quality_image, + max_num_results=51, + ) + + expected_value = ( + "Integer out of range (51) in form data part " + "'max_result'. " + "Accepted range is from 1 to 50 (inclusive)." + ) + assert str(object=exc.value) == exc.value.response.text == expected_value + + +@pytest.mark.asyncio +async def test_image_too_large( + *, + async_cloud_reco_client: AsyncCloudRecoService, + png_too_large: io.BytesIO | io.BufferedRandom, +) -> None: + """A ``RequestEntityTooLarge`` exception is raised if an + image which is too large is given. + """ + with pytest.raises( + expected_exception=RequestEntityTooLargeError, + ) as exc: + await async_cloud_reco_client.query( + image=png_too_large, + ) + + assert ( + exc.value.response.status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE + ) + + +@pytest.mark.asyncio +async def test_authentication_failure( + high_quality_image: io.BytesIO, +) -> None: + """An ``AuthenticationFailure`` exception is raised when the + client secret key is incorrect. + """ + database = CloudDatabase() + async_cloud_reco_client = AsyncCloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=uuid.uuid4().hex, + ) + with MockVWS() as mock: + mock.add_cloud_database(cloud_database=database) + + with pytest.raises( + expected_exception=AuthenticationFailureError, + ) as exc: + await async_cloud_reco_client.query( + image=high_quality_image, + ) + + assert exc.value.response.status_code == HTTPStatus.UNAUTHORIZED + + +@pytest.mark.asyncio +async def test_inactive_project( + high_quality_image: io.BytesIO, +) -> None: + """An ``InactiveProject`` exception is raised when querying + an inactive database. + """ + database = CloudDatabase(state=States.PROJECT_INACTIVE) + with MockVWS() as mock: + mock.add_cloud_database(cloud_database=database) + async_cloud_reco_client = AsyncCloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + ) + + with pytest.raises( + expected_exception=InactiveProjectError, + ) as exc: + await async_cloud_reco_client.query( + image=high_quality_image, + ) + + response = exc.value.response + assert response.status_code == HTTPStatus.FORBIDDEN + assert response.tell_position != 0 diff --git a/tests/test_async_query.py b/tests/test_async_query.py new file mode 100644 index 000000000..2de1ce833 --- /dev/null +++ b/tests/test_async_query.py @@ -0,0 +1,225 @@ +"""Tests for the ``AsyncCloudRecoService`` querying functionality.""" + +import io +import uuid +from typing import BinaryIO + +import pytest +from mock_vws import MockVWS +from mock_vws.database import CloudDatabase + +from vws import AsyncCloudRecoService, AsyncVWS +from vws.include_target_data import CloudRecoIncludeTargetData + + +class TestQuery: + """Tests for making async image queries.""" + + @staticmethod + @pytest.mark.asyncio + async def test_no_matches( + *, + async_cloud_reco_client: AsyncCloudRecoService, + image: io.BytesIO | BinaryIO, + ) -> None: + """An empty list is returned if there are no matches.""" + result = await async_cloud_reco_client.query( + image=image, + ) + assert result == [] + + @staticmethod + @pytest.mark.asyncio + async def test_match( + *, + async_vws_client: AsyncVWS, + async_cloud_reco_client: AsyncCloudRecoService, + image: io.BytesIO | BinaryIO, + ) -> None: + """Details of matching targets are returned.""" + target_id = await async_vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + await async_vws_client.wait_for_target_processed( + target_id=target_id, + ) + [matching_target] = await async_cloud_reco_client.query( + image=image, + ) + assert matching_target.target_id == target_id + + +class TestCustomBaseVWQURL: + """Tests for using a custom base VWQ URL.""" + + @staticmethod + @pytest.mark.asyncio + async def test_custom_base_url( + image: io.BytesIO | BinaryIO, + ) -> None: + """It is possible to query a target in a database under + a custom VWQ URL. + """ + base_vwq_url = "http://example.com" + with MockVWS(base_vwq_url=base_vwq_url) as mock: + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + async_vws_client = AsyncVWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + + target_id = await async_vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + + await async_vws_client.wait_for_target_processed( + target_id=target_id, + ) + + async_cloud_reco_client = AsyncCloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + base_vwq_url=base_vwq_url, + ) + + matches = await async_cloud_reco_client.query( + image=image, + ) + assert len(matches) == 1 + match = matches[0] + assert match.target_id == target_id + + +class TestMaxNumResults: + """Tests for the ``max_num_results`` parameter of + ``query``. + """ + + @staticmethod + @pytest.mark.asyncio + async def test_custom( + *, + async_vws_client: AsyncVWS, + async_cloud_reco_client: AsyncCloudRecoService, + image: io.BytesIO | BinaryIO, + ) -> None: + """It is possible to set a custom + ``max_num_results``. + """ + target_id = await async_vws_client.add_target( + name=uuid.uuid4().hex, + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + target_id_2 = await async_vws_client.add_target( + name=uuid.uuid4().hex, + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + target_id_3 = await async_vws_client.add_target( + name=uuid.uuid4().hex, + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + await async_vws_client.wait_for_target_processed( + target_id=target_id, + ) + await async_vws_client.wait_for_target_processed( + target_id=target_id_2, + ) + await async_vws_client.wait_for_target_processed( + target_id=target_id_3, + ) + max_num_results = 2 + matches = await async_cloud_reco_client.query( + image=image, + max_num_results=max_num_results, + ) + assert len(matches) == max_num_results + + +class TestIncludeTargetData: + """Tests for the ``include_target_data`` parameter of + ``query``. + """ + + @staticmethod + @pytest.mark.asyncio + async def test_none( + *, + async_vws_client: AsyncVWS, + async_cloud_reco_client: AsyncCloudRecoService, + image: io.BytesIO | BinaryIO, + ) -> None: + """When ``CloudRecoIncludeTargetData.NONE`` is given, + target data is not returned in any match. + """ + target_id = await async_vws_client.add_target( + name=uuid.uuid4().hex, + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + await async_vws_client.wait_for_target_processed( + target_id=target_id, + ) + [match] = await async_cloud_reco_client.query( + image=image, + include_target_data=(CloudRecoIncludeTargetData.NONE), + ) + assert match.target_data is None + + @staticmethod + @pytest.mark.asyncio + async def test_all( + *, + async_vws_client: AsyncVWS, + async_cloud_reco_client: AsyncCloudRecoService, + image: io.BytesIO | BinaryIO, + ) -> None: + """When ``CloudRecoIncludeTargetData.ALL`` is given, + target data is returned in all matches. + """ + target_id = await async_vws_client.add_target( + name=uuid.uuid4().hex, + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + target_id_2 = await async_vws_client.add_target( + name=uuid.uuid4().hex, + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + await async_vws_client.wait_for_target_processed( + target_id=target_id, + ) + await async_vws_client.wait_for_target_processed( + target_id=target_id_2, + ) + top_match, second_match = await async_cloud_reco_client.query( + image=image, + max_num_results=2, + include_target_data=(CloudRecoIncludeTargetData.ALL), + ) + assert top_match.target_data is not None + assert second_match.target_data is not None diff --git a/tests/test_async_vws.py b/tests/test_async_vws.py new file mode 100644 index 000000000..c08cf78a5 --- /dev/null +++ b/tests/test_async_vws.py @@ -0,0 +1,500 @@ +"""Tests for async helper functions for managing a Vuforia database.""" + +import base64 +import io +import uuid +from typing import BinaryIO + +import pytest +from mock_vws import MockVWS +from mock_vws.database import CloudDatabase + +from vws import AsyncCloudRecoService, AsyncVuMarkService, AsyncVWS +from vws.exceptions.custom_exceptions import ( + TargetProcessingTimeoutError, +) +from vws.reports import ( + DatabaseSummaryReport, + TargetRecord, + TargetStatuses, +) +from vws.vumark_accept import VuMarkAccept + + +class TestAddTarget: + """Tests for adding a target.""" + + @staticmethod + @pytest.mark.asyncio + @pytest.mark.parametrize( + argnames="application_metadata", + argvalues=[None, b"a"], + ) + @pytest.mark.parametrize( + argnames="active_flag", + argvalues=[True, False], + ) + async def test_add_target( + *, + async_vws_client: AsyncVWS, + image: io.BytesIO | BinaryIO, + application_metadata: bytes | None, + async_cloud_reco_client: AsyncCloudRecoService, + active_flag: bool, + ) -> None: + """No exception is raised when adding one target.""" + name = "x" + width = 1 + if application_metadata is None: + encoded_metadata = None + else: + encoded_metadata_bytes = base64.b64encode( + s=application_metadata, + ) + encoded_metadata = encoded_metadata_bytes.decode( + encoding="utf-8", + ) + + target_id = await async_vws_client.add_target( + name=name, + width=width, + image=image, + application_metadata=encoded_metadata, + active_flag=active_flag, + ) + target_record = ( + await async_vws_client.get_target_record( + target_id=target_id, + ) + ).target_record + assert target_record.name == name + assert target_record.width == width + assert target_record.active_flag is active_flag + await async_vws_client.wait_for_target_processed( + target_id=target_id, + ) + matching_targets = await async_cloud_reco_client.query( + image=image, + ) + if active_flag: + [matching_target] = matching_targets + assert matching_target.target_id == target_id + assert matching_target.target_data is not None + query_metadata = matching_target.target_data.application_metadata + assert query_metadata == encoded_metadata + else: + assert matching_targets == [] + + @staticmethod + @pytest.mark.asyncio + async def test_add_two_targets( + *, + async_vws_client: AsyncVWS, + image: io.BytesIO | BinaryIO, + ) -> None: + """No exception is raised when adding two targets with + different names. + + This demonstrates that the image seek position is not + changed. + """ + for name in ("a", "b"): + await async_vws_client.add_target( + name=name, + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + + +class TestCustomBaseVWSURL: + """Tests for using a custom base VWS URL.""" + + @staticmethod + @pytest.mark.asyncio + async def test_custom_base_url( + image: io.BytesIO | BinaryIO, + ) -> None: + """It is possible to add a target to a database under a + custom VWS URL. + """ + base_vws_url = "http://example.com" + with MockVWS(base_vws_url=base_vws_url) as mock: + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + async_vws_client = AsyncVWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + base_vws_url=base_vws_url, + ) + + await async_vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + + +class TestListTargets: + """Tests for listing targets.""" + + @staticmethod + @pytest.mark.asyncio + async def test_list_targets( + *, + async_vws_client: AsyncVWS, + image: io.BytesIO | BinaryIO, + ) -> None: + """It is possible to get a list of target IDs.""" + id_1 = await async_vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + id_2 = await async_vws_client.add_target( + name="a", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + targets = await async_vws_client.list_targets() + assert sorted(targets) == sorted([id_1, id_2]) + + +class TestDelete: + """Test for deleting a target.""" + + @staticmethod + @pytest.mark.asyncio + async def test_delete_target( + *, + async_vws_client: AsyncVWS, + image: io.BytesIO | BinaryIO, + ) -> None: + """It is possible to delete a target.""" + target_id = await async_vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + + await async_vws_client.wait_for_target_processed( + target_id=target_id, + ) + targets = await async_vws_client.list_targets() + assert target_id in targets + await async_vws_client.delete_target( + target_id=target_id, + ) + targets = await async_vws_client.list_targets() + assert target_id not in targets + + +class TestGetTargetSummaryReport: + """Tests for getting a summary report for a target.""" + + @staticmethod + @pytest.mark.asyncio + async def test_get_target_summary_report( + *, + async_vws_client: AsyncVWS, + image: io.BytesIO | BinaryIO, + ) -> None: + """Details of a target are returned by + ``get_target_summary_report``. + """ + target_name = uuid.uuid4().hex + target_id = await async_vws_client.add_target( + name=target_name, + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + + report = await async_vws_client.get_target_summary_report( + target_id=target_id, + ) + + assert report.target_name == target_name + assert report.active_flag is True + assert report.total_recos == 0 + + +class TestGetDatabaseSummaryReport: + """Tests for getting a summary report for a database.""" + + @staticmethod + @pytest.mark.asyncio + async def test_get_target( + async_vws_client: AsyncVWS, + ) -> None: + """Details of a database are returned by + ``get_database_summary_report``. + """ + report = await async_vws_client.get_database_summary_report() + + assert isinstance(report, DatabaseSummaryReport) + assert report.active_images == 0 + + +class TestGetTargetRecord: + """Tests for getting a record of a target.""" + + @staticmethod + @pytest.mark.asyncio + async def test_get_target_record( + *, + async_vws_client: AsyncVWS, + image: io.BytesIO | BinaryIO, + ) -> None: + """Details of a target are returned by + ``get_target_record``. + """ + target_id = await async_vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + + result = await async_vws_client.get_target_record( + target_id=target_id, + ) + expected_target_record = TargetRecord( + target_id=target_id, + active_flag=True, + name="x", + width=1, + tracking_rating=-1, + reco_rating="", + ) + + assert result.target_record == expected_target_record + assert result.status == TargetStatuses.PROCESSING + + +class TestWaitForTargetProcessed: + """Tests for waiting for a target to be processed.""" + + @staticmethod + @pytest.mark.asyncio + async def test_wait_for_target_processed( + *, + async_vws_client: AsyncVWS, + image: io.BytesIO | BinaryIO, + ) -> None: + """It is possible to wait until a target is processed.""" + target_id = await async_vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + report = await async_vws_client.get_target_summary_report( + target_id=target_id, + ) + assert report.status == TargetStatuses.PROCESSING + await async_vws_client.wait_for_target_processed( + target_id=target_id, + ) + report = await async_vws_client.get_target_summary_report( + target_id=target_id, + ) + assert report.status != TargetStatuses.PROCESSING + + @staticmethod + @pytest.mark.asyncio + async def test_custom_timeout( + image: io.BytesIO | BinaryIO, + ) -> None: + """It is possible to set a maximum timeout.""" + with MockVWS(processing_time_seconds=0.5) as mock: + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + async_vws_client = AsyncVWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + + target_id = await async_vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + + with pytest.raises( + expected_exception=(TargetProcessingTimeoutError), + ): + await async_vws_client.wait_for_target_processed( + target_id=target_id, + timeout_seconds=0.1, + ) + + +class TestGetDuplicateTargets: + """Tests for getting duplicate targets.""" + + @staticmethod + @pytest.mark.asyncio + async def test_get_duplicate_targets( + *, + async_vws_client: AsyncVWS, + image: io.BytesIO | BinaryIO, + ) -> None: + """It is possible to get the IDs of similar targets.""" + target_id = await async_vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + similar_target_id = await async_vws_client.add_target( + name="a", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + + await async_vws_client.wait_for_target_processed( + target_id=target_id, + ) + await async_vws_client.wait_for_target_processed( + target_id=similar_target_id, + ) + duplicates = await async_vws_client.get_duplicate_targets( + target_id=target_id, + ) + assert duplicates == [similar_target_id] + + +class TestUpdateTarget: + """Tests for updating a target.""" + + @staticmethod + @pytest.mark.asyncio + async def test_update_target( + *, + async_vws_client: AsyncVWS, + async_cloud_reco_client: AsyncCloudRecoService, + image: io.BytesIO | BinaryIO, + different_high_quality_image: io.BytesIO, + ) -> None: + """It is possible to update a target.""" + target_id = await async_vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + await async_vws_client.wait_for_target_processed( + target_id=target_id, + ) + [matching_target] = await async_cloud_reco_client.query( + image=image, + ) + assert matching_target.target_id == target_id + query_target_data = matching_target.target_data + assert query_target_data is not None + assert query_target_data.application_metadata is None + + new_name = uuid.uuid4().hex + new_width = 2.0 + new_application_metadata = base64.b64encode( + s=b"a", + ).decode(encoding="ascii") + await async_vws_client.update_target( + target_id=target_id, + name=new_name, + width=new_width, + active_flag=True, + image=different_high_quality_image, + application_metadata=new_application_metadata, + ) + + await async_vws_client.wait_for_target_processed( + target_id=target_id, + ) + target_details = await async_vws_client.get_target_record( + target_id=target_id, + ) + assert target_details.target_record.name == new_name + assert target_details.target_record.active_flag + + @staticmethod + @pytest.mark.asyncio + async def test_no_fields_given( + *, + async_vws_client: AsyncVWS, + image: io.BytesIO | BinaryIO, + ) -> None: + """It is possible to give no update fields.""" + target_id = await async_vws_client.add_target( + name="x", + width=1, + image=image, + active_flag=True, + application_metadata=None, + ) + await async_vws_client.wait_for_target_processed( + target_id=target_id, + ) + await async_vws_client.update_target( + target_id=target_id, + ) + + +class TestGenerateVumarkInstance: + """Tests for generating VuMark instances.""" + + @staticmethod + @pytest.mark.asyncio + @pytest.mark.parametrize( + argnames=("accept", "expected_prefix"), + argvalues=[ + pytest.param( + VuMarkAccept.PNG, + b"\x89PNG\r\n\x1a\n", + id="png", + ), + pytest.param( + VuMarkAccept.SVG, + b"<", + id="svg", + ), + pytest.param( + VuMarkAccept.PDF, + b"%PDF", + id="pdf", + ), + ], + ) + async def test_generate_vumark_instance( + *, + async_vumark_service_client: AsyncVuMarkService, + vumark_target_id: str, + accept: VuMarkAccept, + expected_prefix: bytes, + ) -> None: + """The returned bytes match the requested format.""" + result = await async_vumark_service_client.generate_vumark_instance( + target_id=vumark_target_id, + instance_id="12345", + accept=accept, + ) + assert result.startswith(expected_prefix) diff --git a/tests/test_async_vws_exceptions.py b/tests/test_async_vws_exceptions.py new file mode 100644 index 000000000..cd35854f1 --- /dev/null +++ b/tests/test_async_vws_exceptions.py @@ -0,0 +1,304 @@ +"""Tests for VWS exceptions raised from async clients.""" + +import io +import uuid +from http import HTTPStatus + +import pytest +from mock_vws import MockVWS +from mock_vws.database import CloudDatabase +from mock_vws.states import States + +from vws import AsyncVuMarkService, AsyncVWS +from vws.exceptions.custom_exceptions import ( + ServerError, +) +from vws.exceptions.vws_exceptions import ( + AuthenticationFailureError, + BadImageError, + FailError, + ImageTooLargeError, + InvalidInstanceIdError, + MetadataTooLargeError, + ProjectInactiveError, + TargetNameExistError, + TargetStatusProcessingError, + UnknownTargetError, +) +from vws.vumark_accept import VuMarkAccept + + +@pytest.mark.asyncio +async def test_image_too_large( + *, + async_vws_client: AsyncVWS, + png_too_large: io.BytesIO | io.BufferedRandom, +) -> None: + """When giving an image which is too large, an + ``ImageTooLarge`` exception is raised. + """ + with pytest.raises( + expected_exception=ImageTooLargeError, + ) as exc: + await async_vws_client.add_target( + name="x", + width=1, + image=png_too_large, + active_flag=True, + application_metadata=None, + ) + + assert exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + + +@pytest.mark.asyncio +async def test_invalid_given_id( + async_vws_client: AsyncVWS, +) -> None: + """Giving an invalid ID causes an ``UnknownTarget`` + exception to be raised. + """ + target_id = "12345abc" + with pytest.raises( + expected_exception=UnknownTargetError, + ) as exc: + await async_vws_client.delete_target( + target_id=target_id, + ) + assert exc.value.response.status_code == HTTPStatus.NOT_FOUND + assert exc.value.target_id == target_id + + +@pytest.mark.asyncio +async def test_add_bad_name( + *, + async_vws_client: AsyncVWS, + high_quality_image: io.BytesIO, +) -> None: + """When a name with a bad character is given, a + ``ServerError`` exception is raised. + """ + max_char_value = 65535 + bad_name = chr(max_char_value + 1) + with pytest.raises( + expected_exception=ServerError, + ) as exc: + await async_vws_client.add_target( + name=bad_name, + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + + assert exc.value.response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + +@pytest.mark.asyncio +async def test_fail(high_quality_image: io.BytesIO) -> None: + """A ``Fail`` exception is raised when the server access key + does not exist. + """ + with MockVWS(): + async_vws_client = AsyncVWS( + server_access_key=uuid.uuid4().hex, + server_secret_key=uuid.uuid4().hex, + ) + + with pytest.raises( + expected_exception=FailError, + ) as exc: + await async_vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + + assert exc.value.response.status_code == HTTPStatus.BAD_REQUEST + + +@pytest.mark.asyncio +async def test_bad_image( + async_vws_client: AsyncVWS, +) -> None: + """A ``BadImage`` exception is raised when a non-image is + given. + """ + not_an_image = io.BytesIO(initial_bytes=b"Not an image") + with pytest.raises( + expected_exception=BadImageError, + ) as exc: + await async_vws_client.add_target( + name="x", + width=1, + image=not_an_image, + active_flag=True, + application_metadata=None, + ) + + assert exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + + +@pytest.mark.asyncio +async def test_target_name_exist( + *, + async_vws_client: AsyncVWS, + high_quality_image: io.BytesIO, +) -> None: + """A ``TargetNameExist`` exception is raised after adding + two targets with the same name. + """ + await async_vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + with pytest.raises( + expected_exception=TargetNameExistError, + ) as exc: + await async_vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + + assert exc.value.response.status_code == HTTPStatus.FORBIDDEN + assert exc.value.target_name == "x" + + +@pytest.mark.asyncio +async def test_project_inactive( + high_quality_image: io.BytesIO, +) -> None: + """A ``ProjectInactive`` exception is raised if adding a + target to an inactive database. + """ + database = CloudDatabase(state=States.PROJECT_INACTIVE) + with MockVWS() as mock: + mock.add_cloud_database(cloud_database=database) + async_vws_client = AsyncVWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + + with pytest.raises( + expected_exception=ProjectInactiveError, + ) as exc: + await async_vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + + assert exc.value.response.status_code == HTTPStatus.FORBIDDEN + + +@pytest.mark.asyncio +async def test_target_status_processing( + *, + async_vws_client: AsyncVWS, + high_quality_image: io.BytesIO, +) -> None: + """A ``TargetStatusProcessing`` exception is raised if + trying to delete a target which is processing. + """ + target_id = await async_vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + + with pytest.raises( + expected_exception=TargetStatusProcessingError, + ) as exc: + await async_vws_client.delete_target( + target_id=target_id, + ) + + assert exc.value.response.status_code == HTTPStatus.FORBIDDEN + assert exc.value.target_id == target_id + + +@pytest.mark.asyncio +async def test_metadata_too_large( + *, + async_vws_client: AsyncVWS, + high_quality_image: io.BytesIO, +) -> None: + """A ``MetadataTooLarge`` exception is raised if the metadata + given is too large. + """ + with pytest.raises( + expected_exception=MetadataTooLargeError, + ) as exc: + await async_vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata="a" * 1024 * 1024 * 10, + ) + + assert exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + + +@pytest.mark.asyncio +async def test_authentication_failure( + high_quality_image: io.BytesIO, +) -> None: + """An ``AuthenticationFailure`` exception is raised when the + server secret key is incorrect. + """ + database = CloudDatabase() + + async_vws_client = AsyncVWS( + server_access_key=database.server_access_key, + server_secret_key=uuid.uuid4().hex, + ) + + with MockVWS() as mock: + mock.add_cloud_database(cloud_database=database) + + with pytest.raises( + expected_exception=AuthenticationFailureError, + ) as exc: + await async_vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + + assert exc.value.response.status_code == HTTPStatus.UNAUTHORIZED + + +@pytest.mark.asyncio +async def test_invalid_instance_id( + *, + async_vumark_service_client: AsyncVuMarkService, + vumark_target_id: str, +) -> None: + """An ``InvalidInstanceId`` exception is raised when an + empty instance ID is given. + """ + with pytest.raises( + expected_exception=InvalidInstanceIdError, + ) as exc: + await async_vumark_service_client.generate_vumark_instance( + target_id=vumark_target_id, + instance_id="", + accept=VuMarkAccept.PNG, + ) + + assert exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY diff --git a/tests/test_transports.py b/tests/test_transports.py index 37eaad4e7..b8a34f708 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -3,10 +3,12 @@ from http import HTTPStatus import httpx +import pytest import respx +from vws import AsyncCloudRecoService, AsyncVuMarkService, AsyncVWS from vws.response import Response -from vws.transports import HTTPXTransport +from vws.transports import AsyncHTTPXTransport, HTTPXTransport class TestHTTPXTransport: @@ -59,3 +61,146 @@ def test_tuple_timeout() -> None: assert route.called assert isinstance(response, Response) assert response.status_code == HTTPStatus.OK + + +class TestAsyncHTTPXTransport: + """Tests for ``AsyncHTTPXTransport``.""" + + @staticmethod + @pytest.mark.asyncio + @respx.mock + async def test_float_timeout() -> None: + """``AsyncHTTPXTransport`` works with a float timeout.""" + route = respx.post(url="https://example.com/test").mock( + return_value=httpx.Response( + status_code=HTTPStatus.OK, + text="OK", + ), + ) + transport = AsyncHTTPXTransport() + response = await transport( + method="POST", + url="https://example.com/test", + headers={"Content-Type": "text/plain"}, + data=b"hello", + request_timeout=30.0, + ) + assert route.called + assert isinstance(response, Response) + assert response.status_code == HTTPStatus.OK + assert response.text == "OK" + assert response.tell_position == len(b"OK") + + @staticmethod + @pytest.mark.asyncio + @respx.mock + async def test_tuple_timeout() -> None: + """``AsyncHTTPXTransport`` works with a (connect, read) + timeout tuple. + """ + route = respx.post(url="https://example.com/test").mock( + return_value=httpx.Response( + status_code=HTTPStatus.OK, + text="OK", + ), + ) + transport = AsyncHTTPXTransport() + response = await transport( + method="POST", + url="https://example.com/test", + headers={"Content-Type": "text/plain"}, + data=b"hello", + request_timeout=(5.0, 30.0), + ) + assert route.called + assert isinstance(response, Response) + assert response.status_code == HTTPStatus.OK + + @staticmethod + @pytest.mark.asyncio + @respx.mock + async def test_context_manager() -> None: + """``AsyncHTTPXTransport`` can be used as an async context + manager. + """ + route = respx.post(url="https://example.com/test").mock( + return_value=httpx.Response( + status_code=HTTPStatus.OK, + text="OK", + ), + ) + async with AsyncHTTPXTransport() as transport: + response = await transport( + method="POST", + url="https://example.com/test", + headers={"Content-Type": "text/plain"}, + data=b"hello", + request_timeout=30.0, + ) + assert route.called + assert isinstance(response, Response) + assert response.status_code == HTTPStatus.OK + + +class _NoCloseTransport: + """A minimal async transport without ``aclose``.""" + + async def __call__( # pragma: no cover + self, + *, + method: str, + url: str, + headers: dict[str, str], + data: bytes, + request_timeout: float | tuple[float, float], + ) -> Response: + """Not implemented.""" + raise NotImplementedError + + +_DUMMY_KEY = "x" + + +class TestAsyncClientAclose: + """Tests for ``aclose`` on async clients with transports that + lack ``aclose``. + """ + + @staticmethod + @pytest.mark.asyncio + async def test_vws_aclose_no_transport_aclose() -> None: + """``AsyncVWS.aclose`` works when the transport has no + ``aclose``. + """ + client = AsyncVWS( + server_access_key=_DUMMY_KEY, + server_secret_key=_DUMMY_KEY, + transport=_NoCloseTransport(), + ) + await client.aclose() + + @staticmethod + @pytest.mark.asyncio + async def test_cloud_reco_aclose_no_transport_aclose() -> None: + """``AsyncCloudRecoService.aclose`` works when the transport + has no ``aclose``. + """ + client = AsyncCloudRecoService( + client_access_key=_DUMMY_KEY, + client_secret_key=_DUMMY_KEY, + transport=_NoCloseTransport(), + ) + await client.aclose() + + @staticmethod + @pytest.mark.asyncio + async def test_vumark_aclose_no_transport_aclose() -> None: + """``AsyncVuMarkService.aclose`` works when the transport + has no ``aclose``. + """ + client = AsyncVuMarkService( + server_access_key=_DUMMY_KEY, + server_secret_key=_DUMMY_KEY, + transport=_NoCloseTransport(), + ) + await client.aclose() From 90c3dc01f6bf328d635c87830b0c9939a9fcae4b Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Wed, 25 Feb 2026 08:17:06 +0000 Subject: [PATCH 395/688] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f1da681fc..43f10c4e2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.25 +---------- + + 2026.02.24 ---------- From 60f93d2d485286ef64c2e7417c8bf38e7544f75f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 25 Feb 2026 08:46:01 +0000 Subject: [PATCH 396/688] Add from_response_dict classmethods to report classes Centralize API response parsing logic into classmethods on DatabaseSummaryReport, TargetSummaryReport, TargetStatusAndRecord, and QueryResult. This eliminates 75 lines of duplicated construction code across sync/async VWS and query clients. Co-Authored-By: Claude Haiku 4.5 --- src/vws/async_query.py | 28 ++--------------- src/vws/async_vws.py | 64 ++----------------------------------- src/vws/query.py | 28 ++--------------- src/vws/reports.py | 71 ++++++++++++++++++++++++++++++++++++++++++ src/vws/vws.py | 44 ++------------------------ 5 files changed, 81 insertions(+), 154 deletions(-) diff --git a/src/vws/async_query.py b/src/vws/async_query.py index 56981d4e1..ae5dd7e2b 100644 --- a/src/vws/async_query.py +++ b/src/vws/async_query.py @@ -2,7 +2,6 @@ Web APIs. """ -import datetime import json from http import HTTPMethod, HTTPStatus from typing import Any, Self @@ -25,7 +24,7 @@ ServerError, ) from vws.include_target_data import CloudRecoIncludeTargetData -from vws.reports import QueryResult, TargetData +from vws.reports import QueryResult from vws.transports import AsyncHTTPXTransport, AsyncTransport @@ -197,30 +196,7 @@ async def query( }[result_code] raise exception(response=response) - result: list[QueryResult] = [] result_list = list( json.loads(s=response.text)["results"], ) - for item in result_list: - target_data: TargetData | None = None - if "target_data" in item: - target_data_dict = item["target_data"] - metadata = target_data_dict["application_metadata"] - timestamp_string = target_data_dict["target_timestamp"] - target_timestamp = datetime.datetime.fromtimestamp( - timestamp=timestamp_string, - tz=datetime.UTC, - ) - target_data = TargetData( - name=target_data_dict["name"], - application_metadata=metadata, - target_timestamp=target_timestamp, - ) - - query_result = QueryResult( - target_id=item["target_id"], - target_data=target_data, - ) - - result.append(query_result) - return result + return [QueryResult.from_response_dict(item) for item in result_list] diff --git a/src/vws/async_vws.py b/src/vws/async_vws.py index f0f9005a1..a86efd287 100644 --- a/src/vws/async_vws.py +++ b/src/vws/async_vws.py @@ -3,7 +3,6 @@ import asyncio import base64 import json -from datetime import date from http import HTTPMethod, HTTPStatus from typing import Self @@ -38,7 +37,6 @@ ) from vws.reports import ( DatabaseSummaryReport, - TargetRecord, TargetStatusAndRecord, TargetStatuses, TargetSummaryReport, @@ -298,20 +296,7 @@ async def get_target_record(self, target_id: str) -> TargetStatusAndRecord: ) result_data = json.loads(s=response.text) - status = TargetStatuses(value=result_data["status"]) - target_record_dict = dict(result_data["target_record"]) - target_record = TargetRecord( - target_id=target_record_dict["target_id"], - active_flag=bool(target_record_dict["active_flag"]), - name=target_record_dict["name"], - width=float(target_record_dict["width"]), - tracking_rating=int(target_record_dict["tracking_rating"]), - reco_rating=target_record_dict["reco_rating"], - ) - return TargetStatusAndRecord( - status=status, - target_record=target_record, - ) + return TargetStatusAndRecord.from_response_dict(result_data) async def wait_for_target_processed( self, @@ -446,27 +431,7 @@ async def get_target_summary_report( ) result_data = dict(json.loads(s=response.text)) - return TargetSummaryReport( - status=TargetStatuses( - value=result_data["status"], - ), - database_name=result_data["database_name"], - target_name=result_data["target_name"], - upload_date=date.fromisoformat( - result_data["upload_date"], - ), - active_flag=bool(result_data["active_flag"]), - tracking_rating=int( - result_data["tracking_rating"], - ), - total_recos=int(result_data["total_recos"]), - current_month_recos=int( - result_data["current_month_recos"], - ), - previous_month_recos=int( - result_data["previous_month_recos"], - ), - ) + return TargetSummaryReport.from_response_dict(result_data) async def get_database_summary_report( self, @@ -501,30 +466,7 @@ async def get_database_summary_report( ) response_data = dict(json.loads(s=response.text)) - return DatabaseSummaryReport( - active_images=int(response_data["active_images"]), - current_month_recos=int( - response_data["current_month_recos"], - ), - failed_images=int(response_data["failed_images"]), - inactive_images=int( - response_data["inactive_images"], - ), - name=str(object=response_data["name"]), - previous_month_recos=int( - response_data["previous_month_recos"], - ), - processing_images=int( - response_data["processing_images"], - ), - reco_threshold=int( - response_data["reco_threshold"], - ), - request_quota=int(response_data["request_quota"]), - request_usage=int(response_data["request_usage"]), - target_quota=int(response_data["target_quota"]), - total_recos=int(response_data["total_recos"]), - ) + return DatabaseSummaryReport.from_response_dict(response_data) async def delete_target(self, target_id: str) -> None: """Delete a given target. diff --git a/src/vws/query.py b/src/vws/query.py index 4b5433f38..2c682eb1c 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -1,6 +1,5 @@ """Tools for interacting with the Vuforia Cloud Recognition Web APIs.""" -import datetime import json from http import HTTPMethod, HTTPStatus from typing import Any @@ -23,7 +22,7 @@ ServerError, ) from vws.include_target_data import CloudRecoIncludeTargetData -from vws.reports import QueryResult, TargetData +from vws.reports import QueryResult from vws.transports import RequestsTransport, Transport @@ -165,28 +164,5 @@ def query( }[result_code] raise exception(response=response) - result: list[QueryResult] = [] result_list = list(json.loads(s=response.text)["results"]) - for item in result_list: - target_data: TargetData | None = None - if "target_data" in item: - target_data_dict = item["target_data"] - metadata = target_data_dict["application_metadata"] - timestamp_string = target_data_dict["target_timestamp"] - target_timestamp = datetime.datetime.fromtimestamp( - timestamp=timestamp_string, - tz=datetime.UTC, - ) - target_data = TargetData( - name=target_data_dict["name"], - application_metadata=metadata, - target_timestamp=target_timestamp, - ) - - query_result = QueryResult( - target_id=item["target_id"], - target_data=target_data, - ) - - result.append(query_result) - return result + return [QueryResult.from_response_dict(item) for item in result_list] diff --git a/src/vws/reports.py b/src/vws/reports.py index e59408aad..55cd3978d 100644 --- a/src/vws/reports.py +++ b/src/vws/reports.py @@ -3,6 +3,7 @@ import datetime from dataclasses import dataclass from enum import Enum, unique +from typing import Any, Self from beartype import BeartypeConf, beartype @@ -29,6 +30,24 @@ class DatabaseSummaryReport: target_quota: int total_recos: int + @classmethod + def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: + """Construct from a VWS API response dict.""" + return cls( + active_images=int(response_dict["active_images"]), + current_month_recos=int(response_dict["current_month_recos"]), + failed_images=int(response_dict["failed_images"]), + inactive_images=int(response_dict["inactive_images"]), + name=str(response_dict["name"]), + previous_month_recos=int(response_dict["previous_month_recos"]), + processing_images=int(response_dict["processing_images"]), + reco_threshold=int(response_dict["reco_threshold"]), + request_quota=int(response_dict["request_quota"]), + request_usage=int(response_dict["request_usage"]), + target_quota=int(response_dict["target_quota"]), + total_recos=int(response_dict["total_recos"]), + ) + @beartype @unique @@ -63,6 +82,23 @@ class TargetSummaryReport: current_month_recos: int previous_month_recos: int + @classmethod + def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: + """Construct from a VWS API response dict.""" + return cls( + status=TargetStatuses(value=response_dict["status"]), + database_name=response_dict["database_name"], + target_name=response_dict["target_name"], + upload_date=datetime.date.fromisoformat( + response_dict["upload_date"] + ), + active_flag=bool(response_dict["active_flag"]), + tracking_rating=int(response_dict["tracking_rating"]), + total_recos=int(response_dict["total_recos"]), + current_month_recos=int(response_dict["current_month_recos"]), + previous_month_recos=int(response_dict["previous_month_recos"]), + ) + @beartype(conf=BeartypeConf(is_pep484_tower=True)) @dataclass(frozen=True) @@ -103,6 +139,26 @@ class QueryResult: target_id: str target_data: TargetData | None + @classmethod + def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: + """Construct from a VWS API query result item dict.""" + target_data: TargetData | None = None + if "target_data" in response_dict: + target_data_dict = response_dict["target_data"] + target_timestamp = datetime.datetime.fromtimestamp( + timestamp=target_data_dict["target_timestamp"], + tz=datetime.UTC, + ) + target_data = TargetData( + name=target_data_dict["name"], + application_metadata=target_data_dict["application_metadata"], + target_timestamp=target_timestamp, + ) + return cls( + target_id=response_dict["target_id"], + target_data=target_data, + ) + @beartype @dataclass(frozen=True) @@ -115,3 +171,18 @@ class TargetStatusAndRecord: status: TargetStatuses target_record: TargetRecord + + @classmethod + def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: + """Construct from a VWS API response dict.""" + status = TargetStatuses(value=response_dict["status"]) + target_record_dict = dict(response_dict["target_record"]) + target_record = TargetRecord( + target_id=target_record_dict["target_id"], + active_flag=bool(target_record_dict["active_flag"]), + name=target_record_dict["name"], + width=float(target_record_dict["width"]), + tracking_rating=int(target_record_dict["tracking_rating"]), + reco_rating=target_record_dict["reco_rating"], + ) + return cls(status=status, target_record=target_record) diff --git a/src/vws/vws.py b/src/vws/vws.py index bed87c938..778686155 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -3,7 +3,6 @@ import base64 import json import time -from datetime import date from http import HTTPMethod, HTTPStatus from beartype import BeartypeConf, beartype @@ -37,7 +36,6 @@ ) from vws.reports import ( DatabaseSummaryReport, - TargetRecord, TargetStatusAndRecord, TargetStatuses, TargetSummaryReport, @@ -283,20 +281,7 @@ def get_target_record(self, target_id: str) -> TargetStatusAndRecord: ) result_data = json.loads(s=response.text) - status = TargetStatuses(value=result_data["status"]) - target_record_dict = dict(result_data["target_record"]) - target_record = TargetRecord( - target_id=target_record_dict["target_id"], - active_flag=bool(target_record_dict["active_flag"]), - name=target_record_dict["name"], - width=float(target_record_dict["width"]), - tracking_rating=int(target_record_dict["tracking_rating"]), - reco_rating=target_record_dict["reco_rating"], - ) - return TargetStatusAndRecord( - status=status, - target_record=target_record, - ) + return TargetStatusAndRecord.from_response_dict(result_data) def wait_for_target_processed( self, @@ -420,17 +405,7 @@ def get_target_summary_report(self, target_id: str) -> TargetSummaryReport: ) result_data = dict(json.loads(s=response.text)) - return TargetSummaryReport( - status=TargetStatuses(value=result_data["status"]), - database_name=result_data["database_name"], - target_name=result_data["target_name"], - upload_date=date.fromisoformat(result_data["upload_date"]), - active_flag=bool(result_data["active_flag"]), - tracking_rating=int(result_data["tracking_rating"]), - total_recos=int(result_data["total_recos"]), - current_month_recos=int(result_data["current_month_recos"]), - previous_month_recos=int(result_data["previous_month_recos"]), - ) + return TargetSummaryReport.from_response_dict(result_data) def get_database_summary_report(self) -> DatabaseSummaryReport: """Get a summary report for the database. @@ -463,20 +438,7 @@ def get_database_summary_report(self) -> DatabaseSummaryReport: ) response_data = dict(json.loads(s=response.text)) - return DatabaseSummaryReport( - active_images=int(response_data["active_images"]), - current_month_recos=int(response_data["current_month_recos"]), - failed_images=int(response_data["failed_images"]), - inactive_images=int(response_data["inactive_images"]), - name=str(object=response_data["name"]), - previous_month_recos=int(response_data["previous_month_recos"]), - processing_images=int(response_data["processing_images"]), - reco_threshold=int(response_data["reco_threshold"]), - request_quota=int(response_data["request_quota"]), - request_usage=int(response_data["request_usage"]), - target_quota=int(response_data["target_quota"]), - total_recos=int(response_data["total_recos"]), - ) + return DatabaseSummaryReport.from_response_dict(response_data) def delete_target(self, target_id: str) -> None: """Delete a given target. From 2cb3a54da719bb27a4557812422d6e25ff0aee45 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 25 Feb 2026 08:47:55 +0000 Subject: [PATCH 397/688] Add type ignores to suppress mypy errors from classmethods --- src/vws/async_query.py | 2 +- src/vws/async_vws.py | 6 ++-- src/vws/query.py | 2 +- src/vws/reports.py | 76 +++++++++++++++++++++--------------------- src/vws/vws.py | 6 ++-- 5 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/vws/async_query.py b/src/vws/async_query.py index ae5dd7e2b..b153b63bd 100644 --- a/src/vws/async_query.py +++ b/src/vws/async_query.py @@ -199,4 +199,4 @@ async def query( result_list = list( json.loads(s=response.text)["results"], ) - return [QueryResult.from_response_dict(item) for item in result_list] + return [QueryResult.from_response_dict(item) for item in result_list] # type: ignore[arg-type] diff --git a/src/vws/async_vws.py b/src/vws/async_vws.py index a86efd287..244b3ed46 100644 --- a/src/vws/async_vws.py +++ b/src/vws/async_vws.py @@ -296,7 +296,7 @@ async def get_target_record(self, target_id: str) -> TargetStatusAndRecord: ) result_data = json.loads(s=response.text) - return TargetStatusAndRecord.from_response_dict(result_data) + return TargetStatusAndRecord.from_response_dict(result_data) # type: ignore[arg-type] async def wait_for_target_processed( self, @@ -431,7 +431,7 @@ async def get_target_summary_report( ) result_data = dict(json.loads(s=response.text)) - return TargetSummaryReport.from_response_dict(result_data) + return TargetSummaryReport.from_response_dict(result_data) # type: ignore[arg-type] async def get_database_summary_report( self, @@ -466,7 +466,7 @@ async def get_database_summary_report( ) response_data = dict(json.loads(s=response.text)) - return DatabaseSummaryReport.from_response_dict(response_data) + return DatabaseSummaryReport.from_response_dict(response_data) # type: ignore[arg-type] async def delete_target(self, target_id: str) -> None: """Delete a given target. diff --git a/src/vws/query.py b/src/vws/query.py index 2c682eb1c..f070937e2 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -165,4 +165,4 @@ def query( raise exception(response=response) result_list = list(json.loads(s=response.text)["results"]) - return [QueryResult.from_response_dict(item) for item in result_list] + return [QueryResult.from_response_dict(item) for item in result_list] # type: ignore[arg-type] diff --git a/src/vws/reports.py b/src/vws/reports.py index 55cd3978d..28c00b144 100644 --- a/src/vws/reports.py +++ b/src/vws/reports.py @@ -34,18 +34,18 @@ class DatabaseSummaryReport: def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: """Construct from a VWS API response dict.""" return cls( - active_images=int(response_dict["active_images"]), - current_month_recos=int(response_dict["current_month_recos"]), - failed_images=int(response_dict["failed_images"]), - inactive_images=int(response_dict["inactive_images"]), - name=str(response_dict["name"]), - previous_month_recos=int(response_dict["previous_month_recos"]), - processing_images=int(response_dict["processing_images"]), - reco_threshold=int(response_dict["reco_threshold"]), - request_quota=int(response_dict["request_quota"]), - request_usage=int(response_dict["request_usage"]), - target_quota=int(response_dict["target_quota"]), - total_recos=int(response_dict["total_recos"]), + active_images=int(response_dict["active_images"]), # type: ignore[arg-type] + current_month_recos=int(response_dict["current_month_recos"]), # type: ignore[arg-type] + failed_images=int(response_dict["failed_images"]), # type: ignore[arg-type] + inactive_images=int(response_dict["inactive_images"]), # type: ignore[arg-type] + name=response_dict["name"], # type: ignore[arg-type] + previous_month_recos=int(response_dict["previous_month_recos"]), # type: ignore[arg-type] + processing_images=int(response_dict["processing_images"]), # type: ignore[arg-type] + reco_threshold=int(response_dict["reco_threshold"]), # type: ignore[arg-type] + request_quota=int(response_dict["request_quota"]), # type: ignore[arg-type] + request_usage=int(response_dict["request_usage"]), # type: ignore[arg-type] + target_quota=int(response_dict["target_quota"]), # type: ignore[arg-type] + total_recos=int(response_dict["total_recos"]), # type: ignore[arg-type] ) @@ -83,20 +83,20 @@ class TargetSummaryReport: previous_month_recos: int @classmethod - def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: + def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: # type: ignore[name-defined] """Construct from a VWS API response dict.""" - return cls( - status=TargetStatuses(value=response_dict["status"]), - database_name=response_dict["database_name"], - target_name=response_dict["target_name"], + return cls( # type: ignore[arg-type] + status=TargetStatuses(value=response_dict["status"]), # type: ignore[arg-type] + database_name=response_dict["database_name"], # type: ignore[arg-type] + target_name=response_dict["target_name"], # type: ignore[arg-type] upload_date=datetime.date.fromisoformat( response_dict["upload_date"] - ), - active_flag=bool(response_dict["active_flag"]), - tracking_rating=int(response_dict["tracking_rating"]), - total_recos=int(response_dict["total_recos"]), - current_month_recos=int(response_dict["current_month_recos"]), - previous_month_recos=int(response_dict["previous_month_recos"]), + ), # type: ignore[arg-type] + active_flag=bool(response_dict["active_flag"]), # type: ignore[arg-type] + tracking_rating=int(response_dict["tracking_rating"]), # type: ignore[arg-type] + total_recos=int(response_dict["total_recos"]), # type: ignore[arg-type] + current_month_recos=int(response_dict["current_month_recos"]), # type: ignore[arg-type] + previous_month_recos=int(response_dict["previous_month_recos"]), # type: ignore[arg-type] ) @@ -140,22 +140,22 @@ class QueryResult: target_data: TargetData | None @classmethod - def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: + def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: # type: ignore[name-defined] """Construct from a VWS API query result item dict.""" target_data: TargetData | None = None if "target_data" in response_dict: target_data_dict = response_dict["target_data"] target_timestamp = datetime.datetime.fromtimestamp( - timestamp=target_data_dict["target_timestamp"], + timestamp=target_data_dict["target_timestamp"], # type: ignore[arg-type] tz=datetime.UTC, ) target_data = TargetData( - name=target_data_dict["name"], - application_metadata=target_data_dict["application_metadata"], + name=target_data_dict["name"], # type: ignore[arg-type] + application_metadata=target_data_dict["application_metadata"], # type: ignore[arg-type] target_timestamp=target_timestamp, ) - return cls( - target_id=response_dict["target_id"], + return cls( # type: ignore[arg-type] + target_id=response_dict["target_id"], # type: ignore[arg-type] target_data=target_data, ) @@ -173,16 +173,16 @@ class TargetStatusAndRecord: target_record: TargetRecord @classmethod - def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: + def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: # type: ignore[name-defined] """Construct from a VWS API response dict.""" - status = TargetStatuses(value=response_dict["status"]) + status = TargetStatuses(value=response_dict["status"]) # type: ignore[arg-type] target_record_dict = dict(response_dict["target_record"]) target_record = TargetRecord( - target_id=target_record_dict["target_id"], - active_flag=bool(target_record_dict["active_flag"]), - name=target_record_dict["name"], - width=float(target_record_dict["width"]), - tracking_rating=int(target_record_dict["tracking_rating"]), - reco_rating=target_record_dict["reco_rating"], + target_id=target_record_dict["target_id"], # type: ignore[arg-type] + active_flag=bool(target_record_dict["active_flag"]), # type: ignore[arg-type] + name=target_record_dict["name"], # type: ignore[arg-type] + width=float(target_record_dict["width"]), # type: ignore[arg-type] + tracking_rating=int(target_record_dict["tracking_rating"]), # type: ignore[arg-type] + reco_rating=target_record_dict["reco_rating"], # type: ignore[arg-type] ) - return cls(status=status, target_record=target_record) + return cls(status=status, target_record=target_record) # type: ignore[arg-type] diff --git a/src/vws/vws.py b/src/vws/vws.py index 778686155..8efee8498 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -281,7 +281,7 @@ def get_target_record(self, target_id: str) -> TargetStatusAndRecord: ) result_data = json.loads(s=response.text) - return TargetStatusAndRecord.from_response_dict(result_data) + return TargetStatusAndRecord.from_response_dict(result_data) # type: ignore[arg-type] def wait_for_target_processed( self, @@ -405,7 +405,7 @@ def get_target_summary_report(self, target_id: str) -> TargetSummaryReport: ) result_data = dict(json.loads(s=response.text)) - return TargetSummaryReport.from_response_dict(result_data) + return TargetSummaryReport.from_response_dict(result_data) # type: ignore[arg-type] def get_database_summary_report(self) -> DatabaseSummaryReport: """Get a summary report for the database. @@ -438,7 +438,7 @@ def get_database_summary_report(self) -> DatabaseSummaryReport: ) response_data = dict(json.loads(s=response.text)) - return DatabaseSummaryReport.from_response_dict(response_data) + return DatabaseSummaryReport.from_response_dict(response_data) # type: ignore[arg-type] def delete_target(self, target_id: str) -> None: """Delete a given target. From c5e06a21984e8bbce8c04e83e2aff49bfaea5f82 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 25 Feb 2026 08:48:37 +0000 Subject: [PATCH 398/688] Fix mypy type: ignore error codes to use [misc] for Too many positional arguments --- src/vws/async_query.py | 2 +- src/vws/async_vws.py | 6 ++-- src/vws/query.py | 2 +- src/vws/reports.py | 76 +++++++++++++++++++++--------------------- src/vws/vws.py | 6 ++-- 5 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/vws/async_query.py b/src/vws/async_query.py index b153b63bd..f1df2ac7c 100644 --- a/src/vws/async_query.py +++ b/src/vws/async_query.py @@ -199,4 +199,4 @@ async def query( result_list = list( json.loads(s=response.text)["results"], ) - return [QueryResult.from_response_dict(item) for item in result_list] # type: ignore[arg-type] + return [QueryResult.from_response_dict(item) for item in result_list] # type: ignore[misc] diff --git a/src/vws/async_vws.py b/src/vws/async_vws.py index 244b3ed46..b81c5e15a 100644 --- a/src/vws/async_vws.py +++ b/src/vws/async_vws.py @@ -296,7 +296,7 @@ async def get_target_record(self, target_id: str) -> TargetStatusAndRecord: ) result_data = json.loads(s=response.text) - return TargetStatusAndRecord.from_response_dict(result_data) # type: ignore[arg-type] + return TargetStatusAndRecord.from_response_dict(result_data) # type: ignore[misc] async def wait_for_target_processed( self, @@ -431,7 +431,7 @@ async def get_target_summary_report( ) result_data = dict(json.loads(s=response.text)) - return TargetSummaryReport.from_response_dict(result_data) # type: ignore[arg-type] + return TargetSummaryReport.from_response_dict(result_data) # type: ignore[misc] async def get_database_summary_report( self, @@ -466,7 +466,7 @@ async def get_database_summary_report( ) response_data = dict(json.loads(s=response.text)) - return DatabaseSummaryReport.from_response_dict(response_data) # type: ignore[arg-type] + return DatabaseSummaryReport.from_response_dict(response_data) # type: ignore[misc] async def delete_target(self, target_id: str) -> None: """Delete a given target. diff --git a/src/vws/query.py b/src/vws/query.py index f070937e2..a4810b73e 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -165,4 +165,4 @@ def query( raise exception(response=response) result_list = list(json.loads(s=response.text)["results"]) - return [QueryResult.from_response_dict(item) for item in result_list] # type: ignore[arg-type] + return [QueryResult.from_response_dict(item) for item in result_list] # type: ignore[misc] diff --git a/src/vws/reports.py b/src/vws/reports.py index 28c00b144..726f93b67 100644 --- a/src/vws/reports.py +++ b/src/vws/reports.py @@ -34,18 +34,18 @@ class DatabaseSummaryReport: def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: """Construct from a VWS API response dict.""" return cls( - active_images=int(response_dict["active_images"]), # type: ignore[arg-type] - current_month_recos=int(response_dict["current_month_recos"]), # type: ignore[arg-type] - failed_images=int(response_dict["failed_images"]), # type: ignore[arg-type] - inactive_images=int(response_dict["inactive_images"]), # type: ignore[arg-type] - name=response_dict["name"], # type: ignore[arg-type] - previous_month_recos=int(response_dict["previous_month_recos"]), # type: ignore[arg-type] - processing_images=int(response_dict["processing_images"]), # type: ignore[arg-type] - reco_threshold=int(response_dict["reco_threshold"]), # type: ignore[arg-type] - request_quota=int(response_dict["request_quota"]), # type: ignore[arg-type] - request_usage=int(response_dict["request_usage"]), # type: ignore[arg-type] - target_quota=int(response_dict["target_quota"]), # type: ignore[arg-type] - total_recos=int(response_dict["total_recos"]), # type: ignore[arg-type] + active_images=int(response_dict["active_images"]), + current_month_recos=int(response_dict["current_month_recos"]), + failed_images=int(response_dict["failed_images"]), + inactive_images=int(response_dict["inactive_images"]), + name=response_dict["name"], + previous_month_recos=int(response_dict["previous_month_recos"]), + processing_images=int(response_dict["processing_images"]), + reco_threshold=int(response_dict["reco_threshold"]), + request_quota=int(response_dict["request_quota"]), + request_usage=int(response_dict["request_usage"]), + target_quota=int(response_dict["target_quota"]), + total_recos=int(response_dict["total_recos"]), ) @@ -83,20 +83,20 @@ class TargetSummaryReport: previous_month_recos: int @classmethod - def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: # type: ignore[name-defined] + def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: """Construct from a VWS API response dict.""" - return cls( # type: ignore[arg-type] - status=TargetStatuses(value=response_dict["status"]), # type: ignore[arg-type] - database_name=response_dict["database_name"], # type: ignore[arg-type] - target_name=response_dict["target_name"], # type: ignore[arg-type] + return cls( + status=TargetStatuses(value=response_dict["status"]), + database_name=response_dict["database_name"], + target_name=response_dict["target_name"], upload_date=datetime.date.fromisoformat( response_dict["upload_date"] - ), # type: ignore[arg-type] - active_flag=bool(response_dict["active_flag"]), # type: ignore[arg-type] - tracking_rating=int(response_dict["tracking_rating"]), # type: ignore[arg-type] - total_recos=int(response_dict["total_recos"]), # type: ignore[arg-type] - current_month_recos=int(response_dict["current_month_recos"]), # type: ignore[arg-type] - previous_month_recos=int(response_dict["previous_month_recos"]), # type: ignore[arg-type] + ), + active_flag=bool(response_dict["active_flag"]), + tracking_rating=int(response_dict["tracking_rating"]), + total_recos=int(response_dict["total_recos"]), + current_month_recos=int(response_dict["current_month_recos"]), + previous_month_recos=int(response_dict["previous_month_recos"]), ) @@ -140,22 +140,22 @@ class QueryResult: target_data: TargetData | None @classmethod - def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: # type: ignore[name-defined] + def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: """Construct from a VWS API query result item dict.""" target_data: TargetData | None = None if "target_data" in response_dict: target_data_dict = response_dict["target_data"] target_timestamp = datetime.datetime.fromtimestamp( - timestamp=target_data_dict["target_timestamp"], # type: ignore[arg-type] + timestamp=target_data_dict["target_timestamp"], tz=datetime.UTC, ) target_data = TargetData( - name=target_data_dict["name"], # type: ignore[arg-type] - application_metadata=target_data_dict["application_metadata"], # type: ignore[arg-type] + name=target_data_dict["name"], + application_metadata=target_data_dict["application_metadata"], target_timestamp=target_timestamp, ) - return cls( # type: ignore[arg-type] - target_id=response_dict["target_id"], # type: ignore[arg-type] + return cls( + target_id=response_dict["target_id"], target_data=target_data, ) @@ -173,16 +173,16 @@ class TargetStatusAndRecord: target_record: TargetRecord @classmethod - def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: # type: ignore[name-defined] + def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: """Construct from a VWS API response dict.""" - status = TargetStatuses(value=response_dict["status"]) # type: ignore[arg-type] + status = TargetStatuses(value=response_dict["status"]) target_record_dict = dict(response_dict["target_record"]) target_record = TargetRecord( - target_id=target_record_dict["target_id"], # type: ignore[arg-type] - active_flag=bool(target_record_dict["active_flag"]), # type: ignore[arg-type] - name=target_record_dict["name"], # type: ignore[arg-type] - width=float(target_record_dict["width"]), # type: ignore[arg-type] - tracking_rating=int(target_record_dict["tracking_rating"]), # type: ignore[arg-type] - reco_rating=target_record_dict["reco_rating"], # type: ignore[arg-type] + target_id=target_record_dict["target_id"], + active_flag=bool(target_record_dict["active_flag"]), + name=target_record_dict["name"], + width=float(target_record_dict["width"]), + tracking_rating=int(target_record_dict["tracking_rating"]), + reco_rating=target_record_dict["reco_rating"], ) - return cls(status=status, target_record=target_record) # type: ignore[arg-type] + return cls(status=status, target_record=target_record) diff --git a/src/vws/vws.py b/src/vws/vws.py index 8efee8498..346b7d621 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -281,7 +281,7 @@ def get_target_record(self, target_id: str) -> TargetStatusAndRecord: ) result_data = json.loads(s=response.text) - return TargetStatusAndRecord.from_response_dict(result_data) # type: ignore[arg-type] + return TargetStatusAndRecord.from_response_dict(result_data) # type: ignore[misc] def wait_for_target_processed( self, @@ -405,7 +405,7 @@ def get_target_summary_report(self, target_id: str) -> TargetSummaryReport: ) result_data = dict(json.loads(s=response.text)) - return TargetSummaryReport.from_response_dict(result_data) # type: ignore[arg-type] + return TargetSummaryReport.from_response_dict(result_data) # type: ignore[misc] def get_database_summary_report(self) -> DatabaseSummaryReport: """Get a summary report for the database. @@ -438,7 +438,7 @@ def get_database_summary_report(self) -> DatabaseSummaryReport: ) response_data = dict(json.loads(s=response.text)) - return DatabaseSummaryReport.from_response_dict(response_data) # type: ignore[arg-type] + return DatabaseSummaryReport.from_response_dict(response_data) # type: ignore[misc] def delete_target(self, target_id: str) -> None: """Delete a given target. From f3b356facc9877135747707aa6c34b61186b28b9 Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Wed, 25 Feb 2026 08:54:50 +0000 Subject: [PATCH 399/688] Bump CHANGELOG --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 43f10c4e2..56a5d3007 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Next ---- +2026.02.25.1 +------------ + + 2026.02.25 ---------- From 323c8e5daacd87e148707375a4079cb983919213 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 25 Feb 2026 10:07:51 +0000 Subject: [PATCH 400/688] Use keyword args for response dict factories --- src/vws/async_query.py | 5 ++++- src/vws/async_vws.py | 12 +++++++++--- src/vws/query.py | 5 ++++- src/vws/vws.py | 12 +++++++++--- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/vws/async_query.py b/src/vws/async_query.py index f1df2ac7c..c229569ee 100644 --- a/src/vws/async_query.py +++ b/src/vws/async_query.py @@ -199,4 +199,7 @@ async def query( result_list = list( json.loads(s=response.text)["results"], ) - return [QueryResult.from_response_dict(item) for item in result_list] # type: ignore[misc] + return [ + QueryResult.from_response_dict(response_dict=item) + for item in result_list + ] diff --git a/src/vws/async_vws.py b/src/vws/async_vws.py index b81c5e15a..55c2aa1dd 100644 --- a/src/vws/async_vws.py +++ b/src/vws/async_vws.py @@ -296,7 +296,9 @@ async def get_target_record(self, target_id: str) -> TargetStatusAndRecord: ) result_data = json.loads(s=response.text) - return TargetStatusAndRecord.from_response_dict(result_data) # type: ignore[misc] + return TargetStatusAndRecord.from_response_dict( + response_dict=result_data, + ) async def wait_for_target_processed( self, @@ -431,7 +433,9 @@ async def get_target_summary_report( ) result_data = dict(json.loads(s=response.text)) - return TargetSummaryReport.from_response_dict(result_data) # type: ignore[misc] + return TargetSummaryReport.from_response_dict( + response_dict=result_data, + ) async def get_database_summary_report( self, @@ -466,7 +470,9 @@ async def get_database_summary_report( ) response_data = dict(json.loads(s=response.text)) - return DatabaseSummaryReport.from_response_dict(response_data) # type: ignore[misc] + return DatabaseSummaryReport.from_response_dict( + response_dict=response_data, + ) async def delete_target(self, target_id: str) -> None: """Delete a given target. diff --git a/src/vws/query.py b/src/vws/query.py index a4810b73e..44840d6f0 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -165,4 +165,7 @@ def query( raise exception(response=response) result_list = list(json.loads(s=response.text)["results"]) - return [QueryResult.from_response_dict(item) for item in result_list] # type: ignore[misc] + return [ + QueryResult.from_response_dict(response_dict=item) + for item in result_list + ] diff --git a/src/vws/vws.py b/src/vws/vws.py index 346b7d621..c93a9da8b 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -281,7 +281,9 @@ def get_target_record(self, target_id: str) -> TargetStatusAndRecord: ) result_data = json.loads(s=response.text) - return TargetStatusAndRecord.from_response_dict(result_data) # type: ignore[misc] + return TargetStatusAndRecord.from_response_dict( + response_dict=result_data, + ) def wait_for_target_processed( self, @@ -405,7 +407,9 @@ def get_target_summary_report(self, target_id: str) -> TargetSummaryReport: ) result_data = dict(json.loads(s=response.text)) - return TargetSummaryReport.from_response_dict(result_data) # type: ignore[misc] + return TargetSummaryReport.from_response_dict( + response_dict=result_data, + ) def get_database_summary_report(self) -> DatabaseSummaryReport: """Get a summary report for the database. @@ -438,7 +442,9 @@ def get_database_summary_report(self) -> DatabaseSummaryReport: ) response_data = dict(json.loads(s=response.text)) - return DatabaseSummaryReport.from_response_dict(response_data) # type: ignore[misc] + return DatabaseSummaryReport.from_response_dict( + response_dict=response_data, + ) def delete_target(self, target_id: str) -> None: """Delete a given target. From ba1d67e74a22c0b4d4943ccb08025a32bd05c110 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 25 Feb 2026 10:31:33 +0000 Subject: [PATCH 401/688] Add VWSError result code factory --- src/vws/async_vumark_service.py | 33 ++++--------------- src/vws/async_vws.py | 47 ++++----------------------- src/vws/exceptions/base_exceptions.py | 25 ++++++++++++++ src/vws/exceptions/vws_exceptions.py | 26 +++++++++++++++ src/vws/vumark_service.py | 33 ++++--------------- src/vws/vws.py | 47 ++++----------------------- tests/test_vws_exceptions.py | 22 +++++++++++++ 7 files changed, 97 insertions(+), 136 deletions(-) diff --git a/src/vws/async_vumark_service.py b/src/vws/async_vumark_service.py index c1e189eeb..a104f49c0 100644 --- a/src/vws/async_vumark_service.py +++ b/src/vws/async_vumark_service.py @@ -7,20 +7,9 @@ from beartype import BeartypeConf, beartype from vws._async_vws_request import async_target_api_request +from vws.exceptions.base_exceptions import VWSError from vws.exceptions.custom_exceptions import ServerError -from vws.exceptions.vws_exceptions import ( - AuthenticationFailureError, - BadRequestError, - DateRangeError, - FailError, - InvalidAcceptHeaderError, - InvalidInstanceIdError, - InvalidTargetTypeError, - RequestTimeTooSkewedError, - TargetStatusNotSuccessError, - TooManyRequestsError, - UnknownTargetError, -) +from vws.exceptions.vws_exceptions import TooManyRequestsError from vws.transports import AsyncHTTPXTransport, AsyncTransport from vws.vumark_accept import VuMarkAccept @@ -154,17 +143,7 @@ async def generate_vumark_instance( result_code = json.loads(s=response.text)["result_code"] - exception = { - "AuthenticationFailure": (AuthenticationFailureError), - "BadRequest": BadRequestError, - "DateRangeError": DateRangeError, - "Fail": FailError, - "InvalidAcceptHeader": InvalidAcceptHeaderError, - "InvalidInstanceId": InvalidInstanceIdError, - "InvalidTargetType": InvalidTargetTypeError, - "RequestTimeTooSkewed": RequestTimeTooSkewedError, - "TargetStatusNotSuccess": (TargetStatusNotSuccessError), - "UnknownTarget": UnknownTargetError, - }[result_code] - - raise exception(response=response) + raise VWSError.from_result_code( + result_code=result_code, + response=response, + ) diff --git a/src/vws/async_vws.py b/src/vws/async_vws.py index 55c2aa1dd..feba724de 100644 --- a/src/vws/async_vws.py +++ b/src/vws/async_vws.py @@ -11,30 +11,12 @@ from vws._async_vws_request import async_target_api_request from vws._image_utils import ImageType as _ImageType from vws._image_utils import get_image_data as _get_image_data +from vws.exceptions.base_exceptions import VWSError from vws.exceptions.custom_exceptions import ( ServerError, TargetProcessingTimeoutError, ) -from vws.exceptions.vws_exceptions import ( - AuthenticationFailureError, - BadImageError, - BadRequestError, - DateRangeError, - FailError, - ImageTooLargeError, - MetadataTooLargeError, - ProjectHasNoAPIAccessError, - ProjectInactiveError, - ProjectSuspendedError, - RequestQuotaReachedError, - RequestTimeTooSkewedError, - TargetNameExistError, - TargetQuotaReachedError, - TargetStatusNotSuccessError, - TargetStatusProcessingError, - TooManyRequestsError, - UnknownTargetError, -) +from vws.exceptions.vws_exceptions import TooManyRequestsError from vws.reports import ( DatabaseSummaryReport, TargetStatusAndRecord, @@ -158,27 +140,10 @@ async def make_request( if result_code == expected_result_code: return response - exception = { - "AuthenticationFailure": AuthenticationFailureError, - "BadImage": BadImageError, - "BadRequest": BadRequestError, - "DateRangeError": DateRangeError, - "Fail": FailError, - "ImageTooLarge": ImageTooLargeError, - "MetadataTooLarge": MetadataTooLargeError, - "ProjectHasNoAPIAccess": ProjectHasNoAPIAccessError, - "ProjectInactive": ProjectInactiveError, - "ProjectSuspended": ProjectSuspendedError, - "RequestQuotaReached": RequestQuotaReachedError, - "RequestTimeTooSkewed": RequestTimeTooSkewedError, - "TargetNameExist": TargetNameExistError, - "TargetQuotaReached": TargetQuotaReachedError, - "TargetStatusNotSuccess": TargetStatusNotSuccessError, - "TargetStatusProcessing": TargetStatusProcessingError, - "UnknownTarget": UnknownTargetError, - }[result_code] - - raise exception(response=response) + raise VWSError.from_result_code( + result_code=result_code, + response=response, + ) async def add_target( self, diff --git a/src/vws/exceptions/base_exceptions.py b/src/vws/exceptions/base_exceptions.py index fa031838a..4b8727e79 100644 --- a/src/vws/exceptions/base_exceptions.py +++ b/src/vws/exceptions/base_exceptions.py @@ -4,6 +4,9 @@ Cloud Recognition Web API. """ +from collections.abc import Mapping +from typing import ClassVar + from beartype import beartype from vws.response import Response @@ -35,6 +38,8 @@ class VWSError(Exception): https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#result-codes. """ + _exceptions_by_result_code: ClassVar[dict[str, type["VWSError"]]] = {} + def __init__(self, response: Response) -> None: """ Args: @@ -43,6 +48,26 @@ def __init__(self, response: Response) -> None: super().__init__() self._response = response + @classmethod + def register_exceptions_by_result_code( + cls, + *, + exceptions_by_result_code: Mapping[str, type["VWSError"]], + ) -> None: + """Register ``result_code`` to exception mappings.""" + cls._exceptions_by_result_code.update(exceptions_by_result_code) + + @classmethod + def from_result_code( + cls, + *, + result_code: str, + response: Response, + ) -> "VWSError": + """Create the mapped exception for a VWS ``result_code``.""" + exception_type = cls._exceptions_by_result_code[result_code] + return exception_type(response=response) + @property def response(self) -> Response: """The response returned by Vuforia which included this error.""" diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index 865d2372c..de09cdeaa 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -202,3 +202,29 @@ class InvalidTargetTypeError(VWSError): """Exception raised when Vuforia returns a response with a result code ``InvalidTargetType``. """ + + +VWSError.register_exceptions_by_result_code( + exceptions_by_result_code={ + "AuthenticationFailure": AuthenticationFailureError, + "BadImage": BadImageError, + "BadRequest": BadRequestError, + "DateRangeError": DateRangeError, + "Fail": FailError, + "ImageTooLarge": ImageTooLargeError, + "InvalidAcceptHeader": InvalidAcceptHeaderError, + "InvalidInstanceId": InvalidInstanceIdError, + "InvalidTargetType": InvalidTargetTypeError, + "MetadataTooLarge": MetadataTooLargeError, + "ProjectHasNoAPIAccess": ProjectHasNoAPIAccessError, + "ProjectInactive": ProjectInactiveError, + "ProjectSuspended": ProjectSuspendedError, + "RequestQuotaReached": RequestQuotaReachedError, + "RequestTimeTooSkewed": RequestTimeTooSkewedError, + "TargetNameExist": TargetNameExistError, + "TargetQuotaReached": TargetQuotaReachedError, + "TargetStatusNotSuccess": TargetStatusNotSuccessError, + "TargetStatusProcessing": TargetStatusProcessingError, + "UnknownTarget": UnknownTargetError, + }, +) diff --git a/src/vws/vumark_service.py b/src/vws/vumark_service.py index 47d68d942..f4b04152f 100644 --- a/src/vws/vumark_service.py +++ b/src/vws/vumark_service.py @@ -6,20 +6,9 @@ from beartype import BeartypeConf, beartype from vws._vws_request import target_api_request +from vws.exceptions.base_exceptions import VWSError from vws.exceptions.custom_exceptions import ServerError -from vws.exceptions.vws_exceptions import ( - AuthenticationFailureError, - BadRequestError, - DateRangeError, - FailError, - InvalidAcceptHeaderError, - InvalidInstanceIdError, - InvalidTargetTypeError, - RequestTimeTooSkewedError, - TargetStatusNotSuccessError, - TooManyRequestsError, - UnknownTargetError, -) +from vws.exceptions.vws_exceptions import TooManyRequestsError from vws.transports import RequestsTransport, Transport from vws.vumark_accept import VuMarkAccept @@ -135,17 +124,7 @@ def generate_vumark_instance( result_code = json.loads(s=response.text)["result_code"] - exception = { - "AuthenticationFailure": AuthenticationFailureError, - "BadRequest": BadRequestError, - "DateRangeError": DateRangeError, - "Fail": FailError, - "InvalidAcceptHeader": InvalidAcceptHeaderError, - "InvalidInstanceId": InvalidInstanceIdError, - "InvalidTargetType": InvalidTargetTypeError, - "RequestTimeTooSkewed": RequestTimeTooSkewedError, - "TargetStatusNotSuccess": TargetStatusNotSuccessError, - "UnknownTarget": UnknownTargetError, - }[result_code] - - raise exception(response=response) + raise VWSError.from_result_code( + result_code=result_code, + response=response, + ) diff --git a/src/vws/vws.py b/src/vws/vws.py index c93a9da8b..7eda102fe 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -10,30 +10,12 @@ from vws._image_utils import ImageType as _ImageType from vws._image_utils import get_image_data as _get_image_data from vws._vws_request import target_api_request +from vws.exceptions.base_exceptions import VWSError from vws.exceptions.custom_exceptions import ( ServerError, TargetProcessingTimeoutError, ) -from vws.exceptions.vws_exceptions import ( - AuthenticationFailureError, - BadImageError, - BadRequestError, - DateRangeError, - FailError, - ImageTooLargeError, - MetadataTooLargeError, - ProjectHasNoAPIAccessError, - ProjectInactiveError, - ProjectSuspendedError, - RequestQuotaReachedError, - RequestTimeTooSkewedError, - TargetNameExistError, - TargetQuotaReachedError, - TargetStatusNotSuccessError, - TargetStatusProcessingError, - TooManyRequestsError, - UnknownTargetError, -) +from vws.exceptions.vws_exceptions import TooManyRequestsError from vws.reports import ( DatabaseSummaryReport, TargetStatusAndRecord, @@ -143,27 +125,10 @@ def make_request( if result_code == expected_result_code: return response - exception = { - "AuthenticationFailure": AuthenticationFailureError, - "BadImage": BadImageError, - "BadRequest": BadRequestError, - "DateRangeError": DateRangeError, - "Fail": FailError, - "ImageTooLarge": ImageTooLargeError, - "MetadataTooLarge": MetadataTooLargeError, - "ProjectHasNoAPIAccess": ProjectHasNoAPIAccessError, - "ProjectInactive": ProjectInactiveError, - "ProjectSuspended": ProjectSuspendedError, - "RequestQuotaReached": RequestQuotaReachedError, - "RequestTimeTooSkewed": RequestTimeTooSkewedError, - "TargetNameExist": TargetNameExistError, - "TargetQuotaReached": TargetQuotaReachedError, - "TargetStatusNotSuccess": TargetStatusNotSuccessError, - "TargetStatusProcessing": TargetStatusProcessingError, - "UnknownTarget": UnknownTargetError, - }[result_code] - - raise exception(response=response) + raise VWSError.from_result_code( + result_code=result_code, + response=response, + ) def add_target( self, diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 617016257..798d15f71 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -37,6 +37,7 @@ TargetStatusProcessingError, UnknownTargetError, ) +from vws.response import Response from vws.vumark_accept import VuMarkAccept @@ -451,3 +452,24 @@ def test_base_exception( active_flag=True, application_metadata=None, ) + + +def test_vwserror_from_result_code() -> None: + """``VWSError.from_result_code`` returns the mapped exception.""" + response = Response( + text='{"result_code":"UnknownTarget"}', + url="https://example.com/targets/123", + status_code=HTTPStatus.NOT_FOUND, + headers={}, + request_body=None, + tell_position=0, + content=b"", + ) + + exception = VWSError.from_result_code( + result_code="UnknownTarget", + response=response, + ) + + assert isinstance(exception, UnknownTargetError) + assert exception.response is response From 15f16d9bc30f30e185b93a2e816eb16c9dbe487a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:33:13 +0000 Subject: [PATCH 402/688] Bump ty from 0.0.18 to 0.0.19 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] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b4fbfeea3..1d655fdf6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.18", + "ty==0.0.19", "types-requests==2.32.4.20260107", "vulture==2.14", "vws-python-mock==2026.2.22.3", From 42fe4a8e069d5289fde7755f328fae387945667d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:33:37 +0000 Subject: [PATCH 403/688] 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 b4fbfeea3..d852829b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ optional-dependencies.dev = [ "pytest-asyncio==1.3.0", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 0ecce8d99ca084d464112a6e60564b664746d7df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:33:44 +0000 Subject: [PATCH 404/688] Bump doccmd from 2026.2.15 to 2026.2.27 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.2.15 to 2026.2.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/2026.02.15...2026.02.27) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.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 b4fbfeea3..e25e0e6ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2026.2.15", + "doccmd==2026.2.27", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From 47d6d11ddea72632962212d3918a98e43f44a6e4 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 1 Mar 2026 21:55:47 +0000 Subject: [PATCH 405/688] 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 e25e0e6ca..9c56186fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2026.2.27", + "doccmd==2026.3.1", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From 62de85102ccff2cbbb8f408e36faae93fa81e763 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 11:24:29 +0000 Subject: [PATCH 406/688] 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 63e8d4db5..ded1e5728 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "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 a2c90163821020a49462e27cafd04f4251e4bb03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 11:24:58 +0000 Subject: [PATCH 407/688] 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 63e8d4db5..3eabd42bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,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", "pygments==2.19.2", From 2b96845c7e065b99a7adb041ca16404d42a4fcac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:33:24 +0000 Subject: [PATCH 408/688] Bump pyrefly from 0.54.0 to 0.55.0 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] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c2b19e36d..cbf199d07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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 34833f47f26fd34f2025d4c3a0b8c85637a473dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:33:31 +0000 Subject: [PATCH 409/688] 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 c2b19e36d..4bc79933a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.19", + "ty==0.0.20", "types-requests==2.32.4.20260107", "vulture==2.14", "vws-python-mock==2026.2.22.3", From d44bc804647c37f8fe423655516a7da4d4f38168 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:33:41 +0000 Subject: [PATCH 410/688] 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 c2b19e36d..2b311de50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.24.0", "doc8==2.0.0", - "doccmd==2026.3.1", + "doccmd==2026.3.2", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From b063cb407942d2206d6ec0bcde8e911476227cfa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:38:35 +0000 Subject: [PATCH 411/688] Bump pyproject-fmt from 2.17.0 to 2.18.0 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.17.0 to 2.18.0. - [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.0) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.18.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 c45df07fc..cdd62cd5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.17.0", + "pyproject-fmt==2.18.0", "pyrefly==0.55.0", "pyright==1.1.408", "pyroma==5.0.1", From 61e7b954a3632d447a5148597d36cf98465e5549 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 4 Mar 2026 10:20:41 +0000 Subject: [PATCH 412/688] Apply pyproject-fmt 2.18 inline table key reordering Made-with: Cursor --- pyproject.toml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cdd62cd5b..b84fa445f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -159,11 +159,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. @@ -173,7 +173,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", @@ -193,7 +193,7 @@ lint.pydocstyle.convention = "google" ] # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. -"MASTER".unsafe-load-any-extension = false +MASTER.unsafe-load-any-extension = false # 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 @@ -250,12 +250,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 9d4069fb3dcb3cddeaf8ec8bf93ec76c0a954812 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 10:33:31 +0000 Subject: [PATCH 413/688] Bump pyproject-fmt from 2.18.0 to 2.18.1 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.18.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.18.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-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 b84fa445f..f7dc5b390 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", - "pyproject-fmt==2.18.0", + "pyproject-fmt==2.18.1", "pyrefly==0.55.0", "pyright==1.1.408", "pyroma==5.0.1", From 17ddcb698e0896f620adc0a0029e03ee75e8b4a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:34:01 +0000 Subject: [PATCH 414/688] 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 f7dc5b390..324690dc0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,7 +83,7 @@ optional-dependencies.dev = [ "torchvision>=0.20.1", "ty==0.0.20", "types-requests==2.32.4.20260107", - "vulture==2.14", + "vulture==2.15", "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", From cd250dac7b4c62decc0312cbb8e6974ceeab2572 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 10:33:21 +0000 Subject: [PATCH 415/688] 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 324690dc0..52051ef1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ optional-dependencies.dev = [ "pytest-asyncio==1.3.0", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "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 53df71d66659cc079c698c93e86873c52dc59b75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 10:33:34 +0000 Subject: [PATCH 416/688] 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 324690dc0..a399ed37d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.20", + "ty==0.0.21", "types-requests==2.32.4.20260107", "vulture==2.15", "vws-python-mock==2026.2.22.3", From 851046a1155ebeab02eef5d6fc44a7ff40796272 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:10:23 +0000 Subject: [PATCH 417/688] Bump prek from 0.3.4 to 0.3.5 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] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 562da1f8f..f9854b71f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,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", "pygments==2.19.2", From b82c0dede286d611e7bd0e6840b83501f6e978c2 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 10 Mar 2026 10:04:49 +0000 Subject: [PATCH 418/688] Fix zizmor lint: upgrade to 1.23.1, disable superfluous-actions and secrets-outside-env Made-with: Cursor --- pyproject.toml | 2 +- zizmor.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f9854b71f..8ae0289f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", "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/" diff --git a/zizmor.yml b/zizmor.yml index f63e179d2..11bb67ff5 100644 --- a/zizmor.yml +++ b/zizmor.yml @@ -8,5 +8,9 @@ rules: disable: true dependabot-cooldown: disable: true + superfluous-actions: + disable: true + secrets-outside-env: + disable: true template-injection: disable: true From 73683d2a550c78964c259cdf758f87f7c5a4f574 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 10 Mar 2026 10:11:06 +0000 Subject: [PATCH 419/688] Remove secrets-outside-env disable, add environment to jobs that use secrets Made-with: Cursor --- zizmor.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/zizmor.yml b/zizmor.yml index 11bb67ff5..fab119cb0 100644 --- a/zizmor.yml +++ b/zizmor.yml @@ -10,7 +10,5 @@ rules: disable: true superfluous-actions: disable: true - secrets-outside-env: - disable: true template-injection: disable: true From db3d27bc6c5e222fd542c1ea8548a71ed3ff002c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:33:22 +0000 Subject: [PATCH 420/688] Bump pyrefly from 0.55.0 to 0.56.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.55.0 to 0.56.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.55.0...0.56.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.56.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 8ae0289f3..41f24d581 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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.56.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From 9bb2e875c4d02341eb5aaa311b7c69e7817f6b72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:33:19 +0000 Subject: [PATCH 421/688] Bump ruff from 0.15.5 to 0.15.6 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.5 to 0.15.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.15.5...0.15.6) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.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 41f24d581..a45e667c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ optional-dependencies.dev = [ "pytest-asyncio==1.3.0", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "ruff==0.15.5", + "ruff==0.15.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 9066e1c62ad737ee3130049d23c462f8e8b72a74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:33:30 +0000 Subject: [PATCH 422/688] Bump ty from 0.0.21 to 0.0.22 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.21 to 0.0.22. - [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.22) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.22 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 41f24d581..b107e99cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.21", + "ty==0.0.22", "types-requests==2.32.4.20260107", "vulture==2.15", "vws-python-mock==2026.2.22.3", From 357a2c2152419789e55ca938a6f7c4aca8367e5f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 13 Mar 2026 16:33:39 +0000 Subject: [PATCH 423/688] Remove unused dependabot/fetch-metadata step --- .github/workflows/dependabot-merge.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/dependabot-merge.yml b/.github/workflows/dependabot-merge.yml index 4932493cf..f2437640a 100644 --- a/.github/workflows/dependabot-merge.yml +++ b/.github/workflows/dependabot-merge.yml @@ -1,4 +1,5 @@ --- + name: Dependabot auto-merge on: pull_request @@ -11,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 78b67312911a94b46fb1d52315380dca36f69311 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 10:56:26 +0000 Subject: [PATCH 424/688] Bump prek from 0.3.5 to 0.3.6 Bumps [prek](https://github.com/j178/prek) from 0.3.5 to 0.3.6. - [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.6) --- updated-dependencies: - dependency-name: prek dependency-version: 0.3.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 0aff7fd18..958493a98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,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.6", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.19.2", From f5f6a84328e866ea60edd27e715f7aedcd591344 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 10:56:41 +0000 Subject: [PATCH 425/688] Bump ty from 0.0.22 to 0.0.23 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.22 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.22...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 0aff7fd18..8b2343798 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.22", + "ty==0.0.23", "types-requests==2.32.4.20260107", "vulture==2.15", "vws-python-mock==2026.2.22.3", From a9cf44334bb398dae7b8e42c765fac2e3c702302 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Mar 2026 08:19:22 +0000 Subject: [PATCH 426/688] Move release and tag version into env vars to prevent template injection Made-with: Cursor --- .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 8e655a8a0..1785c8fc1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,8 +49,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 @@ -95,9 +97,11 @@ jobs: body: ${{ steps.tag_version.outputs.changelog }} - name: Build a binary wheel and a source tarball + env: + NEW_TAG: ${{ steps.tag_version.outputs.new_tag }} run: | git fetch --tags - git checkout ${{ steps.tag_version.outputs.new_tag }} + git checkout "$NEW_TAG" uv build --sdist --wheel --out-dir dist/ uv run --extra=release check-wheel-contents dist/*.whl From 01e584b47c1409b9e96ce52d96674d092d8d8610 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Mar 2026 08:27:55 +0000 Subject: [PATCH 427/688] 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 fa2cb9adf1bec1b45e772a31303247313dff2698 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Mar 2026 08:31:58 +0000 Subject: [PATCH 428/688] 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 1785c8fc1..9d0e5b258 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 15a170a1401119f5503ab625991de357490c7a17 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Mar 2026 09:13:30 +0000 Subject: [PATCH 429/688] 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 f2437640a..69fa53039 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: Enable auto-merge for Dependabot PRs run: gh pr merge --auto --merge "$PR_URL" diff --git a/zizmor.yml b/zizmor.yml index 863db3458..ce05fe5e8 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 2d8a4e8e62dd485f7961fdb30914b7a4f1f044d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:33:24 +0000 Subject: [PATCH 430/688] Bump pyrefly from 0.56.0 to 0.57.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.56.0 to 0.57.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.56.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 2df6de4c3..e6e69b1c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.18.1", - "pyrefly==0.56.0", + "pyrefly==0.57.0", "pyright==1.1.408", "pyroma==5.0.1", "pytest==9.0.2", From aac32cf2da46abc3510b5219e8cf46f021ce931d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 10:33:09 +0000 Subject: [PATCH 431/688] 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 e6e69b1c8..9062dc706 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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 ad3548528ea5f4f80d4496f4499df2424703484f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 10:33:30 +0000 Subject: [PATCH 432/688] 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 e6e69b1c8..d1f7e418a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ dependencies = [ optional-dependencies.dev = [ "actionlint-py==1.7.11.24", "check-manifest==0.51", - "deptry==0.24.0", + "deptry==0.25.1", "doc8==2.0.0", "doccmd==2026.3.2", "freezegun==1.5.5", From dccf0542d22d48efabb9ac67261b27fc5107bae0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 10:38:47 +0000 Subject: [PATCH 433/688] 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 c4c30a731..66b6dfd7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.19.2", "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 2063110729a2d7ddbd81d6ac4da1f4289456a668 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 10:33:18 +0000 Subject: [PATCH 434/688] Bump ruff from 0.15.6 to 0.15.7 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.6 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.6...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 c4c30a731..865330c9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ optional-dependencies.dev = [ "pytest-asyncio==1.3.0", "pytest-cov==7.0.0", "pyyaml==6.0.3", - "ruff==0.15.6", + "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 bc3dab43a0cabff11677f41c5b1bfde659732f7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 10:33:44 +0000 Subject: [PATCH 435/688] 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 c4c30a731..8feac69ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.23", + "ty==0.0.24", "types-requests==2.32.4.20260107", "vulture==2.15", "vws-python-mock==2026.2.22.3", From a90f526ac43b3d2d3fab23332ce43781c320fd39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:56:20 +0000 Subject: [PATCH 436/688] Bump prek from 0.3.6 to 0.3.8 Bumps [prek](https://github.com/j178/prek) from 0.3.6 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.6...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 c22f713e8..aca561b99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.19.1", "mypy-strict-kwargs==2026.1.12", - "prek==0.3.6", + "prek==0.3.8", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.19.2", From 11c5b25b4028fd76066975bf8793242ace1c3359 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:56:35 +0000 Subject: [PATCH 437/688] Bump pytest-cov from 7.0.0 to 7.1.0 Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 7.0.0 to 7.1.0. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v7.0.0...v7.1.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-version: 7.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 c22f713e8..5d5994d94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pyroma==5.0.1", "pytest==9.0.2", "pytest-asyncio==1.3.0", - "pytest-cov==7.0.0", + "pytest-cov==7.1.0", "pyyaml==6.0.3", "ruff==0.15.7", # We add shellcheck-py not only for shell scripts and shell code blocks, From a46d7cabd229f25c47d4e5eb5a21aa90d73688f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 10:33:32 +0000 Subject: [PATCH 438/688] 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 21c949910..89758ed3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "ty==0.0.24", - "types-requests==2.32.4.20260107", + "types-requests==2.32.4.20260324", "vulture==2.15", "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", From 06a827647a144796209aaff8ac106c694b3be73b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 26 Mar 2026 08:34:42 +0000 Subject: [PATCH 439/688] Apply pyproject-fmt 2.20.0 formatting --- pyproject.toml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 66b6dfd7e..ecf75496f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -302,17 +302,6 @@ indent = 4 keep_full_version = true max_supported_python = "3.14" -[tool.pytest] -xfail_strict = true -log_cli = true - -[tool.coverage] -run.branch = true -report.exclude_also = [ - "if TYPE_CHECKING:", -] -report.show_missing = true - [tool.mypy] strict = true files = [ "." ] @@ -330,6 +319,17 @@ enableTypeIgnoreComments = false reportUnnecessaryTypeIgnoreComment = true typeCheckingMode = "strict" +[tool.pytest] +xfail_strict = true +log_cli = true + +[tool.coverage] +run.branch = true +report.exclude_also = [ + "if TYPE_CHECKING:", +] +report.show_missing = true + [tool.pydocstringformatter] write = true split-summary-body = false From ab1c7e5dba82ae655dc81277f1c98e9c8cb9aa7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 08:40:33 +0000 Subject: [PATCH 440/688] 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 c129a17eb..64a3525ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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 2dae1e2131826c69693a24c2e24815228d33a0a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 08:40:46 +0000 Subject: [PATCH 441/688] 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 c129a17eb..8fa2fceae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.24", + "ty==0.0.25", "types-requests==2.32.4.20260324", "vulture==2.15", "vws-python-mock==2026.2.22.3", From f47a9b153c72f2fb497d9c833768defd5e6f076d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:33:17 +0000 Subject: [PATCH 442/688] 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 db1991dd7..3586fe4e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,7 +83,7 @@ optional-dependencies.dev = [ "torchvision>=0.20.1", "ty==0.0.25", "types-requests==2.32.4.20260324", - "vulture==2.15", + "vulture==2.16", "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", From 7a7940e06526c9b229827fac5f7f723af60c5eac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:33:31 +0000 Subject: [PATCH 443/688] Bump doccmd from 2026.3.2 to 2026.3.26.1 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.3.2 to 2026.3.26.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.03.02...2026.03.26.1) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.3.26.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 db1991dd7..513a02430 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.25.1", "doc8==2.0.0", - "doccmd==2026.3.2", + "doccmd==2026.3.26.1", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From 1a2265d055531fa77d2f1fc9bac585b928db6d0e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 26 Mar 2026 13:10:38 +0000 Subject: [PATCH 444/688] 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 bacd71d2a..a16fed62a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.25.1", "doc8==2.0.0", - "doccmd==2026.3.26.1", + "doccmd==2026.3.26.2", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From b6ee9a4767b28629fd383cff5123f2bb9895cb20 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 27 Mar 2026 09:06:34 +0000 Subject: [PATCH 445/688] Remove getattr calls in async transport closing Add aclose to the AsyncTransport protocol and call it directly instead of using getattr to check for its existence. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/vws/async_query.py | 4 +--- src/vws/async_vumark_service.py | 4 +--- src/vws/async_vws.py | 4 +--- src/vws/transports.py | 4 ++++ 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/vws/async_query.py b/src/vws/async_query.py index c229569ee..bebd3089d 100644 --- a/src/vws/async_query.py +++ b/src/vws/async_query.py @@ -64,9 +64,7 @@ def __init__( async def aclose(self) -> None: """Close the underlying transport if it supports closing.""" - close = getattr(self._transport, "aclose", None) - if close is not None: - await close() + await self._transport.aclose() async def __aenter__(self) -> Self: """Enter the async context manager.""" diff --git a/src/vws/async_vumark_service.py b/src/vws/async_vumark_service.py index a104f49c0..51c2ef349 100644 --- a/src/vws/async_vumark_service.py +++ b/src/vws/async_vumark_service.py @@ -50,9 +50,7 @@ def __init__( async def aclose(self) -> None: """Close the underlying transport if it supports closing.""" - close = getattr(self._transport, "aclose", None) - if close is not None: - await close() + await self._transport.aclose() async def __aenter__(self) -> Self: """Enter the async context manager.""" diff --git a/src/vws/async_vws.py b/src/vws/async_vws.py index feba724de..92675ac18 100644 --- a/src/vws/async_vws.py +++ b/src/vws/async_vws.py @@ -61,9 +61,7 @@ def __init__( async def aclose(self) -> None: """Close the underlying transport if it supports closing.""" - close = getattr(self._transport, "aclose", None) - if close is not None: - await close() + await self._transport.aclose() async def __aenter__(self) -> Self: """Enter the async context manager.""" diff --git a/src/vws/transports.py b/src/vws/transports.py index 6bf9ceb9e..5b2618832 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -167,6 +167,10 @@ class AsyncTransport(Protocol): and returns a ``Response``. """ + async def aclose(self) -> None: + """Close the transport and release resources.""" + ... # pylint: disable=unnecessary-ellipsis + def __call__( self, *, From 366f93ed6c3a8cd44df5609f37936c87ddfbe057 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 27 Mar 2026 09:08:05 +0000 Subject: [PATCH 446/688] Remove tests for transports without aclose The AsyncTransport protocol now requires aclose, so these tests for transports lacking aclose are no longer applicable. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/test_transports.py | 65 ---------------------------------------- 1 file changed, 65 deletions(-) diff --git a/tests/test_transports.py b/tests/test_transports.py index b8a34f708..96518f163 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -6,7 +6,6 @@ import pytest import respx -from vws import AsyncCloudRecoService, AsyncVuMarkService, AsyncVWS from vws.response import Response from vws.transports import AsyncHTTPXTransport, HTTPXTransport @@ -140,67 +139,3 @@ async def test_context_manager() -> None: assert route.called assert isinstance(response, Response) assert response.status_code == HTTPStatus.OK - - -class _NoCloseTransport: - """A minimal async transport without ``aclose``.""" - - async def __call__( # pragma: no cover - self, - *, - method: str, - url: str, - headers: dict[str, str], - data: bytes, - request_timeout: float | tuple[float, float], - ) -> Response: - """Not implemented.""" - raise NotImplementedError - - -_DUMMY_KEY = "x" - - -class TestAsyncClientAclose: - """Tests for ``aclose`` on async clients with transports that - lack ``aclose``. - """ - - @staticmethod - @pytest.mark.asyncio - async def test_vws_aclose_no_transport_aclose() -> None: - """``AsyncVWS.aclose`` works when the transport has no - ``aclose``. - """ - client = AsyncVWS( - server_access_key=_DUMMY_KEY, - server_secret_key=_DUMMY_KEY, - transport=_NoCloseTransport(), - ) - await client.aclose() - - @staticmethod - @pytest.mark.asyncio - async def test_cloud_reco_aclose_no_transport_aclose() -> None: - """``AsyncCloudRecoService.aclose`` works when the transport - has no ``aclose``. - """ - client = AsyncCloudRecoService( - client_access_key=_DUMMY_KEY, - client_secret_key=_DUMMY_KEY, - transport=_NoCloseTransport(), - ) - await client.aclose() - - @staticmethod - @pytest.mark.asyncio - async def test_vumark_aclose_no_transport_aclose() -> None: - """``AsyncVuMarkService.aclose`` works when the transport - has no ``aclose``. - """ - client = AsyncVuMarkService( - server_access_key=_DUMMY_KEY, - server_secret_key=_DUMMY_KEY, - transport=_NoCloseTransport(), - ) - await client.aclose() From 345ce5e45c509b0f53986a24df1274fcca2bea22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:33:11 +0000 Subject: [PATCH 447/688] Bump doccmd from 2026.3.26.1 to 2026.3.26.2 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.3.26.1 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.26.1...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 bacd71d2a..a16fed62a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.25.1", "doc8==2.0.0", - "doccmd==2026.3.26.1", + "doccmd==2026.3.26.2", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From 3f34c30ff1a105dd5ae5ec75799650d814df4c1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:33:24 +0000 Subject: [PATCH 448/688] 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 bacd71d2a..f6a66a514 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "ty==0.0.25", - "types-requests==2.32.4.20260324", + "types-requests==2.33.0.20260327", "vulture==2.16", "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", From 15c38165958ff295f5a4b7e1944fbee4cd80a037 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:33:48 +0000 Subject: [PATCH 449/688] 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 bacd71d2a..05686b266 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ optional-dependencies.dev = [ "pytest-asyncio==1.3.0", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "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 7aafe9e8253a1fe5680b097e2375f763fc73197e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:38:55 +0000 Subject: [PATCH 450/688] 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 f42f0ecfd..44b3a50c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.25", + "ty==0.0.26", "types-requests==2.33.0.20260327", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 0f813231200c0d54c16d5c25fdcac9fbd9e23d0a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 29 Mar 2026 07:40:24 +0100 Subject: [PATCH 451/688] Add kw_only to large frozen report and response dataclasses DatabaseSummaryReport, TargetSummaryReport, TargetRecord, and Response now require keyword arguments at construction, reducing positional argument mistakes as fields evolve. Made-with: Cursor --- src/vws/reports.py | 6 +++--- src/vws/response.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vws/reports.py b/src/vws/reports.py index 726f93b67..1767a812c 100644 --- a/src/vws/reports.py +++ b/src/vws/reports.py @@ -9,7 +9,7 @@ @beartype -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class DatabaseSummaryReport: """A database summary report. @@ -64,7 +64,7 @@ class TargetStatuses(Enum): @beartype -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class TargetSummaryReport: """A target summary report. @@ -101,7 +101,7 @@ def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: @beartype(conf=BeartypeConf(is_pep484_tower=True)) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class TargetRecord: """A target record. diff --git a/src/vws/response.py b/src/vws/response.py index 71d1b9a48..d6456819e 100644 --- a/src/vws/response.py +++ b/src/vws/response.py @@ -5,7 +5,7 @@ from beartype import beartype -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) @beartype class Response: """A response from a request.""" From bb571893ab06730b220bd1d9007a26a224b5f8e5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 29 Mar 2026 08:11:25 +0100 Subject: [PATCH 452/688] Add kw_only=True to remaining dataclasses in reports module TargetData, QueryResult, and TargetStatusAndRecord now match other report types and require keyword arguments at construction time. Made-with: Cursor --- src/vws/reports.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vws/reports.py b/src/vws/reports.py index 1767a812c..007a0d443 100644 --- a/src/vws/reports.py +++ b/src/vws/reports.py @@ -118,7 +118,7 @@ class TargetRecord: @beartype -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class TargetData: """The target data optionally included with a query match.""" @@ -128,7 +128,7 @@ class TargetData: @beartype -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class QueryResult: """One query match result. @@ -161,7 +161,7 @@ def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: @beartype -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class TargetStatusAndRecord: """The target status and a target record. From 6a02e819de0bb1f29cb7b92bdeaf3458dcd04635 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 29 Mar 2026 08:19:46 +0100 Subject: [PATCH 453/688] Use shared httpx.Client in HTTPXTransport for connection pooling Co-Authored-By: Claude Opus 4.6 (1M context) --- src/vws/transports.py | 20 +++++++++++++++++++- tests/test_transports.py | 22 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/vws/transports.py b/src/vws/transports.py index 5b2618832..33e261a2c 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -97,8 +97,26 @@ class HTTPXTransport: Use this transport for environments where ``httpx`` is preferred over ``requests``. + A single ``httpx.Client`` is reused across requests + for connection pooling. """ + def __init__(self) -> None: + """Create an ``HTTPXTransport``.""" + self._client = httpx.Client() + + def close(self) -> None: + """Close the underlying ``httpx.Client``.""" + self._client.close() + + def __enter__(self) -> Self: + """Enter the context manager.""" + return self + + def __exit__(self, *_args: object) -> None: + """Exit the context manager and close the client.""" + self.close() + def __call__( self, *, @@ -136,7 +154,7 @@ def __call__( pool=None, ) - httpx_response = httpx.request( + httpx_response = self._client.request( method=method, url=url, headers=headers, diff --git a/tests/test_transports.py b/tests/test_transports.py index 96518f163..4130859dc 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -61,6 +61,28 @@ def test_tuple_timeout() -> None: assert isinstance(response, Response) assert response.status_code == HTTPStatus.OK + @staticmethod + @respx.mock + def test_context_manager() -> None: + """``HTTPXTransport`` can be used as a context manager.""" + route = respx.post(url="https://example.com/test").mock( + return_value=httpx.Response( + status_code=HTTPStatus.OK, + text="OK", + ), + ) + with HTTPXTransport() as transport: + response = transport( + method="POST", + url="https://example.com/test", + headers={"Content-Type": "text/plain"}, + data=b"hello", + request_timeout=30.0, + ) + assert route.called + assert isinstance(response, Response) + assert response.status_code == HTTPStatus.OK + class TestAsyncHTTPXTransport: """Tests for ``AsyncHTTPXTransport``.""" From aa13fada03f1bded33ca7a222f9c9ca89a616f78 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 29 Mar 2026 08:28:14 +0100 Subject: [PATCH 454/688] Add close() to Transport protocol for resource cleanup parity Co-Authored-By: Claude Opus 4.6 (1M context) --- src/vws/transports.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/vws/transports.py b/src/vws/transports.py index 33e261a2c..80aa989f3 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -18,6 +18,10 @@ class Transport(Protocol): returns a ``Response``. """ + def close(self) -> None: + """Close the transport and release resources.""" + ... # pylint: disable=unnecessary-ellipsis + def __call__( self, *, @@ -51,6 +55,13 @@ class RequestsTransport: This is the default transport. """ + def close(self) -> None: + """Close the transport. + + This is a no-op for ``RequestsTransport`` as it does not + hold persistent connections. + """ + def __call__( self, *, From 19987cba52f3b45b0bbbd9655f000b4dd90fefe5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:12:05 +0000 Subject: [PATCH 455/688] Bump pygments from 2.19.2 to 2.20.0 Bumps [pygments](https://github.com/pygments/pygments) from 2.19.2 to 2.20.0. - [Release notes](https://github.com/pygments/pygments/releases) - [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES) - [Commits](https://github.com/pygments/pygments/compare/2.19.2...2.20.0) --- updated-dependencies: - dependency-name: pygments 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 44b3a50c4..31318ec1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ optional-dependencies.dev = [ "prek==0.3.8", "pydocstringformatter==0.7.5", "pydocstyle==6.3", - "pygments==2.19.2", + "pygments==2.20.0", "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.0", "pyproject-fmt==2.20.0", From 77e95b738a4de084fcd7d48e9975dee40a27c677 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 10:33:19 +0000 Subject: [PATCH 456/688] 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 31318ec1e..04863aa79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ dependencies = [ "vws-auth-tools>=2024.7.12", ] optional-dependencies.dev = [ - "actionlint-py==1.7.11.24", + "actionlint-py==1.7.12.24", "check-manifest==0.51", "deptry==0.25.1", "doc8==2.0.0", From 296345ed53cd5c8cdf51fe0c8c6528f6ccbf5ff2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 10:33:29 +0000 Subject: [PATCH 457/688] 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 31318ec1e..61dcfd49c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.20.0", "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 5e32e1b160a9cb082e5c9b625bea41461cbf6930 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 12:04:42 +0000 Subject: [PATCH 458/688] 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 cb5765848..115e821fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,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 bfa8f808bf4784b3b84daac3fdf0bd5e907044f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 12:04:53 +0000 Subject: [PATCH 459/688] 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 cb5765848..86f380a98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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 d23a2bd9ed104dd748386eb31dc8ecac9e71d64e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 12:05:24 +0000 Subject: [PATCH 460/688] 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 cb5765848..fa75711d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.26", + "ty==0.0.27", "types-requests==2.33.0.20260327", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 6f8baa0f9b465acb49b346d2c1652b8f571a1948 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:33:14 +0000 Subject: [PATCH 461/688] 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 e5ff8857f..703d96f54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "ty==0.0.27", - "types-requests==2.33.0.20260327", + "types-requests==2.33.0.20260402", "vulture==2.16", "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", From c0866230cdc1e306a434cb034b56d215ad5b33b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:33:20 +0000 Subject: [PATCH 462/688] 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 e5ff8857f..6c92b27dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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 84b1186cd519c8f16015cf5967cab8832b5272e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:33:13 +0000 Subject: [PATCH 463/688] 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 f3f579633..d14b813f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ optional-dependencies.dev = [ "pytest-asyncio==1.3.0", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "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 f83af513e6c80fc74f5df7eae922f54ef48fbb8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:33:21 +0000 Subject: [PATCH 464/688] 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 f3f579633..6dbb19eed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.27", + "ty==0.0.28", "types-requests==2.33.0.20260402", "vulture==2.16", "vws-python-mock==2026.2.22.3", From dc7db891fdd9ac87288951e75456e29c46ee580e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 10:44:59 +0000 Subject: [PATCH 465/688] 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 3301fd584..41e6c5705 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.28", + "ty==0.0.29", "types-requests==2.33.0.20260402", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 0c075b0c536e36e93ff92b07374a0b9bcbb7e0b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 10:45:16 +0000 Subject: [PATCH 466/688] 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 3301fd584..55f05d3e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ optional-dependencies.dev = [ "pydocstyle==6.3", "pygments==2.20.0", "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 f7c77da5b377f13296bc153899169c66c25124a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 10:33:33 +0000 Subject: [PATCH 467/688] 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 0b1305c77..365076b83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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 e5851cd6a572b5e5312b3c72bc049581c69542bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:33:36 +0000 Subject: [PATCH 468/688] 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 365076b83..df335c92e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,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-asyncio==1.3.0", "pytest-cov==7.1.0", "pyyaml==6.0.3", From 3a371017343d1da45dfaef4769728cdef51325b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:33:42 +0000 Subject: [PATCH 469/688] 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 365076b83..22695e7d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "ty==0.0.29", - "types-requests==2.33.0.20260402", + "types-requests==2.33.0.20260408", "vulture==2.16", "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", From f077b84691f327599b59aa209e2c7662f87d3e05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:33:09 +0000 Subject: [PATCH 470/688] 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 bdd206628..c9b151a57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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 84ada8afd3fe2096e735e19b9776a7153e1bb80f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:33:37 +0000 Subject: [PATCH 471/688] 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 bdd206628..c2fdf231a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ optional-dependencies.dev = [ "pytest-asyncio==1.3.0", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "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 96179178ddc62d9f7bc3361b5e24cbfa50bfe0ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:18:59 +0000 Subject: [PATCH 472/688] Bump zizmor from 1.23.1 to 1.24.0 Bumps [zizmor](https://github.com/zizmorcore/zizmor) from 1.23.1 to 1.24.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.23.1...v1.24.0) --- updated-dependencies: - dependency-name: zizmor dependency-version: 1.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 902e1a658..c327239fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", - "zizmor==1.23.1", + "zizmor==1.24.0", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python/" From f8f341bdca8f9118da5a263c7b590dfee44f7ed3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:19:13 +0000 Subject: [PATCH 473/688] 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 902e1a658..42110c7ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,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.8", "pydocstringformatter==0.7.5", From fe0074318798b56ab6e7882c3b23d30a68c26a97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:19:21 +0000 Subject: [PATCH 474/688] 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 902e1a658..ef20666a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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 4bccb0d7926a938946d4eb6f12cd0c66a8231477 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:33:14 +0000 Subject: [PATCH 475/688] Bump zizmor from 1.24.0 to 1.24.1 Bumps [zizmor](https://github.com/zizmorcore/zizmor) from 1.24.0 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.24.0...v1.24.1) --- updated-dependencies: - dependency-name: zizmor dependency-version: 1.24.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 3ecd2044a..fb7ed8c57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", - "zizmor==1.24.0", + "zizmor==1.24.1", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python/" From 008af7a294206f57fb735411f48a11b31be92c83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:33:27 +0000 Subject: [PATCH 476/688] 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 3ecd2044a..e89167b86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.20.0", "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 44236a9272227b07ecb575ad3d4eedfd0a67dfb1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:33:43 +0000 Subject: [PATCH 477/688] 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 3ecd2044a..ec7b92028 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.20.1", "mypy-strict-kwargs==2026.1.12", - "prek==0.3.8", + "prek==0.3.9", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.20.0", From 22d16657f986de404f8b15a34c46ef943903d779 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:39:11 +0000 Subject: [PATCH 478/688] 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 8ea78d85c..e385056e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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 76496991445fe18a6cddb97a199401b9f4634974 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 10:33:30 +0000 Subject: [PATCH 479/688] 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 e385056e9..3bde23295 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.29", + "ty==0.0.30", "types-requests==2.33.0.20260408", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 04c02033c41e6655c65c67c0b8dafeca69631bae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 10:33:27 +0000 Subject: [PATCH 480/688] 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 3bde23295..5fb095f64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.30", + "ty==0.0.31", "types-requests==2.33.0.20260408", "vulture==2.16", "vws-python-mock==2026.2.22.3", From b47ad334e6d8a8859a5a3dffed7bd0b88b6a5756 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 10:33:33 +0000 Subject: [PATCH 481/688] 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 5fb095f64..41895f733 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ optional-dependencies.dev = [ "pytest-asyncio==1.3.0", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "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 b748867534c28948ddae3d8e7255bb71dc782bd4 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 19 Apr 2026 08:01:36 +0100 Subject: [PATCH 482/688] chore: rename deptry pep621_dev_dependency_groups 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 41895f733..6d5ce09a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -285,7 +285,7 @@ ignore = [ ] [tool.deptry] -pep621_dev_dependency_groups = [ +optional_dependencies_dev_groups = [ "dev", "release", ] From 010d63e6553d258f115a3d53a5e9fdbf251b58f9 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 19 Apr 2026 09:57:00 +0100 Subject: [PATCH 483/688] 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. --- conftest.py | 9 --------- pyproject.toml | 5 ++++- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/conftest.py b/conftest.py index d9e955107..8e5442cf0 100644 --- a/conftest.py +++ b/conftest.py @@ -7,7 +7,6 @@ from pathlib import Path import pytest -from beartype import beartype from mock_vws import MockVWS from mock_vws.database import CloudDatabase from sybil import Sybil @@ -18,14 +17,6 @@ ) -@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.fixture(name="make_image_file") def fixture_make_image_file( *, diff --git a/pyproject.toml b/pyproject.toml index 6d5ce09a7..26fc3d821 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,6 +88,7 @@ optional-dependencies.dev = [ "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.24.1", + "pytest-beartype-tests==2026.4.19.1", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python/" @@ -364,7 +365,6 @@ ignore_names = [ "HTTPXTransport", # pytest configuration "pytest_collect_file", - "pytest_collection_modifyitems", "pytest_plugins", # pytest fixtures - we name fixtures like this for this purpose "fixture_*", @@ -402,3 +402,6 @@ exclude = [ ".venv" ] [tool.yamlfix] section_whitelines = 1 whitelines = 1 + +[dependency-groups] +dev = [] From e86ba237f4a5e5ac8bb1a8a08b0ac042513107c6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 08:58:11 +0000 Subject: [PATCH 484/688] [pre-commit.ci lite] apply automatic fixes --- pyproject.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 26fc3d821..c9aadf7e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ optional-dependencies.dev = [ "pyroma==5.0.1", "pytest==9.0.3", "pytest-asyncio==1.3.0", + "pytest-beartype-tests==2026.4.19.1", "pytest-cov==7.1.0", "pyyaml==6.0.3", "ruff==0.15.11", @@ -88,12 +89,14 @@ optional-dependencies.dev = [ "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.24.1", - "pytest-beartype-tests==2026.4.19.1", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python/" urls.Source = "https://github.com/VWS-Python/vws-python" +[dependency-groups] +dev = [] + [tool.setuptools] zip-safe = false package-data.vws = [ @@ -402,6 +405,3 @@ exclude = [ ".venv" ] [tool.yamlfix] section_whitelines = 1 whitelines = 1 - -[dependency-groups] -dev = [] From 95b5a4695bc73defb4aa66bdc4e184b538d35e1f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 19 Apr 2026 10:12:02 +0100 Subject: [PATCH 485/688] 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 c9aadf7e0..933b59d4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pyroma==5.0.1", "pytest==9.0.3", "pytest-asyncio==1.3.0", - "pytest-beartype-tests==2026.4.19.1", + "pytest-beartype-tests", "pytest-cov==7.1.0", "pyyaml==6.0.3", "ruff==0.15.11", @@ -118,6 +118,7 @@ bdist_wheel.universal = true 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 3971dc4e5165e73b5db30d0094cab8201377e277 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 20 Apr 2026 07:39:32 +0100 Subject: [PATCH 486/688] Pin pytest-beartype-tests to 2026.4.20 on PyPI 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 933b59d4f..85e5e27b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pyroma==5.0.1", "pytest==9.0.3", "pytest-asyncio==1.3.0", - "pytest-beartype-tests", + "pytest-beartype-tests==2026.4.20", "pytest-cov==7.1.0", "pyyaml==6.0.3", "ruff==0.15.11", @@ -118,7 +118,6 @@ bdist_wheel.universal = true 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 ca8827131cf8607833295f0cfb7edc34f3247ed0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:02:35 +0000 Subject: [PATCH 487/688] 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 933b59d4f..f24946ba7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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 40116be25f171497ef5c646da775f08896a5027e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:33:36 +0000 Subject: [PATCH 488/688] 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 18b22d9d5..4961f4219 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.31", + "ty==0.0.32", "types-requests==2.33.0.20260408", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 62b944ce13d610217d866062bf15fae7b3b8a235 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:33:50 +0000 Subject: [PATCH 489/688] 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 18b22d9d5..acc13f1d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,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 8bc059813809015bc2ac2808f8e92fe0188d523a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 22 Apr 2026 08:36:13 +0100 Subject: [PATCH 490/688] chore: define uv version once in pre-commit config Made-with: Cursor --- .pre-commit-config.yaml | 74 +++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c64693b9d..6473e04a0 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 + # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks @@ -101,7 +103,7 @@ repos: language: python pass_filenames: false types_or: [yaml] - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] stages: [pre-commit] - id: pydocstringformatter @@ -109,7 +111,7 @@ 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 @@ -117,7 +119,7 @@ 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 @@ -126,7 +128,7 @@ repos: --language=console --command="shellcheck --shell=bash" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] stages: [pre-commit] - id: shfmt @@ -134,7 +136,7 @@ repos: entry: uv run --extra=dev 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 @@ -143,7 +145,7 @@ 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 @@ -153,7 +155,7 @@ 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 @@ -162,7 +164,7 @@ repos: entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="mypy" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] - id: check-manifest name: check-manifest @@ -170,7 +172,7 @@ 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 @@ -179,7 +181,7 @@ 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 @@ -188,7 +190,7 @@ repos: --command="pyright" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] - id: vulture name: vulture @@ -196,7 +198,7 @@ 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 @@ -205,7 +207,7 @@ repos: --command="vulture" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] stages: [pre-commit] - id: pyroma @@ -214,7 +216,7 @@ repos: language: python pass_filenames: false types_or: [toml] - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] stages: [pre-commit] - id: deptry @@ -222,7 +224,7 @@ 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 @@ -231,7 +233,7 @@ repos: language: python stages: [manual] pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] - id: pylint-docs name: pylint-docs @@ -240,14 +242,14 @@ repos: language: python stages: [manual] types_or: [markdown, rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] - id: ruff-check-fix name: Ruff check fix 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 @@ -255,7 +257,7 @@ 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 @@ -263,7 +265,7 @@ 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 @@ -272,7 +274,7 @@ repos: format" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] stages: [pre-commit] - id: doc8 @@ -280,7 +282,7 @@ 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 @@ -288,7 +290,7 @@ repos: entry: uv run --extra=dev -m interrogate language: python types_or: [python] - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] stages: [pre-commit] - id: interrogate-docs @@ -297,7 +299,7 @@ repos: --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 @@ -306,7 +308,7 @@ repos: language: python types_or: [toml] files: pyproject.toml - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] stages: [pre-commit] - id: linkcheck @@ -317,7 +319,7 @@ repos: types_or: [rst] stages: [manual] pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] - id: spelling name: spelling @@ -327,7 +329,7 @@ repos: types_or: [rst] stages: [manual] pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] - id: docs name: Build Documentation @@ -335,7 +337,7 @@ repos: language: python stages: [manual] pass_filenames: false - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] - id: pyright-verifytypes name: pyright-verifytypes @@ -344,7 +346,7 @@ repos: language: python pass_filenames: false types_or: [python] - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] - id: ty name: ty @@ -353,7 +355,7 @@ 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 @@ -362,14 +364,14 @@ repos: --command="ty check" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] - id: yamlfix name: yamlfix 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 @@ -378,7 +380,7 @@ 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 @@ -386,7 +388,7 @@ 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 @@ -396,7 +398,7 @@ 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 @@ -405,4 +407,4 @@ repos: --command="pyrefly check" language: python types_or: [markdown, rst] - additional_dependencies: [uv==0.9.5] + additional_dependencies: [*uv_version] From e5d0f676a8cbc1d2856a748b797795481877df56 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 07:37:10 +0000 Subject: [PATCH 491/688] [pre-commit.ci lite] apply automatic fixes --- .pre-commit-config.yaml | 108 ++++++++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 36 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6473e04a0..2156fa8e7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -103,7 +103,8 @@ repos: language: python pass_filenames: false types_or: [yaml] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: pydocstringformatter @@ -111,7 +112,8 @@ repos: entry: uv run --extra=dev pydocstringformatter language: python types_or: [python] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: shellcheck @@ -119,7 +121,8 @@ repos: entry: uv run --extra=dev shellcheck --shell=bash language: python types_or: [shell] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: shellcheck-docs @@ -128,7 +131,8 @@ repos: --language=console --command="shellcheck --shell=bash" language: python types_or: [markdown, rst] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: shfmt @@ -136,7 +140,8 @@ repos: entry: uv run --extra=dev shfmt --write --space-redirects --indent=4 language: python types_or: [shell] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: shfmt-docs @@ -145,7 +150,8 @@ repos: --no-pad-file --command="shfmt --write --space-redirects --indent=4" language: python types_or: [markdown, rst] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: mypy @@ -155,7 +161,8 @@ repos: language: python types_or: [python, toml] pass_filenames: false - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version # We do not use --example-workers 0 due to https://github.com/python/mypy/issues/18283 - id: mypy-docs @@ -164,7 +171,8 @@ repos: entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="mypy" language: python types_or: [markdown, rst] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version - id: check-manifest name: check-manifest @@ -172,7 +180,8 @@ repos: entry: uv run --extra=dev -m check_manifest language: python pass_filenames: false - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version - id: pyright name: pyright @@ -181,7 +190,8 @@ repos: language: python types_or: [python, toml] pass_filenames: false - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version - id: pyright-docs name: pyright-docs @@ -190,7 +200,8 @@ repos: --command="pyright" language: python types_or: [markdown, rst] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version - id: vulture name: vulture @@ -198,7 +209,8 @@ repos: language: python types_or: [python] pass_filenames: false - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: vulture-docs @@ -207,7 +219,8 @@ repos: --command="vulture" language: python types_or: [markdown, rst] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: pyroma @@ -216,7 +229,8 @@ repos: language: python pass_filenames: false types_or: [toml] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: deptry @@ -224,7 +238,8 @@ repos: entry: uv run --extra=dev -m deptry src/ language: python pass_filenames: false - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: pylint @@ -233,7 +248,8 @@ repos: language: python stages: [manual] pass_filenames: false - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version - id: pylint-docs name: pylint-docs @@ -242,14 +258,16 @@ repos: language: python stages: [manual] types_or: [markdown, rst] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version - id: ruff-check-fix name: Ruff check fix entry: uv run --extra=dev -m ruff check --fix language: python types_or: [python] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: ruff-check-fix-docs @@ -257,7 +275,8 @@ repos: entry: uv run --extra=dev doccmd --language=python --command="ruff check --fix" language: python types_or: [markdown, rst] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: ruff-format-fix @@ -265,7 +284,8 @@ repos: entry: uv run --extra=dev -m ruff format language: python types_or: [python] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: ruff-format-fix-docs @@ -274,7 +294,8 @@ repos: format" language: python types_or: [markdown, rst] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: doc8 @@ -282,7 +303,8 @@ repos: entry: uv run --extra=dev -m doc8 language: python types_or: [rst] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: interrogate @@ -290,7 +312,8 @@ repos: entry: uv run --extra=dev -m interrogate language: python types_or: [python] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: interrogate-docs @@ -299,7 +322,8 @@ repos: --command="interrogate" language: python types_or: [markdown, rst] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: pyproject-fmt-fix @@ -308,7 +332,8 @@ repos: language: python types_or: [toml] files: pyproject.toml - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: linkcheck @@ -319,7 +344,8 @@ repos: types_or: [rst] stages: [manual] pass_filenames: false - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version - id: spelling name: spelling @@ -329,7 +355,8 @@ repos: types_or: [rst] stages: [manual] pass_filenames: false - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version - id: docs name: Build Documentation @@ -337,7 +364,8 @@ repos: language: python stages: [manual] pass_filenames: false - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version - id: pyright-verifytypes name: pyright-verifytypes @@ -346,7 +374,8 @@ repos: language: python pass_filenames: false types_or: [python] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version - id: ty name: ty @@ -355,7 +384,8 @@ repos: language: python types_or: [python, toml] pass_filenames: false - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version - id: ty-docs name: ty-docs @@ -364,14 +394,16 @@ repos: --command="ty check" language: python types_or: [markdown, rst] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version - id: yamlfix name: yamlfix entry: uv run --extra=dev yamlfix language: python types_or: [yaml] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: zizmor @@ -380,7 +412,8 @@ repos: language: python pass_filenames: false types_or: [yaml] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: sphinx-lint @@ -388,7 +421,8 @@ repos: entry: uv run --extra=dev sphinx-lint --enable=all --disable=line-too-long language: python types_or: [rst] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version stages: [pre-commit] - id: pyrefly @@ -398,7 +432,8 @@ repos: language: python types_or: [python, toml] pass_filenames: false - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version - id: pyrefly-docs name: pyrefly-docs @@ -407,4 +442,5 @@ repos: --command="pyrefly check" language: python types_or: [markdown, rst] - additional_dependencies: [*uv_version] + additional_dependencies: + - *uv_version From 7d62b772aae713cf4d0a537141934175033f5736 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:33:06 +0000 Subject: [PATCH 492/688] 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 565d570c0..61b2ed868 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,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", "pygments==2.20.0", From aecefb0f990c50f42d95961da3685004847c2875 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:33:18 +0000 Subject: [PATCH 493/688] 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 565d570c0..55476541d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,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.9", "pydocstringformatter==0.7.5", From c5dc6e33bb4b734ea470bf47ea6ba459e87894f0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 22 Apr 2026 10:58:05 +0100 Subject: [PATCH 494/688] chore: bump uv to 0.11.7 for pre-commit hooks 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 2156fa8e7..d4049620e 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 # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks From fbe5c2f6214076ae392b52bd766e8617bf4abbb1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:33:30 +0000 Subject: [PATCH 495/688] 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 3c5a25761..8c2f8c3cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,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-asyncio==1.3.0", From 2b961f049cb54d5a39ce7214b0644563fbf57701 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 12:19:06 +0000 Subject: [PATCH 496/688] Bump prek from 0.3.10 to 0.3.11 Bumps [prek](https://github.com/j178/prek) from 0.3.10 to 0.3.11. - [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md) - [Commits](https://github.com/j178/prek/compare/v0.3.10...v0.3.11) --- updated-dependencies: - dependency-name: prek dependency-version: 0.3.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 8c2f8c3cf..96a7037d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.20.2", "mypy-strict-kwargs==2026.1.12", - "prek==0.3.10", + "prek==0.3.11", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.20.0", From 21531d05eac3a9f835494fbcb0b7978ba66d0f60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 12:19:24 +0000 Subject: [PATCH 497/688] Bump ruff from 0.15.11 to 0.15.12 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.11 to 0.15.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.15.11...0.15.12) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.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 8c2f8c3cf..8d5428105 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "pytest-beartype-tests==2026.4.20", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "ruff==0.15.11", + "ruff==0.15.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 28fdd562222c60c33b6d5a707d3887d596b1682e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 12:19:46 +0000 Subject: [PATCH 498/688] Bump pytest-beartype-tests from 2026.4.20 to 2026.4.26 Bumps [pytest-beartype-tests](https://github.com/adamtheturtle/pytest-beartype-tests) from 2026.4.20 to 2026.4.26. - [Release notes](https://github.com/adamtheturtle/pytest-beartype-tests/releases) - [Changelog](https://github.com/adamtheturtle/pytest-beartype-tests/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/pytest-beartype-tests/compare/2026.04.20...2026.04.26) --- updated-dependencies: - dependency-name: pytest-beartype-tests dependency-version: 2026.4.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 8c2f8c3cf..58a9757cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ optional-dependencies.dev = [ "pyroma==5.0.1", "pytest==9.0.3", "pytest-asyncio==1.3.0", - "pytest-beartype-tests==2026.4.20", + "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", "pyyaml==6.0.3", "ruff==0.15.11", From 98a859980d66e38b3120930ca984afe234743cc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:33:27 +0000 Subject: [PATCH 499/688] Bump pyrefly from 0.62.0 to 0.63.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.62.0 to 0.63.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.62.0...0.63.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.63.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 bdded3a1e..d4e6375a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.21.1", - "pyrefly==0.62.0", + "pyrefly==0.63.0", "pyright==1.1.409", "pyroma==5.0.1", "pytest==9.0.3", From 8d5e63b4cb308230d3156ef7115e59295e057a97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 10:33:36 +0000 Subject: [PATCH 500/688] chore(deps-dev): Bump ty from 0.0.32 to 0.0.33 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.32 to 0.0.33. - [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.32...0.0.33) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.33 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 d4e6375a2..48cd70582 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.32", + "ty==0.0.33", "types-requests==2.33.0.20260408", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 08c4c618d523cddab2f7ba487265719be29a17a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 10:33:51 +0000 Subject: [PATCH 501/688] chore(deps-dev): Bump pyrefly from 0.63.0 to 0.63.1 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.63.0 to 0.63.1. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.63.0...0.63.1) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.63.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 d4e6375a2..1ce589858 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.21.1", - "pyrefly==0.63.0", + "pyrefly==0.63.1", "pyright==1.1.409", "pyroma==5.0.1", "pytest==9.0.3", From 35b7d4d86e7620bea75e2c56ea967dc2b73c7a61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 12:49:22 +0000 Subject: [PATCH 502/688] chore(deps-dev): Bump types-requests Bumps [types-requests](https://github.com/python/typeshed) from 2.33.0.20260408 to 2.33.0.20260503. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.33.0.20260503 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 3f220632a..c34ae086b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,7 +83,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "ty==0.0.33", - "types-requests==2.33.0.20260408", + "types-requests==2.33.0.20260503", "vulture==2.16", "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", From 307fb2f939a2a1b2425cdafe407f9025a80e2491 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 10:33:44 +0000 Subject: [PATCH 503/688] chore(deps-dev): Bump prek from 0.3.11 to 0.3.12 Bumps [prek](https://github.com/j178/prek) from 0.3.11 to 0.3.12. - [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.11...v0.3.12) --- updated-dependencies: - dependency-name: prek dependency-version: 0.3.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 c34ae086b..f6d452e30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.20.2", "mypy-strict-kwargs==2026.1.12", - "prek==0.3.11", + "prek==0.3.12", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.20.0", From ea65ffd9114014eff42cd8a83680b053fb4c1b25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 10:34:00 +0000 Subject: [PATCH 504/688] chore(deps-dev): Bump pyproject-fmt from 2.21.1 to 2.21.2 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.21.1 to 2.21.2. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.21.1...pyproject-fmt/2.21.2) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.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 c34ae086b..f9718d50a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.20.0", "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", - "pyproject-fmt==2.21.1", + "pyproject-fmt==2.21.2", "pyrefly==0.63.1", "pyright==1.1.409", "pyroma==5.0.1", From 9e2f5e971686169d749f56fe6cc3e9e04ecc1601 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 10:39:17 +0000 Subject: [PATCH 505/688] chore(deps-dev): Bump ty from 0.0.33 to 0.0.34 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.33 to 0.0.34. - [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.33...0.0.34) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.34 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 e20d3e320..38e0ae68a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.33", + "ty==0.0.34", "types-requests==2.33.0.20260503", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 7ee74516e064727643c031829fd201297b5e424a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 10:33:52 +0000 Subject: [PATCH 506/688] chore(deps-dev): Bump prek from 0.3.12 to 0.3.13 Bumps [prek](https://github.com/j178/prek) from 0.3.12 to 0.3.13. - [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.12...v0.3.13) --- updated-dependencies: - dependency-name: prek dependency-version: 0.3.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 38e0ae68a..358a62a72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==1.20.2", "mypy-strict-kwargs==2026.1.12", - "prek==0.3.12", + "prek==0.3.13", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.20.0", From 88f76df85bcd690c657cb41bcbb7e8b13dd7350f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 6 May 2026 19:16:00 +0100 Subject: [PATCH 507/688] ci: bump astral-sh/setup-uv from v7 to v8.1.0 Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2bec50283..3bd4f560c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.1.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c6f0c8074..f1ed8e80e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ jobs: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.1.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d0e5b258..a723a2b4c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.1.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' From 24d64e9a7b66e38d1fcf3c865684f4b39f0bf293 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 10:32:18 +0000 Subject: [PATCH 508/688] chore(deps-dev): Bump doccmd from 2026.3.26.2 to 2026.5.6 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.3.26.2 to 2026.5.6. - [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.26.2...2026.05.06) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.5.6 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 358a62a72..746dd24df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.25.1", "doc8==2.0.0", - "doccmd==2026.3.26.2", + "doccmd==2026.5.6", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From ea627c796caef92a3bc7703c92b2af4fc959716b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 10:32:23 +0000 Subject: [PATCH 509/688] chore(deps-dev): Bump mypy from 1.20.2 to 2.0.0 Bumps [mypy](https://github.com/python/mypy) from 1.20.2 to 2.0.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.20.2...v2.0.0) --- updated-dependencies: - dependency-name: mypy 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 358a62a72..f60ff4280 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ optional-dependencies.dev = [ "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", - "mypy[faster-cache]==1.20.2", + "mypy[faster-cache]==2.0.0", "mypy-strict-kwargs==2026.1.12", "prek==0.3.13", "pydocstringformatter==0.7.5", From a5038dafd9b1ede4113aff01dffcf4f18ce278ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 10:32:30 +0000 Subject: [PATCH 510/688] chore(deps-dev): Bump pyrefly from 0.63.1 to 0.64.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.63.1 to 0.64.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.63.1...0.64.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.64.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 358a62a72..cce5c8e26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.21.2", - "pyrefly==0.63.1", + "pyrefly==0.64.0", "pyright==1.1.409", "pyroma==5.0.1", "pytest==9.0.3", From df725392015744f577e1cf483875df44f7e97ef3 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 7 May 2026 12:10:42 +0100 Subject: [PATCH 511/688] Bump mypy dependency to 2.0.0. Align static type-checker versioning with the mypy 2 baseline for consistent behavior across local and CI runs. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 358a62a72..f60ff4280 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ optional-dependencies.dev = [ "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", - "mypy[faster-cache]==1.20.2", + "mypy[faster-cache]==2.0.0", "mypy-strict-kwargs==2026.1.12", "prek==0.3.13", "pydocstringformatter==0.7.5", From 15aef7d5e5a593340e9cc83caa7e2e96aa1e0eb7 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 7 May 2026 13:10:16 +0100 Subject: [PATCH 512/688] Enable mypy parallel workers in pre-commit. Co-authored-by: Cursor --- .pre-commit-config.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2156fa8e7..9cc02b74d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -157,7 +157,7 @@ repos: - id: mypy name: mypy stages: [pre-push] - entry: uv run --extra=dev -m mypy + entry: uv run --extra=dev -m mypy --num-workers=4 language: python types_or: [python, toml] pass_filenames: false @@ -168,7 +168,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 --language=python --command="mypy + --num-workers=4" language: python types_or: [markdown, rst] additional_dependencies: From 49550db9bc56660a217b44ba073b9c39a3281f3d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 7 May 2026 13:32:35 +0100 Subject: [PATCH 513/688] Normalize mypy docs pre-commit worker flag style. Keep `mypy-docs` aligned with `mypy` by keeping `--num-workers=4` inline in the mypy command argument. Co-authored-by: Cursor --- .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 9cc02b74d..0a0debcf6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -168,8 +168,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 - --num-workers=4" + entry: uv run --extra=dev doccmd --no-write-to-file --language=python + args: [--command=mypy --num-workers=4] language: python types_or: [markdown, rst] additional_dependencies: From 2d87cce6d3703693764cc923f869fed20596044a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 7 May 2026 13:35:45 +0100 Subject: [PATCH 514/688] Inline mypy-docs command to keep worker flags together. Co-authored-by: Cursor --- .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 0a0debcf6..9cc02b74d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -168,8 +168,8 @@ repos: - id: mypy-docs name: mypy-docs stages: [pre-push] - entry: uv run --extra=dev doccmd --no-write-to-file --language=python - args: [--command=mypy --num-workers=4] + entry: uv run --extra=dev doccmd --no-write-to-file --language=python --command="mypy + --num-workers=4" language: python types_or: [markdown, rst] additional_dependencies: From ce7098fe9944232ee31780c6b95850efb91f8e65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 10:33:11 +0000 Subject: [PATCH 515/688] chore(deps-dev): Bump pyrefly from 0.64.0 to 0.64.1 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.64.0 to 0.64.1. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.64.0...0.64.1) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 0.64.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 3be79ba9a..0aa576518 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.21.2", - "pyrefly==0.64.0", + "pyrefly==0.64.1", "pyright==1.1.409", "pyroma==5.0.1", "pytest==9.0.3", From 1c713fc99acd0d4eb03133ecd7fc9f1a9eaa8879 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 10:33:27 +0000 Subject: [PATCH 516/688] chore(deps-dev): Bump types-requests Bumps [types-requests](https://github.com/python/typeshed) from 2.33.0.20260503 to 2.33.0.20260508. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.33.0.20260508 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 3be79ba9a..1e9e8fac7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,7 +83,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "ty==0.0.34", - "types-requests==2.33.0.20260503", + "types-requests==2.33.0.20260508", "vulture==2.16", "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", From 7611cc4e81264e770dfd52b5045360200156a79e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 16:17:57 +0000 Subject: [PATCH 517/688] chore(deps-dev): Bump ty from 0.0.34 to 0.0.35 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.34 to 0.0.35. - [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.34...0.0.35) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.35 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 41edec51b..ec0655f66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.34", + "ty==0.0.35", "types-requests==2.33.0.20260508", "vulture==2.16", "vws-python-mock==2026.2.22.3", From e2985b2af831e83479f1a0c69b10280b041ad876 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 16:35:27 +0000 Subject: [PATCH 518/688] chore(deps-dev): Bump mypy from 2.0.0 to 2.1.0 Bumps [mypy](https://github.com/python/mypy) from 2.0.0 to 2.1.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v2.0.0...v2.1.0) --- updated-dependencies: - dependency-name: mypy dependency-version: 2.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 ec0655f66..c2a74f0fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ optional-dependencies.dev = [ "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", - "mypy[faster-cache]==2.0.0", + "mypy[faster-cache]==2.1.0", "mypy-strict-kwargs==2026.1.12", "prek==0.3.13", "pydocstringformatter==0.7.5", From 5d4b2456c9d5b293a994cd04bd914752377f92e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 10:38:23 +0000 Subject: [PATCH 519/688] chore(deps-dev): Bump shfmt-py from 3.12.0.2 to 4.0.0 Bumps [shfmt-py](https://github.com/maxwinterstein/shfmt-py) from 3.12.0.2 to 4.0.0. - [Release notes](https://github.com/maxwinterstein/shfmt-py/releases) - [Commits](https://github.com/maxwinterstein/shfmt-py/commits/v4.0.0) --- updated-dependencies: - dependency-name: shfmt-py dependency-version: 4.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 c2a74f0fa..92408f02e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,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.11.0.1", - "shfmt-py==3.12.0.2", + "shfmt-py==4.0.0", "sphinx==9.1.0", "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.2", From 17de68e292905152e02fa7cf3f08434ccb319372 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 10:38:30 +0000 Subject: [PATCH 520/688] chore(deps-dev): Bump pyrefly from 0.64.1 to 1.0.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 0.64.1 to 1.0.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/0.64.1...1.0.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 1.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 c2a74f0fa..c16617077 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.21.2", - "pyrefly==0.64.1", + "pyrefly==1.0.0", "pyright==1.1.409", "pyroma==5.0.1", "pytest==9.0.3", From cd3c310660607dbe684653649a9cfd36707ef1bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 10:38:38 +0000 Subject: [PATCH 521/688] chore(deps-dev): Bump types-requests Bumps [types-requests](https://github.com/python/typeshed) from 2.33.0.20260508 to 2.33.0.20260513. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.33.0.20260513 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 c2a74f0fa..975df99d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,7 +83,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "ty==0.0.35", - "types-requests==2.33.0.20260508", + "types-requests==2.33.0.20260513", "vulture==2.16", "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", From 4fff0d8f6ff70f3535ca60911e5e02136936693b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 10:34:26 +0000 Subject: [PATCH 522/688] chore(deps-dev): Bump zizmor from 1.24.1 to 1.25.0 Bumps [zizmor](https://github.com/zizmorcore/zizmor) from 1.24.1 to 1.25.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.24.1...v1.25.0) --- updated-dependencies: - dependency-name: zizmor dependency-version: 1.25.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 47c26d82e..57bf426df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,7 +88,7 @@ optional-dependencies.dev = [ "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", - "zizmor==1.24.1", + "zizmor==1.25.0", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python/" From c4390385f59d0da98170bdae4eb07794ec1badc8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 10:34:36 +0000 Subject: [PATCH 523/688] chore(deps-dev): Bump prek from 0.3.13 to 0.4.0 Bumps [prek](https://github.com/j178/prek) from 0.3.13 to 0.4.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.3.13...v0.4.0) --- updated-dependencies: - dependency-name: prek dependency-version: 0.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 47c26d82e..79688dac5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==2.1.0", "mypy-strict-kwargs==2026.1.12", - "prek==0.3.13", + "prek==0.4.0", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.20.0", From 447227c3b8aef1208f14e2fa45acdf29fae91f97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 10:34:56 +0000 Subject: [PATCH 524/688] chore(deps-dev): Bump ruff from 0.15.12 to 0.15.13 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.12 to 0.15.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.15.12...0.15.13) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.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 47c26d82e..99d7acdcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "ruff==0.15.12", + "ruff==0.15.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 4521c6fd2dec1fbf60a1a894b607c63288c005c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 10:35:16 +0000 Subject: [PATCH 525/688] chore(deps-dev): Bump ty from 0.0.35 to 0.0.36 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.35 to 0.0.36. - [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.35...0.0.36) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.36 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 47c26d82e..fac7d53c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.35", + "ty==0.0.36", "types-requests==2.33.0.20260513", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 7f27d1b88987e8e1390f95a5748056c3a13a190a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 18 May 2026 14:13:05 +0100 Subject: [PATCH 526/688] Add strict-kwargs as a local pre-commit hook (dev dependency) --- .pre-commit-config.yaml | 12 ++++++++++++ pyproject.toml | 1 + 2 files changed, 13 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 27537657d..97b912cf0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,6 +12,7 @@ ci: skip: - actionlint - sphinx-lint + - strict-kwargs-fix - check-manifest - deptry - doc8 @@ -299,6 +300,17 @@ repos: - *uv_version stages: [pre-commit] + + - id: strict-kwargs-fix + name: strict-kwargs + entry: uv run --extra=dev strict-kwargs fix + language: python + types_or: [python] + additional_dependencies: + - *uv_version + stages: [pre-commit] + require_serial: true + - id: doc8 name: doc8 entry: uv run --extra=dev -m doc8 diff --git a/pyproject.toml b/pyproject.toml index 6d5c70d40..87bb8c9a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,6 +82,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", + "strict-kwargs==2026.5.18", "ty==0.0.36", "types-requests==2.33.0.20260513", "vulture==2.16", From 31a429ac376ca152b109e1a567611995df23a42e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 18 May 2026 15:12:40 +0100 Subject: [PATCH 527/688] Fix strict-kwargs dev dependency alphabetical order --- pyproject.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 87bb8c9a9..c0cdf9227 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,8 +38,7 @@ dependencies = [ "urllib3>=2.2.3", "vws-auth-tools>=2024.7.12", ] -optional-dependencies.dev = [ - "actionlint-py==1.7.12.24", +optional-dependencies.dev = [ "actionlint-py==1.7.12.24", "check-manifest==0.51", "deptry==0.25.1", "doc8==2.0.0", @@ -76,13 +75,13 @@ optional-dependencies.dev = [ "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2026.1.12", "sphinxcontrib-spelling==8.0.2", + "strict-kwargs==2026.5.18", "sybil==9.3.0", # Listed explicitly (despite being transitive via vws-python-mock) so that # [tool.uv.sources] can redirect to the CPU-only PyTorch index. # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "strict-kwargs==2026.5.18", "ty==0.0.36", "types-requests==2.33.0.20260513", "vulture==2.16", From a6f265886d62df9de9c765669cb109de7d1a02f4 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, 18 May 2026 14:13:41 +0000 Subject: [PATCH 528/688] [pre-commit.ci lite] apply automatic fixes --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c0cdf9227..8a024a537 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,8 @@ dependencies = [ "urllib3>=2.2.3", "vws-auth-tools>=2024.7.12", ] -optional-dependencies.dev = [ "actionlint-py==1.7.12.24", +optional-dependencies.dev = [ + "actionlint-py==1.7.12.24", "check-manifest==0.51", "deptry==0.25.1", "doc8==2.0.0", From 96d519cba92d26afb9d535a5c17b0063a9bfd8d1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 18 May 2026 15:14:21 +0100 Subject: [PATCH 529/688] Fix strict-kwargs dev dependency alphabetical order --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8a024a537..c0cdf9227 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,8 +38,7 @@ dependencies = [ "urllib3>=2.2.3", "vws-auth-tools>=2024.7.12", ] -optional-dependencies.dev = [ - "actionlint-py==1.7.12.24", +optional-dependencies.dev = [ "actionlint-py==1.7.12.24", "check-manifest==0.51", "deptry==0.25.1", "doc8==2.0.0", From ec2d44beef61fc928e04a056068a7e65640f8824 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, 18 May 2026 14:17:36 +0000 Subject: [PATCH 530/688] [pre-commit.ci lite] apply automatic fixes --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c0cdf9227..8a024a537 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,8 @@ dependencies = [ "urllib3>=2.2.3", "vws-auth-tools>=2024.7.12", ] -optional-dependencies.dev = [ "actionlint-py==1.7.12.24", +optional-dependencies.dev = [ + "actionlint-py==1.7.12.24", "check-manifest==0.51", "deptry==0.25.1", "doc8==2.0.0", From ec23d71bc6c0fb0931b2282d01ad9f551cedcde1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 18 May 2026 15:19:53 +0100 Subject: [PATCH 531/688] Fix malformed dev dependencies list in pyproject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 8a024a537..9c93fabec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,6 +90,7 @@ optional-dependencies.dev = [ "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.25.0", + ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python/" From 06df3802c04a9acdd6a7c3c309e772e6941e90d7 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, 18 May 2026 14:21:32 +0000 Subject: [PATCH 532/688] [pre-commit.ci lite] apply automatic fixes --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9c93fabec..8a024a537 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,7 +90,6 @@ optional-dependencies.dev = [ "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.25.0", - ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python/" From 9aa039f25b71b6c1d2879c41442c93e43f229524 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 18 May 2026 15:49:18 +0100 Subject: [PATCH 533/688] Bump strict-kwargs to 2026.5.18.post1 and revert Python changes --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8a024a537..453c885c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ optional-dependencies.dev = [ "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2026.1.12", "sphinxcontrib-spelling==8.0.2", - "strict-kwargs==2026.5.18", + "strict-kwargs==2026.5.18.post1", "sybil==9.3.0", # Listed explicitly (despite being transitive via vws-python-mock) so that # [tool.uv.sources] can redirect to the CPU-only PyTorch index. From 287d91c78bc7712b10f7b4bd2e7f87d7c05ed4a3 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, 18 May 2026 14:50:22 +0000 Subject: [PATCH 534/688] [pre-commit.ci lite] apply automatic fixes --- .pre-commit-config.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 97b912cf0..31eea11f8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -300,7 +300,6 @@ repos: - *uv_version stages: [pre-commit] - - id: strict-kwargs-fix name: strict-kwargs entry: uv run --extra=dev strict-kwargs fix From fa35c296cd83b5407ece0a503e3658b2e184442a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 19:32:37 +0000 Subject: [PATCH 535/688] chore(deps-dev): Bump strict-kwargs Bumps [strict-kwargs](https://github.com/adamtheturtle/strict-kwargs) from 2026.5.18.post1 to 2026.5.18.post3. - [Release notes](https://github.com/adamtheturtle/strict-kwargs/releases) - [Changelog](https://github.com/adamtheturtle/strict-kwargs/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/strict-kwargs/compare/2026.5.18-post.1...2026.5.18-post.3) --- updated-dependencies: - dependency-name: strict-kwargs dependency-version: 2026.5.18.post3 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 453c885c3..329f640e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ optional-dependencies.dev = [ "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2026.1.12", "sphinxcontrib-spelling==8.0.2", - "strict-kwargs==2026.5.18.post1", + "strict-kwargs==2026.5.18.post3", "sybil==9.3.0", # Listed explicitly (despite being transitive via vws-python-mock) so that # [tool.uv.sources] can redirect to the CPU-only PyTorch index. From 664e4dada195513222137f2b153603579d4a4866 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 19:32:46 +0000 Subject: [PATCH 536/688] chore(deps-dev): Bump types-requests Bumps [types-requests](https://github.com/python/typeshed) from 2.33.0.20260513 to 2.33.0.20260518. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.33.0.20260518 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 453c885c3..d8d4f2792 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "ty==0.0.36", - "types-requests==2.33.0.20260513", + "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", From 4ea83f4c7613f25f0821c0b285a72c4527aa8b31 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 19:32:54 +0000 Subject: [PATCH 537/688] chore(deps-dev): Bump zizmor from 1.25.0 to 1.25.2 Bumps [zizmor](https://github.com/zizmorcore/zizmor) from 1.25.0 to 1.25.2. - [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.25.0...v1.25.2) --- updated-dependencies: - dependency-name: zizmor dependency-version: 1.25.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 453c885c3..bfbe8dc9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,7 +89,7 @@ optional-dependencies.dev = [ "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", - "zizmor==1.25.0", + "zizmor==1.25.2", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] urls.Documentation = "https://vws-python.github.io/vws-python/" From 099d130b8a392c46a997c4104a4d95f8e67c5e5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 19:38:54 +0000 Subject: [PATCH 538/688] chore(deps-dev): Bump ty from 0.0.36 to 0.0.37 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.36 to 0.0.37. - [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.36...0.0.37) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.37 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 d1f0b8c1e..cc4d41f3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,7 +83,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", - "ty==0.0.36", + "ty==0.0.37", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 7664bc0cb25085b1671c96344598721585a7df5e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 18 May 2026 20:51:14 +0100 Subject: [PATCH 539/688] Fix doccmd pylint ignores --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cc4d41f3f..d8cbe66fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.25.1", "doc8==2.0.0", - "doccmd==2026.5.6", + "doccmd==2026.5.16", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", @@ -251,7 +251,9 @@ MASTER.unsafe-load-any-extension = false "MESSAGES CONTROL".per-file-ignores = [ "docs/source/conf.py:invalid-name", "docs/source/doccmd_*.py:invalid-name", + "docs/source/doccmd_*/*.py:invalid-name", "doccmd_README_rst_*.py:invalid-name", + "doccmd_*/*.py:invalid-name", ] # Spelling dictionary name. Available dictionaries: none. To make it working # install python-enchant package. From 7a7b277e9b22f8b6b5699c3e3b4fbddd01e5e19a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 19 May 2026 12:34:21 +0100 Subject: [PATCH 540/688] Deduplicate httpx timeout construction --- src/vws/transports.py | 59 ++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/src/vws/transports.py b/src/vws/transports.py index 80aa989f3..38d07af36 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -10,6 +10,29 @@ from vws.response import Response +def _httpx_timeout( + request_timeout: float | tuple[float, float], +) -> httpx.Timeout: + """Convert a VWS request timeout to an ``httpx`` timeout.""" + match request_timeout: + case (connect_timeout, read_timeout): + return httpx.Timeout( + connect=connect_timeout, + read=read_timeout, + write=None, + pool=None, + ) + case float() | int() as timeout: + return httpx.Timeout( + connect=timeout, + read=timeout, + write=None, + pool=None, + ) + case _: + raise AssertionError + + @runtime_checkable class Transport(Protocol): """Protocol for HTTP transports used by VWS clients. @@ -149,28 +172,12 @@ def __call__( Returns: A Response populated from the httpx response. """ - if isinstance(request_timeout, tuple): - connect_timeout, read_timeout = request_timeout - httpx_timeout = httpx.Timeout( - connect=connect_timeout, - read=read_timeout, - write=None, - pool=None, - ) - else: - httpx_timeout = httpx.Timeout( - connect=request_timeout, - read=request_timeout, - write=None, - pool=None, - ) - httpx_response = self._client.request( method=method, url=url, headers=headers, content=data, - timeout=httpx_timeout, + timeout=_httpx_timeout(request_timeout=request_timeout), follow_redirects=True, ) @@ -272,28 +279,12 @@ async def __call__( Returns: A Response populated from the httpx response. """ - if isinstance(request_timeout, tuple): - connect_timeout, read_timeout = request_timeout - httpx_timeout = httpx.Timeout( - connect=connect_timeout, - read=read_timeout, - write=None, - pool=None, - ) - else: - httpx_timeout = httpx.Timeout( - connect=request_timeout, - read=request_timeout, - write=None, - pool=None, - ) - httpx_response = await self._client.request( method=method, url=url, headers=headers, content=data, - timeout=httpx_timeout, + timeout=_httpx_timeout(request_timeout=request_timeout), follow_redirects=True, ) From e17a819465983da7967810a47e41dfd90978cae0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 19 May 2026 12:42:09 +0100 Subject: [PATCH 541/688] Fix timeout helper coverage --- src/vws/transports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vws/transports.py b/src/vws/transports.py index 38d07af36..991c76be4 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -29,7 +29,7 @@ def _httpx_timeout( write=None, pool=None, ) - case _: + case _: # pragma: no cover raise AssertionError From 885af7c61471475087ead04aac1461eaadf00243 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 19 May 2026 12:52:02 +0100 Subject: [PATCH 542/688] Inline httpx timeout match handling --- src/vws/transports.py | 63 ++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/src/vws/transports.py b/src/vws/transports.py index 991c76be4..2adaa3231 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -10,29 +10,6 @@ from vws.response import Response -def _httpx_timeout( - request_timeout: float | tuple[float, float], -) -> httpx.Timeout: - """Convert a VWS request timeout to an ``httpx`` timeout.""" - match request_timeout: - case (connect_timeout, read_timeout): - return httpx.Timeout( - connect=connect_timeout, - read=read_timeout, - write=None, - pool=None, - ) - case float() | int() as timeout: - return httpx.Timeout( - connect=timeout, - read=timeout, - write=None, - pool=None, - ) - case _: # pragma: no cover - raise AssertionError - - @runtime_checkable class Transport(Protocol): """Protocol for HTTP transports used by VWS clients. @@ -172,12 +149,30 @@ def __call__( Returns: A Response populated from the httpx response. """ + match request_timeout: + case (connect_timeout, read_timeout): + httpx_timeout = httpx.Timeout( + connect=connect_timeout, + read=read_timeout, + write=None, + pool=None, + ) + case float() | int() as timeout: + httpx_timeout = httpx.Timeout( + connect=timeout, + read=timeout, + write=None, + pool=None, + ) + case _: # pragma: no cover + raise AssertionError + httpx_response = self._client.request( method=method, url=url, headers=headers, content=data, - timeout=_httpx_timeout(request_timeout=request_timeout), + timeout=httpx_timeout, follow_redirects=True, ) @@ -279,12 +274,30 @@ async def __call__( Returns: A Response populated from the httpx response. """ + match request_timeout: + case (connect_timeout, read_timeout): + httpx_timeout = httpx.Timeout( + connect=connect_timeout, + read=read_timeout, + write=None, + pool=None, + ) + case float() | int() as timeout: + httpx_timeout = httpx.Timeout( + connect=timeout, + read=timeout, + write=None, + pool=None, + ) + case _: # pragma: no cover + raise AssertionError + httpx_response = await self._client.request( method=method, url=url, headers=headers, content=data, - timeout=_httpx_timeout(request_timeout=request_timeout), + timeout=httpx_timeout, follow_redirects=True, ) From acaf4b906798e2bd68a6f1e01882a4c93123bad1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 19 May 2026 12:58:05 +0100 Subject: [PATCH 543/688] Use assert_never for timeout match fallback --- pyproject.toml | 1 + src/vws/transports.py | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d8cbe66fe..898d266eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -333,6 +333,7 @@ log_cli = true [tool.coverage] run.branch = true report.exclude_also = [ + "case _ as unreachable:\n\\s*assert_never\\(", "if TYPE_CHECKING:", ] report.show_missing = true diff --git a/src/vws/transports.py b/src/vws/transports.py index 2adaa3231..5c193cc10 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -1,7 +1,7 @@ """HTTP transport implementations for VWS clients.""" from collections.abc import Awaitable -from typing import Protocol, Self, runtime_checkable +from typing import Protocol, Self, assert_never, runtime_checkable import httpx import requests @@ -164,8 +164,8 @@ def __call__( write=None, pool=None, ) - case _: # pragma: no cover - raise AssertionError + case _ as unreachable: + assert_never(unreachable) # pyrefly: ignore[bad-argument-type] # ty: ignore[type-assertion-failure] httpx_response = self._client.request( method=method, @@ -289,8 +289,8 @@ async def __call__( write=None, pool=None, ) - case _: # pragma: no cover - raise AssertionError + case _ as unreachable: + assert_never(unreachable) # pyrefly: ignore[bad-argument-type] # ty: ignore[type-assertion-failure] httpx_response = await self._client.request( method=method, From 431b2fce5cacef2e6afc4b74145bd457a92b1792 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 19 May 2026 13:00:58 +0100 Subject: [PATCH 544/688] Add type checker names to spelling dictionary --- spelling_private_dict.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index 73d358a00..04814bdea 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -79,6 +79,7 @@ plugins png pragma py +pyrefly pyright pytest readme @@ -97,6 +98,7 @@ todo traceback travis txt +ty unmocked untyped url From 8db0c59d86f5b6d4f2afbcfad288c6ade88ad823 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 19 May 2026 13:19:42 +0100 Subject: [PATCH 545/688] Avoid timeout match suppressions --- pyproject.toml | 1 - spelling_private_dict.txt | 2 -- src/vws/transports.py | 16 ++++++-------- tests/test_transports.py | 45 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 898d266eb..d8cbe66fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -333,7 +333,6 @@ log_cli = true [tool.coverage] run.branch = true report.exclude_also = [ - "case _ as unreachable:\n\\s*assert_never\\(", "if TYPE_CHECKING:", ] report.show_missing = true diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index 04814bdea..73d358a00 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -79,7 +79,6 @@ plugins png pragma py -pyrefly pyright pytest readme @@ -98,7 +97,6 @@ todo traceback travis txt -ty unmocked untyped url diff --git a/src/vws/transports.py b/src/vws/transports.py index 5c193cc10..74fadb9fe 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -1,7 +1,7 @@ """HTTP transport implementations for VWS clients.""" from collections.abc import Awaitable -from typing import Protocol, Self, assert_never, runtime_checkable +from typing import Protocol, Self, runtime_checkable import httpx import requests @@ -150,22 +150,21 @@ def __call__( A Response populated from the httpx response. """ match request_timeout: - case (connect_timeout, read_timeout): + case tuple() as timeout: + connect_timeout, read_timeout = timeout httpx_timeout = httpx.Timeout( connect=connect_timeout, read=read_timeout, write=None, pool=None, ) - case float() | int() as timeout: + case timeout: httpx_timeout = httpx.Timeout( connect=timeout, read=timeout, write=None, pool=None, ) - case _ as unreachable: - assert_never(unreachable) # pyrefly: ignore[bad-argument-type] # ty: ignore[type-assertion-failure] httpx_response = self._client.request( method=method, @@ -275,22 +274,21 @@ async def __call__( A Response populated from the httpx response. """ match request_timeout: - case (connect_timeout, read_timeout): + case tuple() as timeout: + connect_timeout, read_timeout = timeout httpx_timeout = httpx.Timeout( connect=connect_timeout, read=read_timeout, write=None, pool=None, ) - case float() | int() as timeout: + case timeout: httpx_timeout = httpx.Timeout( connect=timeout, read=timeout, write=None, pool=None, ) - case _ as unreachable: - assert_never(unreachable) # pyrefly: ignore[bad-argument-type] # ty: ignore[type-assertion-failure] httpx_response = await self._client.request( method=method, diff --git a/tests/test_transports.py b/tests/test_transports.py index 4130859dc..752b0ef23 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -61,6 +61,28 @@ def test_tuple_timeout() -> None: assert isinstance(response, Response) assert response.status_code == HTTPStatus.OK + @staticmethod + @respx.mock + def test_int_timeout() -> None: + """``HTTPXTransport`` works with an int timeout.""" + route = respx.post(url="https://example.com/test").mock( + return_value=httpx.Response( + status_code=HTTPStatus.OK, + text="OK", + ), + ) + transport = HTTPXTransport() + response = transport( + method="POST", + url="https://example.com/test", + headers={"Content-Type": "text/plain"}, + data=b"hello", + request_timeout=30, + ) + assert route.called + assert isinstance(response, Response) + assert response.status_code == HTTPStatus.OK + @staticmethod @respx.mock def test_context_manager() -> None: @@ -137,6 +159,29 @@ async def test_tuple_timeout() -> None: assert isinstance(response, Response) assert response.status_code == HTTPStatus.OK + @staticmethod + @pytest.mark.asyncio + @respx.mock + async def test_int_timeout() -> None: + """``AsyncHTTPXTransport`` works with an int timeout.""" + route = respx.post(url="https://example.com/test").mock( + return_value=httpx.Response( + status_code=HTTPStatus.OK, + text="OK", + ), + ) + transport = AsyncHTTPXTransport() + response = await transport( + method="POST", + url="https://example.com/test", + headers={"Content-Type": "text/plain"}, + data=b"hello", + request_timeout=30, + ) + assert route.called + assert isinstance(response, Response) + assert response.status_code == HTTPStatus.OK + @staticmethod @pytest.mark.asyncio @respx.mock From afd1a6ec682c024555af9622367c7e600381f1dd Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 19 May 2026 13:53:06 +0100 Subject: [PATCH 546/688] Bump strict-kwargs and use diff mode --- .pre-commit-config.yaml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 31eea11f8..feef09332 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -302,7 +302,7 @@ repos: - id: strict-kwargs-fix name: strict-kwargs - entry: uv run --extra=dev strict-kwargs fix + entry: uv run --extra=dev strict-kwargs fix --diff language: python types_or: [python] additional_dependencies: diff --git a/pyproject.toml b/pyproject.toml index d8cbe66fe..1d8cfdca7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ optional-dependencies.dev = [ "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2026.1.12", "sphinxcontrib-spelling==8.0.2", - "strict-kwargs==2026.5.18.post3", + "strict-kwargs==2026.5.19.post1", "sybil==9.3.0", # Listed explicitly (despite being transitive via vws-python-mock) so that # [tool.uv.sources] can redirect to the CPU-only PyTorch index. From 0f0c52a7e6312a0f70831feb1856e99d37dd2b4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 22:24:42 +0000 Subject: [PATCH 547/688] chore(deps-dev): Bump strict-kwargs Bumps [strict-kwargs](https://github.com/adamtheturtle/strict-kwargs) from 2026.5.19.post1 to 2026.5.19.post3. - [Release notes](https://github.com/adamtheturtle/strict-kwargs/releases) - [Changelog](https://github.com/adamtheturtle/strict-kwargs/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/strict-kwargs/compare/2026.5.19-post.1...2026.5.19-post.3) --- updated-dependencies: - dependency-name: strict-kwargs dependency-version: 2026.5.19.post3 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 1d8cfdca7..bfe56a84e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ optional-dependencies.dev = [ "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2026.1.12", "sphinxcontrib-spelling==8.0.2", - "strict-kwargs==2026.5.19.post1", + "strict-kwargs==2026.5.19.post3", "sybil==9.3.0", # Listed explicitly (despite being transitive via vws-python-mock) so that # [tool.uv.sources] can redirect to the CPU-only PyTorch index. From 4b572d639618666bd3b3366a248e894e0b3b2605 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 22:24:54 +0000 Subject: [PATCH 548/688] chore(deps-dev): Bump doccmd from 2026.5.16 to 2026.5.19 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.5.16 to 2026.5.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/2026.05.16...2026.05.19) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.5.19 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 1d8cfdca7..05a212946 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.25.1", "doc8==2.0.0", - "doccmd==2026.5.16", + "doccmd==2026.5.19", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From 1ced42dbc15a0d75004e7705bda7b646d1fa4b6f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 20 May 2026 07:44:12 +0100 Subject: [PATCH 549/688] Ban dynamic builtins and typing.cast in lint config. Align pylint and ruff with literalizer: forbid bare filter, getattr, hasattr, map, and setattr via pylint, and ban typing.cast via ruff. Fix or exempt existing violations. --- pyproject.toml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 75bfea22a..c9b8b1b38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -159,6 +159,7 @@ lint.per-file-ignores."tests/*.py" = [ lint.unfixable = [ "ERA001", ] +lint.flake8-tidy-imports.banned-api."typing.cast".msg = "typing.cast is banned: use explicit type narrowing or a typed variable instead." lint.pydocstyle.convention = "google" [tool.pylint] @@ -199,6 +200,17 @@ MASTER.load-plugins = [ # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. MASTER.unsafe-load-any-extension = false +DEPRECATED_BUILTINS.bad-functions = [ + # Use Pylint until Ruff can ban bare builtin calls, or until custom rules + # make this removable: + # https://github.com/astral-sh/ruff/issues/10079 + # https://github.com/astral-sh/ruff/issues/970 + "filter", + "getattr", + "hasattr", + "map", + "setattr", +] # 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 From a573f07847ecf129e580d70df123dd2cb74a2302 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 20 May 2026 12:05:34 +0100 Subject: [PATCH 550/688] Drive changelog releases from towncrier --- .github/workflows/release.yml | 38 ++++++++++++------------------- CHANGELOG.rst | 3 +-- docs/source/conf.py | 9 ++++++++ docs/source/index.rst | 1 + docs/source/unreleased.rst | 8 +++++++ docs/towncrier_template.rst.jinja | 14 ++++++++++++ newsfragments/.gitkeep | 0 pyproject.toml | 33 +++++++++++++++++++++++++++ 8 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 docs/source/unreleased.rst create mode 100644 docs/towncrier_template.rst.jinja create mode 100644 newsfragments/.gitkeep diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a723a2b4c..44d64e250 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,37 +47,27 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Get the changelog underline - id: changelog_underline + # towncrier writes the rendered notes to stdout (informational + # chatter goes to stderr), so this is the curated release body for + # this version, not github-tag-action's commit-derived changelog. + - name: Generate the GitHub release notes env: RELEASE: ${{ steps.calver.outputs.release }} - run: | - underline="$(echo "$RELEASE" | tr -c '\n' '-')" - echo "underline=${underline}" >> "$GITHUB_OUTPUT" - - - name: Update changelog - id: update_changelog - 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" - include: CHANGELOG.rst - regex: false + run: uv run --extra=release towncrier build --draft --version "$RELEASE" > + release-notes.md - - name: Check Update changelog was modified + # Assemble the same fragments into CHANGELOG.rst under a new + # ``$RELEASE`` section and delete the consumed fragment files. + - name: Update the changelog env: - MODIFIED_FILES: ${{ steps.update_changelog.outputs.modifiedFiles }} - run: | - if [ "$MODIFIED_FILES" = "0" ]; then - echo "Error: No files were modified when updating changelog" - exit 1 - fi + RELEASE: ${{ steps.calver.outputs.release }} + run: uv run --extra=release towncrier build --yes --version "$RELEASE" + - uses: stefanzweifel/git-auto-commit-action@v7 id: commit with: commit_message: Bump CHANGELOG - file_pattern: CHANGELOG.rst + file_pattern: CHANGELOG.rst newsfragments # Error if there are no changes. skip_dirty_check: true @@ -96,7 +86,7 @@ jobs: tag: ${{ steps.tag_version.outputs.new_tag }} makeLatest: true name: Release ${{ steps.tag_version.outputs.new_tag }} - body: ${{ steps.tag_version.outputs.changelog }} + bodyFile: release-notes.md - name: Build a binary wheel and a source tarball env: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 56a5d3007..b6688933c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,8 +1,7 @@ Changelog ========= -Next ----- +.. towncrier release notes start 2026.02.25.1 ------------ diff --git a/docs/source/conf.py b/docs/source/conf.py index 920ba1a4b..12553403f 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -23,8 +23,17 @@ "sphinx.ext.napoleon", "sphinx_substitution_extensions", "sphinxcontrib.spelling", + "sphinxcontrib.towncrier.ext", ] +# Render the unreleased ``newsfragments/`` entries into +# ``docs/source/unreleased.rst`` so the Sphinx spelling, doc-build and +# link-checking gates cover the prose before it is assembled into +# CHANGELOG.rst at release time. +towncrier_draft_autoversion_mode = "draft" +towncrier_draft_include_empty = True +towncrier_draft_working_directory = f"{_pyproject_file.parent}" + templates_path = ["_templates"] source_suffix = ".rst" master_doc = "index" diff --git a/docs/source/index.rst b/docs/source/index.rst index c61c2a302..ae5712825 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -124,4 +124,5 @@ Reference exceptions contributing release-process + unreleased changelog diff --git a/docs/source/unreleased.rst b/docs/source/unreleased.rst new file mode 100644 index 000000000..22ac74723 --- /dev/null +++ b/docs/source/unreleased.rst @@ -0,0 +1,8 @@ +Unreleased changes +================== + +Changes that have landed on the main branch but are not yet part of a +tagged release. These entries are assembled into the +:doc:`changelog` when the next release is published. + +.. towncrier-draft-entries:: diff --git a/docs/towncrier_template.rst.jinja b/docs/towncrier_template.rst.jinja new file mode 100644 index 000000000..6da878330 --- /dev/null +++ b/docs/towncrier_template.rst.jinja @@ -0,0 +1,14 @@ + +{% for section_name, section in sections.items() %} +{% if section %} +{% for category, entries in section.items() %} +{% for text, _ in entries.items() %} +- {{ text }} + +{% endfor %} +{% endfor %} +{% else %} +No significant changes. + +{% endif %} +{% endfor %} diff --git a/newsfragments/.gitkeep b/newsfragments/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/pyproject.toml b/pyproject.toml index c9b8b1b38..91ac42e5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,6 +76,9 @@ optional-dependencies.dev = [ "sphinx-pyproject==0.3.0", "sphinx-substitution-extensions==2026.1.12", "sphinxcontrib-spelling==8.0.2", + # ``sphinxcontrib-towncrier`` renders unreleased news fragments + # into docs/source/unreleased.rst during Sphinx builds. + "sphinxcontrib-towncrier==0.5.0a0", "strict-kwargs==2026.5.19.post3", "sybil==9.3.0", # Listed explicitly (despite being transitive via vws-python-mock) so that @@ -83,6 +86,7 @@ optional-dependencies.dev = [ # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation "torch>=2.5.1", "torchvision>=0.20.1", + "towncrier==25.8.0", "ty==0.0.37", "types-requests==2.33.0.20260518", "vulture==2.16", @@ -284,6 +288,8 @@ ignore = [ "*.enc", ".pre-commit-config.yaml", "CHANGELOG.rst", + "newsfragments", + "newsfragments/**", "CODE_OF_CONDUCT.rst", "CONTRIBUTING.rst", "LICENSE", @@ -349,6 +355,30 @@ report.exclude_also = [ ] report.show_missing = true +[tool.towncrier] +# The changelog and the per-release GitHub release notes are both built +# from news fragments under ``newsfragments/``. The release workflow +# runs ``towncrier build`` to assemble them; contributors add one +# fragment file per user-facing change. +directory = "newsfragments" +filename = "CHANGELOG.rst" +# Custom template so an assembled version reproduces the historical +# style exactly: a bare ```` heading (no project name, no +# date) followed by a flat bullet list with no per-type sub-headings. +template = "docs/towncrier_template.rst.jinja" +title_format = "{version}" +# ``title_format`` underline first, then any nested headings. A bare +# version such as ``2026.05.18`` underlined with ``-`` matches every +# pre-towncrier entry in CHANGELOG.rst. +underlines = [ "-", "~", "^" ] +issue_format = "#{issue}" +type = [ + # A single, unnamed fragment type keeps the assembled output as one + # flat bullet list, matching the historical changelog (which never + # grouped entries under "Features"/"Bugfixes"/... sub-headings). + { directory = "change", name = "", showcontent = true }, +] + [tool.pydocstringformatter] write = true split-summary-body = false @@ -413,6 +443,9 @@ ignore_names = [ "spelling_word_list_filename", "templates_path", "warning_is_error", + "towncrier_draft_autoversion_mode", + "towncrier_draft_include_empty", + "towncrier_draft_working_directory", ] # Duplicate some of .gitignore exclude = [ ".venv" ] From de47475dff8a8ea9cac8d4ee064f3db37caaff2d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 20 May 2026 12:40:43 +0100 Subject: [PATCH 551/688] Add towncrier to release extra --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 91ac42e5f..fdd3e5207 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ optional-dependencies.dev = [ "yamlfix==1.19.1", "zizmor==1.25.2", ] -optional-dependencies.release = [ "check-wheel-contents==0.6.3" ] +optional-dependencies.release = [ "check-wheel-contents==0.6.3", "towncrier==25.8.0" ] urls.Documentation = "https://vws-python.github.io/vws-python/" urls.Source = "https://github.com/VWS-Python/vws-python" From 1fe5f0464aba56f8504bfb01f43347817fd193c3 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 20 May 2026 12:46:38 +0100 Subject: [PATCH 552/688] Make useless Pylint suppressions fail lint --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c9b8b1b38..750ad7142 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -200,6 +200,10 @@ MASTER.load-plugins = [ # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. MASTER.unsafe-load-any-extension = false +# Return non-zero exit code if useless-suppression is emitted. +MAIN.fail-on = [ + "useless-suppression", +] DEPRECATED_BUILTINS.bad-functions = [ # Use Pylint until Ruff can ban bare builtin calls, or until custom rules # make this removable: From bc5dcf3743721d3e0af7fa40b57eacca63a855ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 16:23:05 +0000 Subject: [PATCH 553/688] chore(deps-dev): Bump ty from 0.0.37 to 0.0.38 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.37 to 0.0.38. - [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.37...0.0.38) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.38 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 fc55e6a28..83bf39092 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.37", + "ty==0.0.38", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 44ec4b67fce8bfd73ceb230e695eb81a5d4a2235 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 16:23:31 +0000 Subject: [PATCH 554/688] chore(deps-dev): Bump prek from 0.4.0 to 0.4.1 Bumps [prek](https://github.com/j178/prek) from 0.4.0 to 0.4.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.4.0...v0.4.1) --- updated-dependencies: - dependency-name: prek dependency-version: 0.4.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 fc55e6a28..09d3d9e5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==2.1.0", "mypy-strict-kwargs==2026.1.12", - "prek==0.4.0", + "prek==0.4.1", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.20.0", From d04c145d49cbf6c878ea45e6c72d8d044b2aa930 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 16:30:36 +0000 Subject: [PATCH 555/688] chore(deps-dev): Bump mypy-strict-kwargs from 2026.1.12 to 2026.5.20.1 Bumps [mypy-strict-kwargs](https://github.com/adamtheturtle/mypy-strict-kwargs) from 2026.1.12 to 2026.5.20.1. - [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/2026.01.12...2026.05.20.1) --- updated-dependencies: - dependency-name: mypy-strict-kwargs dependency-version: 2026.5.20.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 417596d44..c2462a248 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ optional-dependencies.dev = [ "furo==2025.12.19", "interrogate==1.7.0", "mypy[faster-cache]==2.1.0", - "mypy-strict-kwargs==2026.1.12", + "mypy-strict-kwargs==2026.5.20.1", "prek==0.4.1", "pydocstringformatter==0.7.5", "pydocstyle==6.3", From f7b3fbfa414a463c2eb4767ca58214fce3e0e342 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 16:30:48 +0000 Subject: [PATCH 556/688] chore(deps-dev): Bump strict-kwargs from 2026.5.19.post3 to 2026.5.20 Bumps [strict-kwargs](https://github.com/adamtheturtle/strict-kwargs) from 2026.5.19.post3 to 2026.5.20. - [Release notes](https://github.com/adamtheturtle/strict-kwargs/releases) - [Changelog](https://github.com/adamtheturtle/strict-kwargs/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/strict-kwargs/compare/2026.5.19-post.3...2026.5.20) --- updated-dependencies: - dependency-name: strict-kwargs dependency-version: 2026.5.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 417596d44..5020ebb82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ # ``sphinxcontrib-towncrier`` renders unreleased news fragments # into docs/source/unreleased.rst during Sphinx builds. "sphinxcontrib-towncrier==0.5.0a0", - "strict-kwargs==2026.5.19.post3", + "strict-kwargs==2026.5.20", "sybil==9.3.0", # Listed explicitly (despite being transitive via vws-python-mock) so that # [tool.uv.sources] can redirect to the CPU-only PyTorch index. From 4e51570f84e72874cb1615a63f7c34be7dbd99d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 10:39:29 +0000 Subject: [PATCH 557/688] chore(deps-dev): Bump ruff from 0.15.13 to 0.15.14 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.13 to 0.15.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.15.13...0.15.14) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.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 64b0a581d..a8405cf19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "ruff==0.15.13", + "ruff==0.15.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 b13f71eabeeb11e574f184940491bda06c18aed7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 May 2026 16:11:55 +0000 Subject: [PATCH 558/688] chore(deps-dev): Bump doccmd from 2026.5.19 to 2026.5.25 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.5.19 to 2026.5.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.05.19...2026.05.25) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.5.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 a8405cf19..6aec88594 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.25.1", "doc8==2.0.0", - "doccmd==2026.5.19", + "doccmd==2026.5.25", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From 91d4dc1033968c7d0f7aeccf96703024e8ddd30a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 May 2026 16:12:35 +0000 Subject: [PATCH 559/688] chore(deps-dev): Bump ty from 0.0.38 to 0.0.39 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.38 to 0.0.39. - [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.38...0.0.39) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.39 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 a8405cf19..d536bd98b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.38", + "ty==0.0.39", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 667fd51a4ce2de4ec4da5af31467a1d5c3029f57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 18:11:54 +0000 Subject: [PATCH 560/688] chore(deps-dev): Bump prek from 0.4.1 to 0.4.2 Bumps [prek](https://github.com/j178/prek) from 0.4.1 to 0.4.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.4.1...v0.4.2) --- updated-dependencies: - dependency-name: prek dependency-version: 0.4.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 b232ba0a8..77308b676 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==2.1.0", "mypy-strict-kwargs==2026.5.20.1", - "prek==0.4.1", + "prek==0.4.2", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.20.0", From 5b7e8ef97cc4a7467cab619f19fc2ec26e9fa1e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 18:12:21 +0000 Subject: [PATCH 561/688] chore(deps-dev): Bump pytest-asyncio from 1.3.0 to 1.4.0 Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 1.3.0 to 1.4.0. - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v1.3.0...v1.4.0) --- updated-dependencies: - dependency-name: pytest-asyncio dependency-version: 1.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 b232ba0a8..dfda929eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ optional-dependencies.dev = [ "pyright==1.1.409", "pyroma==5.0.1", "pytest==9.0.3", - "pytest-asyncio==1.3.0", + "pytest-asyncio==1.4.0", "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", "pyyaml==6.0.3", From 4d9e40b800cb57fa9bd11d19a163ddc4b9f8a5f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 May 2026 12:30:09 +0000 Subject: [PATCH 562/688] chore(deps-dev): Bump prek from 0.4.2 to 0.4.3 Bumps [prek](https://github.com/j178/prek) from 0.4.2 to 0.4.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.4.2...v0.4.3) --- updated-dependencies: - dependency-name: prek dependency-version: 0.4.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 6d56e7802..a4af6ad91 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==2.1.0", "mypy-strict-kwargs==2026.5.20.1", - "prek==0.4.2", + "prek==0.4.3", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.20.0", From 5bd3df6ea1d655eedfd834458c1f5850961ec0d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 11:23:34 +0000 Subject: [PATCH 563/688] chore(deps-dev): Bump ty from 0.0.39 to 0.0.40 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.39 to 0.0.40. - [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.39...0.0.40) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.40 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 a4af6ad91..25cfc013b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.39", + "ty==0.0.40", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 51dbb5213969ff1525d3d4b6563eb195ed9ffdc4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 06:50:42 +0000 Subject: [PATCH 564/688] chore(deps-dev): Bump pyright from 1.1.409 to 1.1.410 Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.409 to 1.1.410. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.409...v1.1.410) --- updated-dependencies: - dependency-name: pyright dependency-version: 1.1.410 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 25cfc013b..dcb03b4c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ optional-dependencies.dev = [ "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.21.2", "pyrefly==1.0.0", - "pyright==1.1.409", + "pyright==1.1.410", "pyroma==5.0.1", "pytest==9.0.3", "pytest-asyncio==1.4.0", From 69af780ed178a0f1d5ec64a0dbf430edf92bd44d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 06:50:59 +0000 Subject: [PATCH 565/688] chore(deps-dev): Bump ty from 0.0.40 to 0.0.42 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.40 to 0.0.42. - [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.40...0.0.42) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.42 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 25cfc013b..c524984d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.40", + "ty==0.0.42", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 5ae7cac07997437059350b53d8341cb9155274ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 06:57:17 +0000 Subject: [PATCH 566/688] chore(deps-dev): Bump pyproject-fmt from 2.21.2 to 2.23.0 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.21.2 to 2.23.0. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.21.2...pyproject-fmt/2.23.0) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.23.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 94e860864..40a03e3a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.20.0", "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", - "pyproject-fmt==2.21.2", + "pyproject-fmt==2.23.0", "pyrefly==1.0.0", "pyright==1.1.410", "pyroma==5.0.1", From 4c9308d7db6e3d814ccb70aa4cd33242c7e77ae5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 3 Jun 2026 06:26:15 +0100 Subject: [PATCH 567/688] Apply pyproject-fmt 2.23.0 formatting changes. Dependabot only bumped the version pin; run the formatter so pre-commit passes. Co-authored-by: Cursor --- pyproject.toml | 294 ++++++++++++++++++++++++------------------------- 1 file changed, 147 insertions(+), 147 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 40a03e3a2..d6d141097 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,13 +103,13 @@ urls.Source = "https://github.com/VWS-Python/vws-python" dev = [] [tool.setuptools] -zip-safe = false -package-data.vws = [ - "py.typed", -] packages.find.where = [ "src", ] +package-data.vws = [ + "py.typed", +] +zip-safe = false [tool.distutils] bdist_wheel.universal = true @@ -167,11 +167,80 @@ lint.flake8-tidy-imports.banned-api."typing.cast".msg = "typing.cast is banned: lint.pydocstyle.convention = "google" [tool.pylint] +# 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 +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --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" +"MESSAGES CONTROL".disable = [ + # Too difficult to please + "duplicate-code", + # Let ruff handle long lines + "line-too-long", + "locally-disabled", + "missing-return-type-doc", + # We don't need everything to be documented because of mypy + "missing-type-doc", + "too-few-public-methods", + "too-many-arguments", + "too-many-instance-attributes", + "too-many-lines", + "too-many-locals", + "too-many-return-statements", + # Let ruff deal with sorting + "ungrouped-imports", + # Let ruff handle unused imports + "unused-import", + # mypy does not want untyped parameters. + "useless-type-doc", + # Let ruff handle imports + "wrong-import-order", +] +# 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. +"MESSAGES CONTROL".enable = [ + "bad-inline-option", + "deprecated-pragma", + "file-ignored", + "spelling", + "use-symbolic-message-instead", + "useless-suppression", +] +# 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. +# - conf.py is a Sphinx configuration file which requires lowercase global variable names. +"MESSAGES CONTROL".per-file-ignores = [ + "docs/source/conf.py:invalid-name", + "docs/source/doccmd_*.py:invalid-name", + "docs/source/doccmd_*/*.py:invalid-name", + "doccmd_README_rst_*.py:invalid-name", + "doccmd_*/*.py:invalid-name", +] +DEPRECATED_BUILTINS.bad-functions = [ + # Use Pylint until Ruff can ban bare builtin calls, or until custom rules + # make this removable: + # https://github.com/astral-sh/ruff/issues/10079 + # https://github.com/astral-sh/ruff/issues/970 + "filter", + "getattr", + "hasattr", + "map", + "setattr", +] # 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. -MASTER.persistent = true +# Return non-zero exit code if useless-suppression is emitted. +MAIN.fail-on = [ + "useless-suppression", +] # Use multiple processes to speed up Pylint. MASTER.jobs = 0 # List of plugins (as comma separated values of python modules names) to load, @@ -184,7 +253,6 @@ MASTER.jobs = 0 # - pylint.extensions.while_used # as they seemed to get in the way. MASTER.load-plugins = [ - "pylint_per_file_ignores", "pylint.extensions.bad_builtin", "pylint.extensions.comparison_placement", "pylint.extensions.consider_refactoring_into_while_condition", @@ -200,81 +268,13 @@ MASTER.load-plugins = [ "pylint.extensions.redefined_variable_type", "pylint.extensions.set_membership", "pylint.extensions.typing", + "pylint_per_file_ignores", ] +# Pickle collected data for later comparisons. +MASTER.persistent = true # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. MASTER.unsafe-load-any-extension = false -# Return non-zero exit code if useless-suppression is emitted. -MAIN.fail-on = [ - "useless-suppression", -] -DEPRECATED_BUILTINS.bad-functions = [ - # Use Pylint until Ruff can ban bare builtin calls, or until custom rules - # make this removable: - # https://github.com/astral-sh/ruff/issues/10079 - # https://github.com/astral-sh/ruff/issues/970 - "filter", - "getattr", - "hasattr", - "map", - "setattr", -] -# 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. -"MESSAGES CONTROL".enable = [ - "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 -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --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" -"MESSAGES CONTROL".disable = [ - "too-few-public-methods", - "too-many-locals", - "too-many-arguments", - "too-many-instance-attributes", - "too-many-return-statements", - "too-many-lines", - "locally-disabled", - # Let ruff handle long lines - "line-too-long", - # Let ruff handle unused imports - "unused-import", - # Let ruff deal with sorting - "ungrouped-imports", - # We don't need everything to be documented because of mypy - "missing-type-doc", - "missing-return-type-doc", - # Too difficult to please - "duplicate-code", - # Let ruff handle imports - "wrong-import-order", - # mypy does not want untyped parameters. - "useless-type-doc", -] -# 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. -# - conf.py is a Sphinx configuration file which requires lowercase global variable names. -"MESSAGES CONTROL".per-file-ignores = [ - "docs/source/conf.py:invalid-name", - "docs/source/doccmd_*.py:invalid-name", - "docs/source/doccmd_*/*.py:invalid-name", - "doccmd_README_rst_*.py:invalid-name", - "doccmd_*/*.py:invalid-name", -] # Spelling dictionary name. Available dictionaries: none. To make it working # install python-enchant package. SPELLING.spelling-dict = "en_US" @@ -284,40 +284,41 @@ SPELLING.spelling-private-dict-file = "spelling_private_dict.txt" # --spelling-private-dict-file option instead of raising a message. SPELLING.spelling-store-unknown-words = "no" +[tool.interrogate] +fail-under = 100 +verbose = 2 +omit-covered-files = true + [tool.check-manifest] ignore = [ + "*.enc", ".checkmake-config.ini", + ".git_archival.txt", + ".pre-commit-config.yaml", ".prettierrc", ".yamlfmt", - "*.enc", - ".pre-commit-config.yaml", "CHANGELOG.rst", - "newsfragments", - "newsfragments/**", - "CODE_OF_CONDUCT.rst", - "CONTRIBUTING.rst", - "LICENSE", - "Makefile", "ci", "ci/**", + "CODE_OF_CONDUCT.rst", + "CONTRIBUTING.rst", "doc8.ini", "docs", "docs/**", - ".git_archival.txt", + "LICENSE", + "lint.mk", + "Makefile", + "newsfragments", + "newsfragments/**", "spelling_private_dict.txt", "tests", "tests-pylintrc", "tests/**", "vuforia_secrets.env.example", - "lint.mk", "zizmor.yml", ] [tool.deptry] -optional_dependencies_dev_groups = [ - "dev", - "release", -] # torch and torchvision are listed explicitly in dev deps to allow # [tool.uv.sources] to redirect them to the CPU-only PyTorch index, # but they are not directly imported in the vws-python source code. @@ -325,6 +326,56 @@ per_rule_ignores.DEP002 = [ "torch", "torchvision", ] +optional_dependencies_dev_groups = [ + "dev", + "release", +] + +[tool.vulture] +# Duplicate some of .gitignore +exclude = [ ".venv" ] +# Ideally we would limit the paths to the source code where we want to ignore names, +# but Vulture does not enable this. +ignore_names = [ + # Public API classes imported by users from vws.transports + "AsyncHTTPXTransport", + # Sphinx + "autoclass_content", + "autoclass_content", + "autodoc_member_order", + "copybutton_exclude", + "extensions", + # pytest fixtures - we name fixtures like this for this purpose + "fixture_*", + "html_show_copyright", + "html_show_sourcelink", + "html_show_sphinx", + "html_theme", + "html_theme_options", + "html_title", + "htmlhelp_basename", + "HTTPXTransport", + "intersphinx_mapping", + "language", + "linkcheck_ignore", + "linkcheck_retries", + "master_doc", + "nitpick_ignore", + "nitpicky", + "project_copyright", + "pygments_style", + # pytest configuration + "pytest_collect_file", + "pytest_plugins", + "rst_prolog", + "source_suffix", + "spelling_word_list_filename", + "templates_path", + "towncrier_draft_autoversion_mode", + "towncrier_draft_include_empty", + "towncrier_draft_working_directory", + "warning_is_error", +] [tool.pyproject-fmt] indent = 4 @@ -332,10 +383,10 @@ keep_full_version = true max_supported_python = "3.14" [tool.mypy] -strict = true files = [ "." ] exclude = [ "build" ] follow_untyped_imports = true +strict = true plugins = [ "mypy_strict_kwargs", ] @@ -344,13 +395,13 @@ plugins = [ errors.non-exhaustive-match = "error" [tool.pyright] +typeCheckingMode = "strict" enableTypeIgnoreComments = false reportUnnecessaryTypeIgnoreComment = true -typeCheckingMode = "strict" [tool.pytest] -xfail_strict = true log_cli = true +xfail_strict = true [tool.coverage] run.branch = true @@ -371,11 +422,11 @@ filename = "CHANGELOG.rst" # date) followed by a flat bullet list with no per-type sub-headings. template = "docs/towncrier_template.rst.jinja" title_format = "{version}" +issue_format = "#{issue}" # ``title_format`` underline first, then any nested headings. A bare # version such as ``2026.05.18`` underlined with ``-`` matches every # pre-towncrier entry in CHANGELOG.rst. underlines = [ "-", "~", "^" ] -issue_format = "#{issue}" type = [ # A single, unnamed fragment type keeps the assembled output as one # flat bullet list, matching the historical changelog (which never @@ -392,11 +443,6 @@ split-summary-body = false max-line-length = 75 linewrap-full-docstring = true -[tool.interrogate] -fail-under = 100 -omit-covered-files = true -verbose = 2 - [tool.doc8] max_line_length = 2000 ignore_path = [ @@ -408,52 +454,6 @@ ignore_path = [ "./src/*/_setuptools_scm_version.txt", ] -[tool.vulture] -# Ideally we would limit the paths to the source code where we want to ignore names, -# but Vulture does not enable this. -ignore_names = [ - # Public API classes imported by users from vws.transports - "AsyncHTTPXTransport", - "HTTPXTransport", - # pytest configuration - "pytest_collect_file", - "pytest_plugins", - # pytest fixtures - we name fixtures like this for this purpose - "fixture_*", - # Sphinx - "autoclass_content", - "autoclass_content", - "autodoc_member_order", - "copybutton_exclude", - "extensions", - "html_show_copyright", - "html_show_sourcelink", - "html_show_sphinx", - "html_theme", - "html_theme_options", - "html_title", - "htmlhelp_basename", - "intersphinx_mapping", - "language", - "linkcheck_ignore", - "linkcheck_retries", - "master_doc", - "nitpicky", - "nitpick_ignore", - "project_copyright", - "pygments_style", - "rst_prolog", - "source_suffix", - "spelling_word_list_filename", - "templates_path", - "warning_is_error", - "towncrier_draft_autoversion_mode", - "towncrier_draft_include_empty", - "towncrier_draft_working_directory", -] -# Duplicate some of .gitignore -exclude = [ ".venv" ] - [tool.yamlfix] section_whitelines = 1 whitelines = 1 From 0b16fc0c481d75d0a53766c2308d0c393fd3b7fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 05:32:51 +0000 Subject: [PATCH 568/688] chore(deps-dev): Bump ruff from 0.15.14 to 0.15.15 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.14 to 0.15.15. - [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.14...0.15.15) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.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 d6d141097..df925f003 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "ruff==0.15.14", + "ruff==0.15.15", # 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 9e6ab8d1a6ba3bc5c32e6722f9a8aacfe2d52c93 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 23:54:40 +0000 Subject: [PATCH 569/688] chore(deps): Bump astral-sh/setup-uv from 8.1.0 to 8.2.0 Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 8.1.0 to 8.2.0. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v8.1.0...v8.2.0) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: 8.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bd4f560c..0fb49823c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v8.1.0 + uses: astral-sh/setup-uv@v8.2.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f1ed8e80e..25caa85e9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ jobs: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v8.1.0 + uses: astral-sh/setup-uv@v8.2.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 44d64e250..b424ca055 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v8.1.0 + uses: astral-sh/setup-uv@v8.2.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' From a64180d7e4ce536de42fa0c4ef595645b64c1ff3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 10:32:57 +0000 Subject: [PATCH 570/688] chore(deps-dev): Bump ty from 0.0.42 to 0.0.43 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.42 to 0.0.43. - [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.42...0.0.43) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.43 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 df925f003..f5a283fb5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.42", + "ty==0.0.43", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From ae0b5a5538478ea9b4aaf800035d3430299a87ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 10:33:22 +0000 Subject: [PATCH 571/688] chore(deps-dev): Bump prek from 0.4.3 to 0.4.4 Bumps [prek](https://github.com/j178/prek) from 0.4.3 to 0.4.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.4.3...v0.4.4) --- updated-dependencies: - dependency-name: prek dependency-version: 0.4.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 df925f003..56d30d22c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==2.1.0", "mypy-strict-kwargs==2026.5.20.1", - "prek==0.4.3", + "prek==0.4.4", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.20.0", From cedb405d618e8f88ed5c78c9a5cdb2e26db37ce1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 10:32:45 +0000 Subject: [PATCH 572/688] chore(deps-dev): Bump ty from 0.0.43 to 0.0.44 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.43 to 0.0.44. - [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.43...0.0.44) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.44 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 76ebcab7a..2f82705d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.43", + "ty==0.0.44", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 064aa33d8fce635bac8ed93428bb578ba36be942 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 10:32:58 +0000 Subject: [PATCH 573/688] chore(deps-dev): Bump ruff from 0.15.15 to 0.15.16 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.15 to 0.15.16. - [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.15...0.15.16) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.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 76ebcab7a..97f8a88a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "ruff==0.15.15", + "ruff==0.15.16", # 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 e49db7319770643d574ca6a4cb7ab5dd67e22b92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 10:33:07 +0000 Subject: [PATCH 574/688] chore(deps-dev): Bump strict-kwargs from 2026.5.20 to 2026.6.4 Bumps [strict-kwargs](https://github.com/adamtheturtle/strict-kwargs) from 2026.5.20 to 2026.6.4. - [Release notes](https://github.com/adamtheturtle/strict-kwargs/releases) - [Changelog](https://github.com/adamtheturtle/strict-kwargs/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/strict-kwargs/compare/2026.5.20...2026.6.4) --- updated-dependencies: - dependency-name: strict-kwargs dependency-version: 2026.6.4 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 76ebcab7a..cbc0ae7a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ # ``sphinxcontrib-towncrier`` renders unreleased news fragments # into docs/source/unreleased.rst during Sphinx builds. "sphinxcontrib-towncrier==0.5.0a0", - "strict-kwargs==2026.5.20", + "strict-kwargs==2026.6.4", "sybil==9.3.0", # Listed explicitly (despite being transitive via vws-python-mock) so that # [tool.uv.sources] can redirect to the CPU-only PyTorch index. From ede14fc7f82010d9cd7e8e2759632041a04348d4 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 5 Jun 2026 15:54:57 +0100 Subject: [PATCH 575/688] Fix strict-kwargs pre-commit hook entry for new CLI strict-kwargs 2026.6.4 switched to a Ruff-like CLI where 'fix' is now 'check --fix'. Update the hook entry accordingly. Co-Authored-By: Claude Sonnet 4.6 --- .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 feef09332..f4629bbd1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -302,7 +302,7 @@ repos: - id: strict-kwargs-fix name: strict-kwargs - entry: uv run --extra=dev strict-kwargs fix --diff + entry: uv run --extra=dev strict-kwargs check --fix --diff language: python types_or: [python] additional_dependencies: From 1e4cddb151ae801d8f0b0f69c92a077d94a828ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 10:32:52 +0000 Subject: [PATCH 576/688] chore(deps-dev): Bump strict-kwargs from 2026.6.4 to 2026.6.8.post1 Bumps [strict-kwargs](https://github.com/adamtheturtle/strict-kwargs) from 2026.6.4 to 2026.6.8.post1. - [Release notes](https://github.com/adamtheturtle/strict-kwargs/releases) - [Changelog](https://github.com/adamtheturtle/strict-kwargs/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/strict-kwargs/compare/2026.6.4...2026.6.8-post.1) --- updated-dependencies: - dependency-name: strict-kwargs dependency-version: 2026.6.8.post1 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 f3cd49b53..f234379b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ # ``sphinxcontrib-towncrier`` renders unreleased news fragments # into docs/source/unreleased.rst during Sphinx builds. "sphinxcontrib-towncrier==0.5.0a0", - "strict-kwargs==2026.6.4", + "strict-kwargs==2026.6.8.post1", "sybil==9.3.0", # Listed explicitly (despite being transitive via vws-python-mock) so that # [tool.uv.sources] can redirect to the CPU-only PyTorch index. From 0908b142de01fbc0f90c172ee766653a39b2fc86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 10:33:16 +0000 Subject: [PATCH 577/688] chore(deps-dev): Bump ty from 0.0.44 to 0.0.46 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.44 to 0.0.46. - [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.44...0.0.46) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.46 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 f3cd49b53..38e5d76ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.44", + "ty==0.0.46", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 64b8ea35db0af6ca02d46bf4223b3f57f4aeacd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 10:32:49 +0000 Subject: [PATCH 578/688] chore(deps-dev): Bump pyproject-fmt from 2.23.0 to 2.24.0 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.23.0 to 2.24.0. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.23.0...pyproject-fmt/2.24.0) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.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 501bf9bcc..b227aafb1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.20.0", "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", - "pyproject-fmt==2.23.0", + "pyproject-fmt==2.24.0", "pyrefly==1.0.0", "pyright==1.1.410", "pyroma==5.0.1", From 33d7d374c93e60701d04b1fbe91eeaa6a42f15f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 10:33:16 +0000 Subject: [PATCH 579/688] chore(deps-dev): Bump ty from 0.0.46 to 0.0.48 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.46 to 0.0.48. - [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.46...0.0.48) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.48 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 501bf9bcc..559889884 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.46", + "ty==0.0.48", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 9e67f7d75f745deb34c231577bfadcf80e940398 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 10:33:00 +0000 Subject: [PATCH 580/688] chore(deps-dev): Bump ty from 0.0.48 to 0.0.49 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.48 to 0.0.49. - [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.48...0.0.49) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.49 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 e351a17f7..76e9ca0f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.48", + "ty==0.0.49", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From e040a567929637ac9eaf510d2b6821b29d6bba61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 10:33:21 +0000 Subject: [PATCH 581/688] chore(deps-dev): Bump ruff from 0.15.16 to 0.15.17 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.16 to 0.15.17. - [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.16...0.15.17) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.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 e351a17f7..179d87da9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "ruff==0.15.16", + "ruff==0.15.17", # 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 2d71b86f56ce6a2459f25b0b1e1d2b4b2e560698 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 10:33:00 +0000 Subject: [PATCH 582/688] chore(deps-dev): Bump pylint from 4.0.5 to 4.0.6 Bumps [pylint](https://github.com/pylint-dev/pylint) from 4.0.5 to 4.0.6. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v4.0.5...v4.0.6) --- updated-dependencies: - dependency-name: pylint dependency-version: 4.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 6bbab5b8b..6ab67667f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ optional-dependencies.dev = [ "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.20.0", - "pylint[spelling]==4.0.5", + "pylint[spelling]==4.0.6", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.24.0", "pyrefly==1.0.0", From 138559376fac4fb78437b6896a2120683be59714 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 10:33:09 +0000 Subject: [PATCH 583/688] chore(deps-dev): Bump pytest from 9.0.3 to 9.1.0 Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.3 to 9.1.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/9.0.3...9.1.0) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.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 6bbab5b8b..23baf3206 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pyrefly==1.0.0", "pyright==1.1.410", "pyroma==5.0.1", - "pytest==9.0.3", + "pytest==9.1.0", "pytest-asyncio==1.4.0", "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", From 2219b83108c1e8e499bf7f62d0a75f8168fe968f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 10:33:14 +0000 Subject: [PATCH 584/688] chore(deps-dev): Bump sybil from 9.3.0 to 10.1.0 Bumps [sybil](https://github.com/simplistix/sybil) from 9.3.0 to 10.1.0. - [Changelog](https://github.com/simplistix/sybil/blob/main/CHANGELOG.rst) - [Commits](https://github.com/simplistix/sybil/compare/9.3.0...10.1.0) --- updated-dependencies: - dependency-name: sybil dependency-version: 10.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 6bbab5b8b..842746806 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ optional-dependencies.dev = [ # into docs/source/unreleased.rst during Sphinx builds. "sphinxcontrib-towncrier==0.5.0a0", "strict-kwargs==2026.6.8.post1", - "sybil==9.3.0", + "sybil==10.1.0", # Listed explicitly (despite being transitive via vws-python-mock) so that # [tool.uv.sources] can redirect to the CPU-only PyTorch index. # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation From 147476db46fee2cfe040c87619b0c6174c98c722 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 10:33:28 +0000 Subject: [PATCH 585/688] chore(deps-dev): Bump pyproject-fmt from 2.24.0 to 2.24.1 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.24.0 to 2.24.1. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.24.0...pyproject-fmt/2.24.1) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.24.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 6bbab5b8b..bc54e77db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.20.0", "pylint[spelling]==4.0.5", "pylint-per-file-ignores==3.2.1", - "pyproject-fmt==2.24.0", + "pyproject-fmt==2.24.1", "pyrefly==1.0.0", "pyright==1.1.410", "pyroma==5.0.1", From 6486d10bdeb67581174eb85f27329d4d58dadf36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:33:17 +0000 Subject: [PATCH 586/688] chore(deps-dev): Bump prek from 0.4.4 to 0.4.5 Bumps [prek](https://github.com/j178/prek) from 0.4.4 to 0.4.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.4.4...v0.4.5) --- updated-dependencies: - dependency-name: prek dependency-version: 0.4.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 cb2f4497b..9506226d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==2.1.0", "mypy-strict-kwargs==2026.5.20.1", - "prek==0.4.4", + "prek==0.4.5", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.20.0", From cc2cf1edcdbf750c6421e95ffd7f32e4563f179c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 10:33:03 +0000 Subject: [PATCH 587/688] chore(deps-dev): Bump sphinx-substitution-extensions Bumps [sphinx-substitution-extensions](https://github.com/adamtheturtle/sphinx-substitution-extensions) from 2026.1.12 to 2026.6.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/2026.01.12...2026.06.17) --- updated-dependencies: - dependency-name: sphinx-substitution-extensions dependency-version: 2026.6.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 9506226d4..7389d5498 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ optional-dependencies.dev = [ "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.2", "sphinx-pyproject==0.3.0", - "sphinx-substitution-extensions==2026.1.12", + "sphinx-substitution-extensions==2026.6.17", "sphinxcontrib-spelling==8.0.2", # ``sphinxcontrib-towncrier`` renders unreleased news fragments # into docs/source/unreleased.rst during Sphinx builds. From 33a1cb5ce848c252b88f175c0146b21c384491c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 10:32:44 +0000 Subject: [PATCH 588/688] chore(deps-dev): Bump pyproject-fmt from 2.24.1 to 2.25.0 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.24.1 to 2.25.0. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.24.1...pyproject-fmt/2.25.0) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.25.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 7389d5498..3de995405 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.20.0", "pylint[spelling]==4.0.6", "pylint-per-file-ignores==3.2.1", - "pyproject-fmt==2.24.1", + "pyproject-fmt==2.25.0", "pyrefly==1.0.0", "pyright==1.1.410", "pyroma==5.0.1", From ecb8449f2e0eb9ce570b9fcbcb3c5ca3fbbfc5a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 10:33:17 +0000 Subject: [PATCH 589/688] chore(deps-dev): Bump ty from 0.0.49 to 0.0.50 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.49 to 0.0.50. - [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.49...0.0.50) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.50 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 7389d5498..67bd4e75f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.49", + "ty==0.0.50", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 2d9fa2c14b58ac83dbe7b1eba65ef1d4512974b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 10:39:13 +0000 Subject: [PATCH 590/688] chore(deps-dev): Bump pyrefly from 1.0.0 to 1.1.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 1.0.0 to 1.1.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/1.0.0...1.1.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 1.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 bea308a58..b62e16bcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.6", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.25.0", - "pyrefly==1.0.0", + "pyrefly==1.1.0", "pyright==1.1.410", "pyroma==5.0.1", "pytest==9.1.0", From e6bfdd8654fd3aac7cb04129f7262c2c190b3846 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Jun 2026 10:32:19 +0000 Subject: [PATCH 591/688] chore(deps): Bump actions/checkout from 6 to 7 Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7. - [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/v6...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' 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 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0fb49823c..503bdb3e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: persist-credentials: false diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 25caa85e9..cb7110624 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,7 +24,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: persist-credentials: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b424ca055..6a4aa244d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: contents: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: # zizmor: ignore[artipacked] git-auto-commit-action requires credentials # See # https://github.com/stefanzweifel/git-auto-commit-action?tab=readme-ov-file#push-to-protected-branches From 1bb2e5e69af1584bbf5bb63841b62858f98f2ae8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Jun 2026 10:32:44 +0000 Subject: [PATCH 592/688] chore(deps-dev): Bump ty from 0.0.50 to 0.0.51 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.50 to 0.0.51. - [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.50...0.0.51) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.51 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 b62e16bcd..e110341f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.50", + "ty==0.0.51", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 3b64d01a8421b89d48fc0a990717f06809366470 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Jun 2026 10:33:13 +0000 Subject: [PATCH 593/688] chore(deps-dev): Bump ruff from 0.15.17 to 0.15.18 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.17 to 0.15.18. - [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.17...0.15.18) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.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 b62e16bcd..a59838c49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "ruff==0.15.17", + "ruff==0.15.18", # 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 c195d7e83efbab65746c68f0ef7eca2d9729c5b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Jun 2026 10:33:20 +0000 Subject: [PATCH 594/688] chore(deps-dev): Bump pyrefly from 1.1.0 to 1.1.1 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 1.1.0 to 1.1.1. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/1.1.0...1.1.1) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 1.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 b62e16bcd..23e929789 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.6", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.25.0", - "pyrefly==1.1.0", + "pyrefly==1.1.1", "pyright==1.1.410", "pyroma==5.0.1", "pytest==9.1.0", From c77e077275aee09498f85612227b8dbd8e82a87a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 10:32:57 +0000 Subject: [PATCH 595/688] chore(deps-dev): Bump pytest from 9.1.0 to 9.1.1 Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.1.0 to 9.1.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.1.0...9.1.1) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.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 f613aba94..07e6237a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.dev = [ "pyrefly==1.1.1", "pyright==1.1.410", "pyroma==5.0.1", - "pytest==9.1.0", + "pytest==9.1.1", "pytest-asyncio==1.4.0", "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", From 5844c59230bf5c08b88ad1883b7e4a854304210e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 10:33:09 +0000 Subject: [PATCH 596/688] chore(deps-dev): Bump zizmor from 1.25.2 to 1.26.1 Bumps [zizmor](https://github.com/zizmorcore/zizmor) from 1.25.2 to 1.26.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.25.2...v1.26.1) --- updated-dependencies: - dependency-name: zizmor dependency-version: 1.26.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 f613aba94..17c3c3c13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,7 +93,7 @@ optional-dependencies.dev = [ "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", - "zizmor==1.25.2", + "zizmor==1.26.1", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3", "towncrier==25.8.0" ] urls.Documentation = "https://vws-python.github.io/vws-python/" From 0da0d65fc3e4fd9327f81d5419f949da385b6bc9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jun 2026 10:32:58 +0000 Subject: [PATCH 597/688] chore(deps-dev): Bump ty from 0.0.51 to 0.0.52 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.51 to 0.0.52. - [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.51...0.0.52) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.52 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 a42af41e7..e2d950865 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.51", + "ty==0.0.52", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 68f4f0e2c8bb4a586e8c360bb6b0e03bbc1f36f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 10:32:43 +0000 Subject: [PATCH 598/688] chore(deps-dev): Bump ty from 0.0.52 to 0.0.53 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.52 to 0.0.53. - [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/commits) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.53 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 e2d950865..e19f7ee94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.52", + "ty==0.0.53", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 646f22efb96cbc70e1d1a0d63c23836b5ba529ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 10:33:21 +0000 Subject: [PATCH 599/688] chore(deps-dev): Bump ruff from 0.15.18 to 0.15.19 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.18 to 0.15.19. - [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.18...0.15.19) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.19 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 e2d950865..f2f102b14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "ruff==0.15.18", + "ruff==0.15.19", # 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 5dbf0de18c41d2fb5bc5cb94188e301cdfb308fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 10:32:45 +0000 Subject: [PATCH 600/688] chore(deps-dev): Bump pyright from 1.1.410 to 1.1.411 Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.410 to 1.1.411. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.410...v1.1.411) --- updated-dependencies: - dependency-name: pyright dependency-version: 1.1.411 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 081832b13..d8ad962f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ optional-dependencies.dev = [ "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.25.0", "pyrefly==1.1.1", - "pyright==1.1.410", + "pyright==1.1.411", "pyroma==5.0.1", "pytest==9.1.1", "pytest-asyncio==1.4.0", From ae7abb1bc9d495a44553139d8d77dd73a70ac50e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 10:32:45 +0000 Subject: [PATCH 601/688] chore(deps-dev): Bump ty from 0.0.53 to 0.0.54 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.53 to 0.0.54. - [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.53...0.0.54) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.54 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 d8ad962f3..b9ea18ce0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.53", + "ty==0.0.54", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 7febd97687a4e2ccd08f5ff35b8cbda23997860e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 10:33:02 +0000 Subject: [PATCH 602/688] chore(deps-dev): Bump pyproject-fmt from 2.25.0 to 2.25.1 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.25.0 to 2.25.1. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.25.0...pyproject-fmt/2.25.1) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.25.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 d8ad962f3..da1a5c9bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.20.0", "pylint[spelling]==4.0.6", "pylint-per-file-ignores==3.2.1", - "pyproject-fmt==2.25.0", + "pyproject-fmt==2.25.1", "pyrefly==1.1.1", "pyright==1.1.411", "pyroma==5.0.1", From 501b9f07db95c671c124e153b2dbf4367f76b9bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 10:33:15 +0000 Subject: [PATCH 603/688] chore(deps-dev): Bump ruff from 0.15.19 to 0.15.20 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.19 to 0.15.20. - [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.19...0.15.20) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.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 d8ad962f3..9f54fadc8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "ruff==0.15.19", + "ruff==0.15.20", # 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 ff249f530487a1d54e9c44d718c20966ffa92cf6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 10:33:03 +0000 Subject: [PATCH 604/688] chore(deps-dev): Bump ty from 0.0.54 to 0.0.55 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.54 to 0.0.55. - [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.54...0.0.55) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.55 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 de8c5fbd2..4a002b406 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.54", + "ty==0.0.55", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From fcc13157fbf6d2e57e397062d73918c49e6302f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:32:55 +0000 Subject: [PATCH 605/688] chore(deps-dev): Bump prek from 0.4.5 to 0.4.6 Bumps [prek](https://github.com/j178/prek) from 0.4.5 to 0.4.6. - [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.4.5...v0.4.6) --- updated-dependencies: - dependency-name: prek dependency-version: 0.4.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 4a002b406..3b821e8f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==2.1.0", "mypy-strict-kwargs==2026.5.20.1", - "prek==0.4.5", + "prek==0.4.6", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.20.0", From 6379135b43f13f4f1b9a6c0c59848beeccc04817 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 10:33:07 +0000 Subject: [PATCH 606/688] chore(deps-dev): Bump ty from 0.0.55 to 0.0.56 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.55 to 0.0.56. - [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.55...0.0.56) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.56 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 3b821e8f3..0a59529b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.55", + "ty==0.0.56", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From d7ae70a00182cd9444f8df93350b45ca422eb42f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 10:32:39 +0000 Subject: [PATCH 607/688] chore(deps): Bump astral-sh/setup-uv from 8.2.0 to 8.3.0 Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 8.2.0 to 8.3.0. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v8.2.0...v8.3.0) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: 8.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 503bdb3e0..d32f8fe0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v8.2.0 + uses: astral-sh/setup-uv@v8.3.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cb7110624..6f0a61090 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ jobs: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v8.2.0 + uses: astral-sh/setup-uv@v8.3.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6a4aa244d..cd142b6db 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v8.2.0 + uses: astral-sh/setup-uv@v8.3.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' From d072b3459269194d50fca3b38ae817b948eab6c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 10:33:00 +0000 Subject: [PATCH 608/688] chore(deps-dev): Bump prek from 0.4.6 to 0.4.8 Bumps [prek](https://github.com/j178/prek) from 0.4.6 to 0.4.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.4.6...v0.4.8) --- updated-dependencies: - dependency-name: prek dependency-version: 0.4.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 0a59529b8..a9660d038 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==2.1.0", "mypy-strict-kwargs==2026.5.20.1", - "prek==0.4.6", + "prek==0.4.8", "pydocstringformatter==0.7.5", "pydocstyle==6.3", "pygments==2.20.0", From d436d4fc42eaeed62958ba0967200068c5eb9916 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 10:39:29 +0000 Subject: [PATCH 609/688] chore(deps-dev): Bump pydocstringformatter from 0.7.5 to 1.0.0 Bumps [pydocstringformatter](https://github.com/DanielNoord/pydocstringformatter) from 0.7.5 to 1.0.0. - [Release notes](https://github.com/DanielNoord/pydocstringformatter/releases) - [Commits](https://github.com/DanielNoord/pydocstringformatter/compare/v0.7.5...v1.0.0) --- updated-dependencies: - dependency-name: pydocstringformatter dependency-version: 1.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 a9660d038..62596a17a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ optional-dependencies.dev = [ "mypy[faster-cache]==2.1.0", "mypy-strict-kwargs==2026.5.20.1", "prek==0.4.8", - "pydocstringformatter==0.7.5", + "pydocstringformatter==1.0.0", "pydocstyle==6.3", "pygments==2.20.0", "pylint[spelling]==4.0.6", From ea481d79c862ce3f36179b427d3e024406acfa3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 10:32:37 +0000 Subject: [PATCH 610/688] chore(deps): Bump astral-sh/setup-uv from 8.3.0 to 8.3.1 Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 8.3.0 to 8.3.1. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v8.3.0...v8.3.1) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: 8.3.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d32f8fe0d..30b0fffba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v8.3.0 + uses: astral-sh/setup-uv@v8.3.1 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6f0a61090..89971549e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ jobs: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v8.3.0 + uses: astral-sh/setup-uv@v8.3.1 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cd142b6db..724ab1aee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v8.3.0 + uses: astral-sh/setup-uv@v8.3.1 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' From 161a2aae5981e661bf0c39f33b1cf6356e56ec28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:33:04 +0000 Subject: [PATCH 611/688] chore(deps-dev): Bump ruff from 0.15.20 to 0.15.21 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.20 to 0.15.21. - [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.20...0.15.21) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.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 62596a17a..45dc64048 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "ruff==0.15.20", + "ruff==0.15.21", # 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 2bc9b90229d866f9f05a3064b26966c65afa36a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:33:36 +0000 Subject: [PATCH 612/688] chore(deps-dev): Bump ty from 0.0.56 to 0.0.58 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.56 to 0.0.58. - [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.56...0.0.58) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.58 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 62596a17a..5a6615553 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.56", + "ty==0.0.58", "types-requests==2.33.0.20260518", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 3f44d754e73503b9db3bdf95623b63bd55745819 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 10:32:58 +0000 Subject: [PATCH 613/688] chore(deps-dev): Bump types-requests --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.33.0.20260712 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 aea789e99..d27281db5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,7 +88,7 @@ optional-dependencies.dev = [ "torchvision>=0.20.1", "towncrier==25.8.0", "ty==0.0.58", - "types-requests==2.33.0.20260518", + "types-requests==2.33.0.20260712", "vulture==2.16", "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", From 54fc2c2fc2296a5d0809875f98e033ac4cb99795 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 10:33:22 +0000 Subject: [PATCH 614/688] chore(deps-dev): Bump prek from 0.4.8 to 0.4.9 Bumps [prek](https://github.com/j178/prek) from 0.4.8 to 0.4.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.4.8...v0.4.9) --- updated-dependencies: - dependency-name: prek dependency-version: 0.4.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 aea789e99..c2ccb439f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==2.1.0", "mypy-strict-kwargs==2026.5.20.1", - "prek==0.4.8", + "prek==0.4.9", "pydocstringformatter==1.0.0", "pydocstyle==6.3", "pygments==2.20.0", From 9866fb9202ea519cd35e95432f63e052d0750d60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 10:39:23 +0000 Subject: [PATCH 615/688] chore(deps-dev): Bump mypy from 2.1.0 to 2.2.0 Bumps [mypy](https://github.com/python/mypy) from 2.1.0 to 2.2.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v2.1.0...v2.2.0) --- updated-dependencies: - dependency-name: mypy dependency-version: 2.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 32671d984..9ac412232 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ optional-dependencies.dev = [ "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", - "mypy[faster-cache]==2.1.0", + "mypy[faster-cache]==2.2.0", "mypy-strict-kwargs==2026.5.20.1", "prek==0.4.9", "pydocstringformatter==1.0.0", From b6e0481b7fb481f3d8687c5b6079f6b402049f1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 10:39:26 +0000 Subject: [PATCH 616/688] chore(deps-dev): Bump ty from 0.0.58 to 0.0.59 --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.59 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 32671d984..565eab46a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.58", + "ty==0.0.59", "types-requests==2.33.0.20260712", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 748ff0490679a642f0b1e8531f8547240fd02691 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 10:32:50 +0000 Subject: [PATCH 617/688] chore(deps-dev): Bump pyproject-fmt from 2.25.1 to 2.25.3 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.25.1 to 2.25.3. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.25.1...pyproject-fmt/2.25.3) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.25.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 ba9c9bb67..856449c5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.20.0", "pylint[spelling]==4.0.6", "pylint-per-file-ignores==3.2.1", - "pyproject-fmt==2.25.1", + "pyproject-fmt==2.25.3", "pyrefly==1.1.1", "pyright==1.1.411", "pyroma==5.0.1", From d988c316f4362ed8094e8ec8451d7ed55c7a1f35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 10:33:02 +0000 Subject: [PATCH 618/688] chore(deps-dev): Bump mypy from 2.2.0 to 2.3.0 Bumps [mypy](https://github.com/python/mypy) from 2.2.0 to 2.3.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v2.2.0...v2.3.0) --- updated-dependencies: - dependency-name: mypy dependency-version: 2.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 ba9c9bb67..f444829b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ optional-dependencies.dev = [ "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", - "mypy[faster-cache]==2.2.0", + "mypy[faster-cache]==2.3.0", "mypy-strict-kwargs==2026.5.20.1", "prek==0.4.9", "pydocstringformatter==1.0.0", From ebc699c1c0cda82dc3dffe439f07872a45d8de75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 10:33:14 +0000 Subject: [PATCH 619/688] chore(deps-dev): Bump zizmor from 1.26.1 to 1.27.0 Bumps [zizmor](https://github.com/zizmorcore/zizmor) from 1.26.1 to 1.27.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.26.1...v1.27.0) --- updated-dependencies: - dependency-name: zizmor dependency-version: 1.27.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 ba9c9bb67..f1cedf3eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,7 +93,7 @@ optional-dependencies.dev = [ "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", - "zizmor==1.26.1", + "zizmor==1.27.0", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3", "towncrier==25.8.0" ] urls.Documentation = "https://vws-python.github.io/vws-python/" From 0fc029e3daf45b50c77a657c0cbd14c8f97f02c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 10:32:58 +0000 Subject: [PATCH 620/688] chore(deps-dev): Bump ty from 0.0.59 to 0.0.60 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.59 to 0.0.60. - [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.59...0.0.60) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.60 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 d5f820845..bf774fc40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.59", + "ty==0.0.60", "types-requests==2.33.0.20260712", "vulture==2.16", "vws-python-mock==2026.2.22.3", From af4e84073c8ca0f2f225c284679e2c399757a335 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 10:33:20 +0000 Subject: [PATCH 621/688] chore(deps-dev): Bump prek from 0.4.9 to 0.4.10 Bumps [prek](https://github.com/j178/prek) from 0.4.9 to 0.4.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.4.9...v0.4.10) --- updated-dependencies: - dependency-name: prek dependency-version: 0.4.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 d5f820845..9f179def3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==2.3.0", "mypy-strict-kwargs==2026.5.20.1", - "prek==0.4.9", + "prek==0.4.10", "pydocstringformatter==1.0.0", "pydocstyle==6.3", "pygments==2.20.0", From 6969f8700751e387f63e3e2da9380f06eb8f003a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 17 Jul 2026 07:59:04 +0100 Subject: [PATCH 622/688] Fix target_id when base_vws_url has a path prefix. Parse the target ID from the path segment after targets, summary, or duplicates so prefixed base URLs work. Closes #3091. Co-authored-by: Cursor --- newsfragments/3091.change.rst | 1 + src/vws/exceptions/vws_exceptions.py | 36 +++++++++++-------- tests/test_vws_exceptions.py | 53 ++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 15 deletions(-) create mode 100644 newsfragments/3091.change.rst diff --git a/newsfragments/3091.change.rst b/newsfragments/3091.change.rst new file mode 100644 index 000000000..b2696ec2a --- /dev/null +++ b/newsfragments/3091.change.rst @@ -0,0 +1 @@ +Fix ``target_id`` on target exceptions when ``base_vws_url`` includes a path prefix. diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index de09cdeaa..ac44cfd3e 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -14,6 +14,24 @@ from vws.exceptions.base_exceptions import VWSError +def _target_id_from_url(*, url: str) -> str: + """Return the target ID from a VWS response URL. + + Paths may include a custom base URL prefix. The target ID is the + path segment after ``targets``, ``summary``, or ``duplicates``. + """ + path = urlparse(url=url).path + parts = [part for part in path.split(sep="/") if part] + for marker in ("targets", "summary", "duplicates"): + try: + marker_index = parts.index(marker) + except ValueError: + continue + return parts[marker_index + 1] + message = f"Could not find a target ID in URL path {path!r}" + raise ValueError(message) # pragma: no cover + + @beartype class UnknownTargetError(VWSError): """Exception raised when Vuforia returns a response with a result code @@ -23,11 +41,7 @@ class UnknownTargetError(VWSError): @property def target_id(self) -> str: """The unknown target ID.""" - path = urlparse(url=self.response.url).path - # Every HTTP path which can raise this error has the target ID as the - # second path segment, e.g. `/something/{target_id}` or - # `/something/{target_id}/more`. - return path.split(sep="/")[2] + return _target_id_from_url(url=self.response.url) @beartype @@ -68,11 +82,7 @@ class TargetStatusProcessingError(VWSError): @property def target_id(self) -> str: """The processing target ID.""" - path = urlparse(url=self.response.url).path - # Every HTTP path which can raise this error has the target ID as the - # second path segment, e.g. `/something/{target_id}` or - # `/something/{target_id}/more`. - return path.split(sep="/")[2] + return _target_id_from_url(url=self.response.url) # This is not simulated by the mock. @@ -158,11 +168,7 @@ class TargetStatusNotSuccessError(VWSError): @property def target_id(self) -> str: """The unknown target ID.""" - path = urlparse(url=self.response.url).path - # Every HTTP path which can raise this error has the target ID as the - # second path segment, e.g. `/something/{target_id}` or - # `/something/{target_id}/more`. - return path.split(sep="/")[2] + return _target_id_from_url(url=self.response.url) @beartype diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 798d15f71..1bb34ce18 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -473,3 +473,56 @@ def test_vwserror_from_result_code() -> None: assert isinstance(exception, UnknownTargetError) assert exception.response is response + + +@pytest.mark.parametrize( + argnames=("exception_type", "url"), + argvalues=[ + (UnknownTargetError, "https://vws.vuforia.com/targets/abc"), + (UnknownTargetError, "https://example.com/prefix/targets/abc"), + (UnknownTargetError, "https://example.com/prefix/summary/abc"), + (UnknownTargetError, "https://example.com/prefix/duplicates/abc"), + ( + TargetStatusProcessingError, + "https://vws.vuforia.com/targets/abc", + ), + ( + TargetStatusProcessingError, + "https://example.com/prefix/targets/abc", + ), + ( + TargetStatusNotSuccessError, + "https://vws.vuforia.com/targets/abc", + ), + ( + TargetStatusNotSuccessError, + "https://example.com/prefix/targets/abc", + ), + ( + TargetStatusNotSuccessError, + "https://example.com/prefix/targets/abc/instances", + ), + ], +) +def test_target_id_with_base_url_prefixes( + *, + exception_type: type[ + UnknownTargetError + | TargetStatusProcessingError + | TargetStatusNotSuccessError + ], + url: str, +) -> None: + """``target_id`` is correct even when ``base_vws_url`` has a path + prefix. + """ + response = Response( + text="{}", + url=url, + status_code=HTTPStatus.NOT_FOUND, + headers={}, + request_body=None, + tell_position=0, + content=b"", + ) + assert exception_type(response=response).target_id == "abc" From 8640d397f9771f00a24e7d2bbefb869e9aa6e05e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 17 Jul 2026 08:01:46 +0100 Subject: [PATCH 623/688] Retain explicitly provided falsy custom transports. Only None should select the default transport across all sync and async clients. Closes #3092. Co-authored-by: Cursor --- newsfragments/3092.change.rst | 1 + src/vws/async_query.py | 4 +- src/vws/async_vumark_service.py | 4 +- src/vws/async_vws.py | 4 +- src/vws/query.py | 4 +- src/vws/vumark_service.py | 4 +- src/vws/vws.py | 4 +- tests/test_transports.py | 201 ++++++++++++++++++++++++++++++++ 8 files changed, 220 insertions(+), 6 deletions(-) create mode 100644 newsfragments/3092.change.rst diff --git a/newsfragments/3092.change.rst b/newsfragments/3092.change.rst new file mode 100644 index 000000000..a15105df8 --- /dev/null +++ b/newsfragments/3092.change.rst @@ -0,0 +1 @@ +Retain explicitly provided falsy custom transports instead of replacing them with defaults. diff --git a/src/vws/async_query.py b/src/vws/async_query.py index bebd3089d..88536144f 100644 --- a/src/vws/async_query.py +++ b/src/vws/async_query.py @@ -60,7 +60,9 @@ def __init__( self._client_secret_key = client_secret_key self._base_vwq_url = base_vwq_url self._request_timeout_seconds = request_timeout_seconds - self._transport = transport or AsyncHTTPXTransport() + self._transport = ( + transport if transport is not None else AsyncHTTPXTransport() + ) async def aclose(self) -> None: """Close the underlying transport if it supports closing.""" diff --git a/src/vws/async_vumark_service.py b/src/vws/async_vumark_service.py index 51c2ef349..24191d7c6 100644 --- a/src/vws/async_vumark_service.py +++ b/src/vws/async_vumark_service.py @@ -46,7 +46,9 @@ def __init__( self._server_secret_key = server_secret_key self._base_vws_url = base_vws_url self._request_timeout_seconds = request_timeout_seconds - self._transport = transport or AsyncHTTPXTransport() + self._transport = ( + transport if transport is not None else AsyncHTTPXTransport() + ) async def aclose(self) -> None: """Close the underlying transport if it supports closing.""" diff --git a/src/vws/async_vws.py b/src/vws/async_vws.py index 92675ac18..5ec523565 100644 --- a/src/vws/async_vws.py +++ b/src/vws/async_vws.py @@ -57,7 +57,9 @@ def __init__( self._server_secret_key = server_secret_key self._base_vws_url = base_vws_url self._request_timeout_seconds = request_timeout_seconds - self._transport = transport or AsyncHTTPXTransport() + self._transport = ( + transport if transport is not None else AsyncHTTPXTransport() + ) async def aclose(self) -> None: """Close the underlying transport if it supports closing.""" diff --git a/src/vws/query.py b/src/vws/query.py index 44840d6f0..3d9e19d7f 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -56,7 +56,9 @@ def __init__( self._client_secret_key = client_secret_key self._base_vwq_url = base_vwq_url self._request_timeout_seconds = request_timeout_seconds - self._transport = transport or RequestsTransport() + self._transport = ( + transport if transport is not None else RequestsTransport() + ) def query( self, diff --git a/src/vws/vumark_service.py b/src/vws/vumark_service.py index f4b04152f..a11eb5fc5 100644 --- a/src/vws/vumark_service.py +++ b/src/vws/vumark_service.py @@ -43,7 +43,9 @@ def __init__( self._server_secret_key = server_secret_key self._base_vws_url = base_vws_url self._request_timeout_seconds = request_timeout_seconds - self._transport = transport or RequestsTransport() + self._transport = ( + transport if transport is not None else RequestsTransport() + ) def generate_vumark_instance( self, diff --git a/src/vws/vws.py b/src/vws/vws.py index 7eda102fe..b5122ae5f 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -56,7 +56,9 @@ def __init__( self._server_secret_key = server_secret_key self._base_vws_url = base_vws_url self._request_timeout_seconds = request_timeout_seconds - self._transport = transport or RequestsTransport() + self._transport = ( + transport if transport is not None else RequestsTransport() + ) def make_request( self, diff --git a/tests/test_transports.py b/tests/test_transports.py index 752b0ef23..f8865e57d 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -1,13 +1,24 @@ """Tests for HTTP transport implementations.""" +import io +import uuid from http import HTTPStatus import httpx import pytest import respx +from vws import ( + VWS, + AsyncCloudRecoService, + AsyncVuMarkService, + AsyncVWS, + CloudRecoService, + VuMarkService, +) from vws.response import Response from vws.transports import AsyncHTTPXTransport, HTTPXTransport +from vws.vumark_accept import VuMarkAccept class TestHTTPXTransport: @@ -206,3 +217,193 @@ async def test_context_manager() -> None: assert route.called assert isinstance(response, Response) assert response.status_code == HTTPStatus.OK + + +class _FalsyTransport: + """A sync transport that is falsy but protocol-conforming.""" + + def __bool__(self) -> bool: + """Return ``False`` so truthiness checks would skip this + transport. + """ + return False + + def close(self) -> None: + """Close the transport.""" + + def __call__( + self, + *, + method: str, + url: str, + headers: dict[str, str], + data: bytes, + request_timeout: float | tuple[float, float], + ) -> Response: + """Return a successful API response for the requested URL.""" + del method, headers, request_timeout + if url.endswith("/query"): + body = '{"result_code":"Success","results":[]}' + return Response( + text=body, + url=url, + status_code=HTTPStatus.OK, + headers={}, + request_body=data, + tell_position=0, + content=body.encode(), + ) + if "/instances" in url: + content = b"vumark-bytes" + return Response( + text="", + url=url, + status_code=HTTPStatus.OK, + headers={}, + request_body=data, + tell_position=0, + content=content, + ) + body = '{"result_code":"Success","results":[]}' + return Response( + text=body, + url=url, + status_code=HTTPStatus.OK, + headers={}, + request_body=data, + tell_position=0, + content=body.encode(), + ) + + +class _FalsyAsyncTransport: + """An async transport that is falsy but protocol-conforming.""" + + def __bool__(self) -> bool: + """Return ``False`` so truthiness checks would skip this + transport. + """ + return False + + async def aclose(self) -> None: + """Close the transport.""" + + async def __call__( + self, + *, + method: str, + url: str, + headers: dict[str, str], + data: bytes, + request_timeout: float | tuple[float, float], + ) -> Response: + """Return a successful API response for the requested URL.""" + del method, headers, request_timeout + if url.endswith("/query"): + body = '{"result_code":"Success","results":[]}' + return Response( + text=body, + url=url, + status_code=HTTPStatus.OK, + headers={}, + request_body=data, + tell_position=0, + content=body.encode(), + ) + if "/instances" in url: + content = b"vumark-bytes" + return Response( + text="", + url=url, + status_code=HTTPStatus.OK, + headers={}, + request_body=data, + tell_position=0, + content=content, + ) + body = '{"result_code":"Success","results":[]}' + return Response( + text=body, + url=url, + status_code=HTTPStatus.OK, + headers={}, + request_body=data, + tell_position=0, + content=body.encode(), + ) + + +def test_falsy_sync_transport_is_retained( + high_quality_image: io.BytesIO, +) -> None: + """Falsy custom sync transports are not replaced by the default.""" + access_key = uuid.uuid4().hex + secret_key = uuid.uuid4().hex + transport = _FalsyTransport() + + assert ( + VWS( + server_access_key=access_key, + server_secret_key=secret_key, + transport=transport, + ).list_targets() + == [] + ) + assert ( + CloudRecoService( + client_access_key=access_key, + client_secret_key=secret_key, + transport=transport, + ).query(image=high_quality_image) + == [] + ) + assert ( + VuMarkService( + server_access_key=access_key, + server_secret_key=secret_key, + transport=transport, + ).generate_vumark_instance( + target_id="target", + instance_id="instance", + accept=VuMarkAccept.PNG, + ) + == b"vumark-bytes" + ) + + +@pytest.mark.asyncio +async def test_falsy_async_transport_is_retained( + high_quality_image: io.BytesIO, +) -> None: + """Falsy custom async transports are not replaced by the default.""" + access_key = uuid.uuid4().hex + secret_key = uuid.uuid4().hex + transport = _FalsyAsyncTransport() + + async with AsyncVWS( + server_access_key=access_key, + server_secret_key=secret_key, + transport=transport, + ) as vws_client: + assert await vws_client.list_targets() == [] + + async with AsyncCloudRecoService( + client_access_key=access_key, + client_secret_key=secret_key, + transport=transport, + ) as cloud_reco_client: + assert await cloud_reco_client.query(image=high_quality_image) == [] + + async with AsyncVuMarkService( + server_access_key=access_key, + server_secret_key=secret_key, + transport=transport, + ) as vumark_client: + assert ( + await vumark_client.generate_vumark_instance( + target_id="target", + instance_id="instance", + accept=VuMarkAccept.PNG, + ) + == b"vumark-bytes" + ) From 9a410355bc78e25d8e8acdbc5b5e3902331ab4ad Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 17 Jul 2026 08:03:42 +0100 Subject: [PATCH 624/688] Cover target_id helper fallback when the URL has no marker. Co-authored-by: Cursor --- src/vws/exceptions/vws_exceptions.py | 2 +- tests/test_vws_exceptions.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index ac44cfd3e..3e7e12265 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -29,7 +29,7 @@ def _target_id_from_url(*, url: str) -> str: continue return parts[marker_index + 1] message = f"Could not find a target ID in URL path {path!r}" - raise ValueError(message) # pragma: no cover + raise ValueError(message) @beartype diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 1bb34ce18..92b33d539 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -526,3 +526,21 @@ def test_target_id_with_base_url_prefixes( content=b"", ) assert exception_type(response=response).target_id == "abc" + + +def test_target_id_missing_from_url() -> None: + """A clear error is raised when the response URL has no target ID.""" + response = Response( + text="{}", + url="https://example.com/no-target-here", + status_code=HTTPStatus.NOT_FOUND, + headers={}, + request_body=None, + tell_position=0, + content=b"", + ) + with pytest.raises( + expected_exception=ValueError, + match="Could not find a target ID", + ): + _ = UnknownTargetError(response=response).target_id From 211f333b83edb390a29a2d85029eae59518ef6bc Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 17 Jul 2026 08:06:06 +0100 Subject: [PATCH 625/688] Fix spelling and pylint issues in falsy transport tests. Co-authored-by: Cursor --- spelling_private_dict.txt | 2 ++ tests/test_transports.py | 50 ++++++++++++++++++--------------------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index 73d358a00..382e8ccd3 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -2,6 +2,7 @@ AuthenticationFailure BadImage ConnectionErrorPossiblyImageTooLarge DateRangeError +Falsy ImageTooLarge InactiveProject JSONDecodeError @@ -45,6 +46,7 @@ dev dict docstring enum +falsy filename foo formdata diff --git a/tests/test_transports.py b/tests/test_transports.py index f8865e57d..fa3c7aef8 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -341,34 +341,30 @@ def test_falsy_sync_transport_is_retained( secret_key = uuid.uuid4().hex transport = _FalsyTransport() - assert ( - VWS( - server_access_key=access_key, - server_secret_key=secret_key, - transport=transport, - ).list_targets() - == [] - ) - assert ( - CloudRecoService( - client_access_key=access_key, - client_secret_key=secret_key, - transport=transport, - ).query(image=high_quality_image) - == [] - ) - assert ( - VuMarkService( - server_access_key=access_key, - server_secret_key=secret_key, - transport=transport, - ).generate_vumark_instance( - target_id="target", - instance_id="instance", - accept=VuMarkAccept.PNG, - ) - == b"vumark-bytes" + targets = VWS( + server_access_key=access_key, + server_secret_key=secret_key, + transport=transport, + ).list_targets() + assert targets == [] + + query_results = CloudRecoService( + client_access_key=access_key, + client_secret_key=secret_key, + transport=transport, + ).query(image=high_quality_image) + assert query_results == [] + + vumark_bytes = VuMarkService( + server_access_key=access_key, + server_secret_key=secret_key, + transport=transport, + ).generate_vumark_instance( + target_id="target", + instance_id="instance", + accept=VuMarkAccept.PNG, ) + assert vumark_bytes == b"vumark-bytes" @pytest.mark.asyncio From 30e00318eda53bd05b07b86d81fcd265b48b5ce3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 07:08:26 +0000 Subject: [PATCH 626/688] chore(deps): Bump astral-sh/setup-uv from 8.3.1 to 8.3.2 Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 8.3.1 to 8.3.2. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v8.3.1...v8.3.2) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: 8.3.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30b0fffba..220fdfa64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v8.3.1 + uses: astral-sh/setup-uv@v8.3.2 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 89971549e..04816ee8e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ jobs: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v8.3.1 + uses: astral-sh/setup-uv@v8.3.2 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 724ab1aee..08139a653 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v8.3.1 + uses: astral-sh/setup-uv@v8.3.2 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' From c1edafb7edc3c225b9be1dd247c89ebba9ac3436 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 17 Jul 2026 08:08:42 +0100 Subject: [PATCH 627/688] Satisfy pylint empty-list comparison check in transport tests. Co-authored-by: Cursor --- tests/test_transports.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_transports.py b/tests/test_transports.py index fa3c7aef8..78c3ff0cf 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -346,14 +346,14 @@ def test_falsy_sync_transport_is_retained( server_secret_key=secret_key, transport=transport, ).list_targets() - assert targets == [] + assert not targets query_results = CloudRecoService( client_access_key=access_key, client_secret_key=secret_key, transport=transport, ).query(image=high_quality_image) - assert query_results == [] + assert not query_results vumark_bytes = VuMarkService( server_access_key=access_key, @@ -381,14 +381,14 @@ async def test_falsy_async_transport_is_retained( server_secret_key=secret_key, transport=transport, ) as vws_client: - assert await vws_client.list_targets() == [] + assert not await vws_client.list_targets() async with AsyncCloudRecoService( client_access_key=access_key, client_secret_key=secret_key, transport=transport, ) as cloud_reco_client: - assert await cloud_reco_client.query(image=high_quality_image) == [] + assert not await cloud_reco_client.query(image=high_quality_image) async with AsyncVuMarkService( server_access_key=access_key, From 9a9bf49c58faf9fd2ec7afb4b6b6daf931be8da6 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 17 Jul 2026 08:14:43 +0100 Subject: [PATCH 628/688] Cover falsy transport __bool__ methods in retention tests. Co-authored-by: Cursor --- tests/test_transports.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_transports.py b/tests/test_transports.py index 78c3ff0cf..f208339d4 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -340,6 +340,7 @@ def test_falsy_sync_transport_is_retained( access_key = uuid.uuid4().hex secret_key = uuid.uuid4().hex transport = _FalsyTransport() + assert not transport targets = VWS( server_access_key=access_key, @@ -375,6 +376,7 @@ async def test_falsy_async_transport_is_retained( access_key = uuid.uuid4().hex secret_key = uuid.uuid4().hex transport = _FalsyAsyncTransport() + assert not transport async with AsyncVWS( server_access_key=access_key, From 937622c75ddfbcbb3f9f7e133014d65f6213c6dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 10:33:02 +0000 Subject: [PATCH 629/688] chore(deps-dev): Bump ruff from 0.15.21 to 0.15.22 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.21 to 0.15.22. - [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.21...0.15.22) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.22 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 2f964703b..7de4546bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "ruff==0.15.21", + "ruff==0.15.22", # 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 498baf99c31c48ea001d3b4cb1550c11b21738f7 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sat, 18 Jul 2026 18:23:05 +0100 Subject: [PATCH 630/688] Remove obsolete bdist_wheel.universal setting. Universal wheels claim Python 2 support; these projects require Python 3 only, and the setting triggers setuptools deprecation warnings. Co-authored-by: Cursor --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7de4546bf..62406665c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,9 +111,6 @@ package-data.vws = [ ] zip-safe = false -[tool.distutils] -bdist_wheel.universal = true - [tool.setuptools_scm] # 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 From dc8eae121685a132bfaf0342f009c1a51486b226 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:33:02 +0000 Subject: [PATCH 631/688] chore(deps-dev): Bump doccmd from 2026.5.25 to 2026.7.19 Bumps [doccmd](https://github.com/adamtheturtle/doccmd) from 2026.5.25 to 2026.7.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/2026.05.25...2026.07.19) --- updated-dependencies: - dependency-name: doccmd dependency-version: 2026.7.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 62406665c..238d4e64c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ optional-dependencies.dev = [ "check-manifest==0.51", "deptry==0.25.1", "doc8==2.0.0", - "doccmd==2026.5.25", + "doccmd==2026.7.19", "freezegun==1.5.5", "furo==2025.12.19", "interrogate==1.7.0", From a656fa07d4b17fa61798d7337c17c99cc48cf39a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:33:25 +0000 Subject: [PATCH 632/688] chore(deps-dev): Bump mypy-strict-kwargs from 2026.5.20.1 to 2026.7.19.1 Bumps [mypy-strict-kwargs](https://github.com/adamtheturtle/mypy-strict-kwargs) from 2026.5.20.1 to 2026.7.19.1. - [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/2026.05.20.1...2026.07.19.1) --- updated-dependencies: - dependency-name: mypy-strict-kwargs dependency-version: 2026.7.19.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 62406665c..2b900867d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ optional-dependencies.dev = [ "furo==2025.12.19", "interrogate==1.7.0", "mypy[faster-cache]==2.3.0", - "mypy-strict-kwargs==2026.5.20.1", + "mypy-strict-kwargs==2026.7.19.1", "prek==0.4.10", "pydocstringformatter==1.0.0", "pydocstyle==6.3", From 54f195d4c8dd2f35fcea050a2e72ef88df5ec15a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:33:36 +0000 Subject: [PATCH 633/688] chore(deps-dev): Bump ty from 0.0.60 to 0.0.61 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.60 to 0.0.61. - [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.60...0.0.61) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.61 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 62406665c..f22f49b69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.60", + "ty==0.0.61", "types-requests==2.33.0.20260712", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 3aecfa222266d5e0d235f02a417d75b0060d17c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 10:32:34 +0000 Subject: [PATCH 634/688] chore(deps): Bump astral-sh/setup-uv from 8.3.2 to 9.0.0 Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 8.3.2 to 9.0.0. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v8.3.2...v9.0.0) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: 9.0.0 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 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 220fdfa64..7a89a87c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v8.3.2 + uses: astral-sh/setup-uv@v9.0.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 04816ee8e..091929e2a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ jobs: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v8.3.2 + uses: astral-sh/setup-uv@v9.0.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 08139a653..3337a2415 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v8.3.2 + uses: astral-sh/setup-uv@v9.0.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' From 3acaae0efa58789386e7d83a446704b8fc5119d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 10:32:43 +0000 Subject: [PATCH 635/688] chore(deps-dev): Bump zizmor from 1.27.0 to 1.28.0 Bumps [zizmor](https://github.com/zizmorcore/zizmor) from 1.27.0 to 1.28.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.27.0...v1.28.0) --- updated-dependencies: - dependency-name: zizmor dependency-version: 1.28.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 4a69e6cc3..0fd555456 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,7 +93,7 @@ optional-dependencies.dev = [ "vws-python-mock==2026.2.22.3", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", - "zizmor==1.27.0", + "zizmor==1.28.0", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3", "towncrier==25.8.0" ] urls.Documentation = "https://vws-python.github.io/vws-python/" From 0d9b9391899e382cbf7aa9aeedd9ccb9a094971d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 10:33:05 +0000 Subject: [PATCH 636/688] chore(deps-dev): Bump ty from 0.0.61 to 0.0.62 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.61 to 0.0.62. - [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.61...0.0.62) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.62 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 4a69e6cc3..75387e4c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.61", + "ty==0.0.62", "types-requests==2.33.0.20260712", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 076d311c6f5fa7fe859e9c71b6de95030f0eb003 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:33:00 +0000 Subject: [PATCH 637/688] chore(deps-dev): Bump strict-kwargs from 2026.6.8.post1 to 2026.7.24 Bumps [strict-kwargs](https://github.com/adamtheturtle/strict-kwargs) from 2026.6.8.post1 to 2026.7.24. - [Release notes](https://github.com/adamtheturtle/strict-kwargs/releases) - [Changelog](https://github.com/adamtheturtle/strict-kwargs/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamtheturtle/strict-kwargs/compare/2026.6.8-post.1...2026.7.24) --- updated-dependencies: - dependency-name: strict-kwargs dependency-version: 2026.7.24 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 9574388b2..9e1f46ab8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ optional-dependencies.dev = [ # ``sphinxcontrib-towncrier`` renders unreleased news fragments # into docs/source/unreleased.rst during Sphinx builds. "sphinxcontrib-towncrier==0.5.0a0", - "strict-kwargs==2026.6.8.post1", + "strict-kwargs==2026.7.24", "sybil==10.1.0", # Listed explicitly (despite being transitive via vws-python-mock) so that # [tool.uv.sources] can redirect to the CPU-only PyTorch index. From 8909f2213e663c0c3ecf163a1a99158199a6914b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:33:17 +0000 Subject: [PATCH 638/688] chore(deps-dev): Bump ty from 0.0.62 to 0.0.63 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.62 to 0.0.63. - [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.62...0.0.63) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.63 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 9574388b2..b294e3eb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.62", + "ty==0.0.63", "types-requests==2.33.0.20260712", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 004e7a9f88ff2f993c0ce29411a78017191ed2c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:33:31 +0000 Subject: [PATCH 639/688] chore(deps-dev): Bump ruff from 0.15.22 to 0.16.0 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.22 to 0.16.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.15.22...0.16.0) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.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 9574388b2..c039d1d11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "ruff==0.15.22", + "ruff==0.16.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 328a4ffbf786e4f6f5c4f3b75d90bc9fd97d32df Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 24 Jul 2026 13:03:33 +0100 Subject: [PATCH 640/688] Fix strict-kwargs pre-commit arguments --- .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 f4629bbd1..863357160 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -302,7 +302,7 @@ repos: - id: strict-kwargs-fix name: strict-kwargs - entry: uv run --extra=dev strict-kwargs check --fix --diff + entry: uv run --extra=dev strict-kwargs check --fix language: python types_or: [python] additional_dependencies: From dfc391ff351b4b18f0ac663be4b01844c7c1933d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 24 Jul 2026 16:55:15 +0100 Subject: [PATCH 641/688] Configure Ruff 0.16 defaults --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c039d1d11..8c9299ce0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -132,6 +132,8 @@ lint.select = [ lint.ignore = [ # Ruff warns that this conflicts with the formatter. "COM812", + # This project does not use per-file copyright notices. + "CPY001", # 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", From 3cadb213cda39be5ce16a1d7268b95aa43b1b5be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 10:32:50 +0000 Subject: [PATCH 642/688] chore(deps-dev): Bump prek from 0.4.10 to 0.4.11 Bumps [prek](https://github.com/j178/prek) from 0.4.10 to 0.4.11. - [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.4.10...v0.4.11) --- updated-dependencies: - dependency-name: prek dependency-version: 0.4.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 f9d908c15..bf05a3854 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==2.3.0", "mypy-strict-kwargs==2026.7.19.1", - "prek==0.4.10", + "prek==0.4.11", "pydocstringformatter==1.0.0", "pydocstyle==6.3", "pygments==2.20.0", From 4f1e5f41df4798b4be6f2edb1f35f87787c597a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 10:33:06 +0000 Subject: [PATCH 643/688] chore(deps-dev): Bump pyproject-fmt from 2.25.3 to 2.25.4 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.25.3 to 2.25.4. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.25.3...pyproject-fmt/2.25.4) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.25.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 f9d908c15..fd3c4d350 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.20.0", "pylint[spelling]==4.0.6", "pylint-per-file-ignores==3.2.1", - "pyproject-fmt==2.25.3", + "pyproject-fmt==2.25.4", "pyrefly==1.1.1", "pyright==1.1.411", "pyroma==5.0.1", From 8dfd770607f45f5d9ed8d607293c1277b0257136 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 10:32:53 +0000 Subject: [PATCH 644/688] chore(deps-dev): Bump ty from 0.0.63 to 0.0.64 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.63 to 0.0.64. - [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.63...0.0.64) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.64 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 8870419d1..3cf6a3476 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.63", + "ty==0.0.64", "types-requests==2.33.0.20260712", "vulture==2.16", "vws-python-mock==2026.2.22.3", From 8498102346bd5d457cf969fc14e54634283299bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 10:33:09 +0000 Subject: [PATCH 645/688] chore(deps-dev): Bump pyproject-fmt from 2.25.4 to 2.26.0 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.25.4 to 2.26.0. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.25.4...pyproject-fmt/2.26.0) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.26.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 8870419d1..9a77c6c47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.20.0", "pylint[spelling]==4.0.6", "pylint-per-file-ignores==3.2.1", - "pyproject-fmt==2.25.4", + "pyproject-fmt==2.26.0", "pyrefly==1.1.1", "pyright==1.1.411", "pyroma==5.0.1", From 0738dc2084b4c53e03e18d7922d2c7a33e1388da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:32:50 +0000 Subject: [PATCH 646/688] chore(deps-dev): Bump ty from 0.0.64 to 0.0.65 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.64 to 0.0.65. - [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.64...0.0.65) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.65 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 8054c2d35..b1825aeba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "torch>=2.5.1", "torchvision>=0.20.1", "towncrier==25.8.0", - "ty==0.0.64", + "ty==0.0.65", "types-requests==2.33.0.20260712", "vulture==2.16", "vws-python-mock==2026.2.22.3", From cc3b349fbc060413ee1fb8a9270394345b6482e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 10:33:19 +0000 Subject: [PATCH 647/688] chore(deps-dev): Bump ruff from 0.16.0 to 0.16.1 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.16.0 to 0.16.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.16.0...0.16.1) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.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 b1825aeba..35b19ff6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "ruff==0.16.0", + "ruff==0.16.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 def8cc92a1954e417dbf1a753bf14e8fa37a66c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 10:33:00 +0000 Subject: [PATCH 648/688] chore(deps-dev): Bump pyrefly from 1.1.1 to 1.2.0 Bumps [pyrefly](https://github.com/facebook/pyrefly) from 1.1.1 to 1.2.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/1.1.1...1.2.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 1.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 35b19ff6c..99e3bb1bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.dev = [ "pylint[spelling]==4.0.6", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.26.0", - "pyrefly==1.1.1", + "pyrefly==1.2.0", "pyright==1.1.411", "pyroma==5.0.1", "pytest==9.1.1", From 8a73d4b9a4b6ea0f666d34122ca0354a2cc552b2 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 4 Aug 2026 11:58:41 +0100 Subject: [PATCH 649/688] Use VWS Python Mock 2026.8.4 --- .github/workflows/ci.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/publish-site.yml | 2 +- README.rst | 2 +- newsfragments/822.change.rst | 1 + pyproject.toml | 5 +- src/vws/exceptions/vws_exceptions.py | 12 ++--- tests/test_async_vws_exceptions.py | 75 ++++++++++++++++++++++++++++ tests/test_vws_exceptions.py | 66 ++++++++++++++++++++++-- 9 files changed, 148 insertions(+), 19 deletions(-) create mode 100644 newsfragments/822.change.rst diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a89a87c1..21f12ba8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: build: strategy: matrix: - python-version: ['3.13', '3.14'] + python-version: ['3.14'] platform: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.platform }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 091929e2a..3a0e1e1d9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,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 8f5a347a4..fceb50805 100644 --- a/.github/workflows/publish-site.yml +++ b/.github/workflows/publish-site.yml @@ -22,7 +22,7 @@ jobs: with: documentation_path: docs/source pyproject_extras: dev - python_version: '3.13' + python_version: '3.14' sphinx_build_options: -W cache: true publish: ${{ github.ref_name == 'main' }} diff --git a/README.rst b/README.rst index dea6bb93d..b11993035 100644 --- a/README.rst +++ b/README.rst @@ -73,4 +73,4 @@ See the `full documentation `__. :target: https://github.com/VWS-Python/vws-python/actions .. |PyPI| image:: https://badge.fury.io/py/VWS-Python.svg :target: https://badge.fury.io/py/VWS-Python -.. |minimum-python-version| replace:: 3.13 +.. |minimum-python-version| replace:: 3.14 diff --git a/newsfragments/822.change.rst b/newsfragments/822.change.rst new file mode 100644 index 000000000..18195db79 --- /dev/null +++ b/newsfragments/822.change.rst @@ -0,0 +1 @@ +Drop Python 3.13 support, update VWS Python Mock, and test quota and project-state error responses against the mock. diff --git a/pyproject.toml b/pyproject.toml index 99e3bb1bd..8bdb08085 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,14 +18,13 @@ 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", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", ] dynamic = [ @@ -90,7 +89,7 @@ optional-dependencies.dev = [ "ty==0.0.65", "types-requests==2.33.0.20260712", "vulture==2.16", - "vws-python-mock==2026.2.22.3", + "vws-python-mock==2026.8.4", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.28.0", diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index 3e7e12265..3749b5619 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -65,9 +65,8 @@ class AuthenticationFailureError(VWSError): """ -# See https://github.com/VWS-Python/vws-python/issues/822. @beartype -class RequestQuotaReachedError(VWSError): # pragma: no cover +class RequestQuotaReachedError(VWSError): """Exception raised when Vuforia returns a response with a result code 'RequestQuotaReached'. """ @@ -93,25 +92,22 @@ class DateRangeError(VWSError): # pragma: no cover """ -# This is not simulated by the mock. @beartype -class TargetQuotaReachedError(VWSError): # pragma: no cover +class TargetQuotaReachedError(VWSError): """Exception raised when Vuforia returns a response with a result code 'TargetQuotaReached'. """ -# This is not simulated by the mock. @beartype -class ProjectSuspendedError(VWSError): # pragma: no cover +class ProjectSuspendedError(VWSError): """Exception raised when Vuforia returns a response with a result code 'ProjectSuspended'. """ -# This is not simulated by the mock. @beartype -class ProjectHasNoAPIAccessError(VWSError): # pragma: no cover +class ProjectHasNoAPIAccessError(VWSError): """Exception raised when Vuforia returns a response with a result code 'ProjectHasNoAPIAccess'. """ diff --git a/tests/test_async_vws_exceptions.py b/tests/test_async_vws_exceptions.py index cd35854f1..daa57423a 100644 --- a/tests/test_async_vws_exceptions.py +++ b/tests/test_async_vws_exceptions.py @@ -10,6 +10,7 @@ from mock_vws.states import States from vws import AsyncVuMarkService, AsyncVWS +from vws.exceptions.base_exceptions import VWSError # noqa: TC001 from vws.exceptions.custom_exceptions import ( ServerError, ) @@ -20,8 +21,12 @@ ImageTooLargeError, InvalidInstanceIdError, MetadataTooLargeError, + ProjectHasNoAPIAccessError, ProjectInactiveError, + ProjectSuspendedError, + RequestQuotaReachedError, TargetNameExistError, + TargetQuotaReachedError, TargetStatusProcessingError, UnknownTargetError, ) @@ -94,6 +99,76 @@ async def test_add_bad_name( assert exc.value.response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR +@pytest.mark.asyncio +async def test_request_quota_reached() -> None: + """A ``RequestQuotaReached`` exception is raised at the quota.""" + database = CloudDatabase(request_quota=0) + with MockVWS() as mock: + mock.add_cloud_database(cloud_database=database) + async_vws_client = AsyncVWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + + with pytest.raises(expected_exception=RequestQuotaReachedError) as exc: + await async_vws_client.list_targets() + + assert exc.value.response.status_code == HTTPStatus.FORBIDDEN + + +@pytest.mark.asyncio +async def test_target_quota_reached( + high_quality_image: io.BytesIO, +) -> None: + """A ``TargetQuotaReached`` exception is raised at the quota.""" + database = CloudDatabase(target_quota=0) + with MockVWS() as mock: + mock.add_cloud_database(cloud_database=database) + async_vws_client = AsyncVWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + + with pytest.raises(expected_exception=TargetQuotaReachedError) as exc: + await async_vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + + assert exc.value.response.status_code == HTTPStatus.FORBIDDEN + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + argnames=("state", "expected_exception"), + argvalues=[ + (States.PROJECT_SUSPENDED, ProjectSuspendedError), + (States.PROJECT_HAS_NO_API_ACCESS, ProjectHasNoAPIAccessError), + ], +) +async def test_project_state_error( + *, + state: States, + expected_exception: type[VWSError], +) -> None: + """Configured project states raise their matching exceptions.""" + database = CloudDatabase(state=state) + with MockVWS() as mock: + mock.add_cloud_database(cloud_database=database) + async_vws_client = AsyncVWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + + with pytest.raises(expected_exception=expected_exception) as exc: + await async_vws_client.list_targets() + + assert exc.value.response.status_code == HTTPStatus.FORBIDDEN + + @pytest.mark.asyncio async def test_fail(high_quality_image: io.BytesIO) -> None: """A ``Fail`` exception is raised when the server access key diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 92b33d539..f05436e5c 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -105,10 +105,68 @@ def test_add_bad_name( def test_request_quota_reached() -> None: - """ - See https://github.com/VWS-Python/vws-python/issues/822 for writing - this test. - """ + """A ``RequestQuotaReached`` exception is raised at the quota.""" + database = CloudDatabase(request_quota=0) + with MockVWS() as mock: + mock.add_cloud_database(cloud_database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + + with pytest.raises(expected_exception=RequestQuotaReachedError) as exc: + vws_client.list_targets() + + assert exc.value.response.status_code == HTTPStatus.FORBIDDEN + + +def test_target_quota_reached(high_quality_image: io.BytesIO) -> None: + """A ``TargetQuotaReached`` exception is raised at the quota.""" + database = CloudDatabase(target_quota=0) + with MockVWS() as mock: + mock.add_cloud_database(cloud_database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + + with pytest.raises(expected_exception=TargetQuotaReachedError) as exc: + vws_client.add_target( + name="x", + width=1, + image=high_quality_image, + active_flag=True, + application_metadata=None, + ) + + assert exc.value.response.status_code == HTTPStatus.FORBIDDEN + + +@pytest.mark.parametrize( + argnames=("state", "expected_exception"), + argvalues=[ + (States.PROJECT_SUSPENDED, ProjectSuspendedError), + (States.PROJECT_HAS_NO_API_ACCESS, ProjectHasNoAPIAccessError), + ], +) +def test_project_state_error( + *, + state: States, + expected_exception: type[VWSError], +) -> None: + """Configured project states raise their matching exceptions.""" + database = CloudDatabase(state=state) + with MockVWS() as mock: + mock.add_cloud_database(cloud_database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + ) + + with pytest.raises(expected_exception=expected_exception) as exc: + vws_client.list_targets() + + assert exc.value.response.status_code == HTTPStatus.FORBIDDEN def test_fail(high_quality_image: io.BytesIO) -> None: From 3dcd12a7b5dae00937ef28672e868a3e7cbfbe96 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 11:00:46 +0000 Subject: [PATCH 650/688] [pre-commit.ci lite] apply automatic fixes --- src/vws/exceptions/base_exceptions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vws/exceptions/base_exceptions.py b/src/vws/exceptions/base_exceptions.py index 4b8727e79..ce96ac4e0 100644 --- a/src/vws/exceptions/base_exceptions.py +++ b/src/vws/exceptions/base_exceptions.py @@ -38,7 +38,7 @@ class VWSError(Exception): https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#result-codes. """ - _exceptions_by_result_code: ClassVar[dict[str, type["VWSError"]]] = {} + _exceptions_by_result_code: ClassVar[dict[str, type[VWSError]]] = {} def __init__(self, response: Response) -> None: """ @@ -52,7 +52,7 @@ def __init__(self, response: Response) -> None: def register_exceptions_by_result_code( cls, *, - exceptions_by_result_code: Mapping[str, type["VWSError"]], + exceptions_by_result_code: Mapping[str, type[VWSError]], ) -> None: """Register ``result_code`` to exception mappings.""" cls._exceptions_by_result_code.update(exceptions_by_result_code) @@ -63,7 +63,7 @@ def from_result_code( *, result_code: str, response: Response, - ) -> "VWSError": + ) -> VWSError: """Create the mapped exception for a VWS ``result_code``.""" exception_type = cls._exceptions_by_result_code[result_code] return exception_type(response=response) From aa0222d29bdf865556437221a1c2f6173a3867f5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 4 Aug 2026 12:11:26 +0100 Subject: [PATCH 651/688] Fix Ruff typing import checks --- conftest.py | 4 ++-- src/vws/_async_vws_request.py | 4 ++-- src/vws/_vws_request.py | 4 ++-- src/vws/async_vumark_service.py | 2 +- src/vws/async_vws.py | 2 +- src/vws/exceptions/base_exceptions.py | 4 ++-- src/vws/exceptions/custom_exceptions.py | 2 +- src/vws/transports.py | 6 ++++-- src/vws/vumark_service.py | 2 +- src/vws/vws.py | 2 +- tests/conftest.py | 6 +++--- tests/test_async_cloud_reco_exceptions.py | 2 +- tests/test_async_query.py | 2 +- tests/test_async_vws.py | 2 +- tests/test_cloud_reco_exceptions.py | 2 +- tests/test_query.py | 2 +- tests/test_transports.py | 2 +- tests/test_vws.py | 2 +- 18 files changed, 27 insertions(+), 25 deletions(-) diff --git a/conftest.py b/conftest.py index 8e5442cf0..f82cefc93 100644 --- a/conftest.py +++ b/conftest.py @@ -1,8 +1,8 @@ """Setup for Sybil.""" -import io +import io # noqa: TC003 import uuid -from collections.abc import Generator +from collections.abc import Generator # noqa: TC003 from doctest import ELLIPSIS from pathlib import Path diff --git a/src/vws/_async_vws_request.py b/src/vws/_async_vws_request.py index 4ebd50c86..bd8d91422 100644 --- a/src/vws/_async_vws_request.py +++ b/src/vws/_async_vws_request.py @@ -5,8 +5,8 @@ from beartype import BeartypeConf, beartype from vws_auth_tools import authorization_header, rfc_1123_date -from vws.response import Response -from vws.transports import AsyncTransport +from vws.response import Response # noqa: TC001 +from vws.transports import AsyncTransport # noqa: TC001 @beartype(conf=BeartypeConf(is_pep484_tower=True)) diff --git a/src/vws/_vws_request.py b/src/vws/_vws_request.py index 8e08cf863..3153dce0c 100644 --- a/src/vws/_vws_request.py +++ b/src/vws/_vws_request.py @@ -5,8 +5,8 @@ from beartype import BeartypeConf, beartype from vws_auth_tools import authorization_header, rfc_1123_date -from vws.response import Response -from vws.transports import Transport +from vws.response import Response # noqa: TC001 +from vws.transports import Transport # noqa: TC001 @beartype(conf=BeartypeConf(is_pep484_tower=True)) diff --git a/src/vws/async_vumark_service.py b/src/vws/async_vumark_service.py index 24191d7c6..9a050afdd 100644 --- a/src/vws/async_vumark_service.py +++ b/src/vws/async_vumark_service.py @@ -11,7 +11,7 @@ from vws.exceptions.custom_exceptions import ServerError from vws.exceptions.vws_exceptions import TooManyRequestsError from vws.transports import AsyncHTTPXTransport, AsyncTransport -from vws.vumark_accept import VuMarkAccept +from vws.vumark_accept import VuMarkAccept # noqa: TC001 @beartype(conf=BeartypeConf(is_pep484_tower=True)) diff --git a/src/vws/async_vws.py b/src/vws/async_vws.py index 5ec523565..aad205768 100644 --- a/src/vws/async_vws.py +++ b/src/vws/async_vws.py @@ -23,7 +23,7 @@ TargetStatuses, TargetSummaryReport, ) -from vws.response import Response +from vws.response import Response # noqa: TC001 from vws.transports import AsyncHTTPXTransport, AsyncTransport diff --git a/src/vws/exceptions/base_exceptions.py b/src/vws/exceptions/base_exceptions.py index ce96ac4e0..616e0682f 100644 --- a/src/vws/exceptions/base_exceptions.py +++ b/src/vws/exceptions/base_exceptions.py @@ -4,12 +4,12 @@ Cloud Recognition Web API. """ -from collections.abc import Mapping +from collections.abc import Mapping # noqa: TC003 from typing import ClassVar from beartype import beartype -from vws.response import Response +from vws.response import Response # noqa: TC001 @beartype diff --git a/src/vws/exceptions/custom_exceptions.py b/src/vws/exceptions/custom_exceptions.py index cf3902264..c53f5db29 100644 --- a/src/vws/exceptions/custom_exceptions.py +++ b/src/vws/exceptions/custom_exceptions.py @@ -6,7 +6,7 @@ from beartype import beartype -from vws.response import Response +from vws.response import Response # noqa: TC001 @beartype diff --git a/src/vws/transports.py b/src/vws/transports.py index 74fadb9fe..e699fc718 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -1,7 +1,6 @@ """HTTP transport implementations for VWS clients.""" -from collections.abc import Awaitable -from typing import Protocol, Self, runtime_checkable +from typing import TYPE_CHECKING, Protocol, Self, runtime_checkable import httpx import requests @@ -9,6 +8,9 @@ from vws.response import Response +if TYPE_CHECKING: + from collections.abc import Awaitable + @runtime_checkable class Transport(Protocol): diff --git a/src/vws/vumark_service.py b/src/vws/vumark_service.py index a11eb5fc5..0a5eb98a1 100644 --- a/src/vws/vumark_service.py +++ b/src/vws/vumark_service.py @@ -10,7 +10,7 @@ from vws.exceptions.custom_exceptions import ServerError from vws.exceptions.vws_exceptions import TooManyRequestsError from vws.transports import RequestsTransport, Transport -from vws.vumark_accept import VuMarkAccept +from vws.vumark_accept import VuMarkAccept # noqa: TC001 @beartype(conf=BeartypeConf(is_pep484_tower=True)) diff --git a/src/vws/vws.py b/src/vws/vws.py index b5122ae5f..fc54c30fd 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -22,7 +22,7 @@ TargetStatuses, TargetSummaryReport, ) -from vws.response import Response +from vws.response import Response # noqa: TC001 from vws.transports import RequestsTransport, Transport diff --git a/tests/conftest.py b/tests/conftest.py index 820908ba9..15ef17dab 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,8 @@ """Configuration, plugins and fixtures for `pytest`.""" -import io -from collections.abc import AsyncGenerator, Generator -from pathlib import Path +import io # noqa: TC003 +from collections.abc import AsyncGenerator, Generator # noqa: TC003 +from pathlib import Path # noqa: TC003 from typing import BinaryIO, Literal import pytest diff --git a/tests/test_async_cloud_reco_exceptions.py b/tests/test_async_cloud_reco_exceptions.py index cbe2b22cc..f425be299 100644 --- a/tests/test_async_cloud_reco_exceptions.py +++ b/tests/test_async_cloud_reco_exceptions.py @@ -2,7 +2,7 @@ AsyncCloudRecoService. """ -import io +import io # noqa: TC003 import uuid from http import HTTPStatus diff --git a/tests/test_async_query.py b/tests/test_async_query.py index 2de1ce833..2026d491a 100644 --- a/tests/test_async_query.py +++ b/tests/test_async_query.py @@ -1,6 +1,6 @@ """Tests for the ``AsyncCloudRecoService`` querying functionality.""" -import io +import io # noqa: TC003 import uuid from typing import BinaryIO diff --git a/tests/test_async_vws.py b/tests/test_async_vws.py index c08cf78a5..f59c5f2e4 100644 --- a/tests/test_async_vws.py +++ b/tests/test_async_vws.py @@ -1,7 +1,7 @@ """Tests for async helper functions for managing a Vuforia database.""" import base64 -import io +import io # noqa: TC003 import uuid from typing import BinaryIO diff --git a/tests/test_cloud_reco_exceptions.py b/tests/test_cloud_reco_exceptions.py index 43a263d40..cb50a9db5 100644 --- a/tests/test_cloud_reco_exceptions.py +++ b/tests/test_cloud_reco_exceptions.py @@ -1,6 +1,6 @@ """Tests for exceptions raised when using the CloudRecoService.""" -import io +import io # noqa: TC003 import uuid from http import HTTPStatus diff --git a/tests/test_query.py b/tests/test_query.py index 66d596118..dac206b25 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,7 +1,7 @@ """Tests for the ``CloudRecoService`` querying functionality.""" import datetime -import io +import io # noqa: TC003 import uuid from typing import BinaryIO diff --git a/tests/test_transports.py b/tests/test_transports.py index f208339d4..c039ea7a1 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -1,6 +1,6 @@ """Tests for HTTP transport implementations.""" -import io +import io # noqa: TC003 import uuid from http import HTTPStatus diff --git a/tests/test_vws.py b/tests/test_vws.py index 29bd3aa3b..dff029c2f 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -2,7 +2,7 @@ import base64 import datetime -import io +import io # noqa: TC003 import secrets import uuid from typing import BinaryIO From bf4cdd947e6529aee89dac537c17eaa1078f7d70 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 4 Aug 2026 14:35:19 +0100 Subject: [PATCH 652/688] Handle non-JSON Cloud Query errors --- newsfragments/3093.change.rst | 1 + pyproject.toml | 19 +-------- src/vws/async_query.py | 25 +++++++++-- src/vws/query.py | 23 +++++++++- tests/test_async_cloud_reco_exceptions.py | 52 ++++++++++++++++++++++- tests/test_cloud_reco_exceptions.py | 50 +++++++++++++++++++++- 6 files changed, 144 insertions(+), 26 deletions(-) create mode 100644 newsfragments/3093.change.rst diff --git a/newsfragments/3093.change.rst b/newsfragments/3093.change.rst new file mode 100644 index 000000000..7a603b777 --- /dev/null +++ b/newsfragments/3093.change.rst @@ -0,0 +1 @@ +Raise a response-carrying ``CloudRecoError`` when Cloud Query returns a documented empty or non-JSON 4xx response instead of leaking ``JSONDecodeError``. diff --git a/pyproject.toml b/pyproject.toml index 8bdb08085..eabcc8401 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,16 +80,11 @@ optional-dependencies.dev = [ "sphinxcontrib-towncrier==0.5.0a0", "strict-kwargs==2026.7.24", "sybil==10.1.0", - # Listed explicitly (despite being transitive via vws-python-mock) so that - # [tool.uv.sources] can redirect to the CPU-only PyTorch index. - # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation - "torch>=2.5.1", - "torchvision>=0.20.1", "towncrier==25.8.0", "ty==0.0.65", "types-requests==2.33.0.20260712", "vulture==2.16", - "vws-python-mock==2026.8.4", + "vws-python-mock==2026.8.4.1", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.28.0", @@ -118,11 +113,6 @@ zip-safe = false # Code to match this is in ``conf.py``. 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] line-length = 79 lint.select = [ @@ -317,13 +307,6 @@ ignore = [ ] [tool.deptry] -# torch and torchvision are listed explicitly in dev deps to allow -# [tool.uv.sources] to redirect them to the CPU-only PyTorch index, -# but they are not directly imported in the vws-python source code. -per_rule_ignores.DEP002 = [ - "torch", - "torchvision", -] optional_dependencies_dev_groups = [ "dev", "release", diff --git a/src/vws/async_query.py b/src/vws/async_query.py index 88536144f..9e5bd782a 100644 --- a/src/vws/async_query.py +++ b/src/vws/async_query.py @@ -12,6 +12,7 @@ from vws._image_utils import ImageType as _ImageType from vws._image_utils import get_image_data as _get_image_data +from vws.exceptions.base_exceptions import CloudRecoError from vws.exceptions.cloud_reco_exceptions import ( AuthenticationFailureError, BadImageError, @@ -119,6 +120,8 @@ async def query( given image is too large. ~vws.exceptions.custom_exceptions.ServerError: There is an error with Vuforia's servers. + ~vws.exceptions.base_exceptions.CloudRecoError: Vuforia returned + a client error without a recognized JSON body. Returns: An ordered list of target details of matching @@ -186,7 +189,23 @@ async def query( ): # pragma: no cover raise ServerError(response=response) - result_code = json.loads(s=response.text)["result_code"] + content_type = { + key.lower(): value for key, value in response.headers.items() + }.get("content-type", "") + if ( + response.status_code >= HTTPStatus.BAD_REQUEST + and not content_type.lower().startswith("application/json") + ): + raise CloudRecoError(response=response) + + try: + response_body = json.loads(s=response.text) + except json.JSONDecodeError as exc: + if response.status_code >= HTTPStatus.BAD_REQUEST: + raise CloudRecoError(response=response) from exc + raise + + result_code = response_body["result_code"] if result_code != "Success": exception = { "AuthenticationFailure": (AuthenticationFailureError), @@ -196,9 +215,7 @@ async def query( }[result_code] raise exception(response=response) - result_list = list( - json.loads(s=response.text)["results"], - ) + result_list = list(response_body["results"]) return [ QueryResult.from_response_dict(response_dict=item) for item in result_list diff --git a/src/vws/query.py b/src/vws/query.py index 3d9e19d7f..7c5fce3d5 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -10,6 +10,7 @@ from vws._image_utils import ImageType as _ImageType from vws._image_utils import get_image_data as _get_image_data +from vws.exceptions.base_exceptions import CloudRecoError from vws.exceptions.cloud_reco_exceptions import ( AuthenticationFailureError, BadImageError, @@ -101,6 +102,8 @@ def query( given image is too large. ~vws.exceptions.custom_exceptions.ServerError: There is an error with Vuforia's servers. + ~vws.exceptions.base_exceptions.CloudRecoError: Vuforia returned + a client error without a recognized JSON body. Returns: An ordered list of target details of matching targets. @@ -156,7 +159,23 @@ def query( ): # pragma: no cover raise ServerError(response=response) - result_code = json.loads(s=response.text)["result_code"] + content_type = { + key.lower(): value for key, value in response.headers.items() + }.get("content-type", "") + if ( + response.status_code >= HTTPStatus.BAD_REQUEST + and not content_type.lower().startswith("application/json") + ): + raise CloudRecoError(response=response) + + try: + response_body = json.loads(s=response.text) + except json.JSONDecodeError as exc: + if response.status_code >= HTTPStatus.BAD_REQUEST: + raise CloudRecoError(response=response) from exc + raise + + result_code = response_body["result_code"] if result_code != "Success": exception = { "AuthenticationFailure": AuthenticationFailureError, @@ -166,7 +185,7 @@ def query( }[result_code] raise exception(response=response) - result_list = list(json.loads(s=response.text)["results"]) + result_list = list(response_body["results"]) return [ QueryResult.from_response_dict(response_dict=item) for item in result_list diff --git a/tests/test_async_cloud_reco_exceptions.py b/tests/test_async_cloud_reco_exceptions.py index f425be299..d3f443d12 100644 --- a/tests/test_async_cloud_reco_exceptions.py +++ b/tests/test_async_cloud_reco_exceptions.py @@ -7,11 +7,12 @@ from http import HTTPStatus import pytest -from mock_vws import MockVWS +from mock_vws import CloudQueryFailureResponse, MockVWS from mock_vws.database import CloudDatabase from mock_vws.states import States from vws import AsyncCloudRecoService +from vws.exceptions.base_exceptions import CloudRecoError from vws.exceptions.cloud_reco_exceptions import ( AuthenticationFailureError, InactiveProjectError, @@ -118,3 +119,52 @@ async def test_inactive_project( response = exc.value.response assert response.status_code == HTTPStatus.FORBIDDEN assert response.tell_position != 0 + + +@pytest.mark.parametrize( + argnames=("body", "headers"), + argvalues=[ + ("", {"X-Query-Failure": "empty"}), + ( + "Arbitrary upstream failure", + { + "Content-Type": "application/json", + "X-Query-Failure": "text", + }, + ), + ], + ids=["empty", "arbitrary-text"], +) +@pytest.mark.asyncio +async def test_non_json_client_error( + *, + high_quality_image: io.BytesIO, + body: str, + headers: dict[str, str], +) -> None: + """Non-JSON 4xx responses raise a response-carrying error.""" + database = CloudDatabase() + failure_response = CloudQueryFailureResponse( + status_code=HTTPStatus.BAD_REQUEST, + headers=headers, + body=body, + ) + cloud_reco_client = AsyncCloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + ) + + with MockVWS(cloud_query_failure_response=failure_response) as mock: + mock.add_cloud_database(cloud_database=database) + with pytest.raises(expected_exception=CloudRecoError) as exc: + await cloud_reco_client.query(image=high_quality_image) + + response = exc.value.response + assert response.status_code == HTTPStatus.BAD_REQUEST + assert response.text == body + assert response.content == body.encode() + response_headers = { + key.lower(): value for key, value in response.headers.items() + } + assert response_headers["x-query-failure"] == headers["X-Query-Failure"] + assert response.request_body diff --git a/tests/test_cloud_reco_exceptions.py b/tests/test_cloud_reco_exceptions.py index cb50a9db5..562247757 100644 --- a/tests/test_cloud_reco_exceptions.py +++ b/tests/test_cloud_reco_exceptions.py @@ -5,7 +5,7 @@ from http import HTTPStatus import pytest -from mock_vws import MockVWS +from mock_vws import CloudQueryFailureResponse, MockVWS from mock_vws.database import CloudDatabase from mock_vws.states import States @@ -126,3 +126,51 @@ def test_inactive_project( # We need one test which checks tell position # and so we choose this one almost at random. assert response.tell_position != 0 + + +@pytest.mark.parametrize( + argnames=("body", "headers"), + argvalues=[ + ("", {"X-Query-Failure": "empty"}), + ( + "Arbitrary upstream failure", + { + "Content-Type": "application/json", + "X-Query-Failure": "text", + }, + ), + ], + ids=["empty", "arbitrary-text"], +) +def test_non_json_client_error( + *, + high_quality_image: io.BytesIO, + body: str, + headers: dict[str, str], +) -> None: + """Non-JSON 4xx responses raise a response-carrying error.""" + database = CloudDatabase() + failure_response = CloudQueryFailureResponse( + status_code=HTTPStatus.BAD_REQUEST, + headers=headers, + body=body, + ) + cloud_reco_client = CloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + ) + + with MockVWS(cloud_query_failure_response=failure_response) as mock: + mock.add_cloud_database(cloud_database=database) + with pytest.raises(expected_exception=CloudRecoError) as exc: + cloud_reco_client.query(image=high_quality_image) + + response = exc.value.response + assert response.status_code == HTTPStatus.BAD_REQUEST + assert response.text == body + assert response.content == body.encode() + response_headers = { + key.lower(): value for key, value in response.headers.items() + } + assert response_headers["x-query-failure"] == headers["X-Query-Failure"] + assert response.request_body From e16688846f19309c16dd21c69e03a26579a560d6 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 4 Aug 2026 16:18:17 +0100 Subject: [PATCH 653/688] Cover malformed successful query responses --- src/vws/async_query.py | 2 ++ src/vws/query.py | 2 ++ tests/test_async_cloud_reco_exceptions.py | 24 +++++++++++++++++++++++ tests/test_cloud_reco_exceptions.py | 23 ++++++++++++++++++++++ 4 files changed, 51 insertions(+) diff --git a/src/vws/async_query.py b/src/vws/async_query.py index 9e5bd782a..ec0704ef4 100644 --- a/src/vws/async_query.py +++ b/src/vws/async_query.py @@ -122,6 +122,8 @@ async def query( error with Vuforia's servers. ~vws.exceptions.base_exceptions.CloudRecoError: Vuforia returned a client error without a recognized JSON body. + json.JSONDecodeError: Vuforia returned a successful response with + an invalid JSON body. Returns: An ordered list of target details of matching diff --git a/src/vws/query.py b/src/vws/query.py index 7c5fce3d5..3f69261ef 100644 --- a/src/vws/query.py +++ b/src/vws/query.py @@ -104,6 +104,8 @@ def query( error with Vuforia's servers. ~vws.exceptions.base_exceptions.CloudRecoError: Vuforia returned a client error without a recognized JSON body. + json.JSONDecodeError: Vuforia returned a successful response with + an invalid JSON body. Returns: An ordered list of target details of matching targets. diff --git a/tests/test_async_cloud_reco_exceptions.py b/tests/test_async_cloud_reco_exceptions.py index d3f443d12..a9c1395a5 100644 --- a/tests/test_async_cloud_reco_exceptions.py +++ b/tests/test_async_cloud_reco_exceptions.py @@ -3,6 +3,7 @@ """ import io # noqa: TC003 +import json import uuid from http import HTTPStatus @@ -168,3 +169,26 @@ async def test_non_json_client_error( } assert response_headers["x-query-failure"] == headers["X-Query-Failure"] assert response.request_body + + +@pytest.mark.asyncio +async def test_non_json_success_response( + *, + high_quality_image: io.BytesIO, +) -> None: + """Malformed successful responses retain the JSON parsing error.""" + database = CloudDatabase() + failure_response = CloudQueryFailureResponse( + status_code=HTTPStatus.OK, + headers={"Content-Type": "application/json"}, + body="Not JSON", + ) + cloud_reco_client = AsyncCloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + ) + + with MockVWS(cloud_query_failure_response=failure_response) as mock: + mock.add_cloud_database(cloud_database=database) + with pytest.raises(expected_exception=json.JSONDecodeError): + await cloud_reco_client.query(image=high_quality_image) diff --git a/tests/test_cloud_reco_exceptions.py b/tests/test_cloud_reco_exceptions.py index 562247757..29632000c 100644 --- a/tests/test_cloud_reco_exceptions.py +++ b/tests/test_cloud_reco_exceptions.py @@ -1,6 +1,7 @@ """Tests for exceptions raised when using the CloudRecoService.""" import io # noqa: TC003 +import json import uuid from http import HTTPStatus @@ -174,3 +175,25 @@ def test_non_json_client_error( } assert response_headers["x-query-failure"] == headers["X-Query-Failure"] assert response.request_body + + +def test_non_json_success_response( + *, + high_quality_image: io.BytesIO, +) -> None: + """Malformed successful responses retain the JSON parsing error.""" + database = CloudDatabase() + failure_response = CloudQueryFailureResponse( + status_code=HTTPStatus.OK, + headers={"Content-Type": "application/json"}, + body="Not JSON", + ) + cloud_reco_client = CloudRecoService( + client_access_key=database.client_access_key, + client_secret_key=database.client_secret_key, + ) + + with MockVWS(cloud_query_failure_response=failure_response) as mock: + mock.add_cloud_database(cloud_database=database) + with pytest.raises(expected_exception=json.JSONDecodeError): + cloud_reco_client.query(image=high_quality_image) From 58c543130af8fa3f438b609835d9683e3bcff467 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 4 Aug 2026 17:30:57 +0100 Subject: [PATCH 654/688] Handle documented VuMark generation failures --- newsfragments/3090.change.rst | 1 + pyproject.toml | 2 +- spelling_private_dict.txt | 3 ++ src/vws/async_vumark_service.py | 6 ++++ src/vws/exceptions/vws_exceptions.py | 24 +++++++++++++ src/vws/vumark_service.py | 6 ++++ tests/test_async_vws_exceptions.py | 49 +++++++++++++++++++++++++- tests/test_vws_exceptions.py | 52 +++++++++++++++++++++++++++- 8 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 newsfragments/3090.change.rst diff --git a/newsfragments/3090.change.rst b/newsfragments/3090.change.rst new file mode 100644 index 000000000..226d9665f --- /dev/null +++ b/newsfragments/3090.change.rst @@ -0,0 +1 @@ +Add ``QuotaExceededError``, ``LicenseCheckFailedError``, and ``AuthorizationFailedError`` for documented VuMark Generation API result codes. diff --git a/pyproject.toml b/pyproject.toml index eabcc8401..43b3771a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,7 @@ optional-dependencies.dev = [ "ty==0.0.65", "types-requests==2.33.0.20260712", "vulture==2.16", - "vws-python-mock==2026.8.4.1", + "vws-python-mock==2026.8.4.2", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.28.0", diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index 382e8ccd3..45ed889ed 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -1,4 +1,5 @@ AuthenticationFailure +AuthorizationFailed BadImage ConnectionErrorPossiblyImageTooLarge DateRangeError @@ -6,6 +7,7 @@ Falsy ImageTooLarge InactiveProject JSONDecodeError +LicenseCheckFailed MatchProcessing MaxNumResultsOutOfRange MetadataTooLarge @@ -14,6 +16,7 @@ OopsAnErrorOccurredPossiblyBadNameError ProjectHasNoAPIAccess ProjectInactive ProjectSuspended +QuotaExceeded RequestQuotaReached RequestTimeTooSkewed TargetNameExist diff --git a/src/vws/async_vumark_service.py b/src/vws/async_vumark_service.py index 9a050afdd..6575f7716 100644 --- a/src/vws/async_vumark_service.py +++ b/src/vws/async_vumark_service.py @@ -87,6 +87,8 @@ async def generate_vumark_instance( Raises: ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The secret key is not correct. + ~vws.exceptions.vws_exceptions.AuthorizationFailedError: There was + a general authentication problem. ~vws.exceptions.vws_exceptions.FailError: There was an error with the request. For example, the given access key does not match a known database. @@ -96,6 +98,10 @@ async def generate_vumark_instance( instance ID is invalid. For example, it may be empty. ~vws.exceptions.vws_exceptions.InvalidTargetTypeError: The target is not a VuMark template target. + ~vws.exceptions.vws_exceptions.LicenseCheckFailedError: The + license state and/or type does not allow this request. + ~vws.exceptions.vws_exceptions.QuotaExceededError: No more + instances can be created for the associated license. ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is an error with the time sent to Vuforia. ~vws.exceptions.vws_exceptions.TargetStatusNotSuccessError: The diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index 3749b5619..a93dd6004 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -206,9 +206,31 @@ class InvalidTargetTypeError(VWSError): """ +@beartype +class QuotaExceededError(VWSError): + """Exception raised when Vuforia returns a response with a result code + ``QuotaExceeded``. + """ + + +@beartype +class LicenseCheckFailedError(VWSError): + """Exception raised when Vuforia returns a response with a result code + ``LicenseCheckFailed``. + """ + + +@beartype +class AuthorizationFailedError(VWSError): + """Exception raised when Vuforia returns a response with a result code + ``AuthorizationFailed``. + """ + + VWSError.register_exceptions_by_result_code( exceptions_by_result_code={ "AuthenticationFailure": AuthenticationFailureError, + "AuthorizationFailed": AuthorizationFailedError, "BadImage": BadImageError, "BadRequest": BadRequestError, "DateRangeError": DateRangeError, @@ -217,10 +239,12 @@ class InvalidTargetTypeError(VWSError): "InvalidAcceptHeader": InvalidAcceptHeaderError, "InvalidInstanceId": InvalidInstanceIdError, "InvalidTargetType": InvalidTargetTypeError, + "LicenseCheckFailed": LicenseCheckFailedError, "MetadataTooLarge": MetadataTooLargeError, "ProjectHasNoAPIAccess": ProjectHasNoAPIAccessError, "ProjectInactive": ProjectInactiveError, "ProjectSuspended": ProjectSuspendedError, + "QuotaExceeded": QuotaExceededError, "RequestQuotaReached": RequestQuotaReachedError, "RequestTimeTooSkewed": RequestTimeTooSkewedError, "TargetNameExist": TargetNameExistError, diff --git a/src/vws/vumark_service.py b/src/vws/vumark_service.py index 0a5eb98a1..168e8b6f8 100644 --- a/src/vws/vumark_service.py +++ b/src/vws/vumark_service.py @@ -71,6 +71,8 @@ def generate_vumark_instance( Raises: ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The secret key is not correct. + ~vws.exceptions.vws_exceptions.AuthorizationFailedError: There was + a general authentication problem. ~vws.exceptions.vws_exceptions.FailError: There was an error with the request. For example, the given access key does not match a known database. @@ -80,6 +82,10 @@ def generate_vumark_instance( instance ID is invalid. For example, it may be empty. ~vws.exceptions.vws_exceptions.InvalidTargetTypeError: The target is not a VuMark template target. + ~vws.exceptions.vws_exceptions.LicenseCheckFailedError: The + license state and/or type does not allow this request. + ~vws.exceptions.vws_exceptions.QuotaExceededError: No more + instances can be created for the associated license. ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is an error with the time sent to Vuforia. ~vws.exceptions.vws_exceptions.TargetStatusNotSuccessError: The diff --git a/tests/test_async_vws_exceptions.py b/tests/test_async_vws_exceptions.py index daa57423a..cc661c9f4 100644 --- a/tests/test_async_vws_exceptions.py +++ b/tests/test_async_vws_exceptions.py @@ -5,7 +5,7 @@ from http import HTTPStatus import pytest -from mock_vws import MockVWS +from mock_vws import MockVWS, VuMarkGenerationFailure from mock_vws.database import CloudDatabase from mock_vws.states import States @@ -16,14 +16,17 @@ ) from vws.exceptions.vws_exceptions import ( AuthenticationFailureError, + AuthorizationFailedError, BadImageError, FailError, ImageTooLargeError, InvalidInstanceIdError, + LicenseCheckFailedError, MetadataTooLargeError, ProjectHasNoAPIAccessError, ProjectInactiveError, ProjectSuspendedError, + QuotaExceededError, RequestQuotaReachedError, TargetNameExistError, TargetQuotaReachedError, @@ -377,3 +380,47 @@ async def test_invalid_instance_id( ) assert exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + argnames=("failure", "exception_type", "status_code"), + argvalues=[ + ( + VuMarkGenerationFailure.QUOTA_EXCEEDED, + QuotaExceededError, + HTTPStatus.FORBIDDEN, + ), + ( + VuMarkGenerationFailure.LICENSE_CHECK_FAILED, + LicenseCheckFailedError, + HTTPStatus.FORBIDDEN, + ), + ( + VuMarkGenerationFailure.AUTHORIZATION_FAILED, + AuthorizationFailedError, + HTTPStatus.UNAUTHORIZED, + ), + ], +) +async def test_documented_vumark_error_codes( + *, + failure: VuMarkGenerationFailure, + exception_type: type[VWSError], + status_code: HTTPStatus, +) -> None: + """Documented VuMark failures raise matching exceptions.""" + with MockVWS(vumark_generation_failure=failure): + async with AsyncVuMarkService( + server_access_key=uuid.uuid4().hex, + server_secret_key=uuid.uuid4().hex, + ) as vumark_service: + with pytest.raises(expected_exception=exception_type) as exc: + await vumark_service.generate_vumark_instance( + target_id="exampletargetid", + instance_id="example_instance_id", + accept=VuMarkAccept.PNG, + ) + + assert exc.value.response.status_code == status_code + assert failure.value in exc.value.response.text diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index f05436e5c..73ab9f849 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -6,7 +6,7 @@ import pytest from freezegun import freeze_time -from mock_vws import MockVWS +from mock_vws import MockVWS, VuMarkGenerationFailure from mock_vws.database import CloudDatabase from mock_vws.states import States @@ -17,6 +17,7 @@ ) from vws.exceptions.vws_exceptions import ( AuthenticationFailureError, + AuthorizationFailedError, BadImageError, BadRequestError, DateRangeError, @@ -25,10 +26,12 @@ InvalidAcceptHeaderError, InvalidInstanceIdError, InvalidTargetTypeError, + LicenseCheckFailedError, MetadataTooLargeError, ProjectHasNoAPIAccessError, ProjectInactiveError, ProjectSuspendedError, + QuotaExceededError, RequestQuotaReachedError, RequestTimeTooSkewedError, TargetNameExistError, @@ -412,6 +415,7 @@ def test_vwsexception_inheritance() -> None: """VWS-related exceptions should inherit from VWSException.""" subclasses = [ AuthenticationFailureError, + AuthorizationFailedError, BadImageError, BadRequestError, DateRangeError, @@ -420,10 +424,12 @@ def test_vwsexception_inheritance() -> None: InvalidAcceptHeaderError, InvalidInstanceIdError, InvalidTargetTypeError, + LicenseCheckFailedError, MetadataTooLargeError, ProjectInactiveError, ProjectHasNoAPIAccessError, ProjectSuspendedError, + QuotaExceededError, RequestQuotaReachedError, RequestTimeTooSkewedError, TargetNameExistError, @@ -492,6 +498,50 @@ def test_invalid_target_type( assert exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY +@pytest.mark.parametrize( + argnames=("failure", "exception_type", "status_code"), + argvalues=[ + ( + VuMarkGenerationFailure.QUOTA_EXCEEDED, + QuotaExceededError, + HTTPStatus.FORBIDDEN, + ), + ( + VuMarkGenerationFailure.LICENSE_CHECK_FAILED, + LicenseCheckFailedError, + HTTPStatus.FORBIDDEN, + ), + ( + VuMarkGenerationFailure.AUTHORIZATION_FAILED, + AuthorizationFailedError, + HTTPStatus.UNAUTHORIZED, + ), + ], +) +def test_documented_vumark_error_codes( + *, + failure: VuMarkGenerationFailure, + exception_type: type[VWSError], + status_code: HTTPStatus, +) -> None: + """Documented VuMark failures raise matching exceptions.""" + with MockVWS(vumark_generation_failure=failure): + vumark_service = VuMarkService( + server_access_key=uuid.uuid4().hex, + server_secret_key=uuid.uuid4().hex, + ) + + with pytest.raises(expected_exception=exception_type) as exc: + vumark_service.generate_vumark_instance( + target_id="exampletargetid", + instance_id="example_instance_id", + accept=VuMarkAccept.PNG, + ) + + assert exc.value.response.status_code == status_code + assert failure.value in exc.value.response.text + + def test_base_exception( *, vws_client: VWS, From 91a5138fb007ce0cc93e2be3e99478d6e29a95e3 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 5 Aug 2026 11:00:50 +0100 Subject: [PATCH 655/688] Fix async VuMark test coverage --- tests/test_async_vws_exceptions.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/test_async_vws_exceptions.py b/tests/test_async_vws_exceptions.py index cc661c9f4..efc3c05ae 100644 --- a/tests/test_async_vws_exceptions.py +++ b/tests/test_async_vws_exceptions.py @@ -411,16 +411,17 @@ async def test_documented_vumark_error_codes( ) -> None: """Documented VuMark failures raise matching exceptions.""" with MockVWS(vumark_generation_failure=failure): - async with AsyncVuMarkService( + vumark_service = AsyncVuMarkService( server_access_key=uuid.uuid4().hex, server_secret_key=uuid.uuid4().hex, - ) as vumark_service: - with pytest.raises(expected_exception=exception_type) as exc: - await vumark_service.generate_vumark_instance( - target_id="exampletargetid", - instance_id="example_instance_id", - accept=VuMarkAccept.PNG, - ) + ) + with pytest.raises(expected_exception=exception_type) as exc: + await vumark_service.generate_vumark_instance( + target_id="exampletargetid", + instance_id="example_instance_id", + accept=VuMarkAccept.PNG, + ) + await vumark_service.aclose() assert exc.value.response.status_code == status_code assert failure.value in exc.value.response.text From 9b8ab82f5ed2b1fe173783557d36e75ba120243a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:32:48 +0000 Subject: [PATCH 656/688] chore(deps-dev): Bump sphinx-substitution-extensions Bumps [sphinx-substitution-extensions](https://github.com/adamtheturtle/sphinx-substitution-extensions) from 2026.6.17 to 2026.8.2. - [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/2026.06.17...2026.08.02) --- updated-dependencies: - dependency-name: sphinx-substitution-extensions dependency-version: 2026.8.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 43b3771a4..cde5e41f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.dev = [ "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.2", "sphinx-pyproject==0.3.0", - "sphinx-substitution-extensions==2026.6.17", + "sphinx-substitution-extensions==2026.8.2", "sphinxcontrib-spelling==8.0.2", # ``sphinxcontrib-towncrier`` renders unreleased news fragments # into docs/source/unreleased.rst during Sphinx builds. From 47e95957018d08a09180f83d2441e04dd38d52ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:33:07 +0000 Subject: [PATCH 657/688] chore(deps-dev): Bump zizmor from 1.28.0 to 1.29.0 Bumps [zizmor](https://github.com/zizmorcore/zizmor) from 1.28.0 to 1.29.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.28.0...v1.29.0) --- updated-dependencies: - dependency-name: zizmor dependency-version: 1.29.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 43b3771a4..43a565d2a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ optional-dependencies.dev = [ "vws-python-mock==2026.8.4.2", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", - "zizmor==1.28.0", + "zizmor==1.29.0", ] optional-dependencies.release = [ "check-wheel-contents==0.6.3", "towncrier==25.8.0" ] urls.Documentation = "https://vws-python.github.io/vws-python/" From 947fbefa310e84508cf826b1b45758e2d2ef7c52 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 5 Aug 2026 17:11:33 +0100 Subject: [PATCH 658/688] Use prek action with autofix.ci --- .github/dependabot.yml | 5 +++++ .github/workflows/autofix.yml | 37 +++++++++++++++++++++++++++++++ .github/workflows/lint.yml | 14 +++++------- .pre-commit-config.yaml | 41 ----------------------------------- 4 files changed, 48 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/autofix.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 610da79f8..13b3964d1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,3 +12,8 @@ updates: schedule: interval: daily open-pull-requests-limit: 10 + + - package-ecosystem: pre-commit + directory: / + schedule: + interval: daily diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml new file mode 100644 index 000000000..3e5ceda64 --- /dev/null +++ b/.github/workflows/autofix.yml @@ -0,0 +1,37 @@ +--- +name: autofix.ci + +on: + pull_request: + branches: [main] + push: + branches: [main] + +permissions: + contents: read + +jobs: + autofix: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + fetch-tags: true + persist-credentials: false + + - name: Install uv + uses: astral-sh/setup-uv@v9.0.0 + + - name: Run fixers + uses: j178/prek-action@v3.0.0 + with: + prek-version: 0.4.11 + extra-args: >- + --all-files --hook-stage pre-commit --no-fail-fast --verbose + env: + UV_NO_CACHE: '1' + UV_PYTHON: 3.14 + + - uses: autofix-ci/action@v1.3.4 + if: always() diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3a0e1e1d9..a1c124821 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -35,17 +35,15 @@ 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 ${{ matrix.hook-stage }} - --verbose + uses: j178/prek-action@v3.0.0 + with: + prek-version: 0.4.11 + extra-args: >- + --all-files --hook-stage ${{ matrix.hook-stage }} --verbose env: + UV_NO_CACHE: '1' UV_PYTHON: ${{ matrix.python-version }} - - uses: pre-commit-ci/lite-action@v1.1.0 - if: always() - completion-lint: needs: build runs-on: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 863357160..b2441e7b9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,47 +6,6 @@ fail_fast: true # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks -ci: - # We use system Python, with required dependencies specified in pyproject.toml. - # We therefore cannot use those dependencies in pre-commit CI. - skip: - - actionlint - - sphinx-lint - - strict-kwargs-fix - - check-manifest - - deptry - - doc8 - - pydocstringformatter - - docs - - interrogate - - interrogate-docs - - linkcheck - - mypy - - mypy-docs - - pylint - - pyproject-fmt-fix - - pyright - - pyright-docs - - pyright-verifytypes - - ty - - ty-docs - - pyroma - - ruff-check-fix - - ruff-check-fix-docs - - ruff-format-fix - - ruff-format-fix-docs - - shellcheck - - shellcheck-docs - - shfmt - - shfmt-docs - - spelling - - vulture - - vulture-docs - - yamlfix - - zizmor - - pyrefly - - pyrefly-docs - default_install_hook_types: [pre-commit, pre-push] repos: From 0444aa6b5f27fe6379513b9af268d6181cee1399 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 5 Aug 2026 18:34:19 +0100 Subject: [PATCH 659/688] Retrigger checks after retiring pre-commit.ci From 1eb74003b22288756f264dbaf02e665c44ab55bd Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 5 Aug 2026 18:18:37 +0100 Subject: [PATCH 660/688] Add no-defaults linting --- .pre-commit-config.yaml | 6 ++++++ pyproject.toml | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b2441e7b9..662d831d7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -415,3 +415,9 @@ repos: types_or: [markdown, rst] additional_dependencies: - *uv_version + + - repo: https://github.com/adamtheturtle/no-defaults + rev: v1.0.0 + hooks: + - id: no-defaults + stages: [pre-commit] diff --git a/pyproject.toml b/pyproject.toml index 14a1192f5..79716d3a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -151,6 +151,7 @@ lint.per-file-ignores."tests/*.py" = [ lint.unfixable = [ "ERA001", ] +lint.external = [ "NOD" ] lint.flake8-tidy-imports.banned-api."typing.cast".msg = "typing.cast is banned: use explicit type narrowing or a typed variable instead." lint.pydocstyle.convention = "google" @@ -438,3 +439,7 @@ ignore_path = [ [tool.yamlfix] section_whitelines = 1 whitelines = 1 + +[tool.no_defaults] +private_only = true +per_file_enforcement."tests/**" = "all" From 01af8b4f038dd835cd430a47ab6cc2e9ebfdfd14 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 5 Aug 2026 23:14:43 +0100 Subject: [PATCH 661/688] Bump no-defaults to v1.0.1 Picks up the fix for annotated locals in dataclass methods being misreported as fields (adamtheturtle/no-defaults#6). Co-Authored-By: Claude Opus 5 (1M context) --- .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 662d831d7..e2837cfe1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -417,7 +417,7 @@ repos: - *uv_version - repo: https://github.com/adamtheturtle/no-defaults - rev: v1.0.0 + rev: v1.0.1 hooks: - id: no-defaults stages: [pre-commit] From 4c3d1eb67e19e3d92bab2841ac9b0066b8994f63 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 6 Aug 2026 05:31:58 +0100 Subject: [PATCH 662/688] Run no-defaults as a local uv hook Run no-defaults as a local uv hook instead of the upstream pre-commit hook, matching the other locally-defined hooks. The pin moves to the dev extra in pyproject.toml, and the hook joins the pre-commit.ci skip list alongside the other hooks that need project dependencies. Co-Authored-By: Claude Opus 5 (1M context) --- .pre-commit-config.yaml | 16 ++++++++++------ pyproject.toml | 1 + 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e2837cfe1..0cd0ac391 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -269,6 +269,16 @@ repos: stages: [pre-commit] require_serial: true + - id: no-defaults + name: no-defaults + entry: uv run --extra=dev no-defaults + language: python + types_or: [python] + additional_dependencies: + - *uv_version + stages: [pre-commit] + require_serial: true + - id: doc8 name: doc8 entry: uv run --extra=dev -m doc8 @@ -415,9 +425,3 @@ repos: types_or: [markdown, rst] additional_dependencies: - *uv_version - - - repo: https://github.com/adamtheturtle/no-defaults - rev: v1.0.1 - hooks: - - id: no-defaults - stages: [pre-commit] diff --git a/pyproject.toml b/pyproject.toml index 79716d3a3..0410a5095 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==2.3.0", "mypy-strict-kwargs==2026.7.19.1", + "no-defaults==1.0.1", "prek==0.4.11", "pydocstringformatter==1.0.0", "pydocstyle==6.3", From f4216a95931936a72dde8a8c340a5251ff89f74f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 6 Aug 2026 10:52:09 +0100 Subject: [PATCH 663/688] Lint reStructuredText prose with Vale Ban em dashes in reStructuredText using the pinned ClearProse Vale style package. Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 3 +++ .pre-commit-config.yaml | 27 +++++++++++++++++++++++++++ .vale.ini | 12 ++++++++++++ pyproject.toml | 2 ++ 4 files changed, 44 insertions(+) create mode 100644 .vale.ini diff --git a/.gitignore b/.gitignore index 556e31308..1b5882e5e 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,6 @@ secrets.tar src/*/_setuptools_scm_version.txt uv.lock + +# Vale styles downloaded by ``vale sync`` +styles/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0cd0ac391..4171919ae 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -288,6 +288,33 @@ repos: - *uv_version stages: [pre-commit] + # Vale enforces prose style rules, such as banning em dashes, in + # reStructuredText files. + # The rules come from the ``ClearProse`` package pinned in ``.vale.ini``, + # which ``vale sync`` downloads into the (gitignored) ``styles`` + # directory. + # Vale needs ``rst2html`` from Docutils on the ``PATH`` to parse + # reStructuredText. + - id: vale-sync + name: vale sync + entry: uv run --extra=dev vale sync + language: python + pass_filenames: false + types_or: [rst] + additional_dependencies: + - *uv_version + stages: [pre-commit] + + - id: vale + name: vale + entry: uv run --extra=dev vale + language: python + types_or: [rst] + require_serial: true + additional_dependencies: + - *uv_version + stages: [pre-commit] + - id: interrogate name: interrogate entry: uv run --extra=dev -m interrogate diff --git a/.vale.ini b/.vale.ini new file mode 100644 index 000000000..2b57be049 --- /dev/null +++ b/.vale.ini @@ -0,0 +1,12 @@ +StylesPath = styles +MinAlertLevel = error + +Packages = https://github.com/adamtheturtle/vale-style-clear-prose/releases/download/v1.0.1/ClearProse.zip + +[*.rst] +BasedOnStyles = ClearProse + +# Docutils does not know Sphinx roles such as `:ref:`, and renders their +# argument as ordinary prose. Ignoring role syntax keeps target names, which +# are not prose, out of the linter. +TokenIgnores = (:\w+:`[^`]*`) diff --git a/pyproject.toml b/pyproject.toml index 0410a5095..0d6cf82ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,6 +84,7 @@ optional-dependencies.dev = [ "towncrier==25.8.0", "ty==0.0.65", "types-requests==2.33.0.20260712", + "vale==3.13.0.0", "vulture==2.16", "vws-python-mock==2026.8.4.2", "vws-test-fixtures==2023.3.5", @@ -286,6 +287,7 @@ ignore = [ ".git_archival.txt", ".pre-commit-config.yaml", ".prettierrc", + ".vale.ini", ".yamlfmt", "CHANGELOG.rst", "ci", From 5a23cdda16902ee1179f15dc4f0dec37e543d149 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 6 Aug 2026 11:17:37 +0100 Subject: [PATCH 664/688] Use the ClearProse config package ClearProse 1.1.0 ships the ``TokenIgnores`` pattern for Sphinx roles in the package itself, so this repository no longer sets it. Co-Authored-By: Claude Opus 5 (1M context) --- .vale.ini | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.vale.ini b/.vale.ini index 2b57be049..8e3f9f747 100644 --- a/.vale.ini +++ b/.vale.ini @@ -1,12 +1,7 @@ StylesPath = styles MinAlertLevel = error -Packages = https://github.com/adamtheturtle/vale-style-clear-prose/releases/download/v1.0.1/ClearProse.zip +Packages = https://github.com/adamtheturtle/vale-style-clear-prose/releases/download/v1.1.0/ClearProse.zip [*.rst] BasedOnStyles = ClearProse - -# Docutils does not know Sphinx roles such as `:ref:`, and renders their -# argument as ordinary prose. Ignoring role syntax keeps target names, which -# are not prose, out of the linter. -TokenIgnores = (:\w+:`[^`]*`) From 489724bff1f384fc3525b5a04e41afd6ba285e8e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 6 Aug 2026 18:27:30 +0100 Subject: [PATCH 665/688] Sync Vale styles when ``.vale.ini`` changes Gating ``vale sync`` on reStructuredText files alone meant a commit that only bumped the pinned ClearProse package left the gitignored ``styles`` directory on the old rules until the next reStructuredText change. Co-Authored-By: Claude Opus 5 (1M context) --- .pre-commit-config.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4171919ae..13c902150 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -295,12 +295,15 @@ repos: # directory. # Vale needs ``rst2html`` from Docutils on the ``PATH`` to parse # reStructuredText. + # ``vale sync`` also runs when ``.vale.ini`` changes, so a package + # bump takes effect without waiting for the next reStructuredText + # change. - id: vale-sync name: vale sync entry: uv run --extra=dev vale sync language: python pass_filenames: false - types_or: [rst] + files: (\.rst$|^\.vale\.ini$) additional_dependencies: - *uv_version stages: [pre-commit] From 6e698d44473c832fb3081becce84eeee15b815b1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 7 Aug 2026 07:04:05 +0100 Subject: [PATCH 666/688] Lint Markdown prose with Vale Extend the ClearProse em dash ban from reStructuredText to Markdown, so the rule covers all our prose rather than only Sphinx documentation. Co-Authored-By: Claude Opus 5 (1M context) --- .pre-commit-config.yaml | 6 +++--- .vale.ini | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 13c902150..ec38ba1b4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -289,7 +289,7 @@ repos: stages: [pre-commit] # Vale enforces prose style rules, such as banning em dashes, in - # reStructuredText files. + # reStructuredText and Markdown files. # The rules come from the ``ClearProse`` package pinned in ``.vale.ini``, # which ``vale sync`` downloads into the (gitignored) ``styles`` # directory. @@ -303,7 +303,7 @@ repos: entry: uv run --extra=dev vale sync language: python pass_filenames: false - files: (\.rst$|^\.vale\.ini$) + files: (\.(rst|md)$|^\.vale\.ini$) additional_dependencies: - *uv_version stages: [pre-commit] @@ -312,7 +312,7 @@ repos: name: vale entry: uv run --extra=dev vale language: python - types_or: [rst] + types_or: [rst, markdown] require_serial: true additional_dependencies: - *uv_version diff --git a/.vale.ini b/.vale.ini index 8e3f9f747..e111c0462 100644 --- a/.vale.ini +++ b/.vale.ini @@ -3,5 +3,5 @@ MinAlertLevel = error Packages = https://github.com/adamtheturtle/vale-style-clear-prose/releases/download/v1.1.0/ClearProse.zip -[*.rst] +[*.{rst,md}] BasedOnStyles = ClearProse From c747904ec96281aa81e4ea846ba6cf3873a558c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:33:04 +0000 Subject: [PATCH 667/688] chore(deps-dev): Bump prek from 0.4.11 to 0.4.12 Bumps [prek](https://github.com/j178/prek) from 0.4.11 to 0.4.12. - [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.4.11...v0.4.12) --- updated-dependencies: - dependency-name: prek dependency-version: 0.4.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 0d6cf82ba..a1c922b71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "mypy[faster-cache]==2.3.0", "mypy-strict-kwargs==2026.7.19.1", "no-defaults==1.0.1", - "prek==0.4.11", + "prek==0.4.12", "pydocstringformatter==1.0.0", "pydocstyle==6.3", "pygments==2.20.0", From 841524608dc6a617c2aaccd991f32ab967c663ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:33:15 +0000 Subject: [PATCH 668/688] chore(deps-dev): Bump ty from 0.0.65 to 0.0.66 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.65 to 0.0.66. - [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.65...0.0.66) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.66 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 0d6cf82ba..0558d10b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ "strict-kwargs==2026.7.24", "sybil==10.1.0", "towncrier==25.8.0", - "ty==0.0.65", + "ty==0.0.66", "types-requests==2.33.0.20260712", "vale==3.13.0.0", "vulture==2.16", From 708b72674d3bbce86e80fea6d4927e337d1df679 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:33:22 +0000 Subject: [PATCH 669/688] chore(deps-dev): Bump pyproject-fmt from 2.26.0 to 2.27.0 Bumps [pyproject-fmt](https://github.com/tox-dev/toml-fmt) from 2.26.0 to 2.27.0. - [Release notes](https://github.com/tox-dev/toml-fmt/releases) - [Commits](https://github.com/tox-dev/toml-fmt/compare/pyproject-fmt/2.26.0...pyproject-fmt/2.27.0) --- updated-dependencies: - dependency-name: pyproject-fmt dependency-version: 2.27.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 0d6cf82ba..160f004bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ optional-dependencies.dev = [ "pygments==2.20.0", "pylint[spelling]==4.0.6", "pylint-per-file-ignores==3.2.1", - "pyproject-fmt==2.26.0", + "pyproject-fmt==2.27.0", "pyrefly==1.2.0", "pyright==1.1.411", "pyroma==5.0.1", From 19fc78efad96f080a9ac4162dce93506a3a17d1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 10:33:02 +0000 Subject: [PATCH 670/688] chore(deps-dev): Bump ty from 0.0.66 to 0.0.69 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.66 to 0.0.69. - [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.66...0.0.69) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.69 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 783df569f..3b62c5374 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ "strict-kwargs==2026.7.24", "sybil==10.1.0", "towncrier==25.8.0", - "ty==0.0.66", + "ty==0.0.69", "types-requests==2.33.0.20260712", "vale==3.13.0.0", "vulture==2.16", From b210cdb586c9a82db94caf6f1627dd739bb7a03b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 10:33:09 +0000 Subject: [PATCH 671/688] chore(deps-dev): Bump no-defaults from 1.0.1 to 2.0.0 Bumps [no-defaults](https://github.com/adamtheturtle/no-defaults) from 1.0.1 to 2.0.0. - [Release notes](https://github.com/adamtheturtle/no-defaults/releases) - [Changelog](https://github.com/adamtheturtle/no-defaults/blob/main/CHANGELOG.md) - [Commits](https://github.com/adamtheturtle/no-defaults/compare/v1.0.1...v2.0.0) --- updated-dependencies: - dependency-name: no-defaults 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 783df569f..6a854f656 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==2.3.0", "mypy-strict-kwargs==2026.7.19.1", - "no-defaults==1.0.1", + "no-defaults==2.0.0", "prek==0.4.12", "pydocstringformatter==1.0.0", "pydocstyle==6.3", From 528854624aa0d02289cf7ad24d080b8a933cc1a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 10:33:36 +0000 Subject: [PATCH 672/688] chore(deps-dev): Bump sphinx-substitution-extensions Bumps [sphinx-substitution-extensions](https://github.com/adamtheturtle/sphinx-substitution-extensions) from 2026.8.2 to 2026.8.5. - [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/2026.08.02...2026.08.05) --- updated-dependencies: - dependency-name: sphinx-substitution-extensions dependency-version: 2026.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 783df569f..0a3a397bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ optional-dependencies.dev = [ "sphinx-copybutton==0.5.2", "sphinx-lint==1.0.2", "sphinx-pyproject==0.3.0", - "sphinx-substitution-extensions==2026.8.2", + "sphinx-substitution-extensions==2026.8.5", "sphinxcontrib-spelling==8.0.2", # ``sphinxcontrib-towncrier`` renders unreleased news fragments # into docs/source/unreleased.rst during Sphinx builds. From cf25b9100480eb5ba552931d219977de7ef99bbb Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 11 Aug 2026 11:15:47 +0100 Subject: [PATCH 673/688] Switch Vale to the upstream ai-tells package --- .pre-commit-config.yaml | 2 +- .vale.ini | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ec38ba1b4..0bca59790 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -290,7 +290,7 @@ repos: # Vale enforces prose style rules, such as banning em dashes, in # reStructuredText and Markdown files. - # The rules come from the ``ClearProse`` package pinned in ``.vale.ini``, + # The rules come from the ``ai-tells`` package pinned in ``.vale.ini``, # which ``vale sync`` downloads into the (gitignored) ``styles`` # directory. # Vale needs ``rst2html`` from Docutils on the ``PATH`` to parse diff --git a/.vale.ini b/.vale.ini index e111c0462..250e9b2dd 100644 --- a/.vale.ini +++ b/.vale.ini @@ -1,7 +1,15 @@ StylesPath = styles MinAlertLevel = error -Packages = https://github.com/adamtheturtle/vale-style-clear-prose/releases/download/v1.1.0/ClearProse.zip +Packages = https://github.com/tbhb/vale-ai-tells/releases/download/v1.29.0/ai-tells.zip [*.{rst,md}] -BasedOnStyles = ClearProse +BasedOnStyles = ai-tells + +# These rules misclassify established technical, example, or release-note prose +# in this repository. All other ai-tells rules remain enforced. +ai-tells.ColonUsage = NO +ai-tells.FigurativeLands = NO +ai-tells.FormalTransitions = NO +ai-tells.OverusedVocabularyVerbs = NO +ai-tells.VerbTricolon = NO From e7b8b097d061f3d37a37097e503ac576d6cd6ad6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:33:07 +0000 Subject: [PATCH 674/688] chore(deps-dev): Bump no-defaults from 2.0.0 to 2.1.0 Bumps [no-defaults](https://github.com/adamtheturtle/no-defaults) from 2.0.0 to 2.1.0. - [Release notes](https://github.com/adamtheturtle/no-defaults/releases) - [Changelog](https://github.com/adamtheturtle/no-defaults/blob/main/CHANGELOG.md) - [Commits](https://github.com/adamtheturtle/no-defaults/compare/v2.0.0...v2.1.0) --- updated-dependencies: - dependency-name: no-defaults dependency-version: 2.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 b9066196c..cd044416f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ optional-dependencies.dev = [ "interrogate==1.7.0", "mypy[faster-cache]==2.3.0", "mypy-strict-kwargs==2026.7.19.1", - "no-defaults==2.0.0", + "no-defaults==2.1.0", "prek==0.4.12", "pydocstringformatter==1.0.0", "pydocstyle==6.3", From 2de5b0af0c77bfc4518a755ce47edeae95602d1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:33:19 +0000 Subject: [PATCH 675/688] chore(deps-dev): Bump ruff from 0.16.1 to 0.16.2 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.16.1 to 0.16.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.16.1...0.16.2) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.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 b9066196c..054d9f9fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ optional-dependencies.dev = [ "pytest-beartype-tests==2026.4.26", "pytest-cov==7.1.0", "pyyaml==6.0.3", - "ruff==0.16.1", + "ruff==0.16.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 a9a03f9e84ac344980f46f7fa91e7344027ff794 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 12 Aug 2026 08:12:53 +0100 Subject: [PATCH 676/688] Configure mypy to respect gitignore --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index befcd7c64..083f2c0ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -370,6 +370,7 @@ max_supported_python = "3.14" [tool.mypy] files = [ "." ] exclude = [ "build" ] +exclude_gitignore = true follow_untyped_imports = true strict = true plugins = [ From 211a31069a972390d793c7eb867a5cb1b1f5f598 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 10:33:16 +0000 Subject: [PATCH 677/688] chore(deps-dev): Bump prek from 0.4.12 to 0.4.13 Bumps [prek](https://github.com/j178/prek) from 0.4.12 to 0.4.13. - [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.4.12...v0.4.13) --- updated-dependencies: - dependency-name: prek dependency-version: 0.4.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 083f2c0ad..f3180f751 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ optional-dependencies.dev = [ "mypy[faster-cache]==2.3.0", "mypy-strict-kwargs==2026.7.19.1", "no-defaults==2.1.0", - "prek==0.4.12", + "prek==0.4.13", "pydocstringformatter==1.0.0", "pydocstyle==6.3", "pygments==2.20.0", From bf17b130d5c7bd9dcd2aa0c5fb9c27d30d7472f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 10:33:22 +0000 Subject: [PATCH 678/688] chore(deps-dev): Bump pylint from 4.0.6 to 4.0.7 Bumps [pylint](https://github.com/pylint-dev/pylint) from 4.0.6 to 4.0.7. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v4.0.6...v4.0.7) --- updated-dependencies: - dependency-name: pylint dependency-version: 4.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 083f2c0ad..c2d7f1daa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ optional-dependencies.dev = [ "pydocstringformatter==1.0.0", "pydocstyle==6.3", "pygments==2.20.0", - "pylint[spelling]==4.0.6", + "pylint[spelling]==4.0.7", "pylint-per-file-ignores==3.2.1", "pyproject-fmt==2.27.0", "pyrefly==1.2.0", From 964cb5a82f76c8d6f98b2b5c2b2039857c5e693d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:33:11 +0000 Subject: [PATCH 679/688] chore(deps-dev): Bump ty from 0.0.69 to 0.0.70 Bumps [ty](https://github.com/astral-sh/ty) from 0.0.69 to 0.0.70. - [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.69...0.0.70) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.70 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 b8f3e48a8..efcf3a13a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ optional-dependencies.dev = [ "strict-kwargs==2026.7.24", "sybil==10.1.0", "towncrier==25.8.0", - "ty==0.0.69", + "ty==0.0.70", "types-requests==2.33.0.20260712", "vale==3.13.0.0", "vulture==2.16", From 3e7cf019aa2d1b951821247609b49cdd9378a1b5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 14 Aug 2026 12:07:55 +0100 Subject: [PATCH 680/688] Map the ProjectHasNoApiAccess result code Vuforia's result codes table spells this ``ProjectHasNoApiAccess``, while this package mapped only ``ProjectHasNoAPIAccess``, a spelling which came from a hand-written list in ``vws-python-mock`` rather than an observed response. No real response with either casing has been found, so both casings now map to ``ProjectHasNoAPIAccessError`` and the ambiguity is recorded in the code. ``vws-python-mock`` 2026.8.14 changes the mock to the documented spelling, so the pin is bumped to it. Co-Authored-By: Claude Opus 5 (1M context) --- newsfragments/3128.change.rst | 1 + pyproject.toml | 2 +- src/vws/exceptions/vws_exceptions.py | 11 ++++++++++- tests/test_vws_exceptions.py | 29 ++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 newsfragments/3128.change.rst diff --git a/newsfragments/3128.change.rst b/newsfragments/3128.change.rst new file mode 100644 index 000000000..fc07be19c --- /dev/null +++ b/newsfragments/3128.change.rst @@ -0,0 +1 @@ +Map the ``ProjectHasNoApiAccess`` result code, as spelled in Vuforia's result codes table, to ``ProjectHasNoAPIAccessError``. The previously mapped ``ProjectHasNoAPIAccess`` casing still raises the same exception. diff --git a/pyproject.toml b/pyproject.toml index efcf3a13a..7c7d50d69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,7 @@ optional-dependencies.dev = [ "types-requests==2.33.0.20260712", "vale==3.13.0.0", "vulture==2.16", - "vws-python-mock==2026.8.4.2", + "vws-python-mock==2026.8.14", "vws-test-fixtures==2023.3.5", "yamlfix==1.19.1", "zizmor==1.29.0", diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index a93dd6004..5c6be8e12 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -109,7 +109,11 @@ class ProjectSuspendedError(VWSError): @beartype class ProjectHasNoAPIAccessError(VWSError): """Exception raised when Vuforia returns a response with a result code - 'ProjectHasNoAPIAccess'. + 'ProjectHasNoApiAccess'. + + Vuforia's result codes table spells this result code + 'ProjectHasNoApiAccess', but no real response with either casing has + been observed, so 'ProjectHasNoAPIAccess' raises this exception too. """ @@ -241,7 +245,12 @@ class AuthorizationFailedError(VWSError): "InvalidTargetType": InvalidTargetTypeError, "LicenseCheckFailed": LicenseCheckFailedError, "MetadataTooLarge": MetadataTooLargeError, + # Vuforia's result codes table spells this ``ProjectHasNoApiAccess``. + # ``ProjectHasNoAPIAccess`` was mapped first, from a hand-written list + # rather than an observed response. No real response with either + # casing has been observed, so both are mapped. "ProjectHasNoAPIAccess": ProjectHasNoAPIAccessError, + "ProjectHasNoApiAccess": ProjectHasNoAPIAccessError, "ProjectInactive": ProjectInactiveError, "ProjectSuspended": ProjectSuspendedError, "QuotaExceeded": QuotaExceededError, diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 73ab9f849..8fd894931 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -583,6 +583,35 @@ def test_vwserror_from_result_code() -> None: assert exception.response is response +@pytest.mark.parametrize( + argnames="result_code", + argvalues=["ProjectHasNoApiAccess", "ProjectHasNoAPIAccess"], +) +def test_project_has_no_api_access_casings(result_code: str) -> None: + """Both casings of the ``ProjectHasNoApiAccess`` result code map to + ``ProjectHasNoAPIAccessError``. + + Vuforia's result codes table uses ``ProjectHasNoApiAccess``, but no + real response with either casing has been observed. + """ + response = Response( + text=f'{{"result_code":"{result_code}"}}', + url="https://example.com/targets", + status_code=HTTPStatus.FORBIDDEN, + headers={}, + request_body=None, + tell_position=0, + content=b"", + ) + + exception = VWSError.from_result_code( + result_code=result_code, + response=response, + ) + + assert isinstance(exception, ProjectHasNoAPIAccessError) + + @pytest.mark.parametrize( argnames=("exception_type", "url"), argvalues=[ From 43680f54fbd0401358547b6c587d7b98e06d71a0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 14 Aug 2026 12:12:42 +0100 Subject: [PATCH 681/688] Add ProjectHasNoApiAccess to the spelling dictionary Co-Authored-By: Claude Opus 5 (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 45ed889ed..36a335a88 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -14,6 +14,7 @@ MetadataTooLarge OopsAnErrorOccurredPossiblyBadName OopsAnErrorOccurredPossiblyBadNameError ProjectHasNoAPIAccess +ProjectHasNoApiAccess ProjectInactive ProjectSuspended QuotaExceeded From 337ee04f571b61122fb808713df707fda832f08d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 14 Aug 2026 12:19:48 +0100 Subject: [PATCH 682/688] Map only the documented ProjectHasNoApiAccess casing Vuforia does not document the ProjectHasNoAPIAccess casing, so drop it from the result code map rather than keeping it as an alias. Co-Authored-By: Claude Opus 5 (1M context) --- newsfragments/3128.change.rst | 2 +- spelling_private_dict.txt | 1 - src/vws/exceptions/vws_exceptions.py | 12 +++--------- tests/test_vws_exceptions.py | 14 ++++---------- 4 files changed, 8 insertions(+), 21 deletions(-) diff --git a/newsfragments/3128.change.rst b/newsfragments/3128.change.rst index fc07be19c..2a0cc5a51 100644 --- a/newsfragments/3128.change.rst +++ b/newsfragments/3128.change.rst @@ -1 +1 @@ -Map the ``ProjectHasNoApiAccess`` result code, as spelled in Vuforia's result codes table, to ``ProjectHasNoAPIAccessError``. The previously mapped ``ProjectHasNoAPIAccess`` casing still raises the same exception. +Map the ``ProjectHasNoApiAccess`` result code, as spelled in Vuforia's result codes table, to ``ProjectHasNoAPIAccessError``. The previously mapped ``ProjectHasNoAPIAccess`` casing, which Vuforia does not document, is no longer mapped. diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index 36a335a88..608572ce0 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -13,7 +13,6 @@ MaxNumResultsOutOfRange MetadataTooLarge OopsAnErrorOccurredPossiblyBadName OopsAnErrorOccurredPossiblyBadNameError -ProjectHasNoAPIAccess ProjectHasNoApiAccess ProjectInactive ProjectSuspended diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index 5c6be8e12..c9f2fea4b 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -110,10 +110,6 @@ class ProjectSuspendedError(VWSError): class ProjectHasNoAPIAccessError(VWSError): """Exception raised when Vuforia returns a response with a result code 'ProjectHasNoApiAccess'. - - Vuforia's result codes table spells this result code - 'ProjectHasNoApiAccess', but no real response with either casing has - been observed, so 'ProjectHasNoAPIAccess' raises this exception too. """ @@ -245,11 +241,9 @@ class AuthorizationFailedError(VWSError): "InvalidTargetType": InvalidTargetTypeError, "LicenseCheckFailed": LicenseCheckFailedError, "MetadataTooLarge": MetadataTooLargeError, - # Vuforia's result codes table spells this ``ProjectHasNoApiAccess``. - # ``ProjectHasNoAPIAccess`` was mapped first, from a hand-written list - # rather than an observed response. No real response with either - # casing has been observed, so both are mapped. - "ProjectHasNoAPIAccess": ProjectHasNoAPIAccessError, + # This casing comes from Vuforia's result codes table. No real + # response with this result code has been observed, with this or any + # other casing. "ProjectHasNoApiAccess": ProjectHasNoAPIAccessError, "ProjectInactive": ProjectInactiveError, "ProjectSuspended": ProjectSuspendedError, diff --git a/tests/test_vws_exceptions.py b/tests/test_vws_exceptions.py index 8fd894931..5afb6639e 100644 --- a/tests/test_vws_exceptions.py +++ b/tests/test_vws_exceptions.py @@ -583,17 +583,11 @@ def test_vwserror_from_result_code() -> None: assert exception.response is response -@pytest.mark.parametrize( - argnames="result_code", - argvalues=["ProjectHasNoApiAccess", "ProjectHasNoAPIAccess"], -) -def test_project_has_no_api_access_casings(result_code: str) -> None: - """Both casings of the ``ProjectHasNoApiAccess`` result code map to - ``ProjectHasNoAPIAccessError``. - - Vuforia's result codes table uses ``ProjectHasNoApiAccess``, but no - real response with either casing has been observed. +def test_project_has_no_api_access_casing() -> None: + """The ``ProjectHasNoApiAccess`` result code, as spelled in Vuforia's + result codes table, maps to ``ProjectHasNoAPIAccessError``. """ + result_code = "ProjectHasNoApiAccess" response = Response( text=f'{{"result_code":"{result_code}"}}', url="https://example.com/targets", From 7bea124cd917ce0b7d03ab12d044d2f073533fe3 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 14 Aug 2026 12:31:46 +0100 Subject: [PATCH 683/688] Drop the result code casing comment Co-Authored-By: Claude Opus 5 (1M context) --- src/vws/exceptions/vws_exceptions.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vws/exceptions/vws_exceptions.py b/src/vws/exceptions/vws_exceptions.py index c9f2fea4b..216677f15 100644 --- a/src/vws/exceptions/vws_exceptions.py +++ b/src/vws/exceptions/vws_exceptions.py @@ -241,9 +241,6 @@ class AuthorizationFailedError(VWSError): "InvalidTargetType": InvalidTargetTypeError, "LicenseCheckFailed": LicenseCheckFailedError, "MetadataTooLarge": MetadataTooLargeError, - # This casing comes from Vuforia's result codes table. No real - # response with this result code has been observed, with this or any - # other casing. "ProjectHasNoApiAccess": ProjectHasNoAPIAccessError, "ProjectInactive": ProjectInactiveError, "ProjectSuspended": ProjectSuspendedError, From b1c34db705d7bb24229766d89bba760aec090ed4 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 14 Aug 2026 13:37:18 +0100 Subject: [PATCH 684/688] Add Database Reco Counts report client support Add ``request_database_reco_counts_report``, ``download_reco_counts_report`` and ``wait_for_reco_counts_report`` to ``VWS`` and ``AsyncVWS``, along with an optional ``database_id`` constructor argument which the report endpoint needs. The download is not signed, is not against ``base_vws_url``, and 404s until the report is generated, so it does not go through ``make_request``. Closes #3133 Co-Authored-By: Claude Opus 5 (1M context) --- conftest.py | 3 + docs/source/index.rst | 49 +++++ newsfragments/3133.change.rst | 2 + spelling_private_dict.txt | 2 + src/vws/_reco_counts.py | 77 ++++++++ src/vws/async_vws.py | 143 +++++++++++++++ src/vws/exceptions/custom_exceptions.py | 55 ++++++ src/vws/reports.py | 71 ++++++++ src/vws/vws.py | 142 +++++++++++++++ tests/conftest.py | 24 +++ tests/test_async_vws.py | 215 ++++++++++++++++++++++ tests/test_vws.py | 233 +++++++++++++++++++++++- 12 files changed, 1015 insertions(+), 1 deletion(-) create mode 100644 newsfragments/3133.change.rst create mode 100644 src/vws/_reco_counts.py diff --git a/conftest.py b/conftest.py index f82cefc93..1a2445a7c 100644 --- a/conftest.py +++ b/conftest.py @@ -46,18 +46,21 @@ def fixture_mock_vws( server_secret_key = uuid.uuid4().hex client_access_key = uuid.uuid4().hex client_secret_key = uuid.uuid4().hex + database_id = uuid.uuid4().hex database = CloudDatabase( server_access_key=server_access_key, server_secret_key=server_secret_key, client_access_key=client_access_key, client_secret_key=client_secret_key, + database_id=database_id, ) monkeypatch.setenv(name="VWS_SERVER_ACCESS_KEY", value=server_access_key) monkeypatch.setenv(name="VWS_SERVER_SECRET_KEY", value=server_secret_key) monkeypatch.setenv(name="VWS_CLIENT_ACCESS_KEY", value=client_access_key) monkeypatch.setenv(name="VWS_CLIENT_SECRET_KEY", value=client_secret_key) + monkeypatch.setenv(name="VWS_DATABASE_ID", value=database_id) # We use a low processing time so that tests run quickly. with MockVWS(processing_time_seconds=0.2) as mock: mock.add_cloud_database(cloud_database=database) diff --git a/docs/source/index.rst b/docs/source/index.rst index ae5712825..7b82155c8 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -60,6 +60,55 @@ See the :doc:`api-reference` for full usage details. assert matching_targets[0].target_id == target_id +Recognition counts +------------------ + +Vuforia can generate a report of the number of recognitions of each target in a database in a month. +Only the current month and the previous month can be requested. + +This needs the ID of the database, which is shown in the Vuforia target manager. + +The report is generated in the background, and the URL it is served from expires just under seven days after it is requested. + +.. clear-namespace + +.. code-block:: python + + """Get the number of recognitions of each target this month.""" + + import datetime + import os + + from vws import VWS + + server_access_key = os.environ["VWS_SERVER_ACCESS_KEY"] + server_secret_key = os.environ["VWS_SERVER_SECRET_KEY"] + database_id = os.environ["VWS_DATABASE_ID"] + + vws_client = VWS( + server_access_key=server_access_key, + server_secret_key=server_secret_key, + database_id=database_id, + ) + + now = datetime.datetime.now(tz=datetime.UTC) + this_month = now.strftime(format="%Y-%m") + + report_request = vws_client.request_database_reco_counts_report( + month=this_month, + ) + + report = vws_client.wait_for_reco_counts_report( + presigned_url=report_request.presigned_url, + ) + + reco_counts_by_target_id = { + item.target_id: item.reco_count for item in report.reco_counts + } + + # This database has no targets, so nothing has been recognized. + assert not reco_counts_by_target_id + Testing ------- diff --git a/newsfragments/3133.change.rst b/newsfragments/3133.change.rst new file mode 100644 index 000000000..edab9049a --- /dev/null +++ b/newsfragments/3133.change.rst @@ -0,0 +1,2 @@ +Add support for the Database Reco Counts report. +``VWS`` and ``AsyncVWS`` take an optional ``database_id``, and have new ``request_database_reco_counts_report``, ``download_reco_counts_report`` and ``wait_for_reco_counts_report`` methods. diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index 608572ce0..74e7d3856 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -41,6 +41,7 @@ changelog chunked cmyk connectionerror +csv customizable dataclasses datetime @@ -88,6 +89,7 @@ pyright pytest readme readthedocs +reco recognitions refactoring regex diff --git a/src/vws/_reco_counts.py b/src/vws/_reco_counts.py new file mode 100644 index 000000000..7a95c59f9 --- /dev/null +++ b/src/vws/_reco_counts.py @@ -0,0 +1,77 @@ +"""Internal helpers for the database reco counts report endpoints.""" + +import json +from http import HTTPStatus + +from beartype import BeartypeConf, beartype + +from vws.exceptions.custom_exceptions import ( + DatabaseIdNotSetError, + RecoCountsReportDownloadError, + RecoCountsReportNotReadyError, +) +from vws.reports import RecoCountsReport +from vws.response import Response # noqa: TC001 + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def reco_counts_report_path(*, database_id: str | None) -> str: + """Get the path of the reco counts report endpoint for a database. + + Args: + database_id: The ID of the database to get the path for. + + Returns: + The path of the reco counts report endpoint. + + Raises: + ~vws.exceptions.custom_exceptions.DatabaseIdNotSetError: No + ``database_id`` was given to the client. + """ + if database_id is None: + msg = ( + "A database ID is needed to request a reco counts report. Give " + "``database_id`` when creating the client." + ) + raise DatabaseIdNotSetError(msg) + + return f"/imagetargets/databases/{database_id}/reports/recoCounts" + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def reco_counts_report_body(*, month: str) -> bytes: + """Get the request body for requesting a reco counts report. + + Args: + month: The month to request the report for, in ``YYYY-mm`` form. + + Returns: + The body of the request. + """ + return json.dumps(obj={"month": month}).encode(encoding="utf-8") + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def report_from_download_response(*, response: Response) -> RecoCountsReport: + """Get a reco counts report from a response from a report's URL. + + Args: + response: The response from a report's download URL. + + Returns: + The downloaded report. + + Raises: + ~vws.exceptions.custom_exceptions.RecoCountsReportNotReadyError: + Vuforia has not finished generating the report. + ~vws.exceptions.custom_exceptions.RecoCountsReportDownloadError: The + report could not be downloaded. For example, the report's URL may + have expired. + """ + if response.status_code == HTTPStatus.NOT_FOUND: + raise RecoCountsReportNotReadyError(response=response) + + if response.status_code != HTTPStatus.OK: + raise RecoCountsReportDownloadError(response=response) + + return RecoCountsReport.from_csv(csv_bytes=response.content) diff --git a/src/vws/async_vws.py b/src/vws/async_vws.py index aad205768..bdefa8222 100644 --- a/src/vws/async_vws.py +++ b/src/vws/async_vws.py @@ -3,6 +3,7 @@ import asyncio import base64 import json +import time from http import HTTPMethod, HTTPStatus from typing import Self @@ -11,14 +12,23 @@ from vws._async_vws_request import async_target_api_request from vws._image_utils import ImageType as _ImageType from vws._image_utils import get_image_data as _get_image_data +from vws._reco_counts import ( + reco_counts_report_body, + reco_counts_report_path, + report_from_download_response, +) from vws.exceptions.base_exceptions import VWSError from vws.exceptions.custom_exceptions import ( + RecoCountsReportNotReadyError, + RecoCountsReportTimeoutError, ServerError, TargetProcessingTimeoutError, ) from vws.exceptions.vws_exceptions import TooManyRequestsError from vws.reports import ( DatabaseSummaryReport, + RecoCountsReport, + RecoCountsReportRequest, TargetStatusAndRecord, TargetStatuses, TargetSummaryReport, @@ -37,6 +47,7 @@ def __init__( server_access_key: str, server_secret_key: str, base_vws_url: str = "https://vws.vuforia.com", + database_id: str | None = None, request_timeout_seconds: float | tuple[float, float] = 30.0, transport: AsyncTransport | None = None, ) -> None: @@ -45,6 +56,10 @@ def __init__( server_access_key: A VWS server access key. server_secret_key: A VWS server secret key. base_vws_url: The base URL for the VWS API. + database_id: The ID of the database which the + given keys belong to. This is shown in the + target manager. It is needed only by + :meth:`request_database_reco_counts_report`. request_timeout_seconds: The timeout for each HTTP request. This can be a float to set both the connect and read timeouts, or a @@ -56,6 +71,7 @@ def __init__( self._server_access_key = server_access_key self._server_secret_key = server_secret_key self._base_vws_url = base_vws_url + self._database_id = database_id self._request_timeout_seconds = request_timeout_seconds self._transport = ( transport if transport is not None else AsyncHTTPXTransport() @@ -439,6 +455,133 @@ async def get_database_summary_report( response_dict=response_data, ) + async def request_database_reco_counts_report( + self, + *, + month: str, + ) -> RecoCountsReportRequest: + """Request a per-target recognition count report for the database. + + Vuforia generates the report in the background, so the report is not + available to download immediately. Use + :meth:`wait_for_reco_counts_report` to wait for it. + + Args: + month: The month to get recognition counts for, in ``YYYY-mm`` + form. Vuforia accepts only the current month and the previous + month. + + Returns: + The URL to download the report from, and the transaction ID of + the request. + + Raises: + ~vws.exceptions.custom_exceptions.DatabaseIdNotSetError: No + ``database_id`` was given to the client. + ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The + secret key is not correct, or the client's ``database_id`` is + not the ID of the database which the client's keys belong to. + ~vws.exceptions.vws_exceptions.FailError: There was an error with + the request. For example, the given month is not the current + month or the previous month. + ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is + an error with the time sent to Vuforia. + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. + """ + response = await self.make_request( + method=HTTPMethod.POST, + data=reco_counts_report_body(month=month), + request_path=reco_counts_report_path( + database_id=self._database_id, + ), + expected_result_code="Success", + content_type="application/json", + ) + + response_data = dict(json.loads(s=response.text)) + return RecoCountsReportRequest.from_response_dict( + response_dict=response_data, + ) + + async def download_reco_counts_report( + self, + *, + presigned_url: str, + ) -> RecoCountsReport: + """Download a requested reco counts report. + + The report's URL is not part of the VWS API, so this request is not + authorized with the client's keys. + + Args: + presigned_url: The URL of the report, as given by + :meth:`request_database_reco_counts_report`. + + Returns: + The downloaded report. + + Raises: + ~vws.exceptions.custom_exceptions.RecoCountsReportNotReadyError: + Vuforia has not finished generating the report. + ~vws.exceptions.custom_exceptions.RecoCountsReportDownloadError: + The report could not be downloaded. For example, the report's + URL may have expired. + """ + response = await self._transport( + method=HTTPMethod.GET, + url=presigned_url, + headers={}, + data=b"", + request_timeout=self._request_timeout_seconds, + ) + + return report_from_download_response(response=response) + + async def wait_for_reco_counts_report( + self, + *, + presigned_url: str, + seconds_between_requests: float = 0.2, + timeout_seconds: float = 60 * 5, + ) -> RecoCountsReport: + """Wait for a requested reco counts report to be generated, then + download it. + + Args: + presigned_url: The URL of the report, as given by + :meth:`request_database_reco_counts_report`. + seconds_between_requests: The number of seconds to wait between + requests made while polling the report's URL. + timeout_seconds: The maximum number of seconds to wait for the + report to be generated. + + Returns: + The downloaded report. + + Raises: + ~vws.exceptions.custom_exceptions.RecoCountsReportTimeoutError: + The report was not generated within ``timeout_seconds`` + seconds. + ~vws.exceptions.custom_exceptions.RecoCountsReportDownloadError: + The report could not be downloaded. For example, the report's + URL may have expired. + """ + start_time = time.monotonic() + while True: + try: + return await self.download_reco_counts_report( + presigned_url=presigned_url, + ) + except RecoCountsReportNotReadyError: + elapsed_time = time.monotonic() - start_time + if elapsed_time > timeout_seconds: + raise RecoCountsReportTimeoutError from None + + await asyncio.sleep(delay=seconds_between_requests) + async def delete_target(self, target_id: str) -> None: """Delete a given target. diff --git a/src/vws/exceptions/custom_exceptions.py b/src/vws/exceptions/custom_exceptions.py index c53f5db29..70ec81e36 100644 --- a/src/vws/exceptions/custom_exceptions.py +++ b/src/vws/exceptions/custom_exceptions.py @@ -34,6 +34,61 @@ class TargetProcessingTimeoutError(Exception): """ +@beartype +class DatabaseIdNotSetError(Exception): + """Exception raised when an operation which needs a database ID is used + on a client which was not given one. + """ + + +@beartype +class RecoCountsReportNotReadyError(Exception): + """Exception raised when a reco counts report is downloaded before + Vuforia has generated it. + """ + + def __init__(self, response: Response) -> None: + """ + Args: + response: The response returned by the report's download URL. + """ + super().__init__(response.text) + self._response = response + + @property + def response(self) -> Response: + """The response returned by the download URL.""" + return self._response + + +@beartype +class RecoCountsReportDownloadError(Exception): + """Exception raised when downloading a reco counts report fails. + + This is raised, for example, when the report's URL has expired. + """ + + def __init__(self, response: Response) -> None: + """ + Args: + response: The response returned by the report's download URL. + """ + super().__init__(response.text) + self._response = response + + @property + def response(self) -> Response: + """The response returned by the download URL.""" + return self._response + + +@beartype +class RecoCountsReportTimeoutError(Exception): + """Exception raised when waiting for a reco counts report to be + generated times out. + """ + + @beartype class ServerError(Exception): # pragma: no cover """Exception raised when VWS returns a server error.""" diff --git a/src/vws/reports.py b/src/vws/reports.py index 007a0d443..66a9685ed 100644 --- a/src/vws/reports.py +++ b/src/vws/reports.py @@ -1,6 +1,9 @@ """Classes for representing Vuforia reports.""" +import csv import datetime +import io +from collections.abc import Sequence # noqa: TC003 from dataclasses import dataclass from enum import Enum, unique from typing import Any, Self @@ -186,3 +189,71 @@ def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: reco_rating=target_record_dict["reco_rating"], ) return cls(status=status, target_record=target_record) + + +@beartype +@dataclass(frozen=True, kw_only=True) +class RecoCountsReportRequest: + """A requested database reco counts report. + + See + https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api. + """ + + transaction_id: str + presigned_url: str + """The URL to download the report from. + + Real Vuforia's URLs expire just under seven days after the report is + requested. + """ + + @classmethod + def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: + """Construct from a VWS API response dict.""" + return cls( + transaction_id=response_dict["transaction_id"], + presigned_url=response_dict["presigned_url"], + ) + + +@beartype +@dataclass(frozen=True, kw_only=True) +class RecoCount: + """The number of recognitions of one target in a reco counts + report. + """ + + target_id: str + reco_count: int + + +@beartype +@dataclass(frozen=True, kw_only=True) +class RecoCountsReport: + """A downloaded database reco counts report. + + A report for a month with no recognitions has no ``reco_counts``. + """ + + reco_counts: Sequence[RecoCount] + raw_csv: bytes + """The downloaded CSV, before it was parsed. + + Vuforia does not document the format of the report, so it may include + columns which ``reco_counts`` does not expose. + """ + + @classmethod + def from_csv(cls, csv_bytes: bytes) -> Self: + """Construct from the CSV content of a downloaded report.""" + text = csv_bytes.decode(encoding="utf-8") + reader = csv.DictReader(f=io.StringIO(initial_value=text, newline="")) + reco_counts = [ + RecoCount( + target_id=row["target_id"], + reco_count=int(row["reco_count"]), + ) + for row in reader + ] + return cls(reco_counts=reco_counts, raw_csv=csv_bytes) diff --git a/src/vws/vws.py b/src/vws/vws.py index fc54c30fd..a46b84fa0 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -9,15 +9,24 @@ from vws._image_utils import ImageType as _ImageType from vws._image_utils import get_image_data as _get_image_data +from vws._reco_counts import ( + reco_counts_report_body, + reco_counts_report_path, + report_from_download_response, +) from vws._vws_request import target_api_request from vws.exceptions.base_exceptions import VWSError from vws.exceptions.custom_exceptions import ( + RecoCountsReportNotReadyError, + RecoCountsReportTimeoutError, ServerError, TargetProcessingTimeoutError, ) from vws.exceptions.vws_exceptions import TooManyRequestsError from vws.reports import ( DatabaseSummaryReport, + RecoCountsReport, + RecoCountsReportRequest, TargetStatusAndRecord, TargetStatuses, TargetSummaryReport, @@ -36,6 +45,7 @@ def __init__( server_access_key: str, server_secret_key: str, base_vws_url: str = "https://vws.vuforia.com", + database_id: str | None = None, request_timeout_seconds: float | tuple[float, float] = 30.0, transport: Transport | None = None, ) -> None: @@ -44,6 +54,10 @@ def __init__( server_access_key: A VWS server access key. server_secret_key: A VWS server secret key. base_vws_url: The base URL for the VWS API. + database_id: The ID of the database which the + given keys belong to. This is shown in the + target manager. It is needed only by + :meth:`request_database_reco_counts_report`. request_timeout_seconds: The timeout for each HTTP request. This can be a float to set both the connect and read timeouts, or a @@ -55,6 +69,7 @@ def __init__( self._server_access_key = server_access_key self._server_secret_key = server_secret_key self._base_vws_url = base_vws_url + self._database_id = database_id self._request_timeout_seconds = request_timeout_seconds self._transport = ( transport if transport is not None else RequestsTransport() @@ -413,6 +428,133 @@ def get_database_summary_report(self) -> DatabaseSummaryReport: response_dict=response_data, ) + def request_database_reco_counts_report( + self, + *, + month: str, + ) -> RecoCountsReportRequest: + """Request a per-target recognition count report for the database. + + Vuforia generates the report in the background, so the report is not + available to download immediately. Use + :meth:`wait_for_reco_counts_report` to wait for it. + + Args: + month: The month to get recognition counts for, in ``YYYY-mm`` + form. Vuforia accepts only the current month and the previous + month. + + Returns: + The URL to download the report from, and the transaction ID of + the request. + + Raises: + ~vws.exceptions.custom_exceptions.DatabaseIdNotSetError: No + ``database_id`` was given to the client. + ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The + secret key is not correct, or the client's ``database_id`` is + not the ID of the database which the client's keys belong to. + ~vws.exceptions.vws_exceptions.FailError: There was an error with + the request. For example, the given month is not the current + month or the previous month. + ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is + an error with the time sent to Vuforia. + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. + """ + response = self.make_request( + method=HTTPMethod.POST, + data=reco_counts_report_body(month=month), + request_path=reco_counts_report_path( + database_id=self._database_id, + ), + expected_result_code="Success", + content_type="application/json", + ) + + response_data = dict(json.loads(s=response.text)) + return RecoCountsReportRequest.from_response_dict( + response_dict=response_data, + ) + + def download_reco_counts_report( + self, + *, + presigned_url: str, + ) -> RecoCountsReport: + """Download a requested reco counts report. + + The report's URL is not part of the VWS API, so this request is not + authorized with the client's keys. + + Args: + presigned_url: The URL of the report, as given by + :meth:`request_database_reco_counts_report`. + + Returns: + The downloaded report. + + Raises: + ~vws.exceptions.custom_exceptions.RecoCountsReportNotReadyError: + Vuforia has not finished generating the report. + ~vws.exceptions.custom_exceptions.RecoCountsReportDownloadError: + The report could not be downloaded. For example, the report's + URL may have expired. + """ + response = self._transport( + method=HTTPMethod.GET, + url=presigned_url, + headers={}, + data=b"", + request_timeout=self._request_timeout_seconds, + ) + + return report_from_download_response(response=response) + + def wait_for_reco_counts_report( + self, + *, + presigned_url: str, + seconds_between_requests: float = 0.2, + timeout_seconds: float = 60 * 5, + ) -> RecoCountsReport: + """Wait for a requested reco counts report to be generated, then + download it. + + Args: + presigned_url: The URL of the report, as given by + :meth:`request_database_reco_counts_report`. + seconds_between_requests: The number of seconds to wait between + requests made while polling the report's URL. + timeout_seconds: The maximum number of seconds to wait for the + report to be generated. + + Returns: + The downloaded report. + + Raises: + ~vws.exceptions.custom_exceptions.RecoCountsReportTimeoutError: + The report was not generated within ``timeout_seconds`` + seconds. + ~vws.exceptions.custom_exceptions.RecoCountsReportDownloadError: + The report could not be downloaded. For example, the report's + URL may have expired. + """ + start_time = time.monotonic() + while True: + try: + return self.download_reco_counts_report( + presigned_url=presigned_url, + ) + except RecoCountsReportNotReadyError: + elapsed_time = time.monotonic() - start_time + if elapsed_time > timeout_seconds: + raise RecoCountsReportTimeoutError from None + + time.sleep(seconds_between_requests) + def delete_target(self, target_id: str) -> None: """Delete a given target. diff --git a/tests/conftest.py b/tests/conftest.py index 15ef17dab..fde4c7a00 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ """Configuration, plugins and fixtures for `pytest`.""" +import datetime import io # noqa: TC003 from collections.abc import AsyncGenerator, Generator # noqa: TC003 from pathlib import Path # noqa: TC003 @@ -66,6 +67,7 @@ def vws_client(*, _mock_database: CloudDatabase) -> VWS: return VWS( server_access_key=_mock_database.server_access_key, server_secret_key=_mock_database.server_secret_key, + database_id=_mock_database.database_id, ) @@ -87,6 +89,7 @@ async def async_vws_client( async with AsyncVWS( server_access_key=_mock_database.server_access_key, server_secret_key=_mock_database.server_secret_key, + database_id=_mock_database.database_id, ) as client: yield client @@ -121,6 +124,27 @@ async def async_vumark_service_client( yield client +@pytest.fixture(name="current_month") +def fixture_current_month() -> str: + """The current month, in the form which reco counts reports use.""" + now = datetime.datetime.now(tz=datetime.UTC) + return now.strftime(format="%Y-%m") + + +@pytest.fixture(name="report_month", params=["current", "previous"]) +def fixture_report_month(*, request: pytest.FixtureRequest) -> str: + """A month which a reco counts report can be requested for. + + Vuforia accepts only the current month and the previous month. + """ + now = datetime.datetime.now(tz=datetime.UTC) + if request.param == "current": + return now.strftime(format="%Y-%m") + + last_of_previous_month = now.replace(day=1) - datetime.timedelta(days=1) + return last_of_previous_month.strftime(format="%Y-%m") + + @pytest.fixture(name="image_file", params=["r+b", "rb"]) def fixture_image_file( *, diff --git a/tests/test_async_vws.py b/tests/test_async_vws.py index f59c5f2e4..d319b5c69 100644 --- a/tests/test_async_vws.py +++ b/tests/test_async_vws.py @@ -2,7 +2,9 @@ import base64 import io # noqa: TC003 +import time import uuid +from http import HTTPStatus from typing import BinaryIO import pytest @@ -11,13 +13,22 @@ from vws import AsyncCloudRecoService, AsyncVuMarkService, AsyncVWS from vws.exceptions.custom_exceptions import ( + DatabaseIdNotSetError, + RecoCountsReportDownloadError, + RecoCountsReportNotReadyError, + RecoCountsReportTimeoutError, TargetProcessingTimeoutError, ) +from vws.exceptions.vws_exceptions import ( + AuthenticationFailureError, + FailError, +) from vws.reports import ( DatabaseSummaryReport, TargetRecord, TargetStatuses, ) +from vws.response import Response from vws.vumark_accept import VuMarkAccept @@ -459,6 +470,210 @@ async def test_no_fields_given( ) +class _ForbiddenDownloadTransport: + """An async transport which refuses to serve a report, as an expired + URL would. + """ + + async def aclose(self) -> None: + """Close the transport.""" + + async def __call__( + self, + *, + method: str, + url: str, + headers: dict[str, str], + data: bytes, + request_timeout: float | tuple[float, float], + ) -> Response: + """Return a "forbidden" response.""" + del method, headers, request_timeout + body = "AccessDenied" + return Response( + text=body, + url=url, + status_code=HTTPStatus.FORBIDDEN, + headers={}, + request_body=data, + tell_position=0, + content=body.encode(encoding="utf-8"), + ) + + +class TestRecoCountsReport: + """Tests for database reco counts reports.""" + + @staticmethod + @pytest.mark.asyncio + async def test_reco_counts_report( + *, + async_vws_client: AsyncVWS, + report_month: str, + ) -> None: + """A report can be requested, waited for and downloaded.""" + client = async_vws_client + report_request = await client.request_database_reco_counts_report( + month=report_month, + ) + assert report_request.transaction_id + assert report_request.presigned_url + + report = await client.wait_for_reco_counts_report( + presigned_url=report_request.presigned_url, + ) + + # No targets have been recognized, so the report has no rows. + assert not report.reco_counts + assert report.raw_csv.startswith(b"target_id,reco_count") + + @staticmethod + @pytest.mark.asyncio + async def test_not_ready(*, current_month: str) -> None: + """Downloading a report before Vuforia has generated it raises an + error. + """ + with MockVWS(processing_time_seconds=60) as mock: + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + async with AsyncVWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + database_id=database.database_id, + ) as client: + report_request = ( + await client.request_database_reco_counts_report( + month=current_month, + ) + ) + + with pytest.raises( + expected_exception=RecoCountsReportNotReadyError, + ) as exc: + await client.download_reco_counts_report( + presigned_url=report_request.presigned_url, + ) + + assert exc.value.response.status_code == HTTPStatus.NOT_FOUND + + @staticmethod + @pytest.mark.asyncio + async def test_wait_timeout(*, current_month: str) -> None: + """Waiting for a report which is not generated in time raises an + error. + """ + with MockVWS(processing_time_seconds=60) as mock: + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + async with AsyncVWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + database_id=database.database_id, + ) as client: + report_request = ( + await client.request_database_reco_counts_report( + month=current_month, + ) + ) + + maximum_wait_seconds = 5 + start_time = time.monotonic() + + with pytest.raises( + expected_exception=RecoCountsReportTimeoutError, + ): + await client.wait_for_reco_counts_report( + presigned_url=report_request.presigned_url, + seconds_between_requests=0.01, + timeout_seconds=0.05, + ) + + elapsed_time = time.monotonic() - start_time + assert elapsed_time < maximum_wait_seconds + + @staticmethod + @pytest.mark.asyncio + @pytest.mark.parametrize( + argnames="month", + argvalues=["1999-01", "not-a-month"], + ) + async def test_month_not_accepted( + *, + async_vws_client: AsyncVWS, + month: str, + ) -> None: + """Months other than the current and previous month are + rejected. + """ + with pytest.raises(expected_exception=FailError) as exc: + await async_vws_client.request_database_reco_counts_report( + month=month, + ) + + assert exc.value.response.status_code == HTTPStatus.BAD_REQUEST + + @staticmethod + @pytest.mark.asyncio + async def test_database_id_does_not_match_keys( + *, + current_month: str, + ) -> None: + """A database ID which does not match the given keys is + rejected. + """ + with MockVWS() as mock: + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + async with AsyncVWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + database_id=uuid.uuid4().hex, + ) as client: + with pytest.raises( + expected_exception=AuthenticationFailureError, + ) as exc: + await client.request_database_reco_counts_report( + month=current_month, + ) + + assert ( + exc.value.response.status_code == HTTPStatus.UNAUTHORIZED + ) + + @staticmethod + @pytest.mark.asyncio + async def test_download_error() -> None: + """An error response from the report's URL raises an error.""" + async with AsyncVWS( + server_access_key=uuid.uuid4().hex, + server_secret_key=uuid.uuid4().hex, + transport=_ForbiddenDownloadTransport(), + ) as client: + with pytest.raises( + expected_exception=RecoCountsReportDownloadError, + ) as exc: + await client.download_reco_counts_report( + presigned_url="https://example.com/reports/recoCounts/x", + ) + + assert exc.value.response.status_code == HTTPStatus.FORBIDDEN + + @staticmethod + @pytest.mark.asyncio + async def test_no_database_id(*, current_month: str) -> None: + """A client which was given no database ID cannot request a + report. + """ + async with AsyncVWS( + server_access_key=uuid.uuid4().hex, + server_secret_key=uuid.uuid4().hex, + ) as client: + with pytest.raises(expected_exception=DatabaseIdNotSetError): + await client.request_database_reco_counts_report( + month=current_month, + ) + + class TestGenerateVumarkInstance: """Tests for generating VuMark instances.""" diff --git a/tests/test_vws.py b/tests/test_vws.py index dff029c2f..223d2cd97 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -4,7 +4,9 @@ import datetime import io # noqa: TC003 import secrets +import time import uuid +from http import HTTPStatus from typing import BinaryIO import pytest @@ -14,13 +16,26 @@ from mock_vws.database import CloudDatabase from vws import VWS, CloudRecoService, VuMarkService -from vws.exceptions.custom_exceptions import TargetProcessingTimeoutError +from vws.exceptions.custom_exceptions import ( + DatabaseIdNotSetError, + RecoCountsReportDownloadError, + RecoCountsReportNotReadyError, + RecoCountsReportTimeoutError, + TargetProcessingTimeoutError, +) +from vws.exceptions.vws_exceptions import ( + AuthenticationFailureError, + FailError, +) from vws.reports import ( DatabaseSummaryReport, + RecoCount, + RecoCountsReport, TargetRecord, TargetStatuses, TargetSummaryReport, ) +from vws.response import Response from vws.vumark_accept import VuMarkAccept @@ -760,6 +775,222 @@ def test_no_fields_given( vws_client.update_target(target_id=target_id) +class _ForbiddenDownloadTransport: + """A transport which refuses to serve a report, as an expired URL + would. + """ + + def close(self) -> None: + """Close the transport.""" + + def __call__( + self, + *, + method: str, + url: str, + headers: dict[str, str], + data: bytes, + request_timeout: float | tuple[float, float], + ) -> Response: + """Return a "forbidden" response.""" + del method, headers, request_timeout + body = "AccessDenied" + return Response( + text=body, + url=url, + status_code=HTTPStatus.FORBIDDEN, + headers={}, + request_body=data, + tell_position=0, + content=body.encode(encoding="utf-8"), + ) + + +class TestRecoCountsReport: + """Tests for database reco counts reports.""" + + @staticmethod + def test_reco_counts_report( + *, + vws_client: VWS, + report_month: str, + ) -> None: + """A report can be requested, waited for and downloaded.""" + report_request = vws_client.request_database_reco_counts_report( + month=report_month, + ) + assert report_request.transaction_id + assert report_request.presigned_url + + report = vws_client.wait_for_reco_counts_report( + presigned_url=report_request.presigned_url, + ) + + # No targets have been recognized, so the report has no rows. + assert not report.reco_counts + assert report.raw_csv.startswith(b"target_id,reco_count") + + @staticmethod + def test_not_ready(*, current_month: str) -> None: + """Downloading a report before Vuforia has generated it raises an + error. + """ + with MockVWS(processing_time_seconds=60) as mock: + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + database_id=database.database_id, + ) + report_request = vws_client.request_database_reco_counts_report( + month=current_month, + ) + + with pytest.raises( + expected_exception=RecoCountsReportNotReadyError, + ) as exc: + vws_client.download_reco_counts_report( + presigned_url=report_request.presigned_url, + ) + + assert exc.value.response.status_code == HTTPStatus.NOT_FOUND + + @staticmethod + def test_wait_timeout(*, current_month: str) -> None: + """Waiting for a report which is not generated in time raises an + error. + """ + with MockVWS(processing_time_seconds=60) as mock: + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + database_id=database.database_id, + ) + report_request = vws_client.request_database_reco_counts_report( + month=current_month, + ) + + maximum_wait_seconds = 5 + start_time = time.monotonic() + + with pytest.raises( + expected_exception=RecoCountsReportTimeoutError, + ): + vws_client.wait_for_reco_counts_report( + presigned_url=report_request.presigned_url, + seconds_between_requests=0.01, + timeout_seconds=0.05, + ) + + elapsed_time = time.monotonic() - start_time + assert elapsed_time < maximum_wait_seconds + + @staticmethod + @pytest.mark.parametrize( + argnames="month", + argvalues=["1999-01", "not-a-month"], + ) + def test_month_not_accepted(*, vws_client: VWS, month: str) -> None: + """Months other than the current and previous month are + rejected. + """ + with pytest.raises(expected_exception=FailError) as exc: + vws_client.request_database_reco_counts_report(month=month) + + assert exc.value.response.status_code == HTTPStatus.BAD_REQUEST + + @staticmethod + def test_database_id_does_not_match_keys(*, current_month: str) -> None: + """A database ID which does not match the given keys is + rejected. + """ + with MockVWS() as mock: + database = CloudDatabase() + mock.add_cloud_database(cloud_database=database) + vws_client = VWS( + server_access_key=database.server_access_key, + server_secret_key=database.server_secret_key, + database_id=uuid.uuid4().hex, + ) + + with pytest.raises( + expected_exception=AuthenticationFailureError, + ) as exc: + vws_client.request_database_reco_counts_report( + month=current_month, + ) + + assert exc.value.response.status_code == HTTPStatus.UNAUTHORIZED + + @staticmethod + def test_download_error() -> None: + """An error response from the report's URL raises an error.""" + vws_client = VWS( + server_access_key=uuid.uuid4().hex, + server_secret_key=uuid.uuid4().hex, + transport=_ForbiddenDownloadTransport(), + ) + + with pytest.raises( + expected_exception=RecoCountsReportDownloadError, + ) as exc: + vws_client.download_reco_counts_report( + presigned_url="https://example.com/reports/recoCounts/x", + ) + + assert exc.value.response.status_code == HTTPStatus.FORBIDDEN + + @staticmethod + def test_no_database_id(*, current_month: str) -> None: + """A client which was given no database ID cannot request a + report. + """ + vws_client = VWS( + server_access_key=uuid.uuid4().hex, + server_secret_key=uuid.uuid4().hex, + ) + + with pytest.raises(expected_exception=DatabaseIdNotSetError): + vws_client.request_database_reco_counts_report(month=current_month) + + +class TestRecoCountsReportParsing: + """Tests for parsing downloaded reco counts reports.""" + + @staticmethod + def test_rows() -> None: + """Each row of the CSV becomes a ``RecoCount``.""" + csv_bytes = b"target_id,reco_count\r\nabc,3\r\ndef,0\r\n" + + report = RecoCountsReport.from_csv(csv_bytes=csv_bytes) + + assert report.reco_counts == [ + RecoCount(target_id="abc", reco_count=3), + RecoCount(target_id="def", reco_count=0), + ] + assert report.raw_csv == csv_bytes + + @staticmethod + def test_unknown_columns_ignored() -> None: + """Columns which are not known are not exposed in + ``reco_counts``. + """ + expected_reco_count = 3 + header = "target_id,reco_count,new_column\r\n" + csv_text = f"{header}abc,{expected_reco_count},x\r\n" + + report = RecoCountsReport.from_csv( + csv_bytes=csv_text.encode(encoding="utf-8"), + ) + + (item,) = report.reco_counts + assert item.target_id == "abc" + assert item.reco_count == expected_reco_count + + class TestGenerateVumarkInstance: """Tests for generating VuMark instances.""" From e6c52dc90dfe59d55f805c45dc4f624da5ddbb77 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 14 Aug 2026 13:59:36 +0100 Subject: [PATCH 685/688] Take the reco counts report year and month as ints The month was a ``YYYY-mm`` string, which let callers pass a value that could not be a month at all. Taking two ints makes a malformed month unrepresentable, and reads better next to ``datetime`` attributes. Co-Authored-By: Claude Opus 5 (1M context) --- docs/source/index.rst | 4 ++-- src/vws/_reco_counts.py | 8 ++++--- src/vws/async_vws.py | 14 +++++++----- src/vws/vws.py | 14 +++++++----- tests/conftest.py | 14 ++++++------ tests/test_async_vws.py | 37 ++++++++++++++++++++----------- tests/test_vws.py | 49 +++++++++++++++++++++++++++++------------ 7 files changed, 89 insertions(+), 51 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 7b82155c8..7ce0fb3a9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -92,10 +92,10 @@ The report is generated in the background, and the URL it is served from expires ) now = datetime.datetime.now(tz=datetime.UTC) - this_month = now.strftime(format="%Y-%m") report_request = vws_client.request_database_reco_counts_report( - month=this_month, + year=now.year, + month=now.month, ) report = vws_client.wait_for_reco_counts_report( diff --git a/src/vws/_reco_counts.py b/src/vws/_reco_counts.py index 7a95c59f9..68c3fd8ae 100644 --- a/src/vws/_reco_counts.py +++ b/src/vws/_reco_counts.py @@ -39,16 +39,18 @@ def reco_counts_report_path(*, database_id: str | None) -> str: @beartype(conf=BeartypeConf(is_pep484_tower=True)) -def reco_counts_report_body(*, month: str) -> bytes: +def reco_counts_report_body(*, year: int, month: int) -> bytes: """Get the request body for requesting a reco counts report. Args: - month: The month to request the report for, in ``YYYY-mm`` form. + year: The year to request the report for. + month: The month of the year to request the report for. Returns: The body of the request. """ - return json.dumps(obj={"month": month}).encode(encoding="utf-8") + month_string = f"{year:04d}-{month:02d}" + return json.dumps(obj={"month": month_string}).encode(encoding="utf-8") @beartype(conf=BeartypeConf(is_pep484_tower=True)) diff --git a/src/vws/async_vws.py b/src/vws/async_vws.py index bdefa8222..5863d5ef6 100644 --- a/src/vws/async_vws.py +++ b/src/vws/async_vws.py @@ -458,7 +458,8 @@ async def get_database_summary_report( async def request_database_reco_counts_report( self, *, - month: str, + year: int, + month: int, ) -> RecoCountsReportRequest: """Request a per-target recognition count report for the database. @@ -467,8 +468,9 @@ async def request_database_reco_counts_report( :meth:`wait_for_reco_counts_report` to wait for it. Args: - month: The month to get recognition counts for, in ``YYYY-mm`` - form. Vuforia accepts only the current month and the previous + year: The year to get recognition counts for. + month: The month of the year to get recognition counts for. + Vuforia accepts only the current month and the previous month. Returns: @@ -482,8 +484,8 @@ async def request_database_reco_counts_report( secret key is not correct, or the client's ``database_id`` is not the ID of the database which the client's keys belong to. ~vws.exceptions.vws_exceptions.FailError: There was an error with - the request. For example, the given month is not the current - month or the previous month. + the request. For example, the given year and month are not + the current month or the previous month. ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is an error with the time sent to Vuforia. ~vws.exceptions.custom_exceptions.ServerError: There is an error @@ -493,7 +495,7 @@ async def request_database_reco_counts_report( """ response = await self.make_request( method=HTTPMethod.POST, - data=reco_counts_report_body(month=month), + data=reco_counts_report_body(year=year, month=month), request_path=reco_counts_report_path( database_id=self._database_id, ), diff --git a/src/vws/vws.py b/src/vws/vws.py index a46b84fa0..85a24b5b1 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -431,7 +431,8 @@ def get_database_summary_report(self) -> DatabaseSummaryReport: def request_database_reco_counts_report( self, *, - month: str, + year: int, + month: int, ) -> RecoCountsReportRequest: """Request a per-target recognition count report for the database. @@ -440,8 +441,9 @@ def request_database_reco_counts_report( :meth:`wait_for_reco_counts_report` to wait for it. Args: - month: The month to get recognition counts for, in ``YYYY-mm`` - form. Vuforia accepts only the current month and the previous + year: The year to get recognition counts for. + month: The month of the year to get recognition counts for. + Vuforia accepts only the current month and the previous month. Returns: @@ -455,8 +457,8 @@ def request_database_reco_counts_report( secret key is not correct, or the client's ``database_id`` is not the ID of the database which the client's keys belong to. ~vws.exceptions.vws_exceptions.FailError: There was an error with - the request. For example, the given month is not the current - month or the previous month. + the request. For example, the given year and month are not + the current month or the previous month. ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is an error with the time sent to Vuforia. ~vws.exceptions.custom_exceptions.ServerError: There is an error @@ -466,7 +468,7 @@ def request_database_reco_counts_report( """ response = self.make_request( method=HTTPMethod.POST, - data=reco_counts_report_body(month=month), + data=reco_counts_report_body(year=year, month=month), request_path=reco_counts_report_path( database_id=self._database_id, ), diff --git a/tests/conftest.py b/tests/conftest.py index fde4c7a00..d94099ff8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -125,24 +125,24 @@ async def async_vumark_service_client( @pytest.fixture(name="current_month") -def fixture_current_month() -> str: - """The current month, in the form which reco counts reports use.""" +def fixture_current_month() -> datetime.date: + """The current month, as the first day of that month.""" now = datetime.datetime.now(tz=datetime.UTC) - return now.strftime(format="%Y-%m") + return now.date().replace(day=1) @pytest.fixture(name="report_month", params=["current", "previous"]) -def fixture_report_month(*, request: pytest.FixtureRequest) -> str: +def fixture_report_month(*, request: pytest.FixtureRequest) -> datetime.date: """A month which a reco counts report can be requested for. Vuforia accepts only the current month and the previous month. """ now = datetime.datetime.now(tz=datetime.UTC) + first_of_month = now.date().replace(day=1) if request.param == "current": - return now.strftime(format="%Y-%m") + return first_of_month - last_of_previous_month = now.replace(day=1) - datetime.timedelta(days=1) - return last_of_previous_month.strftime(format="%Y-%m") + return first_of_month - datetime.timedelta(days=1) @pytest.fixture(name="image_file", params=["r+b", "rb"]) diff --git a/tests/test_async_vws.py b/tests/test_async_vws.py index d319b5c69..a8df14a63 100644 --- a/tests/test_async_vws.py +++ b/tests/test_async_vws.py @@ -1,6 +1,7 @@ """Tests for async helper functions for managing a Vuforia database.""" import base64 +import datetime # noqa: TC003 import io # noqa: TC003 import time import uuid @@ -509,12 +510,13 @@ class TestRecoCountsReport: async def test_reco_counts_report( *, async_vws_client: AsyncVWS, - report_month: str, + report_month: datetime.date, ) -> None: """A report can be requested, waited for and downloaded.""" client = async_vws_client report_request = await client.request_database_reco_counts_report( - month=report_month, + year=report_month.year, + month=report_month.month, ) assert report_request.transaction_id assert report_request.presigned_url @@ -529,7 +531,7 @@ async def test_reco_counts_report( @staticmethod @pytest.mark.asyncio - async def test_not_ready(*, current_month: str) -> None: + async def test_not_ready(*, current_month: datetime.date) -> None: """Downloading a report before Vuforia has generated it raises an error. """ @@ -543,7 +545,8 @@ async def test_not_ready(*, current_month: str) -> None: ) as client: report_request = ( await client.request_database_reco_counts_report( - month=current_month, + year=current_month.year, + month=current_month.month, ) ) @@ -558,7 +561,7 @@ async def test_not_ready(*, current_month: str) -> None: @staticmethod @pytest.mark.asyncio - async def test_wait_timeout(*, current_month: str) -> None: + async def test_wait_timeout(*, current_month: datetime.date) -> None: """Waiting for a report which is not generated in time raises an error. """ @@ -572,7 +575,8 @@ async def test_wait_timeout(*, current_month: str) -> None: ) as client: report_request = ( await client.request_database_reco_counts_report( - month=current_month, + year=current_month.year, + month=current_month.month, ) ) @@ -594,19 +598,24 @@ async def test_wait_timeout(*, current_month: str) -> None: @staticmethod @pytest.mark.asyncio @pytest.mark.parametrize( - argnames="month", - argvalues=["1999-01", "not-a-month"], + argnames=("year", "month"), + argvalues=[ + pytest.param(1999, 1, id="month-in-the-past"), + pytest.param(1999, 13, id="month-out-of-range"), + ], ) async def test_month_not_accepted( *, async_vws_client: AsyncVWS, - month: str, + year: int, + month: int, ) -> None: """Months other than the current and previous month are rejected. """ with pytest.raises(expected_exception=FailError) as exc: await async_vws_client.request_database_reco_counts_report( + year=year, month=month, ) @@ -616,7 +625,7 @@ async def test_month_not_accepted( @pytest.mark.asyncio async def test_database_id_does_not_match_keys( *, - current_month: str, + current_month: datetime.date, ) -> None: """A database ID which does not match the given keys is rejected. @@ -633,7 +642,8 @@ async def test_database_id_does_not_match_keys( expected_exception=AuthenticationFailureError, ) as exc: await client.request_database_reco_counts_report( - month=current_month, + year=current_month.year, + month=current_month.month, ) assert ( @@ -660,7 +670,7 @@ async def test_download_error() -> None: @staticmethod @pytest.mark.asyncio - async def test_no_database_id(*, current_month: str) -> None: + async def test_no_database_id(*, current_month: datetime.date) -> None: """A client which was given no database ID cannot request a report. """ @@ -670,7 +680,8 @@ async def test_no_database_id(*, current_month: str) -> None: ) as client: with pytest.raises(expected_exception=DatabaseIdNotSetError): await client.request_database_reco_counts_report( - month=current_month, + year=current_month.year, + month=current_month.month, ) diff --git a/tests/test_vws.py b/tests/test_vws.py index 223d2cd97..6cb8358bf 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -813,11 +813,12 @@ class TestRecoCountsReport: def test_reco_counts_report( *, vws_client: VWS, - report_month: str, + report_month: datetime.date, ) -> None: """A report can be requested, waited for and downloaded.""" report_request = vws_client.request_database_reco_counts_report( - month=report_month, + year=report_month.year, + month=report_month.month, ) assert report_request.transaction_id assert report_request.presigned_url @@ -831,7 +832,7 @@ def test_reco_counts_report( assert report.raw_csv.startswith(b"target_id,reco_count") @staticmethod - def test_not_ready(*, current_month: str) -> None: + def test_not_ready(*, current_month: datetime.date) -> None: """Downloading a report before Vuforia has generated it raises an error. """ @@ -844,7 +845,8 @@ def test_not_ready(*, current_month: str) -> None: database_id=database.database_id, ) report_request = vws_client.request_database_reco_counts_report( - month=current_month, + year=current_month.year, + month=current_month.month, ) with pytest.raises( @@ -857,7 +859,7 @@ def test_not_ready(*, current_month: str) -> None: assert exc.value.response.status_code == HTTPStatus.NOT_FOUND @staticmethod - def test_wait_timeout(*, current_month: str) -> None: + def test_wait_timeout(*, current_month: datetime.date) -> None: """Waiting for a report which is not generated in time raises an error. """ @@ -870,7 +872,8 @@ def test_wait_timeout(*, current_month: str) -> None: database_id=database.database_id, ) report_request = vws_client.request_database_reco_counts_report( - month=current_month, + year=current_month.year, + month=current_month.month, ) maximum_wait_seconds = 5 @@ -890,20 +893,34 @@ def test_wait_timeout(*, current_month: str) -> None: @staticmethod @pytest.mark.parametrize( - argnames="month", - argvalues=["1999-01", "not-a-month"], + argnames=("year", "month"), + argvalues=[ + pytest.param(1999, 1, id="month-in-the-past"), + pytest.param(1999, 13, id="month-out-of-range"), + ], ) - def test_month_not_accepted(*, vws_client: VWS, month: str) -> None: + def test_month_not_accepted( + *, + vws_client: VWS, + year: int, + month: int, + ) -> None: """Months other than the current and previous month are rejected. """ with pytest.raises(expected_exception=FailError) as exc: - vws_client.request_database_reco_counts_report(month=month) + vws_client.request_database_reco_counts_report( + year=year, + month=month, + ) assert exc.value.response.status_code == HTTPStatus.BAD_REQUEST @staticmethod - def test_database_id_does_not_match_keys(*, current_month: str) -> None: + def test_database_id_does_not_match_keys( + *, + current_month: datetime.date, + ) -> None: """A database ID which does not match the given keys is rejected. """ @@ -920,7 +937,8 @@ def test_database_id_does_not_match_keys(*, current_month: str) -> None: expected_exception=AuthenticationFailureError, ) as exc: vws_client.request_database_reco_counts_report( - month=current_month, + year=current_month.year, + month=current_month.month, ) assert exc.value.response.status_code == HTTPStatus.UNAUTHORIZED @@ -944,7 +962,7 @@ def test_download_error() -> None: assert exc.value.response.status_code == HTTPStatus.FORBIDDEN @staticmethod - def test_no_database_id(*, current_month: str) -> None: + def test_no_database_id(*, current_month: datetime.date) -> None: """A client which was given no database ID cannot request a report. """ @@ -954,7 +972,10 @@ def test_no_database_id(*, current_month: str) -> None: ) with pytest.raises(expected_exception=DatabaseIdNotSetError): - vws_client.request_database_reco_counts_report(month=current_month) + vws_client.request_database_reco_counts_report( + year=current_month.year, + month=current_month.month, + ) class TestRecoCountsReportParsing: From cb8a5fb24f1155706df52dfc00f40a300ed9ca37 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 14 Aug 2026 14:11:22 +0100 Subject: [PATCH 686/688] Take the reco counts report month as a calendar.Month Vuforia accepts only the current month and the previous month, so a month out of the 1-12 range is now rejected by the type rather than by the server. A month taken from a ``datetime`` needs wrapping in ``calendar.Month``, because ``datetime.month`` is a plain ``int`` and ``beartype`` rejects it otherwise. Co-Authored-By: Claude Opus 5 (1M context) --- docs/source/index.rst | 3 ++- src/vws/_reco_counts.py | 3 ++- src/vws/async_vws.py | 6 ++++-- src/vws/vws.py | 6 ++++-- tests/test_async_vws.py | 21 +++++++++++++-------- tests/test_vws.py | 21 +++++++++++++-------- 6 files changed, 38 insertions(+), 22 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 7ce0fb3a9..ab7bfb0a7 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -76,6 +76,7 @@ The report is generated in the background, and the URL it is served from expires """Get the number of recognitions of each target this month.""" + import calendar import datetime import os @@ -95,7 +96,7 @@ The report is generated in the background, and the URL it is served from expires report_request = vws_client.request_database_reco_counts_report( year=now.year, - month=now.month, + month=calendar.Month(value=now.month), ) report = vws_client.wait_for_reco_counts_report( diff --git a/src/vws/_reco_counts.py b/src/vws/_reco_counts.py index 68c3fd8ae..6ae8010ac 100644 --- a/src/vws/_reco_counts.py +++ b/src/vws/_reco_counts.py @@ -1,5 +1,6 @@ """Internal helpers for the database reco counts report endpoints.""" +import calendar # noqa: TC003 import json from http import HTTPStatus @@ -39,7 +40,7 @@ def reco_counts_report_path(*, database_id: str | None) -> str: @beartype(conf=BeartypeConf(is_pep484_tower=True)) -def reco_counts_report_body(*, year: int, month: int) -> bytes: +def reco_counts_report_body(*, year: int, month: calendar.Month) -> bytes: """Get the request body for requesting a reco counts report. Args: diff --git a/src/vws/async_vws.py b/src/vws/async_vws.py index 5863d5ef6..d2306a63b 100644 --- a/src/vws/async_vws.py +++ b/src/vws/async_vws.py @@ -2,6 +2,7 @@ import asyncio import base64 +import calendar # noqa: TC003 import json import time from http import HTTPMethod, HTTPStatus @@ -459,7 +460,7 @@ async def request_database_reco_counts_report( self, *, year: int, - month: int, + month: calendar.Month, ) -> RecoCountsReportRequest: """Request a per-target recognition count report for the database. @@ -471,7 +472,8 @@ async def request_database_reco_counts_report( year: The year to get recognition counts for. month: The month of the year to get recognition counts for. Vuforia accepts only the current month and the previous - month. + month. A month taken from a :class:`datetime.datetime` needs + wrapping, as in ``calendar.Month(value=now.month)``. Returns: The URL to download the report from, and the transaction ID of diff --git a/src/vws/vws.py b/src/vws/vws.py index 85a24b5b1..2b241ab1d 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -1,6 +1,7 @@ """Tools for interacting with Vuforia APIs.""" import base64 +import calendar # noqa: TC003 import json import time from http import HTTPMethod, HTTPStatus @@ -432,7 +433,7 @@ def request_database_reco_counts_report( self, *, year: int, - month: int, + month: calendar.Month, ) -> RecoCountsReportRequest: """Request a per-target recognition count report for the database. @@ -444,7 +445,8 @@ def request_database_reco_counts_report( year: The year to get recognition counts for. month: The month of the year to get recognition counts for. Vuforia accepts only the current month and the previous - month. + month. A month taken from a :class:`datetime.datetime` needs + wrapping, as in ``calendar.Month(value=now.month)``. Returns: The URL to download the report from, and the transaction ID of diff --git a/tests/test_async_vws.py b/tests/test_async_vws.py index a8df14a63..c99644bec 100644 --- a/tests/test_async_vws.py +++ b/tests/test_async_vws.py @@ -1,6 +1,7 @@ """Tests for async helper functions for managing a Vuforia database.""" import base64 +import calendar import datetime # noqa: TC003 import io # noqa: TC003 import time @@ -516,7 +517,7 @@ async def test_reco_counts_report( client = async_vws_client report_request = await client.request_database_reco_counts_report( year=report_month.year, - month=report_month.month, + month=calendar.Month(value=report_month.month), ) assert report_request.transaction_id assert report_request.presigned_url @@ -546,7 +547,7 @@ async def test_not_ready(*, current_month: datetime.date) -> None: report_request = ( await client.request_database_reco_counts_report( year=current_month.year, - month=current_month.month, + month=calendar.Month(value=current_month.month), ) ) @@ -576,7 +577,7 @@ async def test_wait_timeout(*, current_month: datetime.date) -> None: report_request = ( await client.request_database_reco_counts_report( year=current_month.year, - month=current_month.month, + month=calendar.Month(value=current_month.month), ) ) @@ -600,15 +601,19 @@ async def test_wait_timeout(*, current_month: datetime.date) -> None: @pytest.mark.parametrize( argnames=("year", "month"), argvalues=[ - pytest.param(1999, 1, id="month-in-the-past"), - pytest.param(1999, 13, id="month-out-of-range"), + pytest.param(1999, calendar.Month.JANUARY, id="year-in-the-past"), + pytest.param( + 1999, + calendar.Month.DECEMBER, + id="year-in-the-past-december", + ), ], ) async def test_month_not_accepted( *, async_vws_client: AsyncVWS, year: int, - month: int, + month: calendar.Month, ) -> None: """Months other than the current and previous month are rejected. @@ -643,7 +648,7 @@ async def test_database_id_does_not_match_keys( ) as exc: await client.request_database_reco_counts_report( year=current_month.year, - month=current_month.month, + month=calendar.Month(value=current_month.month), ) assert ( @@ -681,7 +686,7 @@ async def test_no_database_id(*, current_month: datetime.date) -> None: with pytest.raises(expected_exception=DatabaseIdNotSetError): await client.request_database_reco_counts_report( year=current_month.year, - month=current_month.month, + month=calendar.Month(value=current_month.month), ) diff --git a/tests/test_vws.py b/tests/test_vws.py index 6cb8358bf..db6d05fd8 100644 --- a/tests/test_vws.py +++ b/tests/test_vws.py @@ -1,6 +1,7 @@ """Tests for helper functions for managing a Vuforia database.""" import base64 +import calendar import datetime import io # noqa: TC003 import secrets @@ -818,7 +819,7 @@ def test_reco_counts_report( """A report can be requested, waited for and downloaded.""" report_request = vws_client.request_database_reco_counts_report( year=report_month.year, - month=report_month.month, + month=calendar.Month(value=report_month.month), ) assert report_request.transaction_id assert report_request.presigned_url @@ -846,7 +847,7 @@ def test_not_ready(*, current_month: datetime.date) -> None: ) report_request = vws_client.request_database_reco_counts_report( year=current_month.year, - month=current_month.month, + month=calendar.Month(value=current_month.month), ) with pytest.raises( @@ -873,7 +874,7 @@ def test_wait_timeout(*, current_month: datetime.date) -> None: ) report_request = vws_client.request_database_reco_counts_report( year=current_month.year, - month=current_month.month, + month=calendar.Month(value=current_month.month), ) maximum_wait_seconds = 5 @@ -895,15 +896,19 @@ def test_wait_timeout(*, current_month: datetime.date) -> None: @pytest.mark.parametrize( argnames=("year", "month"), argvalues=[ - pytest.param(1999, 1, id="month-in-the-past"), - pytest.param(1999, 13, id="month-out-of-range"), + pytest.param(1999, calendar.Month.JANUARY, id="year-in-the-past"), + pytest.param( + 1999, + calendar.Month.DECEMBER, + id="year-in-the-past-december", + ), ], ) def test_month_not_accepted( *, vws_client: VWS, year: int, - month: int, + month: calendar.Month, ) -> None: """Months other than the current and previous month are rejected. @@ -938,7 +943,7 @@ def test_database_id_does_not_match_keys( ) as exc: vws_client.request_database_reco_counts_report( year=current_month.year, - month=current_month.month, + month=calendar.Month(value=current_month.month), ) assert exc.value.response.status_code == HTTPStatus.UNAUTHORIZED @@ -974,7 +979,7 @@ def test_no_database_id(*, current_month: datetime.date) -> None: with pytest.raises(expected_exception=DatabaseIdNotSetError): vws_client.request_database_reco_counts_report( year=current_month.year, - month=current_month.month, + month=calendar.Month(value=current_month.month), ) From cf2f0498539090f53970380caa5291d4ad0b263b Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 14 Aug 2026 14:52:01 +0100 Subject: [PATCH 687/688] Add Model Target Web API client support Add ``ModelTargetService`` and ``AsyncModelTargetService``, which cover the create, poll, download and delete lifecycle for standard and advanced Model Target datasets. The clients authenticate with OAuth2 client credentials and reuse the returned bearer token until it expires. Typed request structures describe the models, guide views and State-Based Model Target configuration which Vuforia accepts, and the Model Target error envelope is parsed into public exceptions. Closes #3119. Co-Authored-By: Claude Opus 5 (1M context) --- conftest.py | 10 + docs/source/api-reference.rst | 12 + docs/source/exceptions.rst | 9 + docs/source/index.rst | 76 ++ newsfragments/3119.change.rst | 2 + pyproject.toml | 21 + spelling_private_dict.txt | 3 + src/vws/__init__.py | 4 + src/vws/_model_targets.py | 323 +++++++ src/vws/async_model_target_service.py | 381 +++++++++ src/vws/exceptions/model_target_exceptions.py | 210 +++++ src/vws/model_target_datasets.py | 152 ++++ src/vws/model_target_service.py | 366 ++++++++ src/vws/reports.py | 132 +++ tests/conftest.py | 69 ++ tests/test_async_model_targets.py | 409 +++++++++ tests/test_model_targets.py | 802 ++++++++++++++++++ 17 files changed, 2981 insertions(+) create mode 100644 newsfragments/3119.change.rst create mode 100644 src/vws/_model_targets.py create mode 100644 src/vws/async_model_target_service.py create mode 100644 src/vws/exceptions/model_target_exceptions.py create mode 100644 src/vws/model_target_datasets.py create mode 100644 src/vws/model_target_service.py create mode 100644 tests/test_async_model_targets.py create mode 100644 tests/test_model_targets.py diff --git a/conftest.py b/conftest.py index 1a2445a7c..e4c927947 100644 --- a/conftest.py +++ b/conftest.py @@ -61,6 +61,16 @@ def fixture_mock_vws( monkeypatch.setenv(name="VWS_CLIENT_ACCESS_KEY", value=client_access_key) monkeypatch.setenv(name="VWS_CLIENT_SECRET_KEY", value=client_secret_key) monkeypatch.setenv(name="VWS_DATABASE_ID", value=database_id) + # The mock accepts one hard-coded pair of Model Target Web API OAuth2 + # credentials, which it does not expose. + monkeypatch.setenv( + name="VWS_MODEL_TARGET_CLIENT_ID", + value="client-id", + ) + monkeypatch.setenv( + name="VWS_MODEL_TARGET_CLIENT_SECRET", + value="client-secret", + ) # We use a low processing time so that tests run quickly. with MockVWS(processing_time_seconds=0.2) as mock: mock.add_cloud_database(cloud_database=database) diff --git a/docs/source/api-reference.rst b/docs/source/api-reference.rst index c2765245d..f43793ce0 100644 --- a/docs/source/api-reference.rst +++ b/docs/source/api-reference.rst @@ -17,6 +17,18 @@ API Reference :undoc-members: :members: +.. automodule:: vws.model_target_service + :undoc-members: + :members: + +.. automodule:: vws.async_model_target_service + :undoc-members: + :members: + +.. automodule:: vws.model_target_datasets + :undoc-members: + :members: + .. automodule:: vws.reports :undoc-members: :members: diff --git a/docs/source/exceptions.rst b/docs/source/exceptions.rst index f48730bfd..baea7b495 100644 --- a/docs/source/exceptions.rst +++ b/docs/source/exceptions.rst @@ -28,6 +28,15 @@ CloudRecoService exceptions :inherited-members: Exception :exclude-members: errno, filename, filename2, strerror +ModelTargetService exceptions +----------------------------- + +.. automodule:: vws.exceptions.model_target_exceptions + :members: + :show-inheritance: + :inherited-members: Exception + :exclude-members: errno, filename, filename2, strerror + Custom exceptions ----------------- diff --git a/docs/source/index.rst b/docs/source/index.rst index ab7bfb0a7..c8b5bdaf6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -110,6 +110,82 @@ The report is generated in the background, and the URL it is served from expires # This database has no targets, so nothing has been recognized. assert not reco_counts_by_target_id +Model Targets +------------- + +Vuforia generates Model Target datasets from CAD models. +This uses OAuth2 client credentials, which are separate from the VWS server keys. + +Dataset generation happens in the background, and the generated dataset is downloaded as a zip file. + +.. clear-namespace + +.. code-block:: python + + """Generate a Model Target dataset and download it.""" + + import os + + from vws import ModelTargetService + from vws.model_target_datasets import ( + CadDataFormat, + GuideViewPosition, + ModelTargetDatasetType, + ModelTargetModel, + ModelTargetView, + ) + from vws.reports import ModelTargetDatasetStatuses + + client_id = os.environ["VWS_MODEL_TARGET_CLIENT_ID"] + client_secret = os.environ["VWS_MODEL_TARGET_CLIENT_SECRET"] + + model_target_client = ModelTargetService( + client_id=client_id, + client_secret=client_secret, + ) + + model = ModelTargetModel( + name="my_model", + cad_data_url="https://example.com/my_model.zip", + cad_data_format=CadDataFormat.ZIP, + views=[ + ModelTargetView( + name="front", + guide_view_position=GuideViewPosition( + rotation=[0.0, 0.0, 0.0, 1.0], + translation=[0.0, 0.0, 1.0], + ), + ), + ], + ) + + dataset_uuid = model_target_client.create_dataset( + name="my_dataset", + target_sdk="11.0", + models=[model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + report = model_target_client.wait_for_dataset_generated( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert report.status == ModelTargetDatasetStatuses.DONE + + dataset = model_target_client.download_dataset( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + # The dataset is a zip file. + assert dataset.startswith(b"PK") + + model_target_client.delete_dataset( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + Testing ------- diff --git a/newsfragments/3119.change.rst b/newsfragments/3119.change.rst new file mode 100644 index 000000000..5660e237e --- /dev/null +++ b/newsfragments/3119.change.rst @@ -0,0 +1,2 @@ +Add support for the Model Target Web API. +``ModelTargetService`` and ``AsyncModelTargetService`` create standard and advanced Model Target datasets, wait for them to be generated, download them and delete them. diff --git a/pyproject.toml b/pyproject.toml index 7c7d50d69..da92a9be8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -322,14 +322,26 @@ exclude = [ ".venv" ] # Ideally we would limit the paths to the source code where we want to ignore names, # but Vulture does not enable this. ignore_names = [ + # Model Target model option values which this library does not use + # itself, from vws.model_target_datasets + "ADAPTIVE", + "ALWAYS", + "AR_CONTROLLER", # Public API classes imported by users from vws.transports "AsyncHTTPXTransport", + "AUTO", # Sphinx "autoclass_content", "autoclass_content", "autodoc_member_order", + "CAR", "copybutton_exclude", + "DAE", + "DEFAULT", + "DYNAMIC", "extensions", + "FALSE", + "FBX", # pytest fixtures - we name fixtures like this for this purpose "fixture_*", "html_show_copyright", @@ -340,25 +352,34 @@ ignore_names = [ "html_title", "htmlhelp_basename", "HTTPXTransport", + "IGES", "intersphinx_mapping", "language", "linkcheck_ignore", "linkcheck_retries", + "LOW_FEATURE_OBJECTS", "master_doc", + "NEVER", "nitpick_ignore", "nitpicky", + "OBJ", "project_copyright", + "PVZ", "pygments_style", # pytest configuration "pytest_collect_file", "pytest_plugins", "rst_prolog", + "SCAN", "source_suffix", "spelling_word_list_filename", + "STATIC", + "STL", "templates_path", "towncrier_draft_autoversion_mode", "towncrier_draft_include_empty", "towncrier_draft_working_directory", + "VRML", "warning_is_error", ] diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt index 74e7d3856..09386f1d4 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -11,6 +11,7 @@ LicenseCheckFailed MatchProcessing MaxNumResultsOutOfRange MetadataTooLarge +OAuth OopsAnErrorOccurredPossiblyBadName OopsAnErrorOccurredPossiblyBadNameError ProjectHasNoApiAccess @@ -33,6 +34,7 @@ args ascii async asyncio +balancer beartype bool boolean @@ -71,6 +73,7 @@ json keyring kib kwargs +lifecycle linters linting login diff --git a/src/vws/__init__.py b/src/vws/__init__.py index a181cb818..b4d39cc04 100644 --- a/src/vws/__init__.py +++ b/src/vws/__init__.py @@ -1,8 +1,10 @@ """A library for Vuforia Web Services.""" +from .async_model_target_service import AsyncModelTargetService from .async_query import AsyncCloudRecoService from .async_vumark_service import AsyncVuMarkService from .async_vws import AsyncVWS +from .model_target_service import ModelTargetService from .query import CloudRecoService from .vumark_service import VuMarkService from .vws import VWS @@ -10,8 +12,10 @@ __all__ = [ "VWS", "AsyncCloudRecoService", + "AsyncModelTargetService", "AsyncVWS", "AsyncVuMarkService", "CloudRecoService", + "ModelTargetService", "VuMarkService", ] diff --git a/src/vws/_model_targets.py b/src/vws/_model_targets.py new file mode 100644 index 000000000..c391342c2 --- /dev/null +++ b/src/vws/_model_targets.py @@ -0,0 +1,323 @@ +"""Internal helpers for the Vuforia Model Target Web API.""" + +import base64 +import json +from collections.abc import Sequence # noqa: TC003 +from http import HTTPStatus +from typing import Any + +from beartype import BeartypeConf, beartype + +from vws.exceptions.custom_exceptions import ServerError +from vws.exceptions.model_target_exceptions import ( + ModelTargetAuthenticationError, + ModelTargetDatasetNotDoneError, + ModelTargetError, + ModelTargetOAuth2Error, + ModelTargetValidationError, + UnknownModelTargetDatasetError, +) +from vws.exceptions.vws_exceptions import TooManyRequestsError +from vws.model_target_datasets import ( # noqa: TC001 + ModelTargetDatasetType, + ModelTargetModel, + ModelTargetView, +) +from vws.reports import ModelTargetDatasetStatusReport +from vws.response import Response # noqa: TC001 + +OAUTH2_TOKEN_PATH = "/oauth2/token" # noqa: S105 +OAUTH2_TOKEN_BODY = b"grant_type=client_credentials" +OAUTH2_TOKEN_CONTENT_TYPE = "application/x-www-form-urlencoded" # noqa: S105 +JSON_CONTENT_TYPE = "application/json" + +_DATASET_COLLECTION_PATHS = { + "standard": "/modeltargets/datasets", + "advanced": "/modeltargets/advancedDatasets", +} +_EXCEPTIONS_BY_STATUS_CODE: dict[int, type[ModelTargetError]] = { + HTTPStatus.BAD_REQUEST: ModelTargetValidationError, + HTTPStatus.UNAUTHORIZED: ModelTargetAuthenticationError, + HTTPStatus.NOT_FOUND: UnknownModelTargetDatasetError, + HTTPStatus.UNPROCESSABLE_ENTITY: ModelTargetDatasetNotDoneError, +} + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def oauth2_token_headers( + *, client_id: str, client_secret: str +) -> dict[str, str]: + """Get the headers for a request for an access token. + + Args: + client_id: A Model Target Web API client ID. + client_secret: A Model Target Web API client secret. + + Returns: + The headers to send with a token request. + """ + credentials = f"{client_id}:{client_secret}".encode() + encoded_credentials = base64.b64encode(s=credentials).decode( + encoding="ascii", + ) + return { + "Authorization": f"Basic {encoded_credentials}", + "Content-Type": OAUTH2_TOKEN_CONTENT_TYPE, + } + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def access_token_from_response(*, response: Response) -> tuple[str, float]: + """Get an access token and its lifetime from a token response. + + Args: + response: The response from Vuforia's token endpoint. + + Returns: + The access token, and the number of seconds until it expires. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetOAuth2Error: + Vuforia did not give an access token. + """ + if response.status_code != HTTPStatus.OK: + raise ModelTargetOAuth2Error(response=response) + + response_data = dict(json.loads(s=response.text)) + return response_data["access_token"], float(response_data["expires_in"]) + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def dataset_collection_path(*, dataset_type: ModelTargetDatasetType) -> str: + """Get the path of the endpoint for datasets of a given type. + + Args: + dataset_type: The kind of dataset to get the path for. + + Returns: + The path of the dataset collection endpoint. + """ + return _DATASET_COLLECTION_PATHS[dataset_type.value] + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def dataset_path( + *, + dataset_type: ModelTargetDatasetType, + dataset_uuid: str, +) -> str: + """Get the path of the endpoint for one dataset. + + Args: + dataset_type: The kind of dataset to get the path for. + dataset_uuid: The UUID of the dataset. + + Returns: + The path of the dataset endpoint. + """ + collection_path = dataset_collection_path(dataset_type=dataset_type) + return f"{collection_path}/{dataset_uuid}" + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def dataset_status_path( + *, + dataset_type: ModelTargetDatasetType, + dataset_uuid: str, +) -> str: + """Get the path of the status endpoint for one dataset. + + Args: + dataset_type: The kind of dataset to get the path for. + dataset_uuid: The UUID of the dataset. + + Returns: + The path of the dataset status endpoint. + """ + return ( + dataset_path(dataset_type=dataset_type, dataset_uuid=dataset_uuid) + + "/status" + ) + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def dataset_download_path( + *, + dataset_type: ModelTargetDatasetType, + dataset_uuid: str, +) -> str: + """Get the path of the download endpoint for one dataset. + + Args: + dataset_type: The kind of dataset to get the path for. + dataset_uuid: The UUID of the dataset. + + Returns: + The path of the dataset download endpoint. + """ + return ( + dataset_path(dataset_type=dataset_type, dataset_uuid=dataset_uuid) + + "/dataset" + ) + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def _view_dict(*, view: ModelTargetView) -> dict[str, Any]: + """Get the request representation of a guide view. + + Args: + view: The guide view to represent. + + Returns: + The guide view, as it is sent to Vuforia. + """ + view_dict: dict[str, Any] = { + "name": view.name, + "guideViewPosition": { + "rotation": list(view.guide_view_position.rotation), + "translation": list(view.guide_view_position.translation), + }, + } + if view.states is not None: + view_dict["states"] = list(view.states) + + return view_dict + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def _model_dict(*, model: ModelTargetModel) -> dict[str, Any]: + """Get the request representation of a model. + + Args: + model: The model to represent. + + Returns: + The model, as it is sent to Vuforia. + """ + model_dict: dict[str, Any] = {"name": model.name} + optional_values: dict[str, str | None] = { + "automaticColoring": model.automatic_coloring, + "cadDataBlob": model.cad_data_blob, + "cadDataFormat": model.cad_data_format, + "cadDataUrl": model.cad_data_url, + "motionHint": model.motion_hint, + "optimizeTrackingFor": model.optimize_tracking_for, + "realisticAppearance": model.realistic_appearance, + "simplify": model.simplify, + "stateBasedConfigurationJsonString": ( + model.state_based_configuration_json_string + ), + "trackingMode": model.tracking_mode, + } + for field_name, value in optional_values.items(): + if value is not None: + model_dict[field_name] = str(object=value) + + if model.views is not None: + model_dict["views"] = [_view_dict(view=view) for view in model.views] + + return model_dict + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def dataset_request_body( + *, + name: str, + target_sdk: str, + models: Sequence[ModelTargetModel], +) -> bytes: + """Get the request body for creating a Model Target dataset. + + Args: + name: The name of the dataset. + target_sdk: The Vuforia Engine version to generate the dataset + for. + models: The models to generate the dataset from. + + Returns: + The body of the request. + """ + request_dict = { + "models": [_model_dict(model=model) for model in models], + "name": name, + "targetSdk": target_sdk, + } + return json.dumps(obj=request_dict).encode(encoding="utf-8") + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def raise_for_error(*, response: Response) -> None: + """Raise an exception for an unsuccessful Model Target Web API + response. + + Args: + response: A response from the Model Target Web API. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetAuthenticationError: + The request was not authenticated. + ~vws.exceptions.model_target_exceptions.ModelTargetValidationError: + Vuforia rejected the dataset creation request. + ~vws.exceptions.model_target_exceptions.UnknownModelTargetDatasetError: + No dataset of the given type matches the given UUID. + ~vws.exceptions.model_target_exceptions.ModelTargetDatasetNotDoneError: + The dataset has not been generated. + ~vws.exceptions.model_target_exceptions.ModelTargetError: Vuforia + returned another error. + ~vws.exceptions.custom_exceptions.ServerError: There is an error + with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is + rate limiting access. + """ + if ( + response.status_code == HTTPStatus.TOO_MANY_REQUESTS + ): # pragma: no cover + # The Vuforia API returns a 429 response with no JSON body. + raise TooManyRequestsError(response=response) + + if ( + response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR + ): # pragma: no cover + raise ServerError(response=response) + + if response.status_code < HTTPStatus.BAD_REQUEST: + return + + exception_type = _EXCEPTIONS_BY_STATUS_CODE.get( + response.status_code, + ModelTargetError, + ) + raise exception_type(response=response) + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def dataset_uuid_from_response(*, response: Response) -> str: + """Get the UUID of a created dataset. + + Args: + response: A response to a dataset creation request. + + Returns: + The UUID of the created dataset. + """ + response_data = dict(json.loads(s=response.text)) + return str(object=response_data["uuid"]) + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +def status_report_from_response( + *, + response: Response, +) -> ModelTargetDatasetStatusReport: + """Get a dataset status report from a status response. + + Args: + response: A response to a dataset status request. + + Returns: + The status of the dataset. + """ + response_data = dict(json.loads(s=response.text)) + return ModelTargetDatasetStatusReport.from_response_dict( + response_dict=response_data, + ) diff --git a/src/vws/async_model_target_service.py b/src/vws/async_model_target_service.py new file mode 100644 index 000000000..67a5e901c --- /dev/null +++ b/src/vws/async_model_target_service.py @@ -0,0 +1,381 @@ +"""Async interface to the Vuforia Model Target Web API.""" + +import asyncio +import time +from collections.abc import Sequence # noqa: TC003 +from http import HTTPMethod +from typing import Self + +from beartype import BeartypeConf, beartype + +from vws._model_targets import ( + JSON_CONTENT_TYPE, + OAUTH2_TOKEN_BODY, + OAUTH2_TOKEN_PATH, + access_token_from_response, + dataset_collection_path, + dataset_download_path, + dataset_path, + dataset_request_body, + dataset_status_path, + dataset_uuid_from_response, + oauth2_token_headers, + raise_for_error, + status_report_from_response, +) +from vws.exceptions.model_target_exceptions import ( + ModelTargetDatasetTimeoutError, +) +from vws.model_target_datasets import ( # noqa: TC001 + ModelTargetDatasetType, + ModelTargetModel, +) +from vws.reports import ( + ModelTargetDatasetStatuses, + ModelTargetDatasetStatusReport, +) +from vws.response import Response # noqa: TC001 +from vws.transports import AsyncHTTPXTransport, AsyncTransport + +_TOKEN_EXPIRY_MARGIN_SECONDS = 60.0 + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +class AsyncModelTargetService: + """An async interface to the Vuforia Model Target Web API.""" + + def __init__( + self, + *, + client_id: str, + client_secret: str, + base_vws_url: str = "https://vws.vuforia.com", + request_timeout_seconds: float | tuple[float, float] = 30.0, + transport: AsyncTransport | None = None, + ) -> None: + """ + Args: + client_id: A Model Target Web API OAuth2 client + ID. + client_secret: A Model Target Web API OAuth2 + client secret. + base_vws_url: The base URL for the VWS API, which + also serves the Model Target Web API. + request_timeout_seconds: The timeout for each + HTTP request. This can be a float to set both + the connect and read timeouts, or a + (connect, read) tuple. + transport: The async HTTP transport to use for + requests. Defaults to + ``AsyncHTTPXTransport()``. + """ + self._client_id = client_id + self._client_secret = client_secret + self._base_vws_url = base_vws_url + self._request_timeout_seconds = request_timeout_seconds + self._transport = ( + transport if transport is not None else AsyncHTTPXTransport() + ) + self._access_token: str | None = None + self._access_token_expiry_time = 0.0 + + async def aclose(self) -> None: + """Close the underlying transport if it supports closing.""" + await self._transport.aclose() + + async def __aenter__(self) -> Self: + """Enter the async context manager.""" + return self + + async def __aexit__(self, *_args: object) -> None: + """Exit the async context manager and close the transport.""" + await self.aclose() + + async def get_access_token(self) -> str: + """Get an OAuth2 access token for the Model Target Web API. + + A token is requested only when the client has no token which is + still valid, so this can be called before each request. + + Returns: + A bearer token. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetOAuth2Error: + Vuforia did not give an access token. For example, the + given client ID and client secret may not match a set of + Model Target Web API credentials. + """ + request_time = time.monotonic() + if ( + self._access_token is not None + and request_time < self._access_token_expiry_time + ): + return self._access_token + + response = await self._transport( + method=HTTPMethod.POST, + url=self._base_vws_url.rstrip("/") + OAUTH2_TOKEN_PATH, + headers=oauth2_token_headers( + client_id=self._client_id, + client_secret=self._client_secret, + ), + data=OAUTH2_TOKEN_BODY, + request_timeout=self._request_timeout_seconds, + ) + + access_token, expires_in_seconds = access_token_from_response( + response=response, + ) + self._access_token = access_token + self._access_token_expiry_time = ( + request_time + expires_in_seconds - _TOKEN_EXPIRY_MARGIN_SECONDS + ) + return access_token + + async def make_request( + self, + *, + method: str, + data: bytes, + request_path: str, + extra_headers: dict[str, str] | None = None, + ) -> Response: + """Make an authenticated request to the Model Target Web API. + + Args: + method: The HTTP method which will be used in + the request. + data: The request body which will be used in the + request. + request_path: The path to the endpoint which + will be used in the request. + extra_headers: Additional headers to include in + the request. + + Returns: + The response to the request. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetError: + Vuforia returned an error. + ~vws.exceptions.custom_exceptions.ServerError: + There is an error with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: + Vuforia is rate limiting access. + """ + access_token = await self.get_access_token() + headers = { + "Authorization": f"Bearer {access_token}", + **(extra_headers or {}), + } + + response = await self._transport( + method=method, + url=self._base_vws_url.rstrip("/") + request_path, + headers=headers, + data=data, + request_timeout=self._request_timeout_seconds, + ) + + raise_for_error(response=response) + return response + + async def create_dataset( + self, + *, + name: str, + target_sdk: str, + models: Sequence[ModelTargetModel], + dataset_type: ModelTargetDatasetType, + ) -> str: + """Start generating a Model Target dataset. + + Vuforia generates the dataset in the background, so it is not + available to download immediately. Use + :meth:`wait_for_dataset_generated` to wait for it. + + Args: + name: The name of the dataset. + target_sdk: The Vuforia Engine version to generate the dataset + for. + models: The models to generate the dataset from. A standard + dataset takes exactly one model. + dataset_type: Whether to create a standard or an advanced + dataset. + + Returns: + The UUID of the new dataset. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetAuthenticationError: + The request was not authenticated. + ~vws.exceptions.model_target_exceptions.ModelTargetValidationError: + Vuforia rejected the request. For example, a model may + give neither a CAD data URL nor a CAD data blob. + ~vws.exceptions.model_target_exceptions.ModelTargetOAuth2Error: + Vuforia did not give an access token. + """ + response = await self.make_request( + method=HTTPMethod.POST, + data=dataset_request_body( + name=name, + target_sdk=target_sdk, + models=models, + ), + request_path=dataset_collection_path(dataset_type=dataset_type), + extra_headers={"Content-Type": JSON_CONTENT_TYPE}, + ) + + return dataset_uuid_from_response(response=response) + + async def get_dataset_status( + self, + *, + dataset_uuid: str, + dataset_type: ModelTargetDatasetType, + ) -> ModelTargetDatasetStatusReport: + """Get the status of a Model Target dataset. + + Args: + dataset_uuid: The UUID of the dataset, as given by + :meth:`create_dataset`. + dataset_type: The kind of dataset to get the status of. + + Returns: + The status of the dataset. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetAuthenticationError: + The request was not authenticated. + ~vws.exceptions.model_target_exceptions.UnknownModelTargetDatasetError: + No dataset of the given type matches the given UUID. + ~vws.exceptions.model_target_exceptions.ModelTargetOAuth2Error: + Vuforia did not give an access token. + """ + response = await self.make_request( + method=HTTPMethod.GET, + data=b"", + request_path=dataset_status_path( + dataset_type=dataset_type, + dataset_uuid=dataset_uuid, + ), + ) + + return status_report_from_response(response=response) + + async def wait_for_dataset_generated( + self, + *, + dataset_uuid: str, + dataset_type: ModelTargetDatasetType, + seconds_between_requests: float = 0.2, + timeout_seconds: float = 60 * 5, + ) -> ModelTargetDatasetStatusReport: + """Wait for Vuforia to finish generating a Model Target dataset. + + A dataset which failed to generate is also finished, so the + returned report may have a + :attr:`~.ModelTargetDatasetStatusReport.status` of + ``FAILED``. + + Args: + dataset_uuid: The UUID of the dataset, as given by + :meth:`create_dataset`. + dataset_type: The kind of dataset to wait for. + seconds_between_requests: The number of seconds to wait between + requests made while polling the dataset's status. + timeout_seconds: The maximum number of seconds to wait for the + dataset to be generated. + + Returns: + The status of the dataset once it is no longer processing. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetDatasetTimeoutError: + The dataset was not generated within ``timeout_seconds`` + seconds. + ~vws.exceptions.model_target_exceptions.UnknownModelTargetDatasetError: + No dataset of the given type matches the given UUID. + """ + start_time = time.monotonic() + while True: + report = await self.get_dataset_status( + dataset_uuid=dataset_uuid, + dataset_type=dataset_type, + ) + if report.status != ModelTargetDatasetStatuses.PROCESSING: + return report + + elapsed_time = time.monotonic() - start_time + if elapsed_time > timeout_seconds: + raise ModelTargetDatasetTimeoutError + + await asyncio.sleep(delay=seconds_between_requests) + + async def download_dataset( + self, + *, + dataset_uuid: str, + dataset_type: ModelTargetDatasetType, + ) -> bytes: + """Download a generated Model Target dataset. + + Args: + dataset_uuid: The UUID of the dataset, as given by + :meth:`create_dataset`. + dataset_type: The kind of dataset to download. + + Returns: + The dataset, as the bytes of a zip file. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetAuthenticationError: + The request was not authenticated. + ~vws.exceptions.model_target_exceptions.UnknownModelTargetDatasetError: + No dataset of the given type matches the given UUID. + ~vws.exceptions.model_target_exceptions.ModelTargetDatasetNotDoneError: + Vuforia has not generated the dataset. + ~vws.exceptions.model_target_exceptions.ModelTargetOAuth2Error: + Vuforia did not give an access token. + """ + response = await self.make_request( + method=HTTPMethod.GET, + data=b"", + request_path=dataset_download_path( + dataset_type=dataset_type, + dataset_uuid=dataset_uuid, + ), + ) + + return response.content + + async def delete_dataset( + self, + *, + dataset_uuid: str, + dataset_type: ModelTargetDatasetType, + ) -> None: + """Delete a Model Target dataset. + + Args: + dataset_uuid: The UUID of the dataset, as given by + :meth:`create_dataset`. + dataset_type: The kind of dataset to delete. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetAuthenticationError: + The request was not authenticated. + ~vws.exceptions.model_target_exceptions.UnknownModelTargetDatasetError: + No dataset of the given type matches the given UUID. + ~vws.exceptions.model_target_exceptions.ModelTargetOAuth2Error: + Vuforia did not give an access token. + """ + await self.make_request( + method=HTTPMethod.DELETE, + data=b"", + request_path=dataset_path( + dataset_type=dataset_type, + dataset_uuid=dataset_uuid, + ), + ) diff --git a/src/vws/exceptions/model_target_exceptions.py b/src/vws/exceptions/model_target_exceptions.py new file mode 100644 index 000000000..8eaf09878 --- /dev/null +++ b/src/vws/exceptions/model_target_exceptions.py @@ -0,0 +1,210 @@ +"""Exceptions raised by the Vuforia Model Target Web API. + +See +https://developer.vuforia.com/library/vuforia-engine/web-api/model-target-web-api/. +""" + +import json +from typing import Any + +from beartype import beartype + +from vws.reports import ModelTargetGenerationDetail +from vws.response import Response # noqa: TC001 + + +@beartype +def _is_json_object(*, value: object) -> bool: + """Get whether a decoded JSON value is an object. + + Args: + value: A decoded JSON value. + + Returns: + Whether the value is a JSON object. + """ + return isinstance(value, dict) + + +@beartype +def _json_object(*, value: str) -> dict[str, Any]: + """Get a JSON object from a string. + + Args: + value: A string which may be a JSON object. + + Returns: + The JSON object, or an empty dictionary if the string is not a + JSON object. + """ + try: + loaded: Any = json.loads(s=value) + except json.JSONDecodeError: + return {} + + if not _is_json_object(value=loaded): + return {} + + json_object: dict[str, Any] = loaded + return json_object + + +@beartype +def _error_dict(*, response: Response) -> dict[str, Any]: + """Get the error object of a Model Target Web API error response. + + Args: + response: The response returned by Vuforia. + + Returns: + The error object, or an empty dictionary if the response has no + error object. Some errors, such as those given by the load + balancer in front of Vuforia, are not shaped like Model Target + Web API errors. + """ + body = _json_object(value=response.text) + if "error" not in body: + return {} + + error: Any = body["error"] + if not _is_json_object(value=error): + return {} + + error_dict: dict[str, Any] = error + return error_dict + + +@beartype +class ModelTargetError(Exception): + """Base class for Vuforia Model Target Web API exceptions.""" + + def __init__(self, response: Response) -> None: + """ + Args: + response: The response to a request to Vuforia. + """ + super().__init__(response.text) + self._response = response + + @property + def response(self) -> Response: + """The response returned by Vuforia which included this error.""" + return self._response + + @property + def code(self) -> str: + """The error code given by Vuforia, or an empty string.""" + error = _error_dict(response=self._response) + return str(object=error["code"]) if "code" in error else "" + + @property + def message(self) -> str: + """The error message given by Vuforia, or an empty string.""" + error = _error_dict(response=self._response) + return str(object=error["message"]) if "message" in error else "" + + @property + def target(self) -> str: + """The error target given by Vuforia, or an empty string.""" + error = _error_dict(response=self._response) + return str(object=error["target"]) if "target" in error else "" + + @property + def details(self) -> list[ModelTargetGenerationDetail]: + """The error details given by Vuforia. + + Vuforia gives one detail per validation problem it found with a + dataset creation request. + """ + error = _error_dict(response=self._response) + if "details" not in error: + return [] + + return [ + ModelTargetGenerationDetail( + code=detail["code"], + message=detail["message"], + ) + for detail in error["details"] + ] + + +@beartype +class ModelTargetAuthenticationError(ModelTargetError): + """Exception raised when a Model Target Web API request is not + authenticated. + + For example, the bearer token may be missing, malformed or expired. + """ + + +@beartype +class ModelTargetValidationError(ModelTargetError): + """Exception raised when Vuforia rejects a Model Target dataset + creation + request. + + See :attr:`~.ModelTargetError.details` for the problems which Vuforia + found. + """ + + +@beartype +class UnknownModelTargetDatasetError(ModelTargetError): + """Exception raised when no Model Target dataset matches a given UUID. + + Standard and advanced datasets are separate resources, so this is also + raised when the given UUID matches a dataset of the other type. + """ + + +@beartype +class ModelTargetDatasetNotDoneError(ModelTargetError): + """Exception raised when a Model Target dataset is downloaded before + Vuforia has generated it. + """ + + +@beartype +class ModelTargetOAuth2Error(Exception): + """Exception raised when Vuforia does not give an access token. + + For example, the given client ID and client secret may not match a set + of Model Target Web API credentials. + """ + + def __init__(self, response: Response) -> None: + """ + Args: + response: The response to a request to Vuforia's token + endpoint. + """ + super().__init__(response.text) + self._response = response + + @property + def response(self) -> Response: + """The response returned by Vuforia which included this error.""" + return self._response + + @property + def error(self) -> str: + """The OAuth2 error code, or an empty string.""" + body = _json_object(value=self._response.text) + return str(object=body["error"]) if "error" in body else "" + + @property + def error_description(self) -> str: + """The OAuth2 error description, or an empty string.""" + body = _json_object(value=self._response.text) + if "error_description" not in body: + return "" + + return str(object=body["error_description"]) + + +@beartype +class ModelTargetDatasetTimeoutError(Exception): + """Exception raised when waiting for a Model Target dataset to be + generated times out. + """ diff --git a/src/vws/model_target_datasets.py b/src/vws/model_target_datasets.py new file mode 100644 index 000000000..95a9eaa18 --- /dev/null +++ b/src/vws/model_target_datasets.py @@ -0,0 +1,152 @@ +"""Structures for describing Model Target datasets to create. + +See +https://developer.vuforia.com/library/vuforia-engine/web-api/model-target-web-api/. +""" + +from collections.abc import Sequence # noqa: TC003 +from dataclasses import dataclass +from enum import StrEnum, unique + +from beartype import BeartypeConf, beartype + + +@beartype +@unique +class ModelTargetDatasetType(StrEnum): + """The kinds of Model Target dataset which Vuforia generates. + + Standard and advanced datasets are separate resources, so a dataset + created as one type is not visible to requests for the other type. + """ + + STANDARD = "standard" + ADVANCED = "advanced" + + +@beartype +@unique +class AutomaticColoring(StrEnum): + """Options for a model's ``automaticColoring``.""" + + ALWAYS = "always" + AUTO = "auto" + NEVER = "never" + + +@beartype +@unique +class CadDataFormat(StrEnum): + """Options for a model's ``cadDataFormat``.""" + + DAE = "DAE" + FBX = "FBX" + GLB = "GLB" + IGES = "IGES" + OBJ = "OBJ" + PVZ = "PVZ" + STL = "STL" + VRML = "VRML" + ZIP = "ZIP" + + +@beartype +@unique +class MotionHint(StrEnum): + """Options for a model's ``motionHint``.""" + + ADAPTIVE = "adaptive" + DYNAMIC = "dynamic" + STATIC = "static" + + +@beartype +@unique +class OptimizeTrackingFor(StrEnum): + """Options for a model's ``optimizeTrackingFor``.""" + + AR_CONTROLLER = "ar_controller" + DEFAULT = "default" + LOW_FEATURE_OBJECTS = "low_feature_objects" + + +@beartype +@unique +class RealisticAppearance(StrEnum): + """Options for a model's ``realisticAppearance``. + + This is documented for advanced datasets only. + """ + + AUTO = "auto" + FALSE = "false" + TRUE = "true" + + +@beartype +@unique +class Simplify(StrEnum): + """Options for a model's ``simplify``.""" + + ALWAYS = "always" + AUTO = "auto" + NEVER = "never" + + +@beartype +@unique +class TrackingMode(StrEnum): + """Options for a model's ``trackingMode``.""" + + CAR = "car" + DEFAULT = "default" + SCAN = "scan" + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +@dataclass(frozen=True, kw_only=True) +class GuideViewPosition: + """The position of a guide view.""" + + rotation: Sequence[float] + translation: Sequence[float] + + +@beartype +@dataclass(frozen=True, kw_only=True) +class ModelTargetView: + """A guide view of a model.""" + + name: str + guide_view_position: GuideViewPosition + states: Sequence[str] | None = None + """The State-Based Model Target states which this view applies to. + + Every given state must be named by the model's + ``state_based_configuration_json_string``. + """ + + +@beartype +@dataclass(frozen=True, kw_only=True) +class ModelTargetModel: + """A model to generate a Model Target dataset from. + + One and only one of ``cad_data_url`` and ``cad_data_blob`` is + required. + """ + + name: str + cad_data_url: str | None = None + cad_data_blob: str | None = None + automatic_coloring: AutomaticColoring | None = None + cad_data_format: CadDataFormat | None = None + motion_hint: MotionHint | None = None + optimize_tracking_for: OptimizeTrackingFor | None = None + realistic_appearance: RealisticAppearance | None = None + """This is documented for advanced datasets only.""" + + simplify: Simplify | None = None + tracking_mode: TrackingMode | None = None + state_based_configuration_json_string: str | None = None + views: Sequence[ModelTargetView] | None = None diff --git a/src/vws/model_target_service.py b/src/vws/model_target_service.py new file mode 100644 index 000000000..70b116d0a --- /dev/null +++ b/src/vws/model_target_service.py @@ -0,0 +1,366 @@ +"""Interface to the Vuforia Model Target Web API.""" + +import time +from collections.abc import Sequence # noqa: TC003 +from http import HTTPMethod + +from beartype import BeartypeConf, beartype + +from vws._model_targets import ( + JSON_CONTENT_TYPE, + OAUTH2_TOKEN_BODY, + OAUTH2_TOKEN_PATH, + access_token_from_response, + dataset_collection_path, + dataset_download_path, + dataset_path, + dataset_request_body, + dataset_status_path, + dataset_uuid_from_response, + oauth2_token_headers, + raise_for_error, + status_report_from_response, +) +from vws.exceptions.model_target_exceptions import ( + ModelTargetDatasetTimeoutError, +) +from vws.model_target_datasets import ( # noqa: TC001 + ModelTargetDatasetType, + ModelTargetModel, +) +from vws.reports import ( + ModelTargetDatasetStatuses, + ModelTargetDatasetStatusReport, +) +from vws.response import Response # noqa: TC001 +from vws.transports import RequestsTransport, Transport + +_TOKEN_EXPIRY_MARGIN_SECONDS = 60.0 + + +@beartype(conf=BeartypeConf(is_pep484_tower=True)) +class ModelTargetService: + """An interface to the Vuforia Model Target Web API.""" + + def __init__( + self, + *, + client_id: str, + client_secret: str, + base_vws_url: str = "https://vws.vuforia.com", + request_timeout_seconds: float | tuple[float, float] = 30.0, + transport: Transport | None = None, + ) -> None: + """ + Args: + client_id: A Model Target Web API OAuth2 client + ID. + client_secret: A Model Target Web API OAuth2 + client secret. + base_vws_url: The base URL for the VWS API, which + also serves the Model Target Web API. + request_timeout_seconds: The timeout for each + HTTP request. This can be a float to set both + the connect and read timeouts, or a + (connect, read) tuple. + transport: The HTTP transport to use for + requests. Defaults to + ``RequestsTransport()``. + """ + self._client_id = client_id + self._client_secret = client_secret + self._base_vws_url = base_vws_url + self._request_timeout_seconds = request_timeout_seconds + self._transport = ( + transport if transport is not None else RequestsTransport() + ) + self._access_token: str | None = None + self._access_token_expiry_time = 0.0 + + def get_access_token(self) -> str: + """Get an OAuth2 access token for the Model Target Web API. + + A token is requested only when the client has no token which is + still valid, so this can be called before each request. + + Returns: + A bearer token. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetOAuth2Error: + Vuforia did not give an access token. For example, the + given client ID and client secret may not match a set of + Model Target Web API credentials. + """ + request_time = time.monotonic() + if ( + self._access_token is not None + and request_time < self._access_token_expiry_time + ): + return self._access_token + + response = self._transport( + method=HTTPMethod.POST, + url=self._base_vws_url.rstrip("/") + OAUTH2_TOKEN_PATH, + headers=oauth2_token_headers( + client_id=self._client_id, + client_secret=self._client_secret, + ), + data=OAUTH2_TOKEN_BODY, + request_timeout=self._request_timeout_seconds, + ) + + access_token, expires_in_seconds = access_token_from_response( + response=response, + ) + self._access_token = access_token + self._access_token_expiry_time = ( + request_time + expires_in_seconds - _TOKEN_EXPIRY_MARGIN_SECONDS + ) + return access_token + + def make_request( + self, + *, + method: str, + data: bytes, + request_path: str, + extra_headers: dict[str, str] | None = None, + ) -> Response: + """Make an authenticated request to the Model Target Web API. + + Args: + method: The HTTP method which will be used in + the request. + data: The request body which will be used in the + request. + request_path: The path to the endpoint which + will be used in the request. + extra_headers: Additional headers to include in + the request. + + Returns: + The response to the request. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetError: + Vuforia returned an error. + ~vws.exceptions.custom_exceptions.ServerError: + There is an error with Vuforia's servers. + ~vws.exceptions.vws_exceptions.TooManyRequestsError: + Vuforia is rate limiting access. + """ + headers = { + "Authorization": f"Bearer {self.get_access_token()}", + **(extra_headers or {}), + } + + response = self._transport( + method=method, + url=self._base_vws_url.rstrip("/") + request_path, + headers=headers, + data=data, + request_timeout=self._request_timeout_seconds, + ) + + raise_for_error(response=response) + return response + + def create_dataset( + self, + *, + name: str, + target_sdk: str, + models: Sequence[ModelTargetModel], + dataset_type: ModelTargetDatasetType, + ) -> str: + """Start generating a Model Target dataset. + + Vuforia generates the dataset in the background, so it is not + available to download immediately. Use + :meth:`wait_for_dataset_generated` to wait for it. + + Args: + name: The name of the dataset. + target_sdk: The Vuforia Engine version to generate the dataset + for. + models: The models to generate the dataset from. A standard + dataset takes exactly one model. + dataset_type: Whether to create a standard or an advanced + dataset. + + Returns: + The UUID of the new dataset. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetAuthenticationError: + The request was not authenticated. + ~vws.exceptions.model_target_exceptions.ModelTargetValidationError: + Vuforia rejected the request. For example, a model may + give neither a CAD data URL nor a CAD data blob. + ~vws.exceptions.model_target_exceptions.ModelTargetOAuth2Error: + Vuforia did not give an access token. + """ + response = self.make_request( + method=HTTPMethod.POST, + data=dataset_request_body( + name=name, + target_sdk=target_sdk, + models=models, + ), + request_path=dataset_collection_path(dataset_type=dataset_type), + extra_headers={"Content-Type": JSON_CONTENT_TYPE}, + ) + + return dataset_uuid_from_response(response=response) + + def get_dataset_status( + self, + *, + dataset_uuid: str, + dataset_type: ModelTargetDatasetType, + ) -> ModelTargetDatasetStatusReport: + """Get the status of a Model Target dataset. + + Args: + dataset_uuid: The UUID of the dataset, as given by + :meth:`create_dataset`. + dataset_type: The kind of dataset to get the status of. + + Returns: + The status of the dataset. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetAuthenticationError: + The request was not authenticated. + ~vws.exceptions.model_target_exceptions.UnknownModelTargetDatasetError: + No dataset of the given type matches the given UUID. + ~vws.exceptions.model_target_exceptions.ModelTargetOAuth2Error: + Vuforia did not give an access token. + """ + response = self.make_request( + method=HTTPMethod.GET, + data=b"", + request_path=dataset_status_path( + dataset_type=dataset_type, + dataset_uuid=dataset_uuid, + ), + ) + + return status_report_from_response(response=response) + + def wait_for_dataset_generated( + self, + *, + dataset_uuid: str, + dataset_type: ModelTargetDatasetType, + seconds_between_requests: float = 0.2, + timeout_seconds: float = 60 * 5, + ) -> ModelTargetDatasetStatusReport: + """Wait for Vuforia to finish generating a Model Target dataset. + + A dataset which failed to generate is also finished, so the + returned report may have a + :attr:`~.ModelTargetDatasetStatusReport.status` of + ``FAILED``. + + Args: + dataset_uuid: The UUID of the dataset, as given by + :meth:`create_dataset`. + dataset_type: The kind of dataset to wait for. + seconds_between_requests: The number of seconds to wait between + requests made while polling the dataset's status. + timeout_seconds: The maximum number of seconds to wait for the + dataset to be generated. + + Returns: + The status of the dataset once it is no longer processing. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetDatasetTimeoutError: + The dataset was not generated within ``timeout_seconds`` + seconds. + ~vws.exceptions.model_target_exceptions.UnknownModelTargetDatasetError: + No dataset of the given type matches the given UUID. + """ + start_time = time.monotonic() + while True: + report = self.get_dataset_status( + dataset_uuid=dataset_uuid, + dataset_type=dataset_type, + ) + if report.status != ModelTargetDatasetStatuses.PROCESSING: + return report + + elapsed_time = time.monotonic() - start_time + if elapsed_time > timeout_seconds: + raise ModelTargetDatasetTimeoutError + + time.sleep(seconds_between_requests) + + def download_dataset( + self, + *, + dataset_uuid: str, + dataset_type: ModelTargetDatasetType, + ) -> bytes: + """Download a generated Model Target dataset. + + Args: + dataset_uuid: The UUID of the dataset, as given by + :meth:`create_dataset`. + dataset_type: The kind of dataset to download. + + Returns: + The dataset, as the bytes of a zip file. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetAuthenticationError: + The request was not authenticated. + ~vws.exceptions.model_target_exceptions.UnknownModelTargetDatasetError: + No dataset of the given type matches the given UUID. + ~vws.exceptions.model_target_exceptions.ModelTargetDatasetNotDoneError: + Vuforia has not generated the dataset. + ~vws.exceptions.model_target_exceptions.ModelTargetOAuth2Error: + Vuforia did not give an access token. + """ + response = self.make_request( + method=HTTPMethod.GET, + data=b"", + request_path=dataset_download_path( + dataset_type=dataset_type, + dataset_uuid=dataset_uuid, + ), + ) + + return response.content + + def delete_dataset( + self, + *, + dataset_uuid: str, + dataset_type: ModelTargetDatasetType, + ) -> None: + """Delete a Model Target dataset. + + Args: + dataset_uuid: The UUID of the dataset, as given by + :meth:`create_dataset`. + dataset_type: The kind of dataset to delete. + + Raises: + ~vws.exceptions.model_target_exceptions.ModelTargetAuthenticationError: + The request was not authenticated. + ~vws.exceptions.model_target_exceptions.UnknownModelTargetDatasetError: + No dataset of the given type matches the given UUID. + ~vws.exceptions.model_target_exceptions.ModelTargetOAuth2Error: + Vuforia did not give an access token. + """ + self.make_request( + method=HTTPMethod.DELETE, + data=b"", + request_path=dataset_path( + dataset_type=dataset_type, + dataset_uuid=dataset_uuid, + ), + ) diff --git a/src/vws/reports.py b/src/vws/reports.py index 66a9685ed..2be447b03 100644 --- a/src/vws/reports.py +++ b/src/vws/reports.py @@ -217,6 +217,138 @@ def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: ) +@beartype +@unique +class ModelTargetDatasetStatuses(Enum): + """Constants representing Model Target dataset generation statuses. + + See the 'status' field of the dataset status response at + https://developer.vuforia.com/library/vuforia-engine/web-api/model-target-web-api/. + """ + + PROCESSING = "processing" + DONE = "done" + FAILED = "failed" + + +@beartype +@dataclass(frozen=True, kw_only=True) +class ModelTargetGenerationDetail: + """One detail of a Model Target dataset generation warning.""" + + code: str + message: str + + +@beartype +@dataclass(frozen=True, kw_only=True) +class ModelTargetGenerationError: + """The reason a Model Target dataset failed to generate.""" + + code: str + message: str + + +@beartype +@dataclass(frozen=True, kw_only=True) +class ModelTargetGenerationWarning: + """A warning about a generated Model Target dataset. + + A dataset with a warning is generated, and can be downloaded. + """ + + code: str + message: str + target: str + details: Sequence[ModelTargetGenerationDetail] + + +@beartype +@dataclass(frozen=True, kw_only=True) +class ModelTargetDatasetStatusReport: + """The status of a Model Target dataset. + + See + https://developer.vuforia.com/library/vuforia-engine/web-api/model-target-web-api/. + """ + + status: ModelTargetDatasetStatuses + dataset_uuid: str + created_at: datetime.datetime + eta: datetime.datetime | None + """When Vuforia expects to finish generating the dataset. + + This is given only while the dataset is processing. + """ + + completed_at: datetime.datetime | None + """When Vuforia finished generating the dataset. + + This is given only once the dataset is no longer processing. + """ + + error: ModelTargetGenerationError | None + """Why the dataset failed to generate. + + This is given only for a failed dataset. + """ + + warning: ModelTargetGenerationWarning | None + """A warning about the generated dataset. + + This is given only for a generated dataset which has a warning. + """ + + @classmethod + def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: + """Construct from a Model Target Web API response dict.""" + error: ModelTargetGenerationError | None = None + if "error" in response_dict: + error_dict = dict(response_dict["error"]) + error = ModelTargetGenerationError( + code=error_dict["code"], + message=error_dict["message"], + ) + + warning: ModelTargetGenerationWarning | None = None + if "warning" in response_dict: + warning_dict = dict(response_dict["warning"]) + warning = ModelTargetGenerationWarning( + code=warning_dict["code"], + message=warning_dict["message"], + target=warning_dict["target"], + details=[ + ModelTargetGenerationDetail( + code=detail["code"], + message=detail["message"], + ) + for detail in warning_dict["details"] + ], + ) + + eta: datetime.datetime | None = None + if "eta" in response_dict: + eta = datetime.datetime.fromisoformat(response_dict["eta"]) + + completed_at: datetime.datetime | None = None + if "completedAt" in response_dict: + completed_at = datetime.datetime.fromisoformat( + response_dict["completedAt"], + ) + + return cls( + status=ModelTargetDatasetStatuses(value=response_dict["status"]), + dataset_uuid=response_dict["uuid"], + created_at=datetime.datetime.fromisoformat( + response_dict["createdAt"], + ), + eta=eta, + completed_at=completed_at, + error=error, + warning=warning, + ) + + @beartype @dataclass(frozen=True, kw_only=True) class RecoCount: diff --git a/tests/conftest.py b/tests/conftest.py index d94099ff8..74d4137be 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,11 +15,24 @@ from vws import ( VWS, AsyncCloudRecoService, + AsyncModelTargetService, AsyncVuMarkService, AsyncVWS, CloudRecoService, + ModelTargetService, VuMarkService, ) +from vws.model_target_datasets import ( + CadDataFormat, + GuideViewPosition, + ModelTargetModel, + ModelTargetView, +) + +# The mock accepts one hard-coded pair of Model Target Web API OAuth2 +# credentials, which it does not expose. +_MODEL_TARGET_CLIENT_ID = "client-id" +_MODEL_TARGET_CLIENT_SECRET = "client-secret" # noqa: S105 @pytest.fixture(name="_mock_database") @@ -124,6 +137,62 @@ async def async_vumark_service_client( yield client +@pytest.fixture(name="_mock_model_targets") +def fixture_mock_model_targets() -> Generator[None]: + """Yield a mock which serves the Model Target Web API. + + The Model Target Web API is not tied to a VWS database, so no + database is added. + """ + # We use a low processing time so that tests run quickly. + with MockVWS(processing_time_seconds=0.2): + yield + + +@pytest.fixture +def model_target_client( + *, + _mock_model_targets: None, +) -> ModelTargetService: + """A ``ModelTargetService`` client which connects to a mock.""" + return ModelTargetService( + client_id=_MODEL_TARGET_CLIENT_ID, + client_secret=_MODEL_TARGET_CLIENT_SECRET, + ) + + +@pytest_asyncio.fixture +async def async_model_target_client( + *, + _mock_model_targets: None, +) -> AsyncGenerator[AsyncModelTargetService]: + """An async ``ModelTargetService`` client which connects to a mock.""" + async with AsyncModelTargetService( + client_id=_MODEL_TARGET_CLIENT_ID, + client_secret=_MODEL_TARGET_CLIENT_SECRET, + ) as client: + yield client + + +@pytest.fixture(name="model_target_model") +def fixture_model_target_model() -> ModelTargetModel: + """A model which Vuforia accepts for dataset creation.""" + return ModelTargetModel( + name="model", + cad_data_url="https://example.com/model.zip", + cad_data_format=CadDataFormat.ZIP, + views=[ + ModelTargetView( + name="front", + guide_view_position=GuideViewPosition( + rotation=[0.0, 0.0, 0.0, 1.0], + translation=[0.0, 0.0, 1.0], + ), + ), + ], + ) + + @pytest.fixture(name="current_month") def fixture_current_month() -> datetime.date: """The current month, as the first day of that month.""" diff --git a/tests/test_async_model_targets.py b/tests/test_async_model_targets.py new file mode 100644 index 000000000..4bcd6a2f3 --- /dev/null +++ b/tests/test_async_model_targets.py @@ -0,0 +1,409 @@ +"""Tests for the async Model Target Web API client.""" + +import io +import json +import uuid +import zipfile +from http import HTTPStatus + +import pytest +from mock_vws import ( + MockVWS, + ModelTargetGenerationFailure, + ModelTargetGenerationWarning, +) + +from vws import AsyncModelTargetService +from vws.exceptions.model_target_exceptions import ( + ModelTargetDatasetNotDoneError, + ModelTargetDatasetTimeoutError, + ModelTargetOAuth2Error, + ModelTargetValidationError, + UnknownModelTargetDatasetError, +) +from vws.model_target_datasets import ( + CadDataFormat, + ModelTargetDatasetType, + ModelTargetModel, + RealisticAppearance, +) +from vws.reports import ModelTargetDatasetStatuses + +# The mock accepts one hard-coded pair of Model Target Web API OAuth2 +# credentials, which it does not expose. +_CLIENT_ID = "client-id" +_CLIENT_SECRET = "client-secret" # noqa: S105 + +_DATASET_TYPES = [ + ModelTargetDatasetType.STANDARD, + ModelTargetDatasetType.ADVANCED, +] + + +class TestAccessToken: + """Tests for getting an access token.""" + + @staticmethod + @pytest.mark.asyncio + async def test_token_is_a_bearer_token( + *, + async_model_target_client: AsyncModelTargetService, + ) -> None: + """An access token is given for valid credentials.""" + assert await async_model_target_client.get_access_token() + + @staticmethod + @pytest.mark.asyncio + @pytest.mark.usefixtures("_mock_model_targets") + async def test_invalid_credentials() -> None: + """An exception is raised when the credentials are not known.""" + async with AsyncModelTargetService( + client_id="not-a-client-id", + client_secret="not-a-client-secret", # noqa: S106 + ) as client: + with pytest.raises( + expected_exception=ModelTargetOAuth2Error, + ) as exc: + await client.get_access_token() + + assert exc.value.response.status_code == HTTPStatus.UNAUTHORIZED + assert exc.value.error == "invalid_client" + + +class TestDatasetLifecycle: + """Tests for the dataset lifecycle.""" + + @staticmethod + @pytest.mark.asyncio + @pytest.mark.parametrize( + argnames="dataset_type", + argvalues=_DATASET_TYPES, + ) + async def test_create_wait_download_delete( + *, + async_model_target_client: AsyncModelTargetService, + model_target_model: ModelTargetModel, + dataset_type: ModelTargetDatasetType, + ) -> None: + """A dataset can be created, downloaded and then deleted.""" + dataset_uuid = await async_model_target_client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=dataset_type, + ) + + report = await async_model_target_client.wait_for_dataset_generated( + dataset_uuid=dataset_uuid, + dataset_type=dataset_type, + ) + + assert report.status == ModelTargetDatasetStatuses.DONE + assert report.dataset_uuid == dataset_uuid + assert report.completed_at is not None + assert report.eta is None + assert report.error is None + assert report.warning is None + + dataset = await async_model_target_client.download_dataset( + dataset_uuid=dataset_uuid, + dataset_type=dataset_type, + ) + + with zipfile.ZipFile( + file=io.BytesIO(initial_bytes=dataset) + ) as archive: + dataset_json = json.loads(s=archive.read(name="dataset.json")) + + assert dataset_json["uuid"] == dataset_uuid + assert dataset_json["type"] == dataset_type.value + + await async_model_target_client.delete_dataset( + dataset_uuid=dataset_uuid, + dataset_type=dataset_type, + ) + + with pytest.raises(expected_exception=UnknownModelTargetDatasetError): + await async_model_target_client.get_dataset_status( + dataset_uuid=dataset_uuid, + dataset_type=dataset_type, + ) + + @staticmethod + @pytest.mark.asyncio + async def test_status_while_processing( + *, + async_model_target_client: AsyncModelTargetService, + model_target_model: ModelTargetModel, + ) -> None: + """A processing dataset has an estimated completion time.""" + dataset_uuid = await async_model_target_client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + report = await async_model_target_client.get_dataset_status( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert report.status == ModelTargetDatasetStatuses.PROCESSING + assert report.eta is not None + assert report.completed_at is None + + @staticmethod + @pytest.mark.asyncio + async def test_download_while_processing( + *, + async_model_target_client: AsyncModelTargetService, + model_target_model: ModelTargetModel, + ) -> None: + """A dataset cannot be downloaded before it is generated.""" + dataset_uuid = await async_model_target_client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + with pytest.raises( + expected_exception=ModelTargetDatasetNotDoneError, + ) as exc: + await async_model_target_client.download_dataset( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert ( + exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + ) + assert exc.value.code == "UNSUPPORTED_STATE" + + @staticmethod + @pytest.mark.asyncio + async def test_dataset_types_are_separate( + *, + async_model_target_client: AsyncModelTargetService, + model_target_model: ModelTargetModel, + ) -> None: + """A dataset is not visible to requests for the other type.""" + dataset_uuid = await async_model_target_client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.ADVANCED, + ) + + with pytest.raises(expected_exception=UnknownModelTargetDatasetError): + await async_model_target_client.get_dataset_status( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + @staticmethod + @pytest.mark.asyncio + async def test_advanced_dataset_takes_multiple_models( + *, + async_model_target_client: AsyncModelTargetService, + model_target_model: ModelTargetModel, + ) -> None: + """An advanced dataset can be generated from multiple models.""" + other_model = ModelTargetModel( + name="other-model", + cad_data_blob="ZmFrZS1jYWQtZGF0YQ==", + cad_data_format=CadDataFormat.GLB, + realistic_appearance=RealisticAppearance.TRUE, + ) + + assert await async_model_target_client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model, other_model], + dataset_type=ModelTargetDatasetType.ADVANCED, + ) + + +class TestUnknownDataset: + """Tests for requests for datasets which do not exist.""" + + @staticmethod + @pytest.mark.asyncio + async def test_get_status( + *, + async_model_target_client: AsyncModelTargetService, + ) -> None: + """An exception is raised for an unknown dataset.""" + dataset_uuid = uuid.uuid4().hex + with pytest.raises( + expected_exception=UnknownModelTargetDatasetError, + ) as exc: + await async_model_target_client.get_dataset_status( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert exc.value.response.status_code == HTTPStatus.NOT_FOUND + assert dataset_uuid in exc.value.message + + @staticmethod + @pytest.mark.asyncio + async def test_download( + *, + async_model_target_client: AsyncModelTargetService, + ) -> None: + """An exception is raised for an unknown dataset.""" + with pytest.raises(expected_exception=UnknownModelTargetDatasetError): + await async_model_target_client.download_dataset( + dataset_uuid=uuid.uuid4().hex, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + @staticmethod + @pytest.mark.asyncio + async def test_delete( + *, + async_model_target_client: AsyncModelTargetService, + ) -> None: + """An exception is raised for an unknown dataset.""" + with pytest.raises(expected_exception=UnknownModelTargetDatasetError): + await async_model_target_client.delete_dataset( + dataset_uuid=uuid.uuid4().hex, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + +class TestValidation: + """Tests for requests which Vuforia rejects.""" + + @staticmethod + @pytest.mark.asyncio + async def test_no_cad_data( + *, + async_model_target_client: AsyncModelTargetService, + ) -> None: + """A model needs exactly one CAD data source.""" + with pytest.raises( + expected_exception=ModelTargetValidationError, + ) as exc: + await async_model_target_client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[ModelTargetModel(name="model")], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert exc.value.response.status_code == HTTPStatus.BAD_REQUEST + (detail,) = exc.value.details + assert detail.code == "VALIDATION_ERROR" + + +class TestGenerationResult: + """Tests for datasets which Vuforia does not generate cleanly.""" + + @staticmethod + @pytest.mark.asyncio + async def test_generation_failure( + *, + model_target_model: ModelTargetModel, + ) -> None: + """A dataset which fails to generate reports the failure.""" + message = "Model Target dataset generation failed" + failure = ModelTargetGenerationFailure(message=message) + with MockVWS( + processing_time_seconds=0.2, + model_target_generation_failure=failure, + ): + async with AsyncModelTargetService( + client_id=_CLIENT_ID, + client_secret=_CLIENT_SECRET, + ) as client: + dataset_uuid = await client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + report = await client.wait_for_dataset_generated( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert report.status == ModelTargetDatasetStatuses.FAILED + assert report.error is not None + assert report.error.message == message + + @staticmethod + @pytest.mark.asyncio + async def test_generation_warning( + *, + model_target_model: ModelTargetModel, + ) -> None: + """A dataset which generates with a warning reports the + warning. + """ + warning = ModelTargetGenerationWarning() + with MockVWS( + processing_time_seconds=0.2, + model_target_generation_warning=warning, + ): + async with AsyncModelTargetService( + client_id=_CLIENT_ID, + client_secret=_CLIENT_SECRET, + ) as client: + dataset_uuid = await client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + report = await client.wait_for_dataset_generated( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert report.status == ModelTargetDatasetStatuses.DONE + assert report.warning is not None + assert report.warning.target == dataset_uuid + (detail,) = report.warning.details + assert detail.code == "LOW_RECOGNITION_QUALITY" + + +class TestWaitForDatasetGenerated: + """Tests for waiting for a dataset to be generated.""" + + @staticmethod + @pytest.mark.asyncio + async def test_timeout(*, model_target_model: ModelTargetModel) -> None: + """An exception is raised when the wait times out.""" + with MockVWS(processing_time_seconds=60): + async with AsyncModelTargetService( + client_id=_CLIENT_ID, + client_secret=_CLIENT_SECRET, + ) as client: + dataset_uuid = await client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + with pytest.raises( + expected_exception=ModelTargetDatasetTimeoutError, + ): + await client.wait_for_dataset_generated( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + seconds_between_requests=0.01, + timeout_seconds=0.05, + ) + + report = await client.get_dataset_status( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert report.status == ModelTargetDatasetStatuses.PROCESSING diff --git a/tests/test_model_targets.py b/tests/test_model_targets.py new file mode 100644 index 000000000..4ee13db6e --- /dev/null +++ b/tests/test_model_targets.py @@ -0,0 +1,802 @@ +"""Tests for the Model Target Web API client.""" + +import io +import json +import uuid +import zipfile +from http import HTTPStatus + +import pytest +from beartype import beartype +from freezegun import freeze_time +from mock_vws import ( + MockVWS, + ModelTargetGenerationFailure, + ModelTargetGenerationWarning, +) + +from vws import ModelTargetService +from vws.exceptions.model_target_exceptions import ( + ModelTargetAuthenticationError, + ModelTargetDatasetNotDoneError, + ModelTargetDatasetTimeoutError, + ModelTargetError, + ModelTargetOAuth2Error, + ModelTargetValidationError, + UnknownModelTargetDatasetError, +) +from vws.model_target_datasets import ( + CadDataFormat, + GuideViewPosition, + ModelTargetDatasetType, + ModelTargetModel, + ModelTargetView, + RealisticAppearance, +) +from vws.reports import ModelTargetDatasetStatuses +from vws.response import Response +from vws.transports import RequestsTransport, Transport + +# The mock accepts one hard-coded pair of Model Target Web API OAuth2 +# credentials, which it does not expose. +_CLIENT_ID = "client-id" +_CLIENT_SECRET = "client-secret" # noqa: S105 + +_DATASET_TYPES = [ + ModelTargetDatasetType.STANDARD, + ModelTargetDatasetType.ADVANCED, +] + + +@beartype +def _response(*, text: str) -> Response: + """Get a response with a given body. + + Args: + text: The body of the response. + + Returns: + A response with the given body. + """ + content = text.encode(encoding="utf-8") + return Response( + text=text, + url="https://vws.vuforia.com/modeltargets/datasets", + status_code=HTTPStatus.BAD_REQUEST, + headers={}, + request_body=None, + tell_position=len(content), + content=content, + ) + + +@beartype +class _CountingTransport: + """A transport which counts the requests made to each path.""" + + def __init__(self, *, transport: Transport) -> None: + """ + Args: + transport: The transport to make requests with. + """ + self._transport = transport + self.urls: list[str] = [] + + def close(self) -> None: + """Close the wrapped transport.""" + self._transport.close() + + def __call__( + self, + *, + method: str, + url: str, + headers: dict[str, str], + data: bytes, + request_timeout: float | tuple[float, float], + ) -> Response: + """Make a request, recording the URL. + + Args: + method: The HTTP method. + url: The full URL. + headers: Request headers. + data: The request body. + request_timeout: The request timeout. + + Returns: + A Response populated from the HTTP response. + """ + self.urls.append(url) + return self._transport( + method=method, + url=url, + headers=headers, + data=data, + request_timeout=request_timeout, + ) + + +@beartype +class _BadTokenTransport: + """A transport which replaces each bearer token with an invalid + one. + """ + + def __init__(self, *, transport: Transport) -> None: + """ + Args: + transport: The transport to make requests with. + """ + self._transport = transport + + def close(self) -> None: + """Close the wrapped transport.""" + self._transport.close() + + def __call__( + self, + *, + method: str, + url: str, + headers: dict[str, str], + data: bytes, + request_timeout: float | tuple[float, float], + ) -> Response: + """Make a request with an invalid bearer token. + + Args: + method: The HTTP method. + url: The full URL. + headers: Request headers. + data: The request body. + request_timeout: The request timeout. + + Returns: + A Response populated from the HTTP response. + """ + given_headers = dict(headers) + authorization = given_headers.get("Authorization", "") + if authorization.startswith("Bearer "): + given_headers["Authorization"] = "Bearer not-a-json-web-token" + + return self._transport( + method=method, + url=url, + headers=given_headers, + data=data, + request_timeout=request_timeout, + ) + + +class TestAccessToken: + """Tests for getting an access token.""" + + @staticmethod + @pytest.mark.usefixtures("_mock_model_targets") + def test_token_is_a_bearer_token() -> None: + """An access token is given for valid credentials.""" + client = ModelTargetService( + client_id=_CLIENT_ID, + client_secret=_CLIENT_SECRET, + ) + + assert client.get_access_token() + + @staticmethod + @pytest.mark.usefixtures("_mock_model_targets") + def test_token_is_reused( + *, + model_target_model: ModelTargetModel, + ) -> None: + """One access token is used for multiple requests.""" + transport = _CountingTransport(transport=RequestsTransport()) + client = ModelTargetService( + client_id=_CLIENT_ID, + client_secret=_CLIENT_SECRET, + transport=transport, + ) + + for _ in range(2): + client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + token_urls = [url for url in transport.urls if "oauth2" in url] + assert len(token_urls) == 1 + transport.close() + + @staticmethod + @pytest.mark.usefixtures("_mock_model_targets") + def test_expired_token_is_replaced( + *, + model_target_model: ModelTargetModel, + ) -> None: + """A new access token is requested once the old one expires.""" + transport = _CountingTransport(transport=RequestsTransport()) + client = ModelTargetService( + client_id=_CLIENT_ID, + client_secret=_CLIENT_SECRET, + transport=transport, + ) + + with freeze_time(time_to_freeze="2026-01-01") as frozen_time: + client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + # Mock tokens last an hour. + frozen_time.tick(delta=60 * 60 + 1) + client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + token_urls = [url for url in transport.urls if "oauth2" in url] + expected_token_request_count = 2 + assert len(token_urls) == expected_token_request_count + + @staticmethod + @pytest.mark.usefixtures("_mock_model_targets") + def test_invalid_credentials() -> None: + """An exception is raised when the credentials are not known.""" + client = ModelTargetService( + client_id="not-a-client-id", + client_secret="not-a-client-secret", # noqa: S106 + ) + + with pytest.raises( + expected_exception=ModelTargetOAuth2Error, + ) as exc: + client.get_access_token() + + assert exc.value.response.status_code == HTTPStatus.UNAUTHORIZED + assert exc.value.error == "invalid_client" + assert not exc.value.error_description + + @staticmethod + @pytest.mark.usefixtures("_mock_model_targets") + def test_invalid_bearer_token( + *, + model_target_model: ModelTargetModel, + ) -> None: + """An exception is raised when the bearer token is not + accepted. + """ + transport = _BadTokenTransport(transport=RequestsTransport()) + client = ModelTargetService( + client_id=_CLIENT_ID, + client_secret=_CLIENT_SECRET, + transport=transport, + ) + + with pytest.raises( + expected_exception=ModelTargetAuthenticationError, + ) as exc: + client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert exc.value.response.status_code == HTTPStatus.UNAUTHORIZED + assert exc.value.target == "jwt" + assert exc.value.message + transport.close() + + +class TestDatasetLifecycle: + """Tests for the dataset lifecycle.""" + + @staticmethod + @pytest.mark.parametrize( + argnames="dataset_type", + argvalues=_DATASET_TYPES, + ) + def test_create_wait_download_delete( + *, + model_target_client: ModelTargetService, + model_target_model: ModelTargetModel, + dataset_type: ModelTargetDatasetType, + ) -> None: + """A dataset can be created, downloaded and then deleted.""" + dataset_uuid = model_target_client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=dataset_type, + ) + + report = model_target_client.wait_for_dataset_generated( + dataset_uuid=dataset_uuid, + dataset_type=dataset_type, + ) + + assert report.status == ModelTargetDatasetStatuses.DONE + assert report.dataset_uuid == dataset_uuid + assert report.completed_at is not None + assert report.completed_at >= report.created_at + assert report.eta is None + assert report.error is None + assert report.warning is None + + dataset = model_target_client.download_dataset( + dataset_uuid=dataset_uuid, + dataset_type=dataset_type, + ) + + with zipfile.ZipFile( + file=io.BytesIO(initial_bytes=dataset) + ) as archive: + dataset_json = json.loads(s=archive.read(name="dataset.json")) + + assert dataset_json["uuid"] == dataset_uuid + assert dataset_json["type"] == dataset_type.value + + model_target_client.delete_dataset( + dataset_uuid=dataset_uuid, + dataset_type=dataset_type, + ) + + with pytest.raises(expected_exception=UnknownModelTargetDatasetError): + model_target_client.get_dataset_status( + dataset_uuid=dataset_uuid, + dataset_type=dataset_type, + ) + + @staticmethod + def test_status_while_processing( + *, + model_target_client: ModelTargetService, + model_target_model: ModelTargetModel, + ) -> None: + """A processing dataset has an estimated completion time.""" + dataset_uuid = model_target_client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + report = model_target_client.get_dataset_status( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert report.status == ModelTargetDatasetStatuses.PROCESSING + assert report.eta is not None + assert report.eta >= report.created_at + assert report.completed_at is None + + @staticmethod + def test_download_while_processing( + *, + model_target_client: ModelTargetService, + model_target_model: ModelTargetModel, + ) -> None: + """A dataset cannot be downloaded before it is generated.""" + dataset_uuid = model_target_client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + with pytest.raises( + expected_exception=ModelTargetDatasetNotDoneError, + ) as exc: + model_target_client.download_dataset( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert ( + exc.value.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + ) + assert exc.value.code == "UNSUPPORTED_STATE" + assert exc.value.target == dataset_uuid + + @staticmethod + def test_dataset_types_are_separate( + *, + model_target_client: ModelTargetService, + model_target_model: ModelTargetModel, + ) -> None: + """A dataset is not visible to requests for the other type.""" + dataset_uuid = model_target_client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + with pytest.raises(expected_exception=UnknownModelTargetDatasetError): + model_target_client.get_dataset_status( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.ADVANCED, + ) + + @staticmethod + def test_advanced_dataset_takes_multiple_models( + *, + model_target_client: ModelTargetService, + model_target_model: ModelTargetModel, + ) -> None: + """An advanced dataset can be generated from multiple models.""" + other_model = ModelTargetModel( + name="other-model", + cad_data_blob="ZmFrZS1jYWQtZGF0YQ==", + cad_data_format=CadDataFormat.GLB, + realistic_appearance=RealisticAppearance.TRUE, + ) + + dataset_uuid = model_target_client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model, other_model], + dataset_type=ModelTargetDatasetType.ADVANCED, + ) + + assert dataset_uuid + + @staticmethod + def test_state_based_model( + *, + model_target_client: ModelTargetService, + ) -> None: + """A State-Based Model Target dataset can be created.""" + configuration = json.dumps(obj={"states": {"open": {}, "closed": {}}}) + model = ModelTargetModel( + name="model", + cad_data_url="https://example.com/model.zip", + cad_data_format=CadDataFormat.ZIP, + state_based_configuration_json_string=configuration, + views=[ + ModelTargetView( + name="front", + guide_view_position=GuideViewPosition( + rotation=[0.0, 0.0, 0.0, 1.0], + translation=[0.0, 0.0, 1.0], + ), + states=["open"], + ), + ], + ) + + assert model_target_client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + +class TestUnknownDataset: + """Tests for requests for datasets which do not exist.""" + + @staticmethod + def test_get_status(*, model_target_client: ModelTargetService) -> None: + """An exception is raised for an unknown dataset.""" + dataset_uuid = uuid.uuid4().hex + with pytest.raises( + expected_exception=UnknownModelTargetDatasetError, + ) as exc: + model_target_client.get_dataset_status( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert exc.value.response.status_code == HTTPStatus.NOT_FOUND + assert exc.value.code == "NOT_FOUND" + assert dataset_uuid in exc.value.message + + @staticmethod + def test_download(*, model_target_client: ModelTargetService) -> None: + """An exception is raised for an unknown dataset.""" + with pytest.raises(expected_exception=UnknownModelTargetDatasetError): + model_target_client.download_dataset( + dataset_uuid=uuid.uuid4().hex, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + @staticmethod + def test_delete(*, model_target_client: ModelTargetService) -> None: + """An exception is raised for an unknown dataset.""" + with pytest.raises(expected_exception=UnknownModelTargetDatasetError): + model_target_client.delete_dataset( + dataset_uuid=uuid.uuid4().hex, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + +class TestValidation: + """Tests for requests which Vuforia rejects.""" + + @staticmethod + def test_no_cad_data( + *, + model_target_client: ModelTargetService, + ) -> None: + """A model needs exactly one CAD data source.""" + with pytest.raises( + expected_exception=ModelTargetValidationError, + ) as exc: + model_target_client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[ModelTargetModel(name="model")], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert exc.value.response.status_code == HTTPStatus.BAD_REQUEST + assert exc.value.code == "BAD_REQUEST" + (detail,) = exc.value.details + assert detail.code == "VALIDATION_ERROR" + assert "cadDataUrl" in detail.message + + @staticmethod + def test_two_cad_data_sources( + *, + model_target_client: ModelTargetService, + ) -> None: + """A model cannot give two CAD data sources.""" + model = ModelTargetModel( + name="model", + cad_data_url="https://example.com/model.zip", + cad_data_blob="ZmFrZS1jYWQtZGF0YQ==", + ) + + with pytest.raises(expected_exception=ModelTargetValidationError): + model_target_client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + @staticmethod + def test_two_models_in_a_standard_dataset( + *, + model_target_client: ModelTargetService, + model_target_model: ModelTargetModel, + ) -> None: + """A standard dataset takes exactly one model.""" + with pytest.raises( + expected_exception=ModelTargetValidationError, + ) as exc: + model_target_client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model, model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + (detail,) = exc.value.details + assert detail.message == "exactly one model should be provided" + + +class TestGenerationResult: + """Tests for datasets which Vuforia does not generate cleanly.""" + + @staticmethod + def test_generation_failure( + *, + model_target_model: ModelTargetModel, + ) -> None: + """A dataset which fails to generate reports the failure.""" + message = "Model Target dataset generation failed" + failure = ModelTargetGenerationFailure(message=message) + with MockVWS( + processing_time_seconds=0.2, + model_target_generation_failure=failure, + ): + client = ModelTargetService( + client_id=_CLIENT_ID, + client_secret=_CLIENT_SECRET, + ) + dataset_uuid = client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + report = client.wait_for_dataset_generated( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert report.status == ModelTargetDatasetStatuses.FAILED + assert report.error is not None + assert report.error.message == message + assert report.warning is None + + with pytest.raises( + expected_exception=ModelTargetDatasetNotDoneError, + ): + client.download_dataset( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + @staticmethod + def test_generation_warning( + *, + model_target_model: ModelTargetModel, + ) -> None: + """A dataset which generates with a warning reports the + warning. + """ + warning = ModelTargetGenerationWarning() + with MockVWS( + processing_time_seconds=0.2, + model_target_generation_warning=warning, + ): + client = ModelTargetService( + client_id=_CLIENT_ID, + client_secret=_CLIENT_SECRET, + ) + dataset_uuid = client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + report = client.wait_for_dataset_generated( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert report.status == ModelTargetDatasetStatuses.DONE + assert report.error is None + assert report.warning is not None + assert report.warning.message == warning.message + assert report.warning.target == dataset_uuid + (detail,) = report.warning.details + assert detail.code == "LOW_RECOGNITION_QUALITY" + + assert client.download_dataset( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + +class TestWaitForDatasetGenerated: + """Tests for waiting for a dataset to be generated.""" + + @staticmethod + def test_timeout(*, model_target_model: ModelTargetModel) -> None: + """An exception is raised when the wait times out.""" + with MockVWS(processing_time_seconds=60): + client = ModelTargetService( + client_id=_CLIENT_ID, + client_secret=_CLIENT_SECRET, + ) + dataset_uuid = client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + with pytest.raises( + expected_exception=ModelTargetDatasetTimeoutError, + ): + client.wait_for_dataset_generated( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + seconds_between_requests=0.01, + timeout_seconds=0.05, + ) + + +class TestErrorEnvelope: + """Tests for reading responses which are not shaped like Model Target + Web API errors. + """ + + @staticmethod + @pytest.mark.parametrize( + argnames="text", + argvalues=[ + "", + "Not JSON", + "[]", + "{}", + '{"error": "not-an-object"}', + '{"transaction_id": "abc", "result_code": "Fail"}', + ], + ) + def test_unknown_error_shape(*, text: str) -> None: + """An error without a Model Target error object gives empty + values. + """ + error = ModelTargetError(response=_response(text=text)) + + assert not error.code + assert not error.message + assert not error.target + assert not error.details + + @staticmethod + def test_error_without_details() -> None: + """An error which gives no details has no details.""" + text = json.dumps(obj={"error": {"code": "ERROR", "message": "No"}}) + error = ModelTargetError(response=_response(text=text)) + + assert error.code == "ERROR" + assert error.message == "No" + assert not error.target + assert not error.details + + @staticmethod + @pytest.mark.parametrize( + argnames="text", + argvalues=["Not JSON", "[]", "{}"], + ) + def test_unknown_oauth2_error_shape(*, text: str) -> None: + """An OAuth2 error without an error code gives empty values.""" + error = ModelTargetOAuth2Error(response=_response(text=text)) + + assert not error.error + assert not error.error_description + + @staticmethod + def test_oauth2_error_description() -> None: + """An OAuth2 error description is given when Vuforia gives one.""" + description = "Missing or invalid authorization header" + text = json.dumps( + obj={ + "error": "invalid_request", + "error_description": description, + }, + ) + error = ModelTargetOAuth2Error(response=_response(text=text)) + + assert error.error == "invalid_request" + assert error.error_description == description + + +class TestBaseVWSURL: + """Tests for using a custom base URL.""" + + @staticmethod + def test_custom_base_url( + *, + model_target_model: ModelTargetModel, + ) -> None: + """The Model Target Web API can be served from a URL with a + path. + """ + base_vws_url = "https://example.com/vws" + with MockVWS( + base_vws_url=base_vws_url, + processing_time_seconds=0.2, + ): + client = ModelTargetService( + client_id=_CLIENT_ID, + client_secret=_CLIENT_SECRET, + base_vws_url=base_vws_url, + ) + dataset_uuid = client.create_dataset( + name="dataset", + target_sdk="11.0", + models=[model_target_model], + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + report = client.get_dataset_status( + dataset_uuid=dataset_uuid, + dataset_type=ModelTargetDatasetType.STANDARD, + ) + + assert report.dataset_uuid == dataset_uuid From 264631fa55d8e2488f33d025220fc7a6b193627e Mon Sep 17 00:00:00 2001 From: adamtheturtle <797801+adamtheturtle@users.noreply.github.com> Date: Fri, 14 Aug 2026 14:23:12 +0000 Subject: [PATCH 688/688] Bump CHANGELOG --- CHANGELOG.rst | 21 +++++++++++++++++++++ newsfragments/3090.change.rst | 1 - newsfragments/3091.change.rst | 1 - newsfragments/3092.change.rst | 1 - newsfragments/3093.change.rst | 1 - newsfragments/3119.change.rst | 2 -- newsfragments/3128.change.rst | 1 - newsfragments/3133.change.rst | 2 -- newsfragments/822.change.rst | 1 - 9 files changed, 21 insertions(+), 10 deletions(-) delete mode 100644 newsfragments/3090.change.rst delete mode 100644 newsfragments/3091.change.rst delete mode 100644 newsfragments/3092.change.rst delete mode 100644 newsfragments/3093.change.rst delete mode 100644 newsfragments/3119.change.rst delete mode 100644 newsfragments/3128.change.rst delete mode 100644 newsfragments/3133.change.rst delete mode 100644 newsfragments/822.change.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b6688933c..7c995c79a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,27 @@ Changelog .. towncrier release notes start +2026.08.14 +---------- + +- Drop Python 3.13 support, update VWS Python Mock, and test quota and project-state error responses against the mock. + +- Add ``QuotaExceededError``, ``LicenseCheckFailedError``, and ``AuthorizationFailedError`` for documented VuMark Generation API result codes. + +- Fix ``target_id`` on target exceptions when ``base_vws_url`` includes a path prefix. + +- Retain explicitly provided falsy custom transports instead of replacing them with defaults. + +- Raise a response-carrying ``CloudRecoError`` when Cloud Query returns a documented empty or non-JSON 4xx response instead of leaking ``JSONDecodeError``. + +- Add support for the Model Target Web API. + ``ModelTargetService`` and ``AsyncModelTargetService`` create standard and advanced Model Target datasets, wait for them to be generated, download them and delete them. + +- Map the ``ProjectHasNoApiAccess`` result code, as spelled in Vuforia's result codes table, to ``ProjectHasNoAPIAccessError``. The previously mapped ``ProjectHasNoAPIAccess`` casing, which Vuforia does not document, is no longer mapped. + +- Add support for the Database Reco Counts report. + ``VWS`` and ``AsyncVWS`` take an optional ``database_id``, and have new ``request_database_reco_counts_report``, ``download_reco_counts_report`` and ``wait_for_reco_counts_report`` methods. + 2026.02.25.1 ------------ diff --git a/newsfragments/3090.change.rst b/newsfragments/3090.change.rst deleted file mode 100644 index 226d9665f..000000000 --- a/newsfragments/3090.change.rst +++ /dev/null @@ -1 +0,0 @@ -Add ``QuotaExceededError``, ``LicenseCheckFailedError``, and ``AuthorizationFailedError`` for documented VuMark Generation API result codes. diff --git a/newsfragments/3091.change.rst b/newsfragments/3091.change.rst deleted file mode 100644 index b2696ec2a..000000000 --- a/newsfragments/3091.change.rst +++ /dev/null @@ -1 +0,0 @@ -Fix ``target_id`` on target exceptions when ``base_vws_url`` includes a path prefix. diff --git a/newsfragments/3092.change.rst b/newsfragments/3092.change.rst deleted file mode 100644 index a15105df8..000000000 --- a/newsfragments/3092.change.rst +++ /dev/null @@ -1 +0,0 @@ -Retain explicitly provided falsy custom transports instead of replacing them with defaults. diff --git a/newsfragments/3093.change.rst b/newsfragments/3093.change.rst deleted file mode 100644 index 7a603b777..000000000 --- a/newsfragments/3093.change.rst +++ /dev/null @@ -1 +0,0 @@ -Raise a response-carrying ``CloudRecoError`` when Cloud Query returns a documented empty or non-JSON 4xx response instead of leaking ``JSONDecodeError``. diff --git a/newsfragments/3119.change.rst b/newsfragments/3119.change.rst deleted file mode 100644 index 5660e237e..000000000 --- a/newsfragments/3119.change.rst +++ /dev/null @@ -1,2 +0,0 @@ -Add support for the Model Target Web API. -``ModelTargetService`` and ``AsyncModelTargetService`` create standard and advanced Model Target datasets, wait for them to be generated, download them and delete them. diff --git a/newsfragments/3128.change.rst b/newsfragments/3128.change.rst deleted file mode 100644 index 2a0cc5a51..000000000 --- a/newsfragments/3128.change.rst +++ /dev/null @@ -1 +0,0 @@ -Map the ``ProjectHasNoApiAccess`` result code, as spelled in Vuforia's result codes table, to ``ProjectHasNoAPIAccessError``. The previously mapped ``ProjectHasNoAPIAccess`` casing, which Vuforia does not document, is no longer mapped. diff --git a/newsfragments/3133.change.rst b/newsfragments/3133.change.rst deleted file mode 100644 index edab9049a..000000000 --- a/newsfragments/3133.change.rst +++ /dev/null @@ -1,2 +0,0 @@ -Add support for the Database Reco Counts report. -``VWS`` and ``AsyncVWS`` take an optional ``database_id``, and have new ``request_database_reco_counts_report``, ``download_reco_counts_report`` and ``wait_for_reco_counts_report`` methods. diff --git a/newsfragments/822.change.rst b/newsfragments/822.change.rst deleted file mode 100644 index 18195db79..000000000 --- a/newsfragments/822.change.rst +++ /dev/null @@ -1 +0,0 @@ -Drop Python 3.13 support, update VWS Python Mock, and test quota and project-state error responses against the mock.