From 416eac9c4f176c90f4d71b61ce24569bf3a65220 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 10 Sep 2026 14:36:21 +0100 Subject: [PATCH 1/2] Type report responses as JSON --- docs/source/api-reference.rst | 4 +++ src/vws/_json_utils.py | 4 +-- src/vws/_model_targets.py | 3 +- src/vws/exceptions/model_target_exceptions.py | 2 +- src/vws/json_types.py | 6 ++++ src/vws/reports.py | 32 ++++++++++++------- tests/test_reports.py | 7 +++- 7 files changed, 41 insertions(+), 17 deletions(-) create mode 100644 src/vws/json_types.py diff --git a/docs/source/api-reference.rst b/docs/source/api-reference.rst index f43793ce0..355b7f8a4 100644 --- a/docs/source/api-reference.rst +++ b/docs/source/api-reference.rst @@ -33,6 +33,10 @@ API Reference :undoc-members: :members: +.. automodule:: vws.json_types + :undoc-members: + :members: + .. automodule:: vws.include_target_data :undoc-members: :members: diff --git a/src/vws/_json_utils.py b/src/vws/_json_utils.py index aa60dbc8d..9d827ed62 100644 --- a/src/vws/_json_utils.py +++ b/src/vws/_json_utils.py @@ -5,9 +5,7 @@ from beartype.door import TypeHint -type JSONValue = ( - bool | int | float | str | list[JSONValue] | dict[str, JSONValue] | None -) +from vws.json_types import JSONValue def _is_json_object(value: object, /) -> TypeGuard[dict[str, JSONValue]]: diff --git a/src/vws/_model_targets.py b/src/vws/_model_targets.py index d2f714865..5ce4ca772 100644 --- a/src/vws/_model_targets.py +++ b/src/vws/_model_targets.py @@ -7,7 +7,7 @@ from beartype import BeartypeConf, beartype -from vws._json_utils import JSONValue, json_object +from vws._json_utils import json_object from vws.exceptions.custom_exceptions import ServerError from vws.exceptions.model_target_exceptions import ( ModelTargetAuthenticationError, @@ -18,6 +18,7 @@ UnknownModelTargetDatasetError, ) from vws.exceptions.vws_exceptions import TooManyRequestsError +from vws.json_types import JSONValue from vws.model_target_datasets import ( ModelTargetDatasetType, ModelTargetModel, diff --git a/src/vws/exceptions/model_target_exceptions.py b/src/vws/exceptions/model_target_exceptions.py index c810b55de..7fd81f1e5 100644 --- a/src/vws/exceptions/model_target_exceptions.py +++ b/src/vws/exceptions/model_target_exceptions.py @@ -9,12 +9,12 @@ from beartype import beartype from vws._json_utils import ( - JSONValue, json_object, object_field, object_list_field, string_field, ) +from vws.json_types import JSONValue from vws.reports import ModelTargetGenerationDetail from vws.response import Response diff --git a/src/vws/json_types.py b/src/vws/json_types.py new file mode 100644 index 000000000..25e5ec3f4 --- /dev/null +++ b/src/vws/json_types.py @@ -0,0 +1,6 @@ +"""JSON value types used by Vuforia API requests and responses.""" + +type JSONValue = ( + bool | int | float | str | list[JSONValue] | dict[str, JSONValue] | None +) +"""A value which can be represented in a JSON document.""" diff --git a/src/vws/reports.py b/src/vws/reports.py index 86c9a516d..9aaa739e3 100644 --- a/src/vws/reports.py +++ b/src/vws/reports.py @@ -11,6 +11,8 @@ from beartype import BeartypeConf, beartype from beartype.door import TypeHint +from vws.json_types import JSONValue + def _checked[T](value: object, hint: type[T], /) -> T: """Return a value after checking its runtime type.""" @@ -64,7 +66,9 @@ class DatabaseSummaryReport: total_recos: int @classmethod - def from_response_dict(cls, response_dict: Mapping[str, object]) -> Self: + def from_response_dict( + cls, response_dict: Mapping[str, JSONValue] + ) -> Self: """Construct from a VWS API response dict.""" return cls( active_images=int(_number(response_dict["active_images"])), @@ -120,7 +124,9 @@ class TargetSummaryReport: previous_month_recos: int @classmethod - def from_response_dict(cls, response_dict: Mapping[str, object]) -> Self: + def from_response_dict( + cls, response_dict: Mapping[str, JSONValue] + ) -> Self: """Construct from a VWS API response dict.""" return cls( status=TargetStatuses( @@ -185,13 +191,13 @@ class QueryResult: @classmethod def from_response_dict( cls, - response_dict: Mapping[str, object], + response_dict: Mapping[str, JSONValue], ) -> Self: """Construct from a VWS API query result item dict.""" target_data: TargetData | None = None if "target_data" in response_dict: target_data_dict = _checked( - response_dict["target_data"], dict[str, object] + response_dict["target_data"], dict[str, JSONValue] ) target_timestamp = datetime.datetime.fromtimestamp( timestamp=_number(target_data_dict["target_timestamp"]), @@ -223,11 +229,13 @@ class TargetStatusAndRecord: target_record: TargetRecord @classmethod - def from_response_dict(cls, response_dict: Mapping[str, object]) -> Self: + def from_response_dict( + cls, response_dict: Mapping[str, JSONValue] + ) -> Self: """Construct from a VWS API response dict.""" status = TargetStatuses(value=_checked(response_dict["status"], str)) target_record_dict = _checked( - response_dict["target_record"], dict[str, object] + response_dict["target_record"], dict[str, JSONValue] ) target_record = TargetRecord( target_id=_checked(target_record_dict["target_id"], str), @@ -260,7 +268,9 @@ class RecoCountsReportRequest: """ @classmethod - def from_response_dict(cls, response_dict: Mapping[str, object]) -> Self: + def from_response_dict( + cls, response_dict: Mapping[str, JSONValue] + ) -> Self: """Construct from a VWS API response dict.""" return cls( transaction_id=_checked(response_dict["transaction_id"], str), @@ -353,12 +363,12 @@ class ModelTargetDatasetStatusReport: @classmethod def from_response_dict( cls, - response_dict: Mapping[str, object], + response_dict: Mapping[str, JSONValue], ) -> Self: """Construct from a Model Target Web API response dict.""" error: ModelTargetGenerationError | None = None if "error" in response_dict: - error_dict = _checked(response_dict["error"], dict[str, object]) + error_dict = _checked(response_dict["error"], dict[str, JSONValue]) error = ModelTargetGenerationError( code=_checked(error_dict["code"], str), message=_checked(error_dict["message"], str), @@ -367,10 +377,10 @@ def from_response_dict( warning: ModelTargetGenerationWarning | None = None if "warning" in response_dict: warning_dict = _checked( - response_dict["warning"], dict[str, object] + response_dict["warning"], dict[str, JSONValue] ) details = _checked( - warning_dict["details"], list[dict[str, object]] + warning_dict["details"], list[dict[str, JSONValue]] ) warning = ModelTargetGenerationWarning( code=_checked(warning_dict["code"], str), diff --git a/tests/test_reports.py b/tests/test_reports.py index ce8ed27a3..972ac002b 100644 --- a/tests/test_reports.py +++ b/tests/test_reports.py @@ -4,6 +4,10 @@ from vws.reports import QueryResult +type JSONValue = ( + bool | int | float | str | list[JSONValue] | dict[str, JSONValue] | None +) + @pytest.mark.parametrize( argnames="response", @@ -28,7 +32,8 @@ ], ) def test_query_result_rejects_invalid_response_values( - *, response: dict[str, object] + *, + response: dict[str, JSONValue], ) -> None: """Query reports reject values of the wrong type.""" with pytest.raises(expected_exception=TypeError): From 11dfd43766b9b5d4acb952601a24724711504bf9 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 10 Sep 2026 14:37:45 +0100 Subject: [PATCH 2/2] Add news fragment --- newsfragments/3223.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/3223.change.rst diff --git a/newsfragments/3223.change.rst b/newsfragments/3223.change.rst new file mode 100644 index 000000000..fafd85a80 --- /dev/null +++ b/newsfragments/3223.change.rst @@ -0,0 +1 @@ +Type decoded report response dictionaries as JSON values.