vws-python 2026.2.24__tar.gz → 2026.2.25__tar.gz

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.
Files changed (71) hide show
  1. {vws_python-2026.2.24 → vws_python-2026.2.25}/CHANGELOG.rst +4 -0
  2. {vws_python-2026.2.24/src/vws_python.egg-info → vws_python-2026.2.25}/PKG-INFO +2 -1
  3. {vws_python-2026.2.24 → vws_python-2026.2.25}/docs/source/api-reference.rst +12 -0
  4. {vws_python-2026.2.24 → vws_python-2026.2.25}/pyproject.toml +2 -0
  5. {vws_python-2026.2.24 → vws_python-2026.2.25}/spelling_private_dict.txt +2 -0
  6. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws/__init__.py +6 -0
  7. vws_python-2026.2.25/src/vws/_async_vws_request.py +77 -0
  8. vws_python-2026.2.25/src/vws/_image_utils.py +18 -0
  9. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws/_vws_request.py +1 -1
  10. vws_python-2026.2.25/src/vws/async_query.py +226 -0
  11. vws_python-2026.2.25/src/vws/async_vumark_service.py +170 -0
  12. vws_python-2026.2.25/src/vws/async_vws.py +692 -0
  13. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws/query.py +4 -15
  14. vws_python-2026.2.25/src/vws/transports.py +278 -0
  15. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws/vws.py +2 -14
  16. {vws_python-2026.2.24 → vws_python-2026.2.25/src/vws_python.egg-info}/PKG-INFO +2 -1
  17. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws_python.egg-info/SOURCES.txt +9 -0
  18. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws_python.egg-info/requires.txt +1 -0
  19. {vws_python-2026.2.24 → vws_python-2026.2.25}/tests/conftest.py +53 -2
  20. vws_python-2026.2.25/tests/test_async_cloud_reco_exceptions.py +120 -0
  21. vws_python-2026.2.25/tests/test_async_query.py +225 -0
  22. vws_python-2026.2.25/tests/test_async_vws.py +500 -0
  23. vws_python-2026.2.25/tests/test_async_vws_exceptions.py +304 -0
  24. vws_python-2026.2.25/tests/test_transports.py +206 -0
  25. vws_python-2026.2.24/src/vws/transports.py +0 -158
  26. vws_python-2026.2.24/tests/test_transports.py +0 -61
  27. {vws_python-2026.2.24 → vws_python-2026.2.25}/.git_archival.txt +0 -0
  28. {vws_python-2026.2.24 → vws_python-2026.2.25}/.gitattributes +0 -0
  29. {vws_python-2026.2.24 → vws_python-2026.2.25}/.github/dependabot.yml +0 -0
  30. {vws_python-2026.2.24 → vws_python-2026.2.25}/.github/workflows/ci.yml +0 -0
  31. {vws_python-2026.2.24 → vws_python-2026.2.25}/.github/workflows/dependabot-merge.yml +0 -0
  32. {vws_python-2026.2.24 → vws_python-2026.2.25}/.github/workflows/lint.yml +0 -0
  33. {vws_python-2026.2.24 → vws_python-2026.2.25}/.github/workflows/publish-site.yml +0 -0
  34. {vws_python-2026.2.24 → vws_python-2026.2.25}/.github/workflows/release.yml +0 -0
  35. {vws_python-2026.2.24 → vws_python-2026.2.25}/.gitignore +0 -0
  36. {vws_python-2026.2.24 → vws_python-2026.2.25}/.pre-commit-config.yaml +0 -0
  37. {vws_python-2026.2.24 → vws_python-2026.2.25}/.prettierrc +0 -0
  38. {vws_python-2026.2.24 → vws_python-2026.2.25}/.vscode/extensions.json +0 -0
  39. {vws_python-2026.2.24 → vws_python-2026.2.25}/.vscode/settings.json +0 -0
  40. {vws_python-2026.2.24 → vws_python-2026.2.25}/CODE_OF_CONDUCT.rst +0 -0
  41. {vws_python-2026.2.24 → vws_python-2026.2.25}/LICENSE +0 -0
  42. {vws_python-2026.2.24 → vws_python-2026.2.25}/README.rst +0 -0
  43. {vws_python-2026.2.24 → vws_python-2026.2.25}/conftest.py +0 -0
  44. {vws_python-2026.2.24 → vws_python-2026.2.25}/docs/source/__init__.py +0 -0
  45. {vws_python-2026.2.24 → vws_python-2026.2.25}/docs/source/changelog.rst +0 -0
  46. {vws_python-2026.2.24 → vws_python-2026.2.25}/docs/source/conf.py +0 -0
  47. {vws_python-2026.2.24 → vws_python-2026.2.25}/docs/source/contributing.rst +0 -0
  48. {vws_python-2026.2.24 → vws_python-2026.2.25}/docs/source/exceptions.rst +0 -0
  49. {vws_python-2026.2.24 → vws_python-2026.2.25}/docs/source/index.rst +0 -0
  50. {vws_python-2026.2.24 → vws_python-2026.2.25}/docs/source/release-process.rst +0 -0
  51. {vws_python-2026.2.24 → vws_python-2026.2.25}/setup.cfg +0 -0
  52. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws/exceptions/__init__.py +0 -0
  53. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws/exceptions/base_exceptions.py +0 -0
  54. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws/exceptions/cloud_reco_exceptions.py +0 -0
  55. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws/exceptions/custom_exceptions.py +0 -0
  56. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws/exceptions/vws_exceptions.py +0 -0
  57. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws/include_target_data.py +0 -0
  58. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws/py.typed +0 -0
  59. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws/reports.py +0 -0
  60. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws/response.py +0 -0
  61. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws/vumark_accept.py +0 -0
  62. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws/vumark_service.py +0 -0
  63. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws_python.egg-info/dependency_links.txt +0 -0
  64. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws_python.egg-info/not-zip-safe +0 -0
  65. {vws_python-2026.2.24 → vws_python-2026.2.25}/src/vws_python.egg-info/top_level.txt +0 -0
  66. {vws_python-2026.2.24 → vws_python-2026.2.25}/tests/__init__.py +0 -0
  67. {vws_python-2026.2.24 → vws_python-2026.2.25}/tests/test_cloud_reco_exceptions.py +0 -0
  68. {vws_python-2026.2.24 → vws_python-2026.2.25}/tests/test_query.py +0 -0
  69. {vws_python-2026.2.24 → vws_python-2026.2.25}/tests/test_vws.py +0 -0
  70. {vws_python-2026.2.24 → vws_python-2026.2.25}/tests/test_vws_exceptions.py +0 -0
  71. {vws_python-2026.2.24 → vws_python-2026.2.25}/zizmor.yml +0 -0
