From f641786071cbeb7e9748815c9df43c18797691c4 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 14 Aug 2026 12:07:55 +0100 Subject: [PATCH 1/4] 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 00000000..fc07be19 --- /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 efcf3a13..7c7d50d6 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 a93dd600..5c6be8e1 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 73ab9f84..8fd89493 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 845e7a2cb5fe0a8c1c5d71abadbf028cd23807fe Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 14 Aug 2026 12:12:42 +0100 Subject: [PATCH 2/4] 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 45ed889e..36a335a8 100644 --- a/spelling_private_dict.txt +++ b/spelling_private_dict.txt @@ -14,6 +14,7 @@ MetadataTooLarge OopsAnErrorOccurredPossiblyBadName OopsAnErrorOccurredPossiblyBadNameError ProjectHasNoAPIAccess +ProjectHasNoApiAccess ProjectInactive ProjectSuspended QuotaExceeded From deeb0ffe9464f27d8a71d9ec03c6cd74a459cc12 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 14 Aug 2026 12:19:48 +0100 Subject: [PATCH 3/4] 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 fc07be19..2a0cc5a5 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 36a335a8..608572ce 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 5c6be8e1..c9f2fea4 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 8fd89493..5afb6639 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 9eb5e1cab02da6d018ba4a437d37a0bbc0df21fa Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 14 Aug 2026 12:31:46 +0100 Subject: [PATCH 4/4] 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 c9f2fea4..216677f1 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,