From 6969f8700751e387f63e3e2da9380f06eb8f003a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 17 Jul 2026 07:59:04 +0100 Subject: [PATCH 1/2] 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 9a410355bc78e25d8e8acdbc5b5e3902331ab4ad Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 17 Jul 2026 08:03:42 +0100 Subject: [PATCH 2/2] 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