vws-python 2026.8.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ """Custom exceptions raised by this package."""
@@ -0,0 +1,74 @@
1
+ """
2
+ Base exceptions for errors returned by Vuforia Web Services or the
3
+ Vuforia
4
+ Cloud Recognition Web API.
5
+ """
6
+
7
+ from collections.abc import Mapping # noqa: TC003
8
+ from typing import ClassVar
9
+
10
+ from beartype import beartype
11
+
12
+ from vws.response import Response # noqa: TC001
13
+
14
+
15
+ @beartype
16
+ class CloudRecoError(Exception):
17
+ """Base class for Vuforia Cloud Recognition Web API exceptions."""
18
+
19
+ def __init__(self, response: Response) -> None:
20
+ """
21
+ Args:
22
+ response: The response to a request to Vuforia.
23
+ """
24
+ super().__init__(response.text)
25
+ self._response = response
26
+
27
+ @property
28
+ def response(self) -> Response:
29
+ """The response returned by Vuforia which included this error."""
30
+ return self._response
31
+
32
+
33
+ @beartype
34
+ class VWSError(Exception):
35
+ """Base class for Vuforia Web Services errors.
36
+
37
+ These errors are defined at
38
+ https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#result-codes.
39
+ """
40
+
41
+ _exceptions_by_result_code: ClassVar[dict[str, type[VWSError]]] = {}
42
+
43
+ def __init__(self, response: Response) -> None:
44
+ """
45
+ Args:
46
+ response: The response to a request to Vuforia.
47
+ """
48
+ super().__init__()
49
+ self._response = response
50
+
51
+ @classmethod
52
+ def register_exceptions_by_result_code(
53
+ cls,
54
+ *,
55
+ exceptions_by_result_code: Mapping[str, type[VWSError]],
56
+ ) -> None:
57
+ """Register ``result_code`` to exception mappings."""
58
+ cls._exceptions_by_result_code.update(exceptions_by_result_code)
59
+
60
+ @classmethod
61
+ def from_result_code(
62
+ cls,
63
+ *,
64
+ result_code: str,
65
+ response: Response,
66
+ ) -> VWSError:
67
+ """Create the mapped exception for a VWS ``result_code``."""
68
+ exception_type = cls._exceptions_by_result_code[result_code]
69
+ return exception_type(response=response)
70
+
71
+ @property
72
+ def response(self) -> Response:
73
+ """The response returned by Vuforia which included this error."""
74
+ return self._response
@@ -0,0 +1,44 @@
1
+ """Exceptions which match errors raised by the Vuforia Cloud Recognition
2
+ Web
3
+ APIs.
4
+ """
5
+
6
+ from beartype import beartype
7
+
8
+ from vws.exceptions.base_exceptions import CloudRecoError
9
+
10
+
11
+ @beartype
12
+ class MaxNumResultsOutOfRangeError(CloudRecoError):
13
+ """
14
+ Exception raised when the ``max_num_results`` given to the Cloud
15
+ Recognition Web API query endpoint is out of range.
16
+ """
17
+
18
+
19
+ @beartype
20
+ class InactiveProjectError(CloudRecoError):
21
+ """Exception raised when Vuforia returns a response with a result code
22
+ 'InactiveProject'.
23
+ """
24
+
25
+
26
+ @beartype
27
+ class BadImageError(CloudRecoError):
28
+ """Exception raised when Vuforia returns a response with a result code
29
+ 'BadImage'.
30
+ """
31
+
32
+
33
+ @beartype
34
+ class AuthenticationFailureError(CloudRecoError):
35
+ """Exception raised when Vuforia returns a response with a result code
36
+ 'AuthenticationFailure'.
37
+ """
38
+
39
+
40
+ @beartype
41
+ class RequestTimeTooSkewedError(CloudRecoError):
42
+ """Exception raised when Vuforia returns a response with a result code
43
+ 'RequestTimeTooSkewed'.
44
+ """
@@ -0,0 +1,107 @@
1
+ """Exceptions which do not map to errors at the following URL, or simple
2
+ errors given by the cloud recognition service.
3
+
4
+ https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#result-codes
5
+ """
6
+
7
+ from beartype import beartype
8
+
9
+ from vws.response import Response # noqa: TC001
10
+
11
+
12
+ @beartype
13
+ class RequestEntityTooLargeError(Exception):
14
+ """Exception raised when the given image is too large."""
15
+
16
+ def __init__(self, response: Response) -> None:
17
+ """
18
+ Args:
19
+ response: The response returned by Vuforia.
20
+ """
21
+ super().__init__(response.text)
22
+ self._response = response
23
+
24
+ @property
25
+ def response(self) -> Response:
26
+ """The response returned by Vuforia which included this error."""
27
+ return self._response
28
+
29
+
30
+ @beartype
31
+ class TargetProcessingTimeoutError(Exception):
32
+ """Exception raised when waiting for a target to be processed times
33
+ out.
34
+ """
35
+
36
+
37
+ @beartype
38
+ class DatabaseIdNotSetError(Exception):
39
+ """Exception raised when an operation which needs a database ID is used
40
+ on a client which was not given one.
41
+ """
42
+
43
+
44
+ @beartype
45
+ class RecoCountsReportNotReadyError(Exception):
46
+ """Exception raised when a reco counts report is downloaded before
47
+ Vuforia has generated it.
48
+ """
49
+
50
+ def __init__(self, response: Response) -> None:
51
+ """
52
+ Args:
53
+ response: The response returned by the report's download URL.
54
+ """
55
+ super().__init__(response.text)
56
+ self._response = response
57
+
58
+ @property
59
+ def response(self) -> Response:
60
+ """The response returned by the download URL."""
61
+ return self._response
62
+
63
+
64
+ @beartype
65
+ class RecoCountsReportDownloadError(Exception):
66
+ """Exception raised when downloading a reco counts report fails.
67
+
68
+ This is raised, for example, when the report's URL has expired.
69
+ """
70
+
71
+ def __init__(self, response: Response) -> None:
72
+ """
73
+ Args:
74
+ response: The response returned by the report's download URL.
75
+ """
76
+ super().__init__(response.text)
77
+ self._response = response
78
+
79
+ @property
80
+ def response(self) -> Response:
81
+ """The response returned by the download URL."""
82
+ return self._response
83
+
84
+
85
+ @beartype
86
+ class RecoCountsReportTimeoutError(Exception):
87
+ """Exception raised when waiting for a reco counts report to be
88
+ generated times out.
89
+ """
90
+
91
+
92
+ @beartype
93
+ class ServerError(Exception): # pragma: no cover
94
+ """Exception raised when VWS returns a server error."""
95
+
96
+ def __init__(self, response: Response) -> None:
97
+ """
98
+ Args:
99
+ response: The response returned by Vuforia.
100
+ """
101
+ super().__init__(response.text)
102
+ self._response = response
103
+
104
+ @property
105
+ def response(self) -> Response:
106
+ """The response returned by Vuforia which included this error."""
107
+ return self._response
@@ -0,0 +1,210 @@
1
+ """Exceptions raised by the Vuforia Model Target Web API.
2
+
3
+ See
4
+ https://developer.vuforia.com/library/vuforia-engine/web-api/model-target-web-api/.
5
+ """
6
+
7
+ import json
8
+ from typing import Any
9
+
10
+ from beartype import beartype
11
+
12
+ from vws.reports import ModelTargetGenerationDetail
13
+ from vws.response import Response # noqa: TC001
14
+
15
+
16
+ @beartype
17
+ def _is_json_object(*, value: object) -> bool:
18
+ """Get whether a decoded JSON value is an object.
19
+
20
+ Args:
21
+ value: A decoded JSON value.
22
+
23
+ Returns:
24
+ Whether the value is a JSON object.
25
+ """
26
+ return isinstance(value, dict)
27
+
28
+
29
+ @beartype
30
+ def _json_object(*, value: str) -> dict[str, Any]:
31
+ """Get a JSON object from a string.
32
+
33
+ Args:
34
+ value: A string which may be a JSON object.
35
+
36
+ Returns:
37
+ The JSON object, or an empty dictionary if the string is not a
38
+ JSON object.
39
+ """
40
+ try:
41
+ loaded: Any = json.loads(s=value)
42
+ except json.JSONDecodeError:
43
+ return {}
44
+
45
+ if not _is_json_object(value=loaded):
46
+ return {}
47
+
48
+ json_object: dict[str, Any] = loaded
49
+ return json_object
50
+
51
+
52
+ @beartype
53
+ def _error_dict(*, response: Response) -> dict[str, Any]:
54
+ """Get the error object of a Model Target Web API error response.
55
+
56
+ Args:
57
+ response: The response returned by Vuforia.
58
+
59
+ Returns:
60
+ The error object, or an empty dictionary if the response has no
61
+ error object. Some errors, such as those given by the load
62
+ balancer in front of Vuforia, are not shaped like Model Target
63
+ Web API errors.
64
+ """
65
+ body = _json_object(value=response.text)
66
+ if "error" not in body:
67
+ return {}
68
+
69
+ error: Any = body["error"]
70
+ if not _is_json_object(value=error):
71
+ return {}
72
+
73
+ error_dict: dict[str, Any] = error
74
+ return error_dict
75
+
76
+
77
+ @beartype
78
+ class ModelTargetError(Exception):
79
+ """Base class for Vuforia Model Target Web API exceptions."""
80
+
81
+ def __init__(self, response: Response) -> None:
82
+ """
83
+ Args:
84
+ response: The response to a request to Vuforia.
85
+ """
86
+ super().__init__(response.text)
87
+ self._response = response
88
+
89
+ @property
90
+ def response(self) -> Response:
91
+ """The response returned by Vuforia which included this error."""
92
+ return self._response
93
+
94
+ @property
95
+ def code(self) -> str:
96
+ """The error code given by Vuforia, or an empty string."""
97
+ error = _error_dict(response=self._response)
98
+ return str(object=error["code"]) if "code" in error else ""
99
+
100
+ @property
101
+ def message(self) -> str:
102
+ """The error message given by Vuforia, or an empty string."""
103
+ error = _error_dict(response=self._response)
104
+ return str(object=error["message"]) if "message" in error else ""
105
+
106
+ @property
107
+ def target(self) -> str:
108
+ """The error target given by Vuforia, or an empty string."""
109
+ error = _error_dict(response=self._response)
110
+ return str(object=error["target"]) if "target" in error else ""
111
+
112
+ @property
113
+ def details(self) -> list[ModelTargetGenerationDetail]:
114
+ """The error details given by Vuforia.
115
+
116
+ Vuforia gives one detail per validation problem it found with a
117
+ dataset creation request.
118
+ """
119
+ error = _error_dict(response=self._response)
120
+ if "details" not in error:
121
+ return []
122
+
123
+ return [
124
+ ModelTargetGenerationDetail(
125
+ code=detail["code"],
126
+ message=detail["message"],
127
+ )
128
+ for detail in error["details"]
129
+ ]
130
+
131
+
132
+ @beartype
133
+ class ModelTargetAuthenticationError(ModelTargetError):
134
+ """Exception raised when a Model Target Web API request is not
135
+ authenticated.
136
+
137
+ For example, the bearer token may be missing, malformed or expired.
138
+ """
139
+
140
+
141
+ @beartype
142
+ class ModelTargetValidationError(ModelTargetError):
143
+ """Exception raised when Vuforia rejects a Model Target dataset
144
+ creation
145
+ request.
146
+
147
+ See :attr:`~.ModelTargetError.details` for the problems which Vuforia
148
+ found.
149
+ """
150
+
151
+
152
+ @beartype
153
+ class UnknownModelTargetDatasetError(ModelTargetError):
154
+ """Exception raised when no Model Target dataset matches a given UUID.
155
+
156
+ Standard and advanced datasets are separate resources, so this is also
157
+ raised when the given UUID matches a dataset of the other type.
158
+ """
159
+
160
+
161
+ @beartype
162
+ class ModelTargetDatasetNotDoneError(ModelTargetError):
163
+ """Exception raised when a Model Target dataset is downloaded before
164
+ Vuforia has generated it.
165
+ """
166
+
167
+
168
+ @beartype
169
+ class ModelTargetOAuth2Error(Exception):
170
+ """Exception raised when Vuforia does not give an access token.
171
+
172
+ For example, the given client ID and client secret may not match a set
173
+ of Model Target Web API credentials.
174
+ """
175
+
176
+ def __init__(self, response: Response) -> None:
177
+ """
178
+ Args:
179
+ response: The response to a request to Vuforia's token
180
+ endpoint.
181
+ """
182
+ super().__init__(response.text)
183
+ self._response = response
184
+
185
+ @property
186
+ def response(self) -> Response:
187
+ """The response returned by Vuforia which included this error."""
188
+ return self._response
189
+
190
+ @property
191
+ def error(self) -> str:
192
+ """The OAuth2 error code, or an empty string."""
193
+ body = _json_object(value=self._response.text)
194
+ return str(object=body["error"]) if "error" in body else ""
195
+
196
+ @property
197
+ def error_description(self) -> str:
198
+ """The OAuth2 error description, or an empty string."""
199
+ body = _json_object(value=self._response.text)
200
+ if "error_description" not in body:
201
+ return ""
202
+
203
+ return str(object=body["error_description"])
204
+
205
+
206
+ @beartype
207
+ class ModelTargetDatasetTimeoutError(Exception):
208
+ """Exception raised when waiting for a Model Target dataset to be
209
+ generated times out.
210
+ """
@@ -0,0 +1,256 @@
1
+ """
2
+ Exception raised when Vuforia returns a response with a result code
3
+ matching
4
+ one of those documented at
5
+ https://developer.vuforia.com/library/web-api/cloud-targets-web-services-
6
+ api#result-codes.
7
+ """
8
+
9
+ import json
10
+ from urllib.parse import urlparse
11
+
12
+ from beartype import beartype
13
+
14
+ from vws.exceptions.base_exceptions import VWSError
15
+
16
+
17
+ def _target_id_from_url(*, url: str) -> str:
18
+ """Return the target ID from a VWS response URL.
19
+
20
+ Paths may include a custom base URL prefix. The target ID is the
21
+ path segment after ``targets``, ``summary``, or ``duplicates``.
22
+ """
23
+ path = urlparse(url=url).path
24
+ parts = [part for part in path.split(sep="/") if part]
25
+ for marker in ("targets", "summary", "duplicates"):
26
+ try:
27
+ marker_index = parts.index(marker)
28
+ except ValueError:
29
+ continue
30
+ return parts[marker_index + 1]
31
+ message = f"Could not find a target ID in URL path {path!r}"
32
+ raise ValueError(message)
33
+
34
+
35
+ @beartype
36
+ class UnknownTargetError(VWSError):
37
+ """Exception raised when Vuforia returns a response with a result code
38
+ 'UnknownTarget'.
39
+ """
40
+
41
+ @property
42
+ def target_id(self) -> str:
43
+ """The unknown target ID."""
44
+ return _target_id_from_url(url=self.response.url)
45
+
46
+
47
+ @beartype
48
+ class FailError(VWSError):
49
+ """Exception raised when Vuforia returns a response with a result code
50
+ 'Fail'.
51
+ """
52
+
53
+
54
+ @beartype
55
+ class BadImageError(VWSError):
56
+ """Exception raised when Vuforia returns a response with a result code
57
+ 'BadImage'.
58
+ """
59
+
60
+
61
+ @beartype
62
+ class AuthenticationFailureError(VWSError):
63
+ """Exception raised when Vuforia returns a response with a result code
64
+ 'AuthenticationFailure'.
65
+ """
66
+
67
+
68
+ @beartype
69
+ class RequestQuotaReachedError(VWSError):
70
+ """Exception raised when Vuforia returns a response with a result code
71
+ 'RequestQuotaReached'.
72
+ """
73
+
74
+
75
+ @beartype
76
+ class TargetStatusProcessingError(VWSError):
77
+ """Exception raised when Vuforia returns a response with a result code
78
+ 'TargetStatusProcessing'.
79
+ """
80
+
81
+ @property
82
+ def target_id(self) -> str:
83
+ """The processing target ID."""
84
+ return _target_id_from_url(url=self.response.url)
85
+
86
+
87
+ # This is not simulated by the mock.
88
+ @beartype
89
+ class DateRangeError(VWSError): # pragma: no cover
90
+ """Exception raised when Vuforia returns a response with a result code
91
+ 'DateRangeError'.
92
+ """
93
+
94
+
95
+ @beartype
96
+ class TargetQuotaReachedError(VWSError):
97
+ """Exception raised when Vuforia returns a response with a result code
98
+ 'TargetQuotaReached'.
99
+ """
100
+
101
+
102
+ @beartype
103
+ class ProjectSuspendedError(VWSError):
104
+ """Exception raised when Vuforia returns a response with a result code
105
+ 'ProjectSuspended'.
106
+ """
107
+
108
+
109
+ @beartype
110
+ class ProjectHasNoAPIAccessError(VWSError):
111
+ """Exception raised when Vuforia returns a response with a result code
112
+ 'ProjectHasNoApiAccess'.
113
+ """
114
+
115
+
116
+ @beartype
117
+ class ProjectInactiveError(VWSError):
118
+ """Exception raised when Vuforia returns a response with a result code
119
+ 'ProjectInactive'.
120
+ """
121
+
122
+
123
+ @beartype
124
+ class MetadataTooLargeError(VWSError):
125
+ """Exception raised when Vuforia returns a response with a result code
126
+ 'MetadataTooLarge'.
127
+ """
128
+
129
+
130
+ @beartype
131
+ class RequestTimeTooSkewedError(VWSError):
132
+ """Exception raised when Vuforia returns a response with a result code
133
+ 'RequestTimeTooSkewed'.
134
+ """
135
+
136
+
137
+ @beartype
138
+ class TargetNameExistError(VWSError):
139
+ """Exception raised when Vuforia returns a response with a result code
140
+ 'TargetNameExist'.
141
+ """
142
+
143
+ @property
144
+ def target_name(self) -> str:
145
+ """The target name which already exists."""
146
+ response_body = self.response.request_body or b""
147
+ request_json = json.loads(s=response_body)
148
+ return str(object=request_json["name"])
149
+
150
+
151
+ @beartype
152
+ class ImageTooLargeError(VWSError):
153
+ """Exception raised when Vuforia returns a response with a result code
154
+ 'ImageTooLarge'.
155
+ """
156
+
157
+
158
+ @beartype
159
+ class TargetStatusNotSuccessError(VWSError):
160
+ """Exception raised when Vuforia returns a response with a result code
161
+ 'TargetStatusNotSuccess'.
162
+ """
163
+
164
+ @property
165
+ def target_id(self) -> str:
166
+ """The unknown target ID."""
167
+ return _target_id_from_url(url=self.response.url)
168
+
169
+
170
+ @beartype
171
+ class TooManyRequestsError(VWSError): # pragma: no cover
172
+ """Exception raised when Vuforia returns a response with a result code
173
+ 'TooManyRequests'.
174
+ """
175
+
176
+
177
+ # This is not simulated by client code because the accept parameter uses
178
+ # the VuMarkAccept enum, which only allows valid values.
179
+ @beartype
180
+ class InvalidAcceptHeaderError(VWSError): # pragma: no cover
181
+ """Exception raised when Vuforia returns a response with a result code
182
+ ``InvalidAcceptHeader``.
183
+ """
184
+
185
+
186
+ @beartype
187
+ class InvalidInstanceIdError(VWSError):
188
+ """Exception raised when Vuforia returns a response with a result code
189
+ ``InvalidInstanceId``.
190
+ """
191
+
192
+
193
+ # This is not simulated by client code because the request body
194
+ # is always valid JSON when using this client.
195
+ @beartype
196
+ class BadRequestError(VWSError): # pragma: no cover
197
+ """Exception raised when Vuforia returns a response with a result code
198
+ ``BadRequest``.
199
+ """
200
+
201
+
202
+ @beartype
203
+ class InvalidTargetTypeError(VWSError):
204
+ """Exception raised when Vuforia returns a response with a result code
205
+ ``InvalidTargetType``.
206
+ """
207
+
208
+
209
+ @beartype
210
+ class QuotaExceededError(VWSError):
211
+ """Exception raised when Vuforia returns a response with a result code
212
+ ``QuotaExceeded``.
213
+ """
214
+
215
+
216
+ @beartype
217
+ class LicenseCheckFailedError(VWSError):
218
+ """Exception raised when Vuforia returns a response with a result code
219
+ ``LicenseCheckFailed``.
220
+ """
221
+
222
+
223
+ @beartype
224
+ class AuthorizationFailedError(VWSError):
225
+ """Exception raised when Vuforia returns a response with a result code
226
+ ``AuthorizationFailed``.
227
+ """
228
+
229
+
230
+ VWSError.register_exceptions_by_result_code(
231
+ exceptions_by_result_code={
232
+ "AuthenticationFailure": AuthenticationFailureError,
233
+ "AuthorizationFailed": AuthorizationFailedError,
234
+ "BadImage": BadImageError,
235
+ "BadRequest": BadRequestError,
236
+ "DateRangeError": DateRangeError,
237
+ "Fail": FailError,
238
+ "ImageTooLarge": ImageTooLargeError,
239
+ "InvalidAcceptHeader": InvalidAcceptHeaderError,
240
+ "InvalidInstanceId": InvalidInstanceIdError,
241
+ "InvalidTargetType": InvalidTargetTypeError,
242
+ "LicenseCheckFailed": LicenseCheckFailedError,
243
+ "MetadataTooLarge": MetadataTooLargeError,
244
+ "ProjectHasNoApiAccess": ProjectHasNoAPIAccessError,
245
+ "ProjectInactive": ProjectInactiveError,
246
+ "ProjectSuspended": ProjectSuspendedError,
247
+ "QuotaExceeded": QuotaExceededError,
248
+ "RequestQuotaReached": RequestQuotaReachedError,
249
+ "RequestTimeTooSkewed": RequestTimeTooSkewedError,
250
+ "TargetNameExist": TargetNameExistError,
251
+ "TargetQuotaReached": TargetQuotaReachedError,
252
+ "TargetStatusNotSuccess": TargetStatusNotSuccessError,
253
+ "TargetStatusProcessing": TargetStatusProcessingError,
254
+ "UnknownTarget": UnknownTargetError,
255
+ },
256
+ )