@@ -4,6 +4,10 @@ Changelog
4
4
  Next
5
5
  ----
6
6
 
7
+ 2026.02.25
8
+ ----------
9
+
10
+
7
11
  2026.02.24
8
12
  ----------
9
13
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vws-python
3
- Version: 2026.2.24
3
+ Version: 2026.2.25
4
4
  Summary: Interact with the Vuforia Web Services (VWS) API.
5
5
  Author-email: Adam Dangoor <adamdangoor@gmail.com>
6
6
  License-Expression: MIT
@@ -44,6 +44,7 @@ Requires-Dist: pyrefly==0.54.0; extra == "dev"
44
44
  Requires-Dist: pyright==1.1.408; extra == "dev"
45
45
  Requires-Dist: pyroma==5.0.1; extra == "dev"
46
46
  Requires-Dist: pytest==9.0.2; extra == "dev"
47
+ Requires-Dist: pytest-asyncio==1.3.0; extra == "dev"
47
48
  Requires-Dist: pytest-cov==7.0.0; extra == "dev"
48
49
  Requires-Dist: pyyaml==6.0.3; extra == "dev"
49
50
  Requires-Dist: ruff==0.15.2; extra == "dev"
@@ -5,6 +5,18 @@ API Reference
5
5
  :undoc-members:
6
6
  :members:
7
7
 
