vws-python-mock 2026.8.4__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.
Files changed (62) hide show
  1. mock_vws/__init__.py +9 -0
  2. mock_vws/_base64_decoding.py +35 -0
  3. mock_vws/_constants.py +84 -0
  4. mock_vws/_database_matchers.py +107 -0
  5. mock_vws/_flask_server/Dockerfile +32 -0
  6. mock_vws/_flask_server/__init__.py +1 -0
  7. mock_vws/_flask_server/healthcheck.py +31 -0
  8. mock_vws/_flask_server/target_manager.py +447 -0
  9. mock_vws/_flask_server/vwq.py +173 -0
  10. mock_vws/_flask_server/vws.py +954 -0
  11. mock_vws/_mock_common.py +75 -0
  12. mock_vws/_model_target_web_api.py +486 -0
  13. mock_vws/_query_tools.py +136 -0
  14. mock_vws/_query_validators/__init__.py +128 -0
  15. mock_vws/_query_validators/accept_header_validators.py +31 -0
  16. mock_vws/_query_validators/auth_validators.py +143 -0
  17. mock_vws/_query_validators/content_length_validators.py +90 -0
  18. mock_vws/_query_validators/content_type_validators.py +65 -0
  19. mock_vws/_query_validators/date_validators.py +110 -0
  20. mock_vws/_query_validators/exceptions.py +769 -0
  21. mock_vws/_query_validators/fields_validators.py +47 -0
  22. mock_vws/_query_validators/image_validators.py +197 -0
  23. mock_vws/_query_validators/include_target_data_validators.py +51 -0
  24. mock_vws/_query_validators/num_results_validators.py +64 -0
  25. mock_vws/_query_validators/project_state_validators.py +49 -0
  26. mock_vws/_requests_mock_server/__init__.py +1 -0
  27. mock_vws/_requests_mock_server/decorators.py +275 -0
  28. mock_vws/_requests_mock_server/mock_web_query_api.py +139 -0
  29. mock_vws/_requests_mock_server/mock_web_services_api.py +954 -0
  30. mock_vws/_respx_mock_server/__init__.py +1 -0
  31. mock_vws/_respx_mock_server/decorators.py +186 -0
  32. mock_vws/_services_validators/__init__.py +186 -0
  33. mock_vws/_services_validators/active_flag_validators.py +43 -0
  34. mock_vws/_services_validators/auth_validators.py +124 -0
  35. mock_vws/_services_validators/content_length_validators.py +102 -0
  36. mock_vws/_services_validators/content_type_validators.py +42 -0
  37. mock_vws/_services_validators/date_validators.py +78 -0
  38. mock_vws/_services_validators/exceptions.py +858 -0
  39. mock_vws/_services_validators/image_validators.py +220 -0
  40. mock_vws/_services_validators/json_validators.py +69 -0
  41. mock_vws/_services_validators/key_validators.py +171 -0
  42. mock_vws/_services_validators/metadata_validators.py +106 -0
  43. mock_vws/_services_validators/name_validators.py +235 -0
  44. mock_vws/_services_validators/project_state_validators.py +76 -0
  45. mock_vws/_services_validators/request_quota_validators.py +42 -0
  46. mock_vws/_services_validators/target_quota_validators.py +41 -0
  47. mock_vws/_services_validators/target_validators.py +69 -0
  48. mock_vws/_services_validators/width_validators.py +38 -0
  49. mock_vws/database.py +257 -0
  50. mock_vws/database_type.py +13 -0
  51. mock_vws/image_matchers.py +124 -0
  52. mock_vws/model_target.py +79 -0
  53. mock_vws/py.typed +0 -0
  54. mock_vws/states.py +20 -0
  55. mock_vws/target.py +285 -0
  56. mock_vws/target_manager.py +178 -0
  57. mock_vws/target_raters.py +109 -0
  58. vws_python_mock-2026.8.4.dist-info/METADATA +177 -0
  59. vws_python_mock-2026.8.4.dist-info/RECORD +62 -0
  60. vws_python_mock-2026.8.4.dist-info/WHEEL +5 -0
  61. vws_python_mock-2026.8.4.dist-info/licenses/LICENSE +21 -0
  62. vws_python_mock-2026.8.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,769 @@
