vws-python-mock 2026.9.6__tar.gz → 2026.9.8__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 (205) hide show
  1. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/workflows/publish-site.yml +1 -1
  2. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/workflows/release.yml +3 -3
  3. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/workflows/test.yml +15 -12
  4. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/CHANGELOG.rst +10 -0
  5. {vws_python_mock-2026.9.6/src/vws_python_mock.egg-info → vws_python_mock-2026.9.8}/PKG-INFO +5 -69
  6. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/README.rst +3 -3
  7. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/admin/create_secrets_files.py +10 -10
  8. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/basic-example.rst +2 -2
  9. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/contributing.rst +17 -9
  10. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/differences-to-vws.rst +48 -15
  11. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/httpx-example.rst +1 -1
  12. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/httpx2-example.rst +1 -1
  13. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/unverified-behavior.rst +16 -21
  14. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/prek.toml +42 -42
  15. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/pyproject.toml +38 -20
  16. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/spelling_private_dict.txt +1 -0
  17. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_flask_server/Dockerfile +2 -2
  18. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_flask_server/target_manager.py +30 -33
  19. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_flask_server/vwq.py +3 -7
  20. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_flask_server/vws.py +30 -30
  21. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_httpx2_mock_server/decorators.py +65 -65
  22. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_matching.py +1 -1
  23. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_mock_common.py +43 -6
  24. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_model_target_web_api.py +74 -60
  25. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_tools.py +12 -38
  26. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/__init__.py +54 -38
  27. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/auth_validators.py +7 -4
  28. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/content_length_validators.py +1 -1
  29. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/content_type_validators.py +1 -1
  30. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/date_validators.py +3 -1
  31. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/exceptions.py +37 -0
  32. vws_python_mock-2026.9.8/src/mock_vws/_query_validators/fields_validators.py +30 -0
  33. vws_python_mock-2026.9.8/src/mock_vws/_query_validators/header_size_validators.py +32 -0
  34. vws_python_mock-2026.9.8/src/mock_vws/_query_validators/image_validators.py +119 -0
  35. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/include_target_data_validators.py +4 -14
  36. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/multipart.py +32 -3
  37. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/num_results_validators.py +4 -14
  38. vws_python_mock-2026.9.8/src/mock_vws/_query_validators/project_state_validators.py +28 -0
  39. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_reco_counts_web_api.py +6 -4
  40. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_requests_mock_server/mock_web_query_api.py +2 -6
  41. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_requests_mock_server/mock_web_services_api.py +30 -24
  42. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_respx_mock_server/decorators.py +11 -21
  43. vws_python_mock-2026.9.8/src/mock_vws/_services_validators/__init__.py +95 -0
  44. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/active_flag_validators.py +6 -9
  45. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/auth_validators.py +10 -5
  46. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/content_length_validators.py +29 -36
  47. vws_python_mock-2026.9.8/src/mock_vws/_services_validators/content_type_validators.py +27 -0
  48. vws_python_mock-2026.9.8/src/mock_vws/_services_validators/context.py +105 -0
  49. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/database_id_validators.py +8 -19
  50. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/date_validators.py +14 -13
  51. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/exceptions.py +53 -7
  52. vws_python_mock-2026.9.8/src/mock_vws/_services_validators/header_size_validators.py +32 -0
  53. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/image_validators.py +85 -135
  54. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/instance_id_validators.py +7 -21
  55. vws_python_mock-2026.9.8/src/mock_vws/_services_validators/json_validators.py +133 -0
  56. vws_python_mock-2026.9.8/src/mock_vws/_services_validators/key_validators.py +36 -0
  57. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/metadata_validators.py +12 -27
  58. vws_python_mock-2026.9.8/src/mock_vws/_services_validators/name_validators.py +229 -0
  59. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/project_state_validators.py +9 -17
  60. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/request_quota_validators.py +12 -3
  61. vws_python_mock-2026.9.6/src/mock_vws/_services_validators/request_rate_validators.py → vws_python_mock-2026.9.8/src/mock_vws/_services_validators/request_rate_limiter.py +5 -46
  62. vws_python_mock-2026.9.8/src/mock_vws/_services_validators/routes.py +391 -0
  63. vws_python_mock-2026.9.8/src/mock_vws/_services_validators/target_quota_validators.py +27 -0
  64. vws_python_mock-2026.9.8/src/mock_vws/_services_validators/target_validators.py +57 -0
  65. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/width_validators.py +6 -9
  66. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/database.py +4 -2
  67. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/decorators.py +20 -48
  68. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/model_target.py +7 -7
  69. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/request_rate_limits.py +10 -4
  70. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/target.py +6 -6
  71. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/target_manager.py +1 -1
  72. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8/src/vws_python_mock.egg-info}/PKG-INFO +5 -69
  73. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/vws_python_mock.egg-info/SOURCES.txt +6 -3
  74. vws_python_mock-2026.9.8/src/vws_python_mock.egg-info/requires.txt +19 -0
  75. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/vws_python_mock.egg-info/scm_file_list.json +6 -3
  76. vws_python_mock-2026.9.8/src/vws_python_mock.egg-info/scm_version.json +8 -0
  77. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/conftest.py +6 -6
  78. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/fixtures/model_target_prepared_requests.py +2 -2
  79. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/fixtures/prepared_requests.py +1 -1
  80. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/fixtures/vuforia_backends.py +60 -54
  81. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_add_target.py +40 -40
  82. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_authorization_header.py +9 -9
  83. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_content_length.py +5 -5
  84. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_database_summary.py +5 -5
  85. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_date_header.py +1 -1
  86. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_delete_target.py +1 -1
  87. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_docker.py +22 -22
  88. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_flask_app_usage.py +75 -47
  89. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_get_duplicates.py +1 -1
  90. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_get_target.py +3 -1
  91. vws_python_mock-2026.9.8/tests/mock_vws/test_header_size.py +189 -0
  92. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_healthcheck.py +1 -1
  93. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_httpx2_mock_usage.py +12 -15
  94. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_invalid_given_id.py +1 -1
  95. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_invalid_json.py +2 -2
  96. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_model_target_failure_response.py +11 -10
  97. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_model_target_generation_failure.py +7 -7
  98. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_model_target_generation_warning.py +7 -7
  99. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_model_target_retries.py +9 -7
  100. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_model_target_training_allowance.py +1 -1
  101. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_model_target_web_api.py +36 -36
  102. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_query.py +16 -16
  103. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_reco_counts_report.py +7 -7
  104. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_requests_mock_usage.py +109 -83
  105. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_respx_mock_usage.py +5 -5
  106. vws_python_mock-2026.9.8/tests/mock_vws/test_target_list.py +181 -0
  107. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_target_summary.py +65 -8
  108. vws_python_mock-2026.9.8/tests/mock_vws/test_target_validators.py +23 -0
  109. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_unexpected_json.py +2 -2
  110. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_update_target.py +11 -11
  111. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_vumark_generation_api.py +3 -3
  112. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/utils/__init__.py +2 -2
  113. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/utils/assertions.py +50 -8
  114. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/utils/model_target_retries.py +1 -1
  115. vws_python_mock-2026.9.8/tests/mock_vws/utils/retries.py +75 -0
  116. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/uv.lock +230 -198
  117. vws_python_mock-2026.9.6/src/mock_vws/_query_validators/fields_validators.py +0 -42
  118. vws_python_mock-2026.9.6/src/mock_vws/_query_validators/image_validators.py +0 -191
  119. vws_python_mock-2026.9.6/src/mock_vws/_query_validators/project_state_validators.py +0 -49
  120. vws_python_mock-2026.9.6/src/mock_vws/_services_validators/__init__.py +0 -203
  121. vws_python_mock-2026.9.6/src/mock_vws/_services_validators/content_type_validators.py +0 -42
  122. vws_python_mock-2026.9.6/src/mock_vws/_services_validators/json_validators.py +0 -96
  123. vws_python_mock-2026.9.6/src/mock_vws/_services_validators/key_validators.py +0 -174
  124. vws_python_mock-2026.9.6/src/mock_vws/_services_validators/name_validators.py +0 -209
  125. vws_python_mock-2026.9.6/src/mock_vws/_services_validators/target_quota_validators.py +0 -28
  126. vws_python_mock-2026.9.6/src/mock_vws/_services_validators/target_validators.py +0 -60
  127. vws_python_mock-2026.9.6/src/vws_python_mock.egg-info/requires.txt +0 -85
  128. vws_python_mock-2026.9.6/src/vws_python_mock.egg-info/scm_version.json +0 -8
  129. vws_python_mock-2026.9.6/tests/backend_harness.py +0 -159
  130. vws_python_mock-2026.9.6/tests/mock_vws/test_target_list.py +0 -72
  131. vws_python_mock-2026.9.6/tests/mock_vws/test_target_validators.py +0 -54
  132. vws_python_mock-2026.9.6/tests/mock_vws/utils/httpx2_transports.py +0 -156
  133. vws_python_mock-2026.9.6/tests/mock_vws/utils/retries.py +0 -41
  134. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.dockerignore +0 -0
  135. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.git_archival.txt +0 -0
  136. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.gitattributes +0 -0
  137. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/dependabot.yml +0 -0
  138. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/workflows/autofix.yml +0 -0
  139. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/workflows/dependabot-merge.yml +0 -0
  140. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/workflows/docker-build.yml +0 -0
  141. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/workflows/lint.yml +0 -0
  142. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.gitignore +0 -0
  143. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.prettierrc +0 -0
  144. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.vale.ini +0 -0
  145. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.vscode/extensions.json +0 -0
  146. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.vscode/settings.json +0 -0
  147. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/CODE_OF_CONDUCT.rst +0 -0
  148. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/LICENSE +0 -0
  149. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/MANIFEST.in +0 -0
  150. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/admin/__init__.py +0 -0
  151. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/admin/vumark_template.svg +0 -0
  152. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/ci/__init__.py +0 -0
  153. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/ci/decrypt_secret.sh +0 -0
  154. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/conftest.py +0 -0
  155. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docker-bake.hcl +0 -0
  156. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/__init__.py +0 -0
  157. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/changelog.rst +0 -0
  158. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/ci-setup.rst +0 -0
  159. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/conf.py +0 -0
  160. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/docker.rst +0 -0
  161. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/getting-started.rst +0 -0
  162. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/index.rst +0 -0
  163. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/installation.rst +0 -0
  164. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/mock-api-reference.rst +0 -0
  165. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/release-process.rst +0 -0
  166. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/unreleased.rst +0 -0
  167. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/versioning-and-api-stability.rst +0 -0
  168. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/towncrier_template.rst.jinja +0 -0
  169. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/newsfragments/.gitkeep +0 -0
  170. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/secrets.tar.gpg +0 -0
  171. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/setup.cfg +0 -0
  172. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/__init__.py +0 -0
  173. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_base64_decoding.py +0 -0
  174. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_constants.py +0 -0
  175. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_database_matchers.py +0 -0
  176. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_flask_server/__init__.py +0 -0
  177. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_flask_server/healthcheck.py +0 -0
  178. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_httpx2_mock_server/__init__.py +0 -0
  179. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_image_opening.py +0 -0
  180. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/accept_header_validators.py +0 -0
  181. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_requests_mock_server/__init__.py +0 -0
  182. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_respx_mock_server/__init__.py +0 -0
  183. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/cloud_query.py +0 -0
  184. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/database_type.py +0 -0
  185. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/image_matchers.py +0 -0
  186. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/py.typed +0 -0
  187. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/reco_counts.py +0 -0
  188. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/states.py +0 -0
  189. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/target_raters.py +0 -0
  190. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/vumark.py +0 -0
  191. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/vws_python_mock.egg-info/dependency_links.txt +0 -0
  192. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/vws_python_mock.egg-info/not-zip-safe +0 -0
  193. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/vws_python_mock.egg-info/top_level.txt +0 -0
  194. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/__init__.py +0 -0
  195. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/__init__.py +0 -0
  196. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/fixtures/__init__.py +0 -0
  197. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/fixtures/credentials.py +0 -0
  198. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_cloud_query_failure_response.py +0 -0
  199. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_target_raters.py +0 -0
  200. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_vumark_generation_failure.py +0 -0
  201. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/utils/recognition_counts.py +0 -0
  202. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/utils/too_many_requests.py +0 -0
  203. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/utils/usage_test_helpers.py +0 -0
  204. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/vuforia_secrets.env.example +0 -0
  205. {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/zizmor.yml +0 -0
@@ -21,7 +21,7 @@ jobs:
21
21
  uses: sphinx-notes/pages@v3
22
22
  with:
23
23
  documentation_path: docs/source
24
- pyproject_extras: dev
24
+ pyproject_group: dev
25
25
  python_version: '3.14'
26
26
  sphinx_build_options: -W
27
27
  publish: ${{ github.ref_name == 'main' }}
@@ -55,7 +55,7 @@ jobs:
55
55
  - name: Generate the GitHub release notes
56
56
  env:
57
57
  RELEASE: ${{ steps.calver.outputs.release }}
58
- run: uv run --extra=release towncrier build --draft --version "$RELEASE" >
58
+ run: uv run --group=release towncrier build --draft --version "$RELEASE" >
59
59
  release-notes.md
60
60
 
61
61
  # Assemble the same fragments into CHANGELOG.rst under a new
@@ -63,7 +63,7 @@ jobs:
63
63
  - name: Update the changelog
64
64
  env:
65
65
  RELEASE: ${{ steps.calver.outputs.release }}
66
- run: uv run --extra=release towncrier build --yes --version "$RELEASE"
66
+ run: uv run --group=release towncrier build --yes --version "$RELEASE"
67
67
 
68
68
  - uses: stefanzweifel/git-auto-commit-action@v7
69
69
  id: commit
@@ -122,7 +122,7 @@ jobs:
122
122
  - name: Build a binary wheel and a source tarball
123
123
  run: |
124
124
  uv build --sdist --wheel --out-dir dist/
125
- uv run --extra=release check-wheel-contents dist/*.whl
125
+ uv run --group=release check-wheel-contents dist/*.whl
126
126
 
127
127
  # We use PyPI trusted publishing rather than a PyPI API token.
128
128
  # See https://github.com/pypa/gh-action-pypi-publish/tree/release/v1/?tab=readme-ov-file#trusted-publishing.
@@ -65,6 +65,9 @@ jobs:
65
65
  - tests/mock_vws/test_content_length.py::TestIncorrect::test_not_integer
66
66
  - tests/mock_vws/test_content_length.py::TestIncorrect::test_too_large
67
67
  - tests/mock_vws/test_content_length.py::TestIncorrect::test_too_small
68
+ - tests/mock_vws/test_header_size.py::TestOversizedHeaderLine::test_header_too_large
69
+ - tests/mock_vws/test_header_size.py::TestOversizedHeaderLine::test_cookie_too_large
70
+ - tests/mock_vws/test_header_size.py::TestOversizedHeaderLine::test_large_header_within_limit
68
71
  - tests/mock_vws/test_database_summary.py::TestDatabaseSummary::test_success
69
72
  - tests/mock_vws/test_database_summary.py::TestDatabaseSummary::test_active_images
70
73
  - tests/mock_vws/test_database_summary.py::TestDatabaseSummary::test_failed_images
@@ -185,7 +188,7 @@ jobs:
185
188
 
186
189
  - name: Run tests
187
190
  run: |
188
- uv run --extra=dev \
191
+ uv run --group=dev \
189
192
  coverage run -m pytest \
190
193
  -s \
191
194
  -vvv \
@@ -234,12 +237,12 @@ jobs:
234
237
 
235
238
  - name: Run tests
236
239
  run: |
237
- uv run --extra=dev \
240
+ uv run --group=dev \
238
241
  coverage run -m pytest \
239
- --skip-docker_build_tests \
240
- --skip-docker_in_memory \
241
- --skip-mock \
242
- --skip-real \
242
+ --skip-marker=requires_docker_build \
243
+ --skip-backend=flask_in_process \
244
+ --skip-backend=mock \
245
+ --skip-backend=real \
243
246
  --capture=no \
244
247
  -vvv \
245
248
  --exitfirst \
@@ -300,9 +303,9 @@ jobs:
300
303
  # We use pytest-xdist to make this run much faster.
301
304
  # The downside is that we cannot use -s / --capture=no.
302
305
  #
303
- uv run --extra=dev \
306
+ uv run --group=dev \
304
307
  coverage run -m pytest \
305
- --skip-real \
308
+ --skip-backend=real \
306
309
  -vvv \
307
310
  --exitfirst \
308
311
  -n auto \
@@ -344,15 +347,15 @@ jobs:
344
347
  - name: Require 100% Coverage
345
348
  id: coverage
346
349
  run: |
347
- uv run --extra=dev coverage combine
348
- uv run --extra=dev coverage html --skip-covered --skip-empty
350
+ uv run --group=dev coverage combine
351
+ uv run --group=dev coverage html --skip-covered --skip-empty
349
352
 
350
353
  # Report and write to summary, without failing yet.
351
- uv run --extra=dev coverage report --format=markdown \
354
+ uv run --group=dev coverage report --format=markdown \
352
355
  >> "$GITHUB_STEP_SUMMARY" || true
353
356
 
354
357
  # Report again and fail if under 100%.
355
- uv run --extra=dev coverage report
358
+ uv run --group=dev coverage report
356
359
 
357
360
  - name: Upload HTML report if check failed
358
361
  uses: actions/upload-artifact@v7
@@ -3,6 +3,16 @@ Changelog
3
3
 
4
4
  .. towncrier release notes start
5
5
 
6
+ 2026.09.08
7
+ ----------
8
+
9
+ - The response delay and client timeout simulation is now provided by the ``mock-response-delay`` package.
10
+
11
+ - The mock now returns NGINX's ``400 Request Header Or Cookie Too Large`` response for any request header line longer than 8190 bytes, as real Vuforia does.
12
+
13
+ - Match real Vuforia's request rate limiting, which was checked against it on 2026-09-08.
14
+ ``DOCUMENTED_REQUEST_RATE_LIMITS`` now allows two ``GET /targets`` requests per minute rather than one, a rate-limited request gets Envoy's empty-bodied ``429`` response rather than a JSON ``TooManyRequests`` body, and the limits are applied before the request's signature is checked.
15
+
6
16
  2026.09.06
7
17
  ----------
8
18
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vws-python-mock
3
- Version: 2026.9.6
3
+ Version: 2026.9.8
4
4
  Summary: A mock for the Vuforia Web Services (VWS) API.
5
5
  Author-email: Adam Dangoor <adamdangoor@gmail.com>
6
6
  License-Expression: MIT
@@ -21,6 +21,7 @@ Requires-Dist: beartype>=0.22.9
21
21
  Requires-Dist: flask>=3.0.3
22
22
  Requires-Dist: httpx>=0.27.0
23
23
  Requires-Dist: httpx2>=2.12
24
+ Requires-Dist: mock-response-delay>=2026.9.7
24
25
  Requires-Dist: numpy>=2.4.4
25
26
  Requires-Dist: opencv-contrib-python-headless>=5.0.0.93
26
27
  Requires-Dist: pillow>=12.2.0
@@ -33,71 +34,6 @@ Requires-Dist: respx>=0.21.0
33
34
  Requires-Dist: tzdata; sys_platform == "win32"
34
35
  Requires-Dist: vws-auth-tools>=2024.7.12
35
36
  Requires-Dist: werkzeug>=3.1.2
36
- Provides-Extra: dev
37
- Requires-Dist: actionlint-py==1.7.12.24; extra == "dev"
38
- Requires-Dist: check-manifest==0.51; extra == "dev"
39
- Requires-Dist: check-wheel-contents==0.6.3; extra == "dev"
40
- Requires-Dist: coverage==7.16.0; extra == "dev"
41
- Requires-Dist: deptry==0.25.1; extra == "dev"
42
- Requires-Dist: dirty-equals==0.11; extra == "dev"
43
- Requires-Dist: doc8==2.0.0; extra == "dev"
44
- Requires-Dist: doccmd==2026.8.30.1; extra == "dev"
45
- Requires-Dist: docker==7.2.0; extra == "dev"
46
- Requires-Dist: freezegun==1.5.5; extra == "dev"
47
- Requires-Dist: furo==2025.12.19; extra == "dev"
48
- Requires-Dist: hadolint-coatl==2.15.1; extra == "dev"
49
- Requires-Dist: interrogate==1.7.0; extra == "dev"
50
- Requires-Dist: mypy[faster-cache]==2.3.1; extra == "dev"
51
- Requires-Dist: mypy-strict-kwargs==2026.8.25.1; extra == "dev"
52
- Requires-Dist: no-defaults==2.3.0; extra == "dev"
53
- Requires-Dist: prek==0.5.1; extra == "dev"
54
- Requires-Dist: pydocstringformatter==1.0.0; extra == "dev"
55
- Requires-Dist: pydocstyle==6.3; extra == "dev"
56
- Requires-Dist: pylint[spelling]==4.0.8; extra == "dev"
57
- Requires-Dist: pylint-per-file-ignores==3.2.1; extra == "dev"
58
- Requires-Dist: pyproject-fmt==2.29.2; extra == "dev"
59
- Requires-Dist: pyrefly==1.2.0; extra == "dev"
60
- Requires-Dist: pyright==1.1.411; extra == "dev"
61
- Requires-Dist: pyroma==5.0.1; extra == "dev"
62
- Requires-Dist: pytest==9.1.1; extra == "dev"
63
- Requires-Dist: pytest-beartype-tests==2026.8.16; extra == "dev"
64
- Requires-Dist: pytest-partition-check==2026.8.23; extra == "dev"
65
- Requires-Dist: pytest-retry==1.7.0; extra == "dev"
66
- Requires-Dist: pytest-xdist==3.8.0; extra == "dev"
67
- Requires-Dist: pyyaml==6.0.3; extra == "dev"
68
- Requires-Dist: requests-mock-flask==2026.8.16; extra == "dev"
69
- Requires-Dist: ruff==0.16.5; extra == "dev"
70
- Requires-Dist: shellcheck-py==0.11.0.1; extra == "dev"
71
- Requires-Dist: shfmt-py==4.1.0; extra == "dev"
72
- Requires-Dist: sphinx==9.1.0; extra == "dev"
73
- Requires-Dist: sphinx-copybutton==0.5.2; extra == "dev"
74
- Requires-Dist: sphinx-lint==1.0.2; extra == "dev"
75
- Requires-Dist: sphinx-paramlinks==0.6; extra == "dev"
76
- Requires-Dist: sphinx-pyproject==0.3.0; extra == "dev"
77
- Requires-Dist: sphinx-substitution-extensions==2026.8.13.1; extra == "dev"
78
- Requires-Dist: sphinx-toolbox==4.3.0; extra == "dev"
79
- Requires-Dist: sphinxcontrib-httpdomain==2.0.0; extra == "dev"
80
- Requires-Dist: sphinxcontrib-spelling==8.0.2; extra == "dev"
81
- Requires-Dist: sphinxcontrib-towncrier==0.5.0a0; extra == "dev"
82
- Requires-Dist: strict-kwargs==2026.8.28.post2; extra == "dev"
83
- Requires-Dist: sybil==10.1.0; extra == "dev"
84
- Requires-Dist: tenacity==9.1.4; extra == "dev"
85
- Requires-Dist: towncrier==25.8.0; extra == "dev"
86
- Requires-Dist: ty==0.0.77; extra == "dev"
87
- Requires-Dist: types-docker==7.2.0.20260827; extra == "dev"
88
- Requires-Dist: types-pyyaml==6.0.12.20260815; extra == "dev"
89
- Requires-Dist: types-requests==2.33.0.20260712; extra == "dev"
90
- Requires-Dist: urllib3==2.7.0; extra == "dev"
91
- Requires-Dist: vale==3.19.0.0; extra == "dev"
92
- Requires-Dist: vulture==2.16; extra == "dev"
93
- Requires-Dist: vws-python==2026.8.26; extra == "dev"
94
- Requires-Dist: vws-test-fixtures==2026.8.26; extra == "dev"
95
- Requires-Dist: vws-web-tools==2026.8.27; extra == "dev"
96
- Requires-Dist: yamlfix==1.19.1; extra == "dev"
97
- Requires-Dist: zizmor==1.30.0; extra == "dev"
98
- Provides-Extra: release
99
- Requires-Dist: check-wheel-contents==0.6.3; extra == "release"
100
- Requires-Dist: towncrier==25.8.0; extra == "release"
101
37
  Dynamic: license-file
102
38
 
103
39
  |Build Status| |PyPI|
@@ -134,7 +70,7 @@ This requires Python |minimum-python-version|\+.
134
70
  database = CloudDatabase()
135
71
  mock.add_cloud_database(cloud_database=database)
136
72
  # This will use the Vuforia mock.
137
- requests.get(url="https://vws.vuforia.com/summary", timeout=30)
73
+ _ = requests.get(url="https://vws.vuforia.com/summary", timeout=30)
138
74
 
139
75
  ``MockVWS`` also intercepts `httpx`_ requests:
140
76
 
@@ -151,7 +87,7 @@ This requires Python |minimum-python-version|\+.
151
87
  database = CloudDatabase()
152
88
  mock.add_cloud_database(cloud_database=database)
153
89
  # This will use the Vuforia mock.
154
- httpx.get(url="https://vws.vuforia.com/summary", timeout=30)
90
+ _ = httpx.get(url="https://vws.vuforia.com/summary", timeout=30)
155
91
 
156
92
  ``MockVWS`` also intercepts `HTTPX2`_ requests, with no need for ``httpx2.alias_httpx()``:
157
93
 
@@ -168,7 +104,7 @@ This requires Python |minimum-python-version|\+.
168
104
  database = CloudDatabase()
169
105
  mock.add_cloud_database(cloud_database=database)
170
106
  # This will use the Vuforia mock.
171
- httpx2.get(url="https://vws.vuforia.com/summary", timeout=30)
107
+ _ = httpx2.get(url="https://vws.vuforia.com/summary", timeout=30)
172
108
 
173
109
  Asynchronous ``httpx`` and `HTTPX2`_ clients are intercepted as well.
174
110
 
@@ -32,7 +32,7 @@ This requires Python |minimum-python-version|\+.
32
32
  database = CloudDatabase()
33
33
  mock.add_cloud_database(cloud_database=database)
34
34
  # This will use the Vuforia mock.
35
- requests.get(url="https://vws.vuforia.com/summary", timeout=30)
35
+ _ = requests.get(url="https://vws.vuforia.com/summary", timeout=30)
36
36
 
37
37
  ``MockVWS`` also intercepts `httpx`_ requests:
38
38
 
@@ -49,7 +49,7 @@ This requires Python |minimum-python-version|\+.
49
49
  database = CloudDatabase()
50
50
  mock.add_cloud_database(cloud_database=database)
51
51
  # This will use the Vuforia mock.
52
- httpx.get(url="https://vws.vuforia.com/summary", timeout=30)
52
+ _ = httpx.get(url="https://vws.vuforia.com/summary", timeout=30)
53
53
 
54
54
  ``MockVWS`` also intercepts `HTTPX2`_ requests, with no need for ``httpx2.alias_httpx()``:
55
55
 
@@ -66,7 +66,7 @@ This requires Python |minimum-python-version|\+.
66
66
  database = CloudDatabase()
67
67
  mock.add_cloud_database(cloud_database=database)
68
68
  # This will use the Vuforia mock.
69
- httpx2.get(url="https://vws.vuforia.com/summary", timeout=30)
69
+ _ = httpx2.get(url="https://vws.vuforia.com/summary", timeout=30)
70
70
 
71
71
  Asynchronous ``httpx`` and `HTTPX2`_ clients are intercepted as well.
72
72
 
@@ -133,7 +133,7 @@ def _create_and_get_vumark_target_id(
133
133
  vumark_template_name: str,
134
134
  ) -> str:
135
135
  """Upload a VuMark template and get its target ID."""
136
- vws_web_tools.upload_vumark_template(
136
+ _ = vws_web_tools.upload_vumark_template(
137
137
  driver=driver,
138
138
  database_name=vumark_database_name,
139
139
  svg_file_path=VUMARK_TEMPLATE_SVG_FILE_PATH,
@@ -292,11 +292,11 @@ def main() -> None:
292
292
  files_to_create = [file for file in required_files if not file.exists()]
293
293
  driver: WebDriver | None = None
294
294
 
295
- while files_to_create:
295
+ while bool(files_to_create):
296
296
  if driver is None:
297
297
  driver = vws_web_tools.create_chrome_driver()
298
298
  file = files_to_create[-1]
299
- sys.stdout.write(f"Creating database {file.name}\n")
299
+ _ = sys.stdout.write(f"Creating database {file.name}\n")
300
300
  (
301
301
  cloud_license_name,
302
302
  cloud_database_name,
@@ -305,7 +305,7 @@ def main() -> None:
305
305
  ) = _create_vuforia_resource_names()
306
306
 
307
307
  try:
308
- sys.stdout.write("Creating cloud database details\n")
308
+ _ = sys.stdout.write("Creating cloud database details\n")
309
309
  cloud_database_details = _create_and_get_cloud_database_details(
310
310
  driver=driver,
311
311
  email_address=email_address,
@@ -313,19 +313,19 @@ def main() -> None:
313
313
  cloud_license_name=cloud_license_name,
314
314
  cloud_database_name=cloud_database_name,
315
315
  )
316
- sys.stdout.write("Creating VuMark database details\n")
316
+ _ = sys.stdout.write("Creating VuMark database details\n")
317
317
  vumark_details = _create_and_get_vumark_details(
318
318
  driver=driver,
319
319
  vumark_database_name=vumark_database_name,
320
320
  )
321
- sys.stdout.write("Creating VuMark target\n")
321
+ _ = sys.stdout.write("Creating VuMark target\n")
322
322
  vumark_target_id = _create_and_get_vumark_target_id(
323
323
  driver=driver,
324
324
  vumark_database_name=vumark_database_name,
325
325
  vumark_template_name=vumark_template_name,
326
326
  )
327
327
  except TimeoutException:
328
- sys.stderr.write("Timed out during database setup\n")
328
+ _ = sys.stderr.write("Timed out during database setup\n")
329
329
  driver.quit()
330
330
  driver = None
331
331
  continue
@@ -343,9 +343,9 @@ def main() -> None:
343
343
  model_target_username=email_address,
344
344
  model_target_password=password,
345
345
  )
346
- file.write_text(data=file_contents)
347
- sys.stdout.write(f"Created database {file.name}\n")
348
- files_to_create.pop()
346
+ _ = file.write_text(data=file_contents)
347
+ _ = sys.stdout.write(f"Created database {file.name}\n")
348
+ _ = files_to_create.pop()
349
349
 
350
350
 
351
351
  if __name__ == "__main__":
@@ -13,7 +13,7 @@
13
13
  database = CloudDatabase()
14
14
  mock.add_cloud_database(cloud_database=database)
15
15
  # This will use the Vuforia mock.
16
- requests.get(url="https://vws.vuforia.com/summary", timeout=30)
16
+ _ = requests.get(url="https://vws.vuforia.com/summary", timeout=30)
17
17
 
18
18
  By default, an exception will be raised if any requests to unmocked addresses are made.
19
19
 
@@ -35,7 +35,7 @@ A ``MockVWS`` instance can also decorate a function:
35
35
  @mock
36
36
  def get_summary() -> None:
37
37
  """Make a request which uses the Vuforia mock."""
38
- requests.get(url="https://vws.vuforia.com/summary", timeout=30)
38
+ _ = requests.get(url="https://vws.vuforia.com/summary", timeout=30)
39
39
 
40
40
 
41
41
  get_summary()
@@ -12,7 +12,7 @@ Install Python dependencies in a virtual environment.
12
12
 
13
13
  .. code-block:: console
14
14
 
15
- $ pip install --editable '.[dev]'
15
+ $ pip install --editable . --group dev
16
16
 
17
17
  Spell checking requires ``enchant``.
18
18
  This can be installed on macOS, for example, with `Homebrew`_:
@@ -113,16 +113,24 @@ For the complete archive and GitHub Actions setup procedure, see
113
113
  Skipping Some Tests
114
114
  -------------------
115
115
 
116
- Use the following custom ``pytest`` options to skip some tests:
116
+ The tests run against several backends: the real Vuforia, the in-memory mock, and the Flask applications of the mock served in the test process.
117
+ The last of these exercises the handlers which the Docker deployment runs, but not the split between its containers.
118
+ The tests in ``tests/mock_vws/test_docker.py`` build and run the containers for that.
119
+ The `pytest-multi-backend`_ plugin adds options which skip some tests:
117
120
 
118
121
  .. code-block:: text
119
122
 
120
- --skip-real Skip tests for Real Vuforia
121
- --skip-mock Skip tests for In Memory Mock Vuforia
122
- --skip-docker_in_memory
123
- Skip tests for In Memory version of Docker application
124
- --skip-docker_build_tests
125
- Skip tests for building Docker images
123
+ --skip-backend=real Skip tests against the real Vuforia
124
+ --skip-backend=mock Skip tests against the in-memory mock Vuforia
125
+ --skip-backend=flask_in_process
126
+ Skip tests against the Flask applications served
127
+ in the test process
128
+ --skip-marker=requires_docker_build
129
+ Skip tests which build and run the Docker images
130
+
131
+ Give an option once per backend or marker to skip.
132
+
133
+ .. _pytest-multi-backend: https://adamtheturtle.github.io/pytest-multi-backend/
126
134
 
127
135
  Verifying signed Model Target requests
128
136
  --------------------------------------
@@ -169,7 +177,7 @@ Run the following commands to build and view documentation locally:
169
177
 
170
178
  .. code-block:: console
171
179
 
172
- $ uv run --extra=dev sphinx-build -M html docs/source docs/build -W
180
+ $ uv run --group=dev sphinx-build -M html docs/source docs/build -W
173
181
  $ python -c 'import os, webbrowser; webbrowser.open("file://" + os.path.abspath("docs/build/html/index.html"))'
174
182
 
175
183
  Continuous Integration
@@ -134,16 +134,26 @@ The mock uses the fixed sample value ``us-east-2, us-west-2`` for
134
134
  ``x-aws-region`` response headers. The regions returned by the real Vuforia
135
135
  Web Services can differ, so tests should not rely on the mock's exact value.
136
136
 
137
+ .. _differences-nginx-error-cases:
138
+
137
139
  NGINX Error cases
138
140
  -----------------
139
141
 
140
- Vuforia uses NGINX.
141
- This has error handling which is not duplicated in the mock.
142
- For example, Vuforia is documented as returning a 400 (``BAD REQUEST``) response if a header or cookie is given which is larger than 8 KiB.
142
+ Vuforia uses NGINX in front of both the Target API and the Query API.
143
+ NGINX reads each request header line into an 8 KiB buffer, and returns a 400 (``BAD REQUEST``) response with an HTML body titled ``400 Request Header Or Cookie Too Large`` for a line which does not fit.
144
+ The line's terminating CRLF also counts towards the buffer, so the longest accepted line is 8190 bytes, where a line is the header name, a colon, a space and the value.
145
+ This was observed against real Vuforia on 2026-09-08.
143
146
 
144
- .. admonition:: Unverified assumption
147
+ The mock returns that response for any header line longer than 8190 bytes.
148
+ The mock does not implement the following related behaviors, which were observed in the same session:
145
149
 
146
- :ref:`unverified-nginx-oversized-header-or-cookie`
150
+ * The Target API's Envoy layer lets a ``Cookie`` line slightly over the limit through.
151
+ A ``Cookie`` line of 8193 bytes was accepted and one of 8300 bytes was rejected.
152
+ * The Target API's AWS load balancer rejects a header line of 16384 bytes or more itself, with a shorter HTML body and a ``Server: awselb/2.0`` header.
153
+ A ``Cookie`` line of that size passes the load balancer and is rejected by NGINX instead.
154
+ * The Query API's application server rejects a request whose headers total about 8 KiB with a 431 (``REQUEST HEADER FIELDS TOO LARGE``) HTML response before the NGINX limit is reached.
155
+ With the headers which a query normally has, a header line of 7500 bytes was accepted and one of 8000 bytes was rejected this way.
156
+ * The Model Target Web API, the OAuth2 token endpoint and reco counts report downloads in the mock do not apply the limit.
147
157
 
148
158
  Result codes
149
159
  ------------
@@ -174,16 +184,30 @@ endpoints in general, with 45 requests per second for
174
184
  ``GET /targets/{target_id}``, 10 requests per second for
175
185
  ``GET /duplicates/{target_id}``, and 1 request per minute for ``GET /targets``.
176
186
 
177
- The mock models these limits separately for each group of endpoints, but it applies no limit by default.
178
- Applying a limit of 1 request per minute to ``GET /targets`` by default would break the tests of anything which uses the mock.
187
+ The limits were checked against real Vuforia on 2026-09-08, by sending bursts of requests to read-only endpoints:
179
188
 
180
- .. admonition:: Unverified assumption
189
+ * ``GET /targets`` accepts two requests per minute, not one.
190
+ The window is a fixed clock minute: two requests at 40 seconds past the minute were accepted, a third was rejected, and a request three seconds into the next minute was accepted again.
191
+ * The per-second limits are enforced roughly, not exactly.
192
+ Bursts of 40 concurrent ``GET /summary`` requests saw between 17 and 37 succeed against the documented 15, and a burst of 120 ``GET /targets/{target_id}`` requests saw 74 succeed against the documented 45, so the limiter appears to be spread over more than one instance or window.
193
+ * A limit is keyed on the server access key in the ``Authorization`` header, so one database's burst does not affect another database.
194
+ Vuforia applies the limit before checking the signature, so a request with a bad signature counts towards the limit, and a request over the limit gets a ``429`` response whether or not it is signed correctly.
195
+ Requests without an ``Authorization`` header are not rate limited.
196
+ * A rate-limited request gets a ``429`` (``TOO MANY REQUESTS``) response from Envoy with an empty body, no ``Content-Type`` header and an ``x-envoy-ratelimited: true`` header.
197
+ Vuforia has an Envoy layer at its edge and another in front of the application, and either may reject the request.
198
+ Only a rejection by the inner layer carries an ``x-envoy-upstream-service-time`` header, which the mock always includes.
199
+ The ``TooManyRequests`` result code from Vuforia's result codes table does not appear.
181
200
 
182
- :ref:`unverified-request-rate-limits`
201
+ The mock returns the empty Envoy response, applies each limit before checking the request's signature, and tracks each limit separately for each database and each group of endpoints.
202
+ The mock's windows are rolling rather than clock-aligned, so two ``GET /targets`` requests block a third until a minute has passed since the first, and the mock enforces the per-second limits exactly.
203
+ The mock only limits requests whose access key belongs to a database, because the limits are configured on the database.
204
+
205
+ The mock applies no limit by default.
206
+ Applying a limit of two requests per minute to ``GET /targets`` by default would break the tests of anything which uses the mock.
183
207
 
184
208
  Set ``request_rate_limits`` to
185
209
  :data:`mock_vws.request_rate_limits.DOCUMENTED_REQUEST_RATE_LIMITS` to apply
186
- the documented limits::
210
+ the limits which real Vuforia applies::
187
211
 
188
212
  from mock_vws import MockVWS
189
213
  from mock_vws.database import CloudDatabase
@@ -195,8 +219,8 @@ the documented limits::
195
219
 
196
220
  with MockVWS() as mock:
197
221
  mock.add_cloud_database(cloud_database=database)
198
- # A second ``GET /targets`` request within a minute returns
199
- # ``TooManyRequests``.
222
+ # A third ``GET /targets`` request within a minute gets a ``429``
223
+ # response.
200
224
  ...
201
225
 
202
226
  ``requests_per_second_limit`` remains available. It applies one limit to all
@@ -267,9 +291,13 @@ supported by the Flask/Docker backend.
267
291
  Other configurable result codes
268
292
  -------------------------------
269
293
 
270
- The mock also supports four other result codes which come from Vuforia's result codes table rather than from a response which a real database gave:
294
+ The mock also supports four other result codes.
295
+ ``ProjectSuspended`` has been seen from a real database.
296
+ The other three come from Vuforia's result codes table rather than from a response which a real database gave:
271
297
 
272
- .. admonition:: Unverified assumption
298
+ .. admonition:: Unverified assumptions
299
+
300
+ :ref:`unverified-project-suspended`
273
301
 
274
302
  :ref:`unverified-additional-result-codes`
275
303
 
@@ -277,8 +305,13 @@ The mock also supports four other result codes which come from Vuforia's result
277
305
  * ``TargetQuotaReached`` is returned when adding a target to a
278
306
  :class:`mock_vws.database.CloudDatabase` which already contains
279
307
  ``target_quota`` targets.
280
- * ``ProjectSuspended`` is returned by VWS endpoints when a database uses the
308
+ * ``ProjectSuspended`` is returned with status code 403 by every VWS endpoint
309
+ when a database uses the
281
310
  :attr:`mock_vws.states.States.PROJECT_SUSPENDED` state.
311
+ Real Vuforia has returned this result code for a database which passed its
312
+ monthly recognition threshold, but its status code, body and headers were
313
+ not recorded, and reads such as ``GET /targets`` and the database summary
314
+ kept working there.
282
315
  * ``ProjectHasNoApiAccess`` is returned by VWS endpoints when a database uses
283
316
  the :attr:`mock_vws.states.States.PROJECT_HAS_NO_API_ACCESS` state.
284
317
  This casing comes from Vuforia's result codes table, as no response from a
@@ -13,6 +13,6 @@
13
13
  database = CloudDatabase()
14
14
  mock.add_cloud_database(cloud_database=database)
15
15
  # This will use the Vuforia mock.
16
- httpx.get(url="https://vws.vuforia.com/summary", timeout=30)
16
+ _ = httpx.get(url="https://vws.vuforia.com/summary", timeout=30)
17
17
 
18
18
  .. _httpx: https://pypi.org/project/httpx/
@@ -13,7 +13,7 @@
13
13
  database = CloudDatabase()
14
14
  mock.add_cloud_database(cloud_database=database)
15
15
  # This will use the Vuforia mock.
16
- httpx2.get(url="https://vws.vuforia.com/summary", timeout=30)
16
+ _ = httpx2.get(url="https://vws.vuforia.com/summary", timeout=30)
17
17
 
18
18
  Asynchronous ``httpx`` and `HTTPX2`_ clients are intercepted as well.
19
19
 
@@ -38,18 +38,25 @@ The status code and the body shape come from Vuforia's documentation and from th
38
38
  A real database with an exhausted request quota would verify this.
39
39
  No such response has been seen.
40
40
 
41
- .. _unverified-request-rate-limits:
41
+ .. _unverified-project-suspended:
42
42
 
43
- Request rate limits
44
- -------------------
43
+ A suspended database
44
+ --------------------
45
45
 
46
- :Category: never-attempted
46
+ :Category: temporarily-unverifiable
47
47
  :API: VWS Target API
48
48
 
49
- Vuforia documents a limit of 15 requests per second for VWS endpoints in general, 45 per second for ``GET /targets/{target_id}``, 10 per second for ``GET /duplicates/{target_id}`` and one per minute for ``GET /targets``.
50
- The mock models the limits separately for each group of endpoints, and applies them only when it is asked to.
49
+ The mock returns a ``ProjectSuspended`` response with status code 403 from every VWS endpoint for a database in the :attr:`mock_vws.states.States.PROJECT_SUSPENDED` state.
50
+
51
+ The result code string is confirmed.
52
+ In August 2026 real Vuforia suspended one of the CI databases after it passed its monthly recognition threshold of 1000, and ``vws-python`` raised ``ProjectSuspendedError`` from the real response.
53
+ Reads such as ``GET /targets`` and the database summary kept working, and writes such as ``POST /targets`` were rejected.
51
54
 
52
- Sending more than the documented number of requests to a real database, and seeing what it returns, would verify this.
55
+ The status code, body and headers of the real response were not recorded, and which endpoints reject requests was only partly observed.
56
+ The mock rejects requests to every VWS endpoint, so it is stricter than the real service was seen to be.
57
+
58
+ Driving a throwaway database past 1000 recognitions in a month with query requests, and recording the response from each VWS endpoint, would verify the rest.
59
+ That is a deliberate one-off rather than a suite test because of the quota cost.
53
60
 
54
61
  .. _unverified-additional-result-codes:
55
62
 
@@ -59,8 +66,9 @@ Additional result codes
59
66
  :Category: never-attempted
60
67
  :API: VWS Target API
61
68
 
62
- ``ProjectSuspended``, ``ProjectHasNoApiAccess``, ``TargetQuotaReached`` and ``TooManyRequests`` come from Vuforia's result codes table.
69
+ ``ProjectHasNoApiAccess`` and ``TargetQuotaReached`` come from Vuforia's result codes table.
63
70
  No response from a real database in any of those states has been seen, which is why the mock's ``ProjectHasNoApiAccess`` casing is the table's casing rather than an observed one.
71
+ The table also lists ``TooManyRequests``, but a rate-limited request to a real database gets a ``429`` response with no body at all, so the mock never returns that result code.
64
72
 
65
73
  A database put into each state by the Target Manager portal would verify these.
66
74
 
@@ -77,19 +85,6 @@ The mock does not implement this, so a user of the mock sees a successful respon
77
85
 
78
86
  A database with more than a million images would verify this, which a test account cannot hold.
79
87
 
80
- .. _unverified-nginx-oversized-header-or-cookie:
81
-
82
- Large headers and cookies
83
- -------------------------
84
-
85
- :Category: never-attempted
86
- :API: Cross-cutting request handling
87
-
88
- Vuforia runs behind NGINX, which is documented as returning a 400 (``BAD REQUEST``) response for a header or a cookie larger than 8 KiB.
89
- The mock does not implement this, and no test sends such a request to either.
90
-
91
- Sending a request with a header larger than 8 KiB to a real database would verify this.
92
-
93
88
  .. _unverified-reco-counts-report-not-ready:
94
89
 
95
90
  A reco counts report which is not ready