8
+ .. automodule:: vws.async_vws
9
+ :undoc-members:
10
+ :members:
11
+
12
+ .. automodule:: vws.async_query
13
+ :undoc-members:
14
+ :members:
15
+
16
+ .. automodule:: vws.async_vumark_service
17
+ :undoc-members:
18
+ :members:
19
+
8
20
  .. automodule:: vws.reports
9
21
  :undoc-members:
10
22
  :members:
@@ -60,6 +60,7 @@ optional-dependencies.dev = [
60
60
  "pyright==1.1.408",
61
61
  "pyroma==5.0.1",
62
62
  "pytest==9.0.2",
63
+ "pytest-asyncio==1.3.0",
63
64
  "pytest-cov==7.0.0",
64
65
  "pyyaml==6.0.3",
65
66
  "ruff==0.15.2",
@@ -359,6 +360,7 @@ ignore_path = [
359
360
  # but Vulture does not enable this.
360
361
  ignore_names = [
361
362
  # Public API classes imported by users from vws.transports
363
+ "AsyncHTTPXTransport",
362
364
  "HTTPXTransport",
363
365
  # pytest configuration
364
366
  "pytest_collect_file",
@@ -27,6 +27,8 @@ admin
27
27
  api
28
28
  args
29
29
  ascii
30
+ async
31
+ asyncio
30
32
  beartype
31
33
  bool
32
34
  boolean
@@ -1,11 +1,17 @@
1
1
  """A library for Vuforia Web Services."""
2
2
 
3
+ from .async_query import AsyncCloudRecoService
4
+ from .async_vumark_service import AsyncVuMarkService
5
+ from .async_vws import AsyncVWS
3
6
  from .query import CloudRecoService
4
7
  from .vumark_service import VuMarkService
5
8
  from .vws import VWS
6
9
 
7
10
  __all__ = [
8
11
  "VWS",
12
+ "AsyncCloudRecoService",
13
+ "AsyncVWS",
14
+ "AsyncVuMarkService",
9
15
  "CloudRecoService",
10
16
  "VuMarkService",
11
17
  ]
@@ -0,0 +1,77 @@
1
+ """Internal helper for making authenticated async requests to the
2
+ Vuforia Target API.
3
+ """
4
+
5
+ from beartype import BeartypeConf, beartype
6
+ from vws_auth_tools import authorization_header, rfc_1123_date
7
+
8
+ from vws.response import Response
9
+ from vws.transports import AsyncTransport
10
+
11
+
12
+ @beartype(conf=BeartypeConf(is_pep484_tower=True))
13
+ async def async_target_api_request(
14
+ *,
15
+ content_type: str,
16
+ server_access_key: str,
17
+ server_secret_key: str,
18
+ method: str,
19
+ data: bytes,
20
+ request_path: str,
21
+ base_vws_url: str,
22
+ request_timeout_seconds: float | tuple[float, float],
23
+ extra_headers: dict[str, str],
24
+ transport: AsyncTransport,
25
+ ) -> Response:
26
+ """Make an async request to the Vuforia Target API.
27
+
28
+ Args:
29
+ content_type: The content type of the request.
30
+ server_access_key: A VWS server access key.
31
+ server_secret_key: A VWS server secret key.
32
+ method: The HTTP method which will be used in the
33
+ request.
34
+ data: The request body which will be used in the
35
+ request.
36
+ request_path: The path to the endpoint which will be
37
+ used in the request.
38
+ base_vws_url: The base URL for the VWS API.
39
+ request_timeout_seconds: The timeout for the request.
40
+ This can be a float to set both the connect and
41
+ read timeouts, or a (connect, read) tuple.
42
+ extra_headers: Additional headers to include in the
43
+ request.
44
+ transport: The async HTTP transport to use for the
45
+ request.
46
+
47
+ Returns:
48
+ The response to the request.
49
+ """
50
+ date_string = rfc_1123_date()
51
+
52
+ signature_string = authorization_header(
53
+ access_key=server_access_key,
54
+ secret_key=server_secret_key,
55
+ method=method,
56
+ content=data,
57
+ content_type=content_type,
58
+ date=date_string,
59
+ request_path=request_path,
60
+ )
61
+
62
+ headers = {
63
+ "Authorization": signature_string,
64
+ "Date": date_string,
65
+ "Content-Type": content_type,
66
+ **extra_headers,
67
+ }
68
+
69
+ url = base_vws_url.rstrip("/") + request_path
70
+
71
+ return await transport(
72
+ method=method,
73
+ url=url,
74
+ headers=headers,
75
+ data=data,
76
+ request_timeout=request_timeout_seconds,
77
+ )
@@ -0,0 +1,18 @@
1
+ """Image utility functions shared across VWS modules."""
2
+
3
+ import io
4
+ from typing import BinaryIO
5
+
6
+ from beartype import beartype
7
+
8
+ ImageType = io.BytesIO | BinaryIO
9
+
10
+
11
+ @beartype
12
+ def get_image_data(image: ImageType) -> bytes:
13
+ """Get the data of an image file."""
14
+ original_tell = image.tell()
15
+ image.seek(0)
16
+ image_data = image.read()
17
+ image.seek(original_tell)
18
+ return image_data
@@ -72,5 +72,5 @@ def target_api_request(
72
72
  url=url,
73
73
  headers=headers,
74
74
  data=data,
75
- timeout=request_timeout_seconds,
75
+ request_timeout=request_timeout_seconds,
76
76
  )
@@ -0,0 +1,226 @@
1
+ """Async tools for interacting with the Vuforia Cloud Recognition
2
+ Web APIs.
3
+ """
4
+
5
+ import datetime
6
+ import json
7
+ from http import HTTPMethod, HTTPStatus
8
+ from typing import Any, Self
9
+
10
+ from beartype import BeartypeConf, beartype
11
+ from urllib3.filepost import encode_multipart_formdata
12
+ from vws_auth_tools import authorization_header, rfc_1123_date
13
+
14
+ from vws._image_utils import ImageType as _ImageType
15
+ from vws._image_utils import get_image_data as _get_image_data
16
+ from vws.exceptions.cloud_reco_exceptions import (
17
+ AuthenticationFailureError,
18
+ BadImageError,
19
+ InactiveProjectError,
20
+ MaxNumResultsOutOfRangeError,
21
+ RequestTimeTooSkewedError,
22
+ )
23
+ from vws.exceptions.custom_exceptions import (
24
+ RequestEntityTooLargeError,
25
+ ServerError,
26
+ )
27
+ from vws.include_target_data import CloudRecoIncludeTargetData
28
+ from vws.reports import QueryResult, TargetData
29
+ from vws.transports import AsyncHTTPXTransport, AsyncTransport
30
+
31
+
32
+ @beartype(conf=BeartypeConf(is_pep484_tower=True))
33
+ class AsyncCloudRecoService:
34
+ """An async interface to the Vuforia Cloud Recognition Web
35
+ APIs.
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ *,
41
+ client_access_key: str,
42
+ client_secret_key: str,
43
+ base_vwq_url: str = "https://cloudreco.vuforia.com",
44
+ request_timeout_seconds: float | tuple[float, float] = 30.0,
45
+ transport: AsyncTransport | None = None,
46
+ ) -> None:
47
+ """
48
+ Args:
49
+ client_access_key: A VWS client access key.
50
+ client_secret_key: A VWS client secret key.
51
+ base_vwq_url: The base URL for the VWQ API.
52
+ request_timeout_seconds: The timeout for each
53
+ HTTP request. This can be a float to set both
54
+ the connect and read timeouts, or a
55
+ (connect, read) tuple.
56
+ transport: The async HTTP transport to use for
57
+ requests. Defaults to
58
+ ``AsyncHTTPXTransport()``.
59
+ """
60
+ self._client_access_key = client_access_key
61
+ self._client_secret_key = client_secret_key
62
+ self._base_vwq_url = base_vwq_url
63
+ self._request_timeout_seconds = request_timeout_seconds
64
+ self._transport = transport or AsyncHTTPXTransport()
65
+
66
+ async def aclose(self) -> None:
67
+ """Close the underlying transport if it supports closing."""
68
+ close = getattr(self._transport, "aclose", None)
69
+ if close is not None:
70
+ await close()
71
+
72
+ async def __aenter__(self) -> Self:
73
+ """Enter the async context manager."""
74
+ return self
75
+
76
+ async def __aexit__(self, *_args: object) -> None:
77
+ """Exit the async context manager and close the transport."""
78
+ await self.aclose()
79
+
80
+ async def query(
81
+ self,
82
+ *,
83
+ image: _ImageType,
84
+ max_num_results: int = 1,
85
+ include_target_data: CloudRecoIncludeTargetData = (
86
+ CloudRecoIncludeTargetData.TOP
87
+ ),
88
+ ) -> list[QueryResult]:
89
+ """Use the Vuforia Web Query API to make an Image
90
+ Recognition Query.
91
+
92
+ See
93
+ https://developer.vuforia.com/library/web-api/vuforia-query-web-api
94
+ for parameter details.
95
+
96
+ Args:
97
+ image: The image to make a query against.
98
+ max_num_results: The maximum number of matching
99
+ targets to be returned.
100
+ include_target_data: Indicates if target_data
101
+ records shall be returned for the matched
102
+ targets. Accepted values are top (default
103
+ value, only return target_data for top ranked
104
+ match), none (return no target_data), all
105
+ (for all matched targets).
106
+
107
+ Raises:
108
+ ~vws.exceptions.cloud_reco_exceptions.AuthenticationFailureError:
109
+ The client access key pair is not correct.
110
+ ~vws.exceptions.cloud_reco_exceptions.MaxNumResultsOutOfRangeError:
111
+ ``max_num_results`` is not within the range (1, 50).
112
+ ~vws.exceptions.cloud_reco_exceptions.InactiveProjectError: The
113
+ project is inactive.
114
+ ~vws.exceptions.cloud_reco_exceptions.RequestTimeTooSkewedError:
115
+ There is an error with the time sent to Vuforia.
116
+ ~vws.exceptions.cloud_reco_exceptions.BadImageError: There is a
117
+ problem with the given image. For example, it must be a JPEG or
118
+ PNG file in the grayscale or RGB color space.
119
+ ~vws.exceptions.custom_exceptions.RequestEntityTooLargeError: The
120
+ given image is too large.
121
+ ~vws.exceptions.custom_exceptions.ServerError: There is an
122
+ error with Vuforia's servers.
123
+
124
+ Returns:
125
+ An ordered list of target details of matching
126
+ targets.
127
+ """
128
+ image_content = _get_image_data(image=image)
129
+ body: dict[str, Any] = {
130
+ "image": (
131
+ "image.jpeg",
132
+ image_content,
133
+ "image/jpeg",
134
+ ),
135
+ "max_num_results": (
136
+ None,
137
+ int(max_num_results),
138
+ "text/plain",
139
+ ),
140
+ "include_target_data": (
141
+ None,
142
+ include_target_data.value,
143
+ "text/plain",
144
+ ),
145
+ }
146
+ date = rfc_1123_date()
147
+ request_path = "/v1/query"
148
+ content, content_type_header = encode_multipart_formdata(fields=body)
149
+ method = HTTPMethod.POST
150
+
151
+ authorization_string = authorization_header(
152
+ access_key=self._client_access_key,
153
+ secret_key=self._client_secret_key,
154
+ method=method,
155
+ content=content,
156
+ # Note that this is not the actual Content-Type
157
+ # header value sent.
158
+ content_type="multipart/form-data",
159
+ date=date,
160
+ request_path=request_path,
161
+ )
162
+
163
+ headers = {
164
+ "Authorization": authorization_string,
165
+ "Date": date,
166
+ "Content-Type": content_type_header,
167
+ }
168
+
169
+ response = await self._transport(
170
+ method=method,
171
+ url=self._base_vwq_url.rstrip("/") + request_path,
172
+ headers=headers,
173
+ data=content,
174
+ request_timeout=self._request_timeout_seconds,
175
+ )
176
+
177
+ if response.status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE:
178
+ raise RequestEntityTooLargeError(response=response)
179
+
180
+ if "Integer out of range" in response.text:
181
+ raise MaxNumResultsOutOfRangeError(
182
+ response=response,
183
+ )
184
+
185
+ if (
186
+ response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR
187
+ ): # pragma: no cover
188
+ raise ServerError(response=response)
189
+
190
+ result_code = json.loads(s=response.text)["result_code"]
191
+ if result_code != "Success":
192
+ exception = {
193
+ "AuthenticationFailure": (AuthenticationFailureError),
194
+ "BadImage": BadImageError,
195
+ "InactiveProject": InactiveProjectError,
196
+ "RequestTimeTooSkewed": (RequestTimeTooSkewedError),
197
+ }[result_code]
198
+ raise exception(response=response)
199
+
200
+ result: list[QueryResult] = []
201
+ result_list = list(
202
+ json.loads(s=response.text)["results"],
203
+ )
204
+ for item in result_list:
205
+ target_data: TargetData | None = None
206
+ if "target_data" in item:
207
+ target_data_dict = item["target_data"]
208
+ metadata = target_data_dict["application_metadata"]
209
+ timestamp_string = target_data_dict["target_timestamp"]
210
+ target_timestamp = datetime.datetime.fromtimestamp(
211
+ timestamp=timestamp_string,
212
+ tz=datetime.UTC,
213
+ )
214
+ target_data = TargetData(
215
+ name=target_data_dict["name"],
216
+ application_metadata=metadata,
217
+ target_timestamp=target_timestamp,
218
+ )
219
+
220
+ query_result = QueryResult(
221
+ target_id=item["target_id"],
222
+ target_data=target_data,
223
+ )
224
+
225
+ result.append(query_result)
226
+ return result
@@ -0,0 +1,170 @@
1
+ """Async interface to the Vuforia VuMark Generation Web API."""
2
+
3
+ import json
4
+ from http import HTTPMethod, HTTPStatus
5
+ from typing import Self
6
+
7
+ from beartype import BeartypeConf, beartype
8
+
9
+ from vws._async_vws_request import async_target_api_request
10
+ from vws.exceptions.custom_exceptions import ServerError
11
+ from vws.exceptions.vws_exceptions import (
12
+ AuthenticationFailureError,
13
+ BadRequestError,
14
+ DateRangeError,
15
+ FailError,
16
+ InvalidAcceptHeaderError,
17
+ InvalidInstanceIdError,
18
+ InvalidTargetTypeError,
19
+ RequestTimeTooSkewedError,
20
+ TargetStatusNotSuccessError,
21
+ TooManyRequestsError,
22
+ UnknownTargetError,
23
+ )
24
+ from vws.transports import AsyncHTTPXTransport, AsyncTransport
25
+ from vws.vumark_accept import VuMarkAccept
26
+
27
+
28
+ @beartype(conf=BeartypeConf(is_pep484_tower=True))
29
+ class AsyncVuMarkService:
30
+ """An async interface to the Vuforia VuMark Generation Web
31
+ API.
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ *,
37
+ server_access_key: str,
38
+ server_secret_key: str,
39
+ base_vws_url: str = "https://vws.vuforia.com",
40
+ request_timeout_seconds: float | tuple[float, float] = 30.0,
41
+ transport: AsyncTransport | None = None,
42
+ ) -> None:
43
+ """
44
+ Args:
45
+ server_access_key: A VWS server access key.
46
+ server_secret_key: A VWS server secret key.
47
+ base_vws_url: The base URL for the VWS API.
48
+ request_timeout_seconds: The timeout for each
49
+ HTTP request. This can be a float to set both
50
+ the connect and read timeouts, or a
51
+ (connect, read) tuple.
52
+ transport: The async HTTP transport to use for
53
+ requests. Defaults to
54
+ ``AsyncHTTPXTransport()``.
55
+ """
56
+ self._server_access_key = server_access_key
57
+ self._server_secret_key = server_secret_key
58
+ self._base_vws_url = base_vws_url
59
+ self._request_timeout_seconds = request_timeout_seconds
60
+ self._transport = transport or AsyncHTTPXTransport()
61
+
62
+ async def aclose(self) -> None:
63
+ """Close the underlying transport if it supports closing."""
64
+ close = getattr(self._transport, "aclose", None)
65
+ if close is not None:
66
+ await close()
67
+
68
+ async def __aenter__(self) -> Self:
69
+ """Enter the async context manager."""
70
+ return self
71
+
72
+ async def __aexit__(self, *_args: object) -> None:
73
+ """Exit the async context manager and close the transport."""
74
+ await self.aclose()
75
+
76
+ async def generate_vumark_instance(
77
+ self,
78
+ *,
79
+ target_id: str,
80
+ instance_id: str,
81
+ accept: VuMarkAccept,
82
+ ) -> bytes:
83
+ """Generate a VuMark instance image.
84
+
85
+ See
86
+ https://developer.vuforia.com/library/vuforia-engine/web-api/vumark-generation-web-api/
87
+ for parameter details.
88
+
89
+ Args:
90
+ target_id: The ID of the VuMark target.
91
+ instance_id: The instance ID to encode in the
92
+ VuMark.
93
+ accept: The image format to return.
94
+
95
+ Returns:
96
+ The VuMark instance image bytes.
97
+
98
+ Raises:
99
+ ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The
100
+ secret key is not correct.
101
+ ~vws.exceptions.vws_exceptions.FailError: There was an error with
102
+ the request. For example, the given access key does not match a
103
+ known database.
104
+ ~vws.exceptions.vws_exceptions.InvalidAcceptHeaderError: The
105
+ Accept header value is not supported.
106
+ ~vws.exceptions.vws_exceptions.InvalidInstanceIdError: The
107
+ instance ID is invalid. For example, it may be empty.
108
+ ~vws.exceptions.vws_exceptions.InvalidTargetTypeError: The target
109
+ is not a VuMark template target.
110
+ ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is
111
+ an error with the time sent to Vuforia.
112
+ ~vws.exceptions.vws_exceptions.TargetStatusNotSuccessError: The
113
+ target is not in the success state.
114
+ ~vws.exceptions.vws_exceptions.UnknownTargetError: The given target
115
+ ID does not match a target in the database.
116
+ ~vws.exceptions.custom_exceptions.ServerError: There is an error
117
+ with Vuforia's servers.
118
+ ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is
119
+ rate limiting access.
120
+ """
121
+ request_path = f"/targets/{target_id}/instances"
122
+ content_type = "application/json"
123
+ request_data = json.dumps(
124
+ obj={"instance_id": instance_id},
125
+ ).encode(encoding="utf-8")
126
+
127
+ response = await async_target_api_request(
128
+ content_type=content_type,
129
+ server_access_key=self._server_access_key,
130
+ server_secret_key=self._server_secret_key,
131
+ method=HTTPMethod.POST,
132
+ data=request_data,
133
+ request_path=request_path,
134
+ base_vws_url=self._base_vws_url,
135
+ request_timeout_seconds=(self._request_timeout_seconds),
136
+ extra_headers={"Accept": accept},
137
+ transport=self._transport,
138
+ )
139
+
140
+ if (
141
+ response.status_code == HTTPStatus.TOO_MANY_REQUESTS
142
+ ): # pragma: no cover
143
+ # The Vuforia API returns a 429 response with no
144
+ # JSON body.
145
+ raise TooManyRequestsError(response=response)
146
+
147
+ if (
148
+ response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR
149
+ ): # pragma: no cover
150
+ raise ServerError(response=response)
151
+
152
+ if response.status_code == HTTPStatus.OK:
153
+ return response.content
154
+
155
+ result_code = json.loads(s=response.text)["result_code"]
156
+
157
+ exception = {
158
+ "AuthenticationFailure": (AuthenticationFailureError),
159
+ "BadRequest": BadRequestError,
160
+ "DateRangeError": DateRangeError,
161
+ "Fail": FailError,
162
+ "InvalidAcceptHeader": InvalidAcceptHeaderError,
163
+ "InvalidInstanceId": InvalidInstanceIdError,
164
+ "InvalidTargetType": InvalidTargetTypeError,
165
+ "RequestTimeTooSkewed": RequestTimeTooSkewedError,
166
+ "TargetStatusNotSuccess": (TargetStatusNotSuccessError),
167
+ "UnknownTarget": UnknownTargetError,
168
+ }[result_code]
169
+
170
+ raise exception(response=response)