1
+ """Exceptions to raise from validators."""
2
+
3
+ import email.utils
4
+ import textwrap
5
+ import uuid
6
+ from collections.abc import Mapping
7
+ from http import HTTPStatus
8
+
9
+ from beartype import beartype
10
+
11
+ from mock_vws._constants import ResultCodes
12
+ from mock_vws._mock_common import json_dump
13
+
14
+
15
+ @beartype
16
+ class ValidatorError(Exception):
17
+ """
18
+ A base class for exceptions thrown from mock Vuforia cloud
19
+ recognition
20
+ client endpoints.
21
+ """
22
+
23
+ status_code: HTTPStatus
24
+ response_text: str
25
+ headers: Mapping[str, str]
26
+
27
+
28
+ @beartype
29
+ class DateHeaderNotGivenError(ValidatorError):
30
+ """Exception raised when a date header is not given."""
31
+
32
+ def __init__(self) -> None:
33
+ """
34
+ Attributes:
35
+ status_code: The status code to use in a response if this is
36
+ raised.
37
+ response_text: The response text to use in a response if this
38
+ is
39
+ raised.
40
+ """
41
+ super().__init__()
42
+ self.status_code = HTTPStatus.BAD_REQUEST
43
+ self.response_text = "Date header required."
44
+ date = email.utils.formatdate(
45
+ timeval=None,
46
+ localtime=False,
47
+ usegmt=True,
48
+ )
49
+ self.headers = {
50
+ "Content-Type": "text/plain;charset=iso-8859-1",
51
+ "Connection": "keep-alive",
52
+ "Server": "nginx",
53
+ "Date": date,
54
+ "Content-Length": str(object=len(self.response_text)),
55
+ }
56
+
57
+
58
+ @beartype
59
+ class DateFormatNotValidError(ValidatorError):
60
+ """Exception raised when the date format is not valid."""
61
+
62
+ def __init__(self) -> None:
63
+ """
64
+ Attributes:
65
+ status_code: The status code to use in a response if this is
66
+ raised.
67
+ response_text: The response text to use in a response if this
68
+ is
69
+ raised.
70
+ """
71
+ super().__init__()
72
+ self.status_code = HTTPStatus.UNAUTHORIZED
73
+ self.response_text = "Malformed date header."
74
+ date = email.utils.formatdate(
75
+ timeval=None,
76
+ localtime=False,
77
+ usegmt=True,
78
+ )
79
+ self.headers = {
80
+ "Content-Type": "text/plain;charset=iso-8859-1",
81
+ "Connection": "keep-alive",
82
+ "Server": "nginx",
83
+ "Date": date,
84
+ "WWW-Authenticate": "KWS",
85
+ "Content-Length": str(object=len(self.response_text)),
86
+ }
87
+
88
+
89
+ @beartype
90
+ class RequestTimeTooSkewedError(ValidatorError):
91
+ """Exception raised when Vuforia returns a response with a result code
92
+ 'RequestTimeTooSkewed'.
93
+ """
94
+
95
+ def __init__(self) -> None:
96
+ """
97
+ Attributes:
98
+ status_code: The status code to use in a response if this is
99
+ raised.
100
+ response_text: The response text to use in a response if this
101
+ is
102
+ raised.
103
+ """
104
+ super().__init__()
105
+ self.status_code = HTTPStatus.FORBIDDEN
106
+ body = {
107
+ "transaction_id": uuid.uuid4().hex,
108
+ "result_code": ResultCodes.REQUEST_TIME_TOO_SKEWED.value,
109
+ }
110
+ self.response_text = json_dump(body=body)
111
+ date = email.utils.formatdate(
112
+ timeval=None,
113
+ localtime=False,
114
+ usegmt=True,
115
+ )
116
+ self.headers = {
117
+ "Content-Type": "application/json",
118
+ "Connection": "keep-alive",
119
+ "Server": "nginx",
120
+ "Date": date,
121
+ "Content-Length": str(object=len(self.response_text)),
122
+ }
123
+
124
+
125
+ @beartype
126
+ class BadImageError(ValidatorError):
127
+ """Exception raised when Vuforia returns a response with a result code
128
+ 'BadImage'.
129
+ """
130
+
131
+ def __init__(self) -> None:
132
+ """
133
+ Attributes:
134
+ status_code: The status code to use in a response if this is
135
+ raised.
136
+ response_text: The response text to use in a response if this
137
+ is
138
+ raised.
139
+ """
140
+ super().__init__()
141
+ self.status_code = HTTPStatus.UNPROCESSABLE_ENTITY
142
+ transaction_id = uuid.uuid4().hex
143
+ result_code = ResultCodes.BAD_IMAGE.value
144
+
145
+ # The response has an unusual format of separators, so we construct it
146
+ # manually.
147
+ self.response_text = (
148
+ '{"transaction_id": '
149
+ f'"{transaction_id}",'
150
+ f'"result_code":"{result_code}"'
151
+ "}"
152
+ )
153
+
154
+ date = email.utils.formatdate(
155
+ timeval=None,
156
+ localtime=False,
157
+ usegmt=True,
158
+ )
159
+ self.headers = {
160
+ "Content-Type": "application/json",
161
+ "Connection": "keep-alive",
162
+ "Server": "nginx",
163
+ "Date": date,
164
+ "Content-Length": str(object=len(self.response_text)),
165
+ }
166
+
167
+
168
+ @beartype
169
+ class AuthenticationFailureError(ValidatorError):
170
+ """Exception raised when Vuforia returns a response with a result code
171
+ 'AuthenticationFailure'.
172
+ """
173
+
174
+ def __init__(self) -> None:
175
+ """
176
+ Attributes:
177
+ status_code: The status code to use in a response if this is
178
+ raised.
179
+ response_text: The response text to use in a response if this
180
+ is
181
+ raised.
182
+ """
183
+ super().__init__()
184
+ self.status_code = HTTPStatus.UNAUTHORIZED
185
+ transaction_id = uuid.uuid4().hex
186
+ result_code = ResultCodes.AUTHENTICATION_FAILURE.value
187
+
188
+ # The response has an unusual format of separators, so we construct it
189
+ # manually.
190
+ self.response_text = (
191
+ '{"transaction_id":'
192
+ f'"{transaction_id}",'
193
+ f'"result_code":"{result_code}"'
194
+ "}"
195
+ )
196
+ date = email.utils.formatdate(
197
+ timeval=None,
198
+ localtime=False,
199
+ usegmt=True,
200
+ )
201
+ self.headers = {
202
+ "Content-Type": "application/json",
203
+ "Connection": "keep-alive",
204
+ "Server": "nginx",
205
+ "Date": date,
206
+ "WWW-Authenticate": "VWS",
207
+ "Content-Length": str(object=len(self.response_text)),
208
+ }
209
+
210
+
211
+ @beartype
212
+ class AuthenticationFailureGoodFormattingError(ValidatorError):
213
+ """Exception raised when Vuforia returns a response with a result code
214
+ 'AuthenticationFailure' with a standard JSON formatting.
215
+ """
216
+
217
+ def __init__(self) -> None:
218
+ """
219
+ Attributes:
220
+ status_code: The status code to use in a response if this is
221
+ raised.
222
+ response_text: The response text to use in a response if this
223
+ is
224
+ raised.
225
+ """
226
+ super().__init__()
227
+ self.status_code = HTTPStatus.UNAUTHORIZED
228
+
229
+ body = {
230
+ "transaction_id": uuid.uuid4().hex,
231
+ "result_code": ResultCodes.AUTHENTICATION_FAILURE.value,
232
+ }
233
+ self.response_text = json_dump(body=body)
234
+ date = email.utils.formatdate(
235
+ timeval=None,
236
+ localtime=False,
237
+ usegmt=True,
238
+ )
239
+ self.headers = {
240
+ "Content-Type": "application/json",
241
+ "Connection": "keep-alive",
242
+ "Server": "nginx",
243
+ "Date": date,
244
+ "WWW-Authenticate": "VWS",
245
+ "Content-Length": str(object=len(self.response_text)),
246
+ }
247
+
248
+
249
+ @beartype
250
+ class ImageNotGivenError(ValidatorError):
251
+ """Exception raised when an image is not given."""
252
+
253
+ def __init__(self) -> None:
254
+ """
255
+ Attributes:
256
+ status_code: The status code to use in a response if this is
257
+ raised.
258
+ response_text: The response text to use in a response if this
259
+ is
260
+ raised.
261
+ """
262
+ super().__init__()
263
+ self.status_code = HTTPStatus.BAD_REQUEST
264
+ self.response_text = "No image."
265
+
266
+ date = email.utils.formatdate(
267
+ timeval=None,
268
+ localtime=False,
269
+ usegmt=True,
270
+ )
271
+ self.headers = {
272
+ "Content-Type": "application/json",
273
+ "Connection": "keep-alive",
274
+ "Server": "nginx",
275
+ "Date": date,
276
+ "Content-Length": str(object=len(self.response_text)),
277
+ }
278
+
279
+
280
+ @beartype
281
+ class AuthHeaderMissingError(ValidatorError):
282
+ """Exception raised when an auth header is not given."""
283
+
284
+ def __init__(self) -> None:
285
+ """
286
+ Attributes:
287
+ status_code: The status code to use in a response if this is
288
+ raised.
289
+ response_text: The response text to use in a response if this
290
+ is
291
+ raised.
292
+ """
293
+ super().__init__()
294
+ self.status_code = HTTPStatus.UNAUTHORIZED
295
+ self.response_text = "Authorization header missing."
296
+
297
+ date = email.utils.formatdate(
298
+ timeval=None,
299
+ localtime=False,
300
+ usegmt=True,
301
+ )
302
+ self.headers = {
303
+ "Content-Type": "text/plain;charset=iso-8859-1",
304
+ "Connection": "keep-alive",
305
+ "Server": "nginx",
306
+ "Date": date,
307
+ "WWW-Authenticate": "KWS",
308
+ "Content-Length": str(object=len(self.response_text)),
309
+ }
310
+
311
+
312
+ @beartype
313
+ class MalformedAuthHeaderError(ValidatorError):
314
+ """Exception raised when an auth header is not given."""
315
+
316
+ def __init__(self) -> None:
317
+ """
318
+ Attributes:
319
+ status_code: The status code to use in a response if this is
320
+ raised.
321
+ response_text: The response text to use in a response if this
322
+ is
323
+ raised.
324
+ www_authenticate: The WWW-Authenticate header value.
325
+ """
326
+ super().__init__()
327
+ self.status_code = HTTPStatus.UNAUTHORIZED
328
+ self.response_text = "Malformed authorization header."
329
+
330
+ date = email.utils.formatdate(
331
+ timeval=None,
332
+ localtime=False,
333
+ usegmt=True,
334
+ )
335
+ self.headers = {
336
+ "Content-Type": "text/plain;charset=iso-8859-1",
337
+ "Connection": "keep-alive",
338
+ "Server": "nginx",
339
+ "Date": date,
340
+ "WWW-Authenticate": "KWS",
341
+ "Content-Length": str(object=len(self.response_text)),
342
+ }
343
+
344
+
345
+ @beartype
346
+ class UnknownParametersError(ValidatorError):
347
+ """Exception raised when unknown parameters are given."""
348
+
349
+ def __init__(self) -> None:
350
+ """
351
+ Attributes:
352
+ status_code: The status code to use in a response if this is
353
+ raised.
354
+ response_text: The response text to use in a response if this
355
+ is
356
+ raised.
357
+ """
358
+ super().__init__()
359
+ self.status_code = HTTPStatus.BAD_REQUEST
360
+ self.response_text = "Unknown parameters in the request."
361
+
362
+ date = email.utils.formatdate(
363
+ timeval=None,
364
+ localtime=False,
365
+ usegmt=True,
366
+ )
367
+ self.headers = {
368
+ "Content-Type": "application/json",
369
+ "Connection": "keep-alive",
370
+ "Server": "nginx",
371
+ "Date": date,
372
+ "Content-Length": str(object=len(self.response_text)),
373
+ }
374
+
375
+
376
+ @beartype
377
+ class InactiveProjectError(ValidatorError):
378
+ """Exception raised when Vuforia returns a response with a result code
379
+ 'InactiveProject'.
380
+ """
381
+
382
+ def __init__(self) -> None:
383
+ """
384
+ Attributes:
385
+ status_code: The status code to use in a response if this is
386
+ raised.
387
+ response_text: The response text to use in a response if this
388
+ is
389
+ raised.
390
+ """
391
+ super().__init__()
392
+ self.status_code = HTTPStatus.FORBIDDEN
393
+ transaction_id = uuid.uuid4().hex
394
+ result_code = ResultCodes.INACTIVE_PROJECT.value
395
+ # The response has an unusual format of separators, so we construct it
396
+ # manually.
397
+ self.response_text = (
398
+ '{"transaction_id": '
399
+ f'"{transaction_id}",'
400
+ f'"result_code":"{result_code}"'
401
+ "}"
402
+ )
403
+
404
+ date = email.utils.formatdate(
405
+ timeval=None,
406
+ localtime=False,
407
+ usegmt=True,
408
+ )
409
+ self.headers = {
410
+ "Content-Type": "application/json",
411
+ "Connection": "keep-alive",
412
+ "Server": "nginx",
413
+ "Date": date,
414
+ "Content-Length": str(object=len(self.response_text)),
415
+ }
416
+
417
+
418
+ @beartype
419
+ class InvalidMaxNumResultsError(ValidatorError):
420
+ """Exception raised when an invalid value is given as the
421
+ "max_num_results"
422
+ field.
423
+ """
424
+
425
+ def __init__(self, given_value: str) -> None:
426
+ """
427
+ Attributes:
428
+ status_code: The status code to use in a response if this is
429
+ raised.
430
+ response_text: The response text to use in a response if this
431
+ is
432
+ raised.
433
+ """
434
+ super().__init__()
435
+ self.status_code = HTTPStatus.BAD_REQUEST
436
+ invalid_value_message = (
437
+ f"Invalid value '{given_value}' in form data part 'max_result'. "
438
+ "Expecting integer value in range from 1 to 50 (inclusive)."
439
+ )
440
+ self.response_text = invalid_value_message
441
+
442
+ date = email.utils.formatdate(
443
+ timeval=None,
444
+ localtime=False,
445
+ usegmt=True,
446
+ )
447
+ self.headers = {
448
+ "Content-Type": "application/json",
449
+ "Connection": "keep-alive",
450
+ "Server": "nginx",
451
+ "Date": date,
452
+ "Content-Length": str(object=len(self.response_text)),
453
+ }
454
+
455
+
456
+ @beartype
457
+ class MaxNumResultsOutOfRangeError(ValidatorError):
458
+ """Exception raised when an integer value is given as the
459
+ "max_num_results"
460
+ field which is out of range.
461
+ """
462
+
463
+ def __init__(self, given_value: str) -> None:
464
+ """
465
+ Attributes:
466
+ status_code: The status code to use in a response if this is
467
+ raised.
468
+ response_text: The response text to use in a response if this
469
+ is
470
+ raised.
471
+ """
472
+ super().__init__()
473
+ self.status_code = HTTPStatus.BAD_REQUEST
474
+ integer_out_of_range_message = (
475
+ f"Integer out of range ({given_value}) in form data part "
476
+ "'max_result'. Accepted range is from 1 to 50 (inclusive)."
477
+ )
478
+ self.response_text = integer_out_of_range_message
479
+
480
+ date = email.utils.formatdate(
481
+ timeval=None,
482
+ localtime=False,
483
+ usegmt=True,
484
+ )
485
+ self.headers = {
486
+ "Content-Type": "application/json",
487
+ "Connection": "keep-alive",
488
+ "Server": "nginx",
489
+ "Date": date,
490
+ "Content-Length": str(object=len(self.response_text)),
491
+ }
492
+
493
+
494
+ @beartype
495
+ class InvalidIncludeTargetDataError(ValidatorError):
496
+ """Exception raised when an invalid value is given as the
497
+ "include_target_data" field.
498
+ """
499
+
500
+ def __init__(self, given_value: str) -> None:
501
+ """
502
+ Attributes:
503
+ status_code: The status code to use in a response if this is
504
+ raised.
505
+ response_text: The response text to use in a response if this
506
+ is
507
+ raised.
508
+ """
509
+ super().__init__()
510
+ self.status_code = HTTPStatus.BAD_REQUEST
511
+ unexpected_target_data_message = (
512
+ f"Invalid value '{given_value.lower()}' in form data part "
513
+ "'include_target_data'. "
514
+ "Expecting one of the (unquoted) string values 'all', 'none' or "
515
+ "'top'."
516
+ )
517
+ self.response_text = unexpected_target_data_message
518
+
519
+ date = email.utils.formatdate(
520
+ timeval=None,
521
+ localtime=False,
522
+ usegmt=True,
523
+ )
524
+ self.headers = {
525
+ "Content-Type": "application/json",
526
+ "Connection": "keep-alive",
527
+ "Server": "nginx",
528
+ "Date": date,
529
+ "Content-Length": str(object=len(self.response_text)),
530
+ }
531
+
532
+
533
+ @beartype
534
+ class UnsupportedMediaTypeError(ValidatorError):
535
+ """Exception raised when no boundary is found for multipart data."""
536
+
537
+ def __init__(self) -> None:
538
+ """
539
+ Attributes:
540
+ status_code: The status code to use in a response if this is
541
+ raised.
542
+ response_text: The response text to use in a response if this
543
+ is
544
+ raised.
545
+ """
546
+ super().__init__()
547
+ self.status_code = HTTPStatus.UNSUPPORTED_MEDIA_TYPE
548
+ self.response_text = ""
549
+
550
+ date = email.utils.formatdate(
551
+ timeval=None,
552
+ localtime=False,
553
+ usegmt=True,
554
+ )
555
+ self.headers = {
556
+ "Connection": "keep-alive",
557
+ "Server": "nginx",
558
+ "Date": date,
559
+ "Content-Length": str(object=len(self.response_text)),
560
+ }
561
+
562
+
563
+ @beartype
564
+ class InvalidAcceptHeaderError(ValidatorError):
565
+ """Exception raised when there is an invalid accept header given."""
566
+
567
+ def __init__(self) -> None:
568
+ """
569
+ Attributes:
570
+ status_code: The status code to use in a response if this is
571
+ raised.
572
+ response_text: The response text to use in a response if this
573
+ is
574
+ raised.
575
+ """
576
+ super().__init__()
577
+ self.status_code = HTTPStatus.NOT_ACCEPTABLE
578
+ self.response_text = ""
579
+
580
+ date = email.utils.formatdate(
581
+ timeval=None,
582
+ localtime=False,
583
+ usegmt=True,
584
+ )
585
+ self.headers = {
586
+ "Connection": "keep-alive",
587
+ "Server": "nginx",
588
+ "Date": date,
589
+ "Content-Length": str(object=len(self.response_text)),
590
+ }
591
+
592
+
593
+ @beartype
594
+ class NoBoundaryFoundError(ValidatorError):
595
+ """Exception raised when an invalid media type is given."""
596
+
597
+ def __init__(self) -> None:
598
+ """
599
+ Attributes:
600
+ status_code: The status code to use in a response if this is
601
+ raised.
602
+ response_text: The response text to use in a response if this
603
+ is
604
+ raised.
605
+ """
606
+ super().__init__()
607
+ self.status_code = HTTPStatus.BAD_REQUEST
608
+ self.response_text = "Unable to get boundary for multipart"
609
+
610
+ date = email.utils.formatdate(
611
+ timeval=None,
612
+ localtime=False,
613
+ usegmt=True,
614
+ )
615
+ self.headers = {
616
+ "Content-Type": "text/plain;charset=utf-8",
617
+ "Connection": "keep-alive",
618
+ "Server": "nginx",
619
+ "Date": date,
620
+ "Content-Length": str(object=len(self.response_text)),
621
+ }
622
+
623
+
624
+ @beartype
625
+ class ContentLengthHeaderTooLargeError(ValidatorError):
626
+ """
627
+ Exception raised when the given content length header is too
628
+ large.
629
+ """
630
+
631
+ # We skip coverage here as running a test to cover this is very slow.
632
+ def __init__(self) -> None: # pragma: no cover
633
+ """
634
+ Attributes:
635
+ status_code: The status code to use in a response if this is
636
+ raised.
637
+ response_text: The response text to use in a response if this
638
+ is
639
+ raised.
640
+ """
641
+ super().__init__()
642
+ self.status_code = HTTPStatus.GATEWAY_TIMEOUT
643
+ self.response_text = ""
644
+ self.headers = {
645
+ "Connection": "keep-alive",
646
+ "Content-Length": str(object=len(self.response_text)),
647
+ }
648
+
649
+
650
+ @beartype
651
+ class ContentLengthHeaderNotIntError(ValidatorError):
652
+ """
653
+ Exception raised when the given content length header is not an
654
+ integer.
655
+ """
656
+
657
+ def __init__(self) -> None:
658
+ """
659
+ Attributes:
660
+ status_code: The status code to use in a response if this is
661
+ raised.
662
+ response_text: The response text to use in a response if this
663
+ is
664
+ raised.
665
+ """
666
+ super().__init__()
667
+ self.status_code = HTTPStatus.BAD_REQUEST
668
+ self.response_text = ""
669
+ self.headers = {
670
+ "Connection": "Close",
671
+ "Content-Length": str(object=len(self.response_text)),
672
+ }
673
+
674
+
675
+ @beartype
676
+ class RequestEntityTooLargeError(ValidatorError):
677
+ """Exception raised when the given image file size is too large."""
678
+
679
+ # Ignore coverage on this as there is a bug in urllib3 which means that we
680
+ # do not trigger this exception.
681
+ # See https://github.com/urllib3/urllib3/issues/2733.
682
+ def __init__(self) -> None: # pragma: no cover
683
+ """
684
+ Attributes:
685
+ status_code: The status code to use in a response if this is
686
+ raised.
687
+ response_text: The response text to use in a response if this
688
+ is
689
+ raised.
690
+ """
691
+ super().__init__()
692
+ self.status_code = HTTPStatus.REQUEST_ENTITY_TOO_LARGE
693
+ self.response_text = textwrap.dedent(
694
+ text="""\
695
+ <html>\r
696
+ <head><title>413 Request Entity Too Large</title></head>\r
697
+ <body>\r
698
+ <center><h1>413 Request Entity Too Large</h1></center>\r
699
+ <hr><center>nginx</center>\r
700
+ </body>\r
701
+ </html>\r
702
+ """,
703
+ )
704
+ date = email.utils.formatdate(
705
+ timeval=None,
706
+ localtime=False,
707
+ usegmt=True,
708
+ )
709
+ self.headers = {
710
+ "Connection": "Close",
711
+ "Date": date,
712
+ "Server": "nginx",
713
+ "Content-Type": "text/html",
714
+ "Content-Length": str(object=len(self.response_text)),
715
+ }
716
+
717
+
718
+ @beartype
719
+ class NoContentTypeError(ValidatorError):
720
+ """
721
+ Exception raised when a content type is either not given or is
722
+ empty.
723
+ """
724
+
725
+ def __init__(self) -> None:
726
+ """
727
+ Attributes:
728
+ status_code: The status code to use in a response if this is
729
+ raised.
730
+ response_text: The response text to use in a response if this
731
+ is
732
+ raised.
733
+ """
734
+ super().__init__()
735
+ self.status_code = HTTPStatus.BAD_REQUEST
736
+ date = email.utils.formatdate(
737
+ timeval=None,
738
+ localtime=False,
739
+ usegmt=True,
740
+ )
741
+ jetty_content_type_error = textwrap.dedent(
742
+ text="""\
743
+ <html>
744
+ <head>
745
+ <meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
746
+ <title>Error 400 Bad Request</title>
747
+ </head>
748
+ <body>
749
+ <h2>HTTP ERROR 400 Bad Request</h2>
750
+ <table>
751
+ <tr><th>URI:</th><td>http://cloudreco.vuforia.com/v1/query</td></tr>
752
+ <tr><th>STATUS:</th><td>400</td></tr>
753
+ <tr><th>MESSAGE:</th><td>Bad Request</td></tr>
754
+ </table>
755
+ <hr/><a href="https://jetty.org/">Powered by Jetty:// 12.0.20</a><hr/>
756
+
757
+ </body>
758
+ </html>
759
+ """, # noqa: E501
760
+ )
761
+ self.response_text = jetty_content_type_error
762
+ self.headers = {
763
+ "Connection": "keep-alive",
764
+ "Content-Type": "text/html;charset=iso-8859-1",
765
+ "Server": "nginx",
766
+ "Cache-Control": "must-revalidate,no-cache,no-store",
767
+ "Date": date,
768
+ "Content-Length": str(object=len(self.response_text)),
769
+ }