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.
vws/vws.py ADDED
@@ -0,0 +1,719 @@
1
+ """Tools for interacting with Vuforia APIs."""
2
+
3
+ import base64
4
+ import calendar # noqa: TC003
5
+ import json
6
+ import time
7
+ from http import HTTPMethod, HTTPStatus
8
+
9
+ from beartype import BeartypeConf, beartype
10
+
11
+ from vws._image_utils import ImageType as _ImageType
12
+ from vws._image_utils import get_image_data as _get_image_data
13
+ from vws._reco_counts import (
14
+ reco_counts_report_body,
15
+ reco_counts_report_path,
16
+ report_from_download_response,
17
+ )
18
+ from vws._vws_request import target_api_request
19
+ from vws.exceptions.base_exceptions import VWSError
20
+ from vws.exceptions.custom_exceptions import (
21
+ RecoCountsReportNotReadyError,
22
+ RecoCountsReportTimeoutError,
23
+ ServerError,
24
+ TargetProcessingTimeoutError,
25
+ )
26
+ from vws.exceptions.vws_exceptions import TooManyRequestsError
27
+ from vws.reports import (
28
+ DatabaseSummaryReport,
29
+ RecoCountsReport,
30
+ RecoCountsReportRequest,
31
+ TargetStatusAndRecord,
32
+ TargetStatuses,
33
+ TargetSummaryReport,
34
+ )
35
+ from vws.response import Response # noqa: TC001
36
+ from vws.transports import RequestsTransport, Transport
37
+
38
+
39
+ @beartype(conf=BeartypeConf(is_pep484_tower=True))
40
+ class VWS:
41
+ """An interface to Vuforia Web Services APIs."""
42
+
43
+ def __init__(
44
+ self,
45
+ *,
46
+ server_access_key: str,
47
+ server_secret_key: str,
48
+ base_vws_url: str = "https://vws.vuforia.com",
49
+ database_id: str | None = None,
50
+ request_timeout_seconds: float | tuple[float, float] = 30.0,
51
+ transport: Transport | None = None,
52
+ ) -> None:
53
+ """
54
+ Args:
55
+ server_access_key: A VWS server access key.
56
+ server_secret_key: A VWS server secret key.
57
+ base_vws_url: The base URL for the VWS API.
58
+ database_id: The ID of the database which the
59
+ given keys belong to. This is shown in the
60
+ target manager. It is needed only by
61
+ :meth:`request_database_reco_counts_report`.
62
+ request_timeout_seconds: The timeout for each
63
+ HTTP request. This can be a float to set both
64
+ the connect and read timeouts, or a
65
+ (connect, read) tuple.
66
+ transport: The HTTP transport to use for
67
+ requests. Defaults to
68
+ ``RequestsTransport()``.
69
+ """
70
+ self._server_access_key = server_access_key
71
+ self._server_secret_key = server_secret_key
72
+ self._base_vws_url = base_vws_url
73
+ self._database_id = database_id
74
+ self._request_timeout_seconds = request_timeout_seconds
75
+ self._transport = (
76
+ transport if transport is not None else RequestsTransport()
77
+ )
78
+
79
+ def make_request(
80
+ self,
81
+ *,
82
+ method: str,
83
+ data: bytes,
84
+ request_path: str,
85
+ expected_result_code: str,
86
+ content_type: str,
87
+ extra_headers: dict[str, str] | None = None,
88
+ ) -> Response:
89
+ """Make a request to the Vuforia Target API.
90
+
91
+ Args:
92
+ method: The HTTP method which will be used in
93
+ the request.
94
+ data: The request body which will be used in the
95
+ request.
96
+ request_path: The path to the endpoint which
97
+ will be used in the request.
98
+ expected_result_code: See
99
+ "VWS API Result Codes" on
100
+ https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api.
101
+ content_type: The content type of the request.
102
+ extra_headers: Additional headers to include in
103
+ the request.
104
+
105
+ Returns:
106
+ The response to the request.
107
+
108
+ Raises:
109
+ ~vws.exceptions.custom_exceptions.ServerError:
110
+ There is an error with Vuforia's servers.
111
+ ~vws.exceptions.vws_exceptions.TooManyRequestsError:
112
+ Vuforia is rate limiting access.
113
+ json.JSONDecodeError: The server did not respond
114
+ with valid JSON. This may happen if the
115
+ server address is not a valid Vuforia server.
116
+ """
117
+ response = target_api_request(
118
+ content_type=content_type,
119
+ server_access_key=self._server_access_key,
120
+ server_secret_key=self._server_secret_key,
121
+ method=method,
122
+ data=data,
123
+ request_path=request_path,
124
+ base_vws_url=self._base_vws_url,
125
+ request_timeout_seconds=self._request_timeout_seconds,
126
+ extra_headers=extra_headers or {},
127
+ transport=self._transport,
128
+ )
129
+
130
+ if (
131
+ response.status_code == HTTPStatus.TOO_MANY_REQUESTS
132
+ ): # pragma: no cover
133
+ # The Vuforia API returns a 429 response with no JSON body.
134
+ raise TooManyRequestsError(response=response)
135
+
136
+ if (
137
+ response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR
138
+ ): # pragma: no cover
139
+ raise ServerError(response=response)
140
+
141
+ result_code = json.loads(s=response.text)["result_code"]
142
+
143
+ if result_code == expected_result_code:
144
+ return response
145
+
146
+ raise VWSError.from_result_code(
147
+ result_code=result_code,
148
+ response=response,
149
+ )
150
+
151
+ def add_target(
152
+ self,
153
+ *,
154
+ name: str,
155
+ width: float,
156
+ image: _ImageType,
157
+ application_metadata: str | None,
158
+ active_flag: bool,
159
+ ) -> str:
160
+ """Add a target to a Vuforia Web Services database.
161
+
162
+ See
163
+ https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#add
164
+ for parameter details.
165
+
166
+ Args:
167
+ name: The name of the target.
168
+ width: The width of the target.
169
+ image: The image of the target.
170
+ active_flag: Whether or not the target is active for query.
171
+ application_metadata: The application metadata of the target.
172
+ This must be base64 encoded, for example by using::
173
+
174
+ base64.b64encode('input_string').decode('ascii')
175
+
176
+ Returns:
177
+ The target ID of the new target.
178
+
179
+ Raises:
180
+ ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The
181
+ secret key is not correct.
182
+ ~vws.exceptions.vws_exceptions.BadImageError: There is a problem
183
+ with the given image. For example, it must be a JPEG or PNG
184
+ file in the grayscale or RGB color space.
185
+ ~vws.exceptions.vws_exceptions.FailError: There was an error with
186
+ the request. For example, the given access key does not match a
187
+ known database.
188
+ ~vws.exceptions.vws_exceptions.MetadataTooLargeError: The given
189
+ metadata is too large. The maximum size is 1 MB of data when
190
+ Base64 encoded.
191
+ ~vws.exceptions.vws_exceptions.ImageTooLargeError: The given image
192
+ is too large.
193
+ ~vws.exceptions.vws_exceptions.TargetNameExistError: A target with
194
+ the given ``name`` already exists.
195
+ ~vws.exceptions.vws_exceptions.ProjectInactiveError: The project is
196
+ inactive.
197
+ ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is
198
+ an error with the time sent to Vuforia.
199
+ ~vws.exceptions.custom_exceptions.ServerError: There is an error
200
+ with Vuforia's servers. This has been seen to happen when the
201
+ given name includes a bad character.
202
+ ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is
203
+ rate limiting access.
204
+ """
205
+ image_data = _get_image_data(image=image)
206
+ image_data_encoded = base64.b64encode(s=image_data).decode(
207
+ encoding="ascii",
208
+ )
209
+
210
+ data = {
211
+ "name": name,
212
+ "width": width,
213
+ "image": image_data_encoded,
214
+ "active_flag": active_flag,
215
+ "application_metadata": application_metadata,
216
+ }
217
+
218
+ content = json.dumps(obj=data).encode(encoding="utf-8")
219
+
220
+ response = self.make_request(
221
+ method=HTTPMethod.POST,
222
+ data=content,
223
+ request_path="/targets",
224
+ expected_result_code="TargetCreated",
225
+ content_type="application/json",
226
+ )
227
+
228
+ return str(object=json.loads(s=response.text)["target_id"])
229
+
230
+ def get_target_record(self, target_id: str) -> TargetStatusAndRecord:
231
+ """Get a given target's target record from the Target Management
232
+ System.
233
+
234
+ See
235
+ https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#target-record.
236
+
237
+ Args:
238
+ target_id: The ID of the target to get details of.
239
+
240
+ Returns:
241
+ Response details of a target from Vuforia.
242
+
243
+ Raises:
244
+ ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The
245
+ secret key is not correct.
246
+ ~vws.exceptions.vws_exceptions.FailError: There was an error with
247
+ the request. For example, the given access key does not match a
248
+ known database.
249
+ ~vws.exceptions.vws_exceptions.UnknownTargetError: The given target
250
+ ID does not match a target in the database.
251
+ ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is
252
+ an error with the time sent to Vuforia.
253
+ ~vws.exceptions.custom_exceptions.ServerError: There is an error
254
+ with Vuforia's servers.
255
+ ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is
256
+ rate limiting access.
257
+ """
258
+ response = self.make_request(
259
+ method=HTTPMethod.GET,
260
+ data=b"",
261
+ request_path=f"/targets/{target_id}",
262
+ expected_result_code="Success",
263
+ content_type="application/json",
264
+ )
265
+
266
+ result_data = json.loads(s=response.text)
267
+ return TargetStatusAndRecord.from_response_dict(
268
+ response_dict=result_data,
269
+ )
270
+
271
+ def wait_for_target_processed(
272
+ self,
273
+ *,
274
+ target_id: str,
275
+ seconds_between_requests: float = 0.2,
276
+ timeout_seconds: float = 60 * 5,
277
+ ) -> None:
278
+ """Wait up to five minutes (arbitrary) for a target to get past the
279
+ processing stage.
280
+
281
+ Args:
282
+ target_id: The ID of the target to wait for.
283
+ seconds_between_requests: The number of seconds to wait between
284
+ requests made while polling the target status.
285
+ We wait 0.2 seconds by default, rather than less, than that to
286
+ decrease the number of calls made to the API, to decrease the
287
+ likelihood of hitting the request quota.
288
+ timeout_seconds: The maximum number of seconds to wait for the
289
+ target to be processed.
290
+
291
+ Raises:
292
+ ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The
293
+ secret key is not correct.
294
+ ~vws.exceptions.vws_exceptions.FailError: There was an error with
295
+ the request. For example, the given access key does not match a
296
+ known database.
297
+ ~vws.exceptions.custom_exceptions.TargetProcessingTimeoutError: The
298
+ target remained in the processing stage for more than
299
+ ``timeout_seconds`` seconds.
300
+ ~vws.exceptions.vws_exceptions.UnknownTargetError: The given target
301
+ ID does not match a target in the database.
302
+ ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is
303
+ an error with the time sent to Vuforia.
304
+ ~vws.exceptions.custom_exceptions.ServerError: There is an error
305
+ with Vuforia's servers.
306
+ ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is
307
+ rate limiting access.
308
+ """
309
+ start_time = time.monotonic()
310
+ while True:
311
+ report = self.get_target_summary_report(target_id=target_id)
312
+ if report.status != TargetStatuses.PROCESSING:
313
+ # Guard against the target still being seen as
314
+ # processing by other endpoints due to eventual
315
+ # consistency.
316
+ time.sleep(seconds_between_requests)
317
+ return
318
+
319
+ elapsed_time = time.monotonic() - start_time
320
+ if elapsed_time > timeout_seconds: # pragma: no cover
321
+ raise TargetProcessingTimeoutError
322
+
323
+ time.sleep(seconds_between_requests)
324
+
325
+ def list_targets(self) -> list[str]:
326
+ """List target IDs.
327
+
328
+ See
329
+ https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#details-list.
330
+
331
+ Returns:
332
+ The IDs of all targets in the database.
333
+
334
+ Raises:
335
+ ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The
336
+ secret key is not correct.
337
+ ~vws.exceptions.vws_exceptions.FailError: There was an error with
338
+ the request. For example, the given access key does not match a
339
+ known database.
340
+ ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is
341
+ an error with the time sent to Vuforia.
342
+ ~vws.exceptions.custom_exceptions.ServerError: There is an error
343
+ with Vuforia's servers.
344
+ ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is
345
+ rate limiting access.
346
+ """
347
+ response = self.make_request(
348
+ method=HTTPMethod.GET,
349
+ data=b"",
350
+ request_path="/targets",
351
+ expected_result_code="Success",
352
+ content_type="application/json",
353
+ )
354
+
355
+ return list(json.loads(s=response.text)["results"])
356
+
357
+ def get_target_summary_report(self, target_id: str) -> TargetSummaryReport:
358
+ """Get a summary report for a target.
359
+
360
+ See
361
+ https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#summary-report.
362
+
363
+ Args:
364
+ target_id: The ID of the target to get a summary report for.
365
+
366
+ Returns:
367
+ Details of the target.
368
+
369
+ Raises:
370
+ ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The
371
+ secret key is not correct.
372
+ ~vws.exceptions.vws_exceptions.FailError: There was an error with
373
+ the request. For example, the given access key does not match a
374
+ known database.
375
+ ~vws.exceptions.vws_exceptions.UnknownTargetError: The given target
376
+ ID does not match a target in the database.
377
+ ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is
378
+ an error with the time sent to Vuforia.
379
+ ~vws.exceptions.custom_exceptions.ServerError: There is an error
380
+ with Vuforia's servers.
381
+ ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is
382
+ rate limiting access.
383
+ """
384
+ response = self.make_request(
385
+ method=HTTPMethod.GET,
386
+ data=b"",
387
+ request_path=f"/summary/{target_id}",
388
+ expected_result_code="Success",
389
+ content_type="application/json",
390
+ )
391
+
392
+ result_data = dict(json.loads(s=response.text))
393
+ return TargetSummaryReport.from_response_dict(
394
+ response_dict=result_data,
395
+ )
396
+
397
+ def get_database_summary_report(self) -> DatabaseSummaryReport:
398
+ """Get a summary report for the database.
399
+
400
+ See
401
+ https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#summary-report.
402
+
403
+ Returns:
404
+ Details of the database.
405
+
406
+ Raises:
407
+ ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The
408
+ secret key is not correct.
409
+ ~vws.exceptions.vws_exceptions.FailError: There was an error with
410
+ the request. For example, the given access key does not match a
411
+ known database.
412
+ ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is
413
+ an error with the time sent to Vuforia.
414
+ ~vws.exceptions.custom_exceptions.ServerError: There is an error
415
+ with Vuforia's servers.
416
+ ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is
417
+ rate limiting access.
418
+ """
419
+ response = self.make_request(
420
+ method=HTTPMethod.GET,
421
+ data=b"",
422
+ request_path="/summary",
423
+ expected_result_code="Success",
424
+ content_type="application/json",
425
+ )
426
+
427
+ response_data = dict(json.loads(s=response.text))
428
+ return DatabaseSummaryReport.from_response_dict(
429
+ response_dict=response_data,
430
+ )
431
+
432
+ def request_database_reco_counts_report(
433
+ self,
434
+ *,
435
+ year: int,
436
+ month: calendar.Month,
437
+ ) -> RecoCountsReportRequest:
438
+ """Request a per-target recognition count report for the database.
439
+
440
+ Vuforia generates the report in the background, so the report is not
441
+ available to download immediately. Use
442
+ :meth:`wait_for_reco_counts_report` to wait for it.
443
+
444
+ Args:
445
+ year: The year to get recognition counts for.
446
+ month: The month of the year to get recognition counts for.
447
+ Vuforia accepts only the current month and the previous
448
+ month. A month taken from a :class:`datetime.datetime` needs
449
+ wrapping, as in ``calendar.Month(value=now.month)``.
450
+
451
+ Returns:
452
+ The URL to download the report from, and the transaction ID of
453
+ the request.
454
+
455
+ Raises:
456
+ ~vws.exceptions.custom_exceptions.DatabaseIdNotSetError: No
457
+ ``database_id`` was given to the client.
458
+ ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The
459
+ secret key is not correct, or the client's ``database_id`` is
460
+ not the ID of the database which the client's keys belong to.
461
+ ~vws.exceptions.vws_exceptions.FailError: There was an error with
462
+ the request. For example, the given year and month are not
463
+ the current month or the previous month.
464
+ ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is
465
+ an error with the time sent to Vuforia.
466
+ ~vws.exceptions.custom_exceptions.ServerError: There is an error
467
+ with Vuforia's servers.
468
+ ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is
469
+ rate limiting access.
470
+ """
471
+ response = self.make_request(
472
+ method=HTTPMethod.POST,
473
+ data=reco_counts_report_body(year=year, month=month),
474
+ request_path=reco_counts_report_path(
475
+ database_id=self._database_id,
476
+ ),
477
+ expected_result_code="Success",
478
+ content_type="application/json",
479
+ )
480
+
481
+ response_data = dict(json.loads(s=response.text))
482
+ return RecoCountsReportRequest.from_response_dict(
483
+ response_dict=response_data,
484
+ )
485
+
486
+ def download_reco_counts_report(
487
+ self,
488
+ *,
489
+ presigned_url: str,
490
+ ) -> RecoCountsReport:
491
+ """Download a requested reco counts report.
492
+
493
+ The report's URL is not part of the VWS API, so this request is not
494
+ authorized with the client's keys.
495
+
496
+ Args:
497
+ presigned_url: The URL of the report, as given by
498
+ :meth:`request_database_reco_counts_report`.
499
+
500
+ Returns:
501
+ The downloaded report.
502
+
503
+ Raises:
504
+ ~vws.exceptions.custom_exceptions.RecoCountsReportNotReadyError:
505
+ Vuforia has not finished generating the report.
506
+ ~vws.exceptions.custom_exceptions.RecoCountsReportDownloadError:
507
+ The report could not be downloaded. For example, the report's
508
+ URL may have expired.
509
+ """
510
+ response = self._transport(
511
+ method=HTTPMethod.GET,
512
+ url=presigned_url,
513
+ headers={},
514
+ data=b"",
515
+ request_timeout=self._request_timeout_seconds,
516
+ )
517
+
518
+ return report_from_download_response(response=response)
519
+
520
+ def wait_for_reco_counts_report(
521
+ self,
522
+ *,
523
+ presigned_url: str,
524
+ seconds_between_requests: float = 0.2,
525
+ timeout_seconds: float = 60 * 5,
526
+ ) -> RecoCountsReport:
527
+ """Wait for a requested reco counts report to be generated, then
528
+ download it.
529
+
530
+ Args:
531
+ presigned_url: The URL of the report, as given by
532
+ :meth:`request_database_reco_counts_report`.
533
+ seconds_between_requests: The number of seconds to wait between
534
+ requests made while polling the report's URL.
535
+ timeout_seconds: The maximum number of seconds to wait for the
536
+ report to be generated.
537
+
538
+ Returns:
539
+ The downloaded report.
540
+
541
+ Raises:
542
+ ~vws.exceptions.custom_exceptions.RecoCountsReportTimeoutError:
543
+ The report was not generated within ``timeout_seconds``
544
+ seconds.
545
+ ~vws.exceptions.custom_exceptions.RecoCountsReportDownloadError:
546
+ The report could not be downloaded. For example, the report's
547
+ URL may have expired.
548
+ """
549
+ start_time = time.monotonic()
550
+ while True:
551
+ try:
552
+ return self.download_reco_counts_report(
553
+ presigned_url=presigned_url,
554
+ )
555
+ except RecoCountsReportNotReadyError:
556
+ elapsed_time = time.monotonic() - start_time
557
+ if elapsed_time > timeout_seconds:
558
+ raise RecoCountsReportTimeoutError from None
559
+
560
+ time.sleep(seconds_between_requests)
561
+
562
+ def delete_target(self, target_id: str) -> None:
563
+ """Delete a given target.
564
+
565
+ See
566
+ https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#delete.
567
+
568
+ Args:
569
+ target_id: The ID of the target to delete.
570
+
571
+ Raises:
572
+ ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The
573
+ secret key is not correct.
574
+ ~vws.exceptions.vws_exceptions.FailError: There was an error with
575
+ the request. For example, the given access key does not match a
576
+ known database.
577
+ ~vws.exceptions.vws_exceptions.UnknownTargetError: The given target
578
+ ID does not match a target in the database.
579
+ ~vws.exceptions.vws_exceptions.TargetStatusProcessingError: The
580
+ given target is in the processing state.
581
+ ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is
582
+ an error with the time sent to Vuforia.
583
+ ~vws.exceptions.custom_exceptions.ServerError: There is an error
584
+ with Vuforia's servers.
585
+ ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is
586
+ rate limiting access.
587
+ """
588
+ self.make_request(
589
+ method=HTTPMethod.DELETE,
590
+ data=b"",
591
+ request_path=f"/targets/{target_id}",
592
+ expected_result_code="Success",
593
+ content_type="application/json",
594
+ )
595
+
596
+ def get_duplicate_targets(self, target_id: str) -> list[str]:
597
+ """Get targets which may be considered duplicates of a given
598
+ target.
599
+
600
+ See
601
+ https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#check.
602
+
603
+ Args:
604
+ target_id: The ID of the target to delete.
605
+
606
+ Returns:
607
+ The target IDs of duplicate targets.
608
+
609
+ Raises:
610
+ ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The
611
+ secret key is not correct.
612
+ ~vws.exceptions.vws_exceptions.FailError: There was an error with
613
+ the request. For example, the given access key does not match a
614
+ known database.
615
+ ~vws.exceptions.vws_exceptions.UnknownTargetError: The given target
616
+ ID does not match a target in the database.
617
+ ~vws.exceptions.vws_exceptions.ProjectInactiveError: The project is
618
+ inactive.
619
+ ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is
620
+ an error with the time sent to Vuforia.
621
+ ~vws.exceptions.custom_exceptions.ServerError: There is an error
622
+ with Vuforia's servers.
623
+ ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is
624
+ rate limiting access.
625
+ """
626
+ response = self.make_request(
627
+ method=HTTPMethod.GET,
628
+ data=b"",
629
+ request_path=f"/duplicates/{target_id}",
630
+ expected_result_code="Success",
631
+ content_type="application/json",
632
+ )
633
+
634
+ return list(json.loads(s=response.text)["similar_targets"])
635
+
636
+ def update_target(
637
+ self,
638
+ *,
639
+ target_id: str,
640
+ name: str | None = None,
641
+ width: float | None = None,
642
+ image: _ImageType | None = None,
643
+ active_flag: bool | None = None,
644
+ application_metadata: str | None = None,
645
+ ) -> None:
646
+ """Update a target in a Vuforia Web Services database.
647
+
648
+ See
649
+ https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#update
650
+ for parameter details.
651
+
652
+ Args:
653
+ target_id: The ID of the target to update.
654
+ name: The name of the target.
655
+ width: The width of the target.
656
+ image: The image of the target.
657
+ active_flag: Whether or not the target is active for query.
658
+ application_metadata: The application metadata of the target.
659
+ This must be base64 encoded, for example by using::
660
+
661
+ base64.b64encode('input_string').decode('ascii')
662
+
663
+ Giving ``None`` will not change the application metadata.
664
+
665
+ Raises:
666
+ ~vws.exceptions.vws_exceptions.AuthenticationFailureError: The
667
+ secret key is not correct.
668
+ ~vws.exceptions.vws_exceptions.BadImageError: There is a problem
669
+ with the given image. For example, it must be a JPEG or PNG
670
+ file in the grayscale or RGB color space.
671
+ ~vws.exceptions.vws_exceptions.FailError: There was an error with
672
+ the request. For example, the given access key does not match a
673
+ known database.
674
+ ~vws.exceptions.vws_exceptions.MetadataTooLargeError: The given
675
+ metadata is too large. The maximum size is 1 MB of data when
676
+ Base64 encoded.
677
+ ~vws.exceptions.vws_exceptions.ImageTooLargeError: The given image
678
+ is too large.
679
+ ~vws.exceptions.vws_exceptions.TargetNameExistError: A target with
680
+ the given ``name`` already exists.
681
+ ~vws.exceptions.vws_exceptions.ProjectInactiveError: The project is
682
+ inactive.
683
+ ~vws.exceptions.vws_exceptions.RequestTimeTooSkewedError: There is
684
+ an error with the time sent to Vuforia.
685
+ ~vws.exceptions.custom_exceptions.ServerError: There is an error
686
+ with Vuforia's servers.
687
+ ~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is
688
+ rate limiting access.
689
+ """
690
+ data: dict[str, str | bool | float | int] = {}
691
+
692
+ if name is not None:
693
+ data["name"] = name
694
+
695
+ if width is not None:
696
+ data["width"] = width
697
+
698
+ if image is not None:
699
+ image_data = _get_image_data(image=image)
700
+ image_data_encoded = base64.b64encode(s=image_data).decode(
701
+ encoding="ascii",
702
+ )
703
+ data["image"] = image_data_encoded
704
+
705
+ if active_flag is not None:
706
+ data["active_flag"] = active_flag
707
+
708
+ if application_metadata is not None:
709
+ data["application_metadata"] = application_metadata
710
+
711
+ content = json.dumps(obj=data).encode(encoding="utf-8")
712
+
713
+ self.make_request(
714
+ method=HTTPMethod.PUT,
715
+ data=content,
716
+ request_path=f"/targets/{target_id}",
717
+ expected_result_code="Success",
718
+ content_type="application/json",
719
+ )