vws-python-mock 2026.2.21__tar.gz → 2026.2.22__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 (144) hide show
  1. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/CHANGELOG.rst +4 -0
  2. {vws_python_mock-2026.2.21/src/vws_python_mock.egg-info → vws_python_mock-2026.2.22}/PKG-INFO +28 -2
  3. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/README.rst +24 -0
  4. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/ci/test_custom_linters.py +1 -0
  5. vws_python_mock-2026.2.22/docs/source/getting-started.rst +12 -0
  6. vws_python_mock-2026.2.22/docs/source/httpx-example.rst +22 -0
  7. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/docs/source/index.rst +5 -0
  8. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/docs/source/mock-api-reference.rst +4 -0
  9. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/pyproject.toml +10 -8
  10. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/spelling_private_dict.txt +2 -0
  11. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/__init__.py +2 -0
  12. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_flask_server/target_manager.py +10 -5
  13. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_flask_server/vwq.py +2 -0
  14. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_flask_server/vws.py +10 -3
  15. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_mock_common.py +20 -1
  16. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_query_validators/content_type_validators.py +1 -0
  17. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_query_validators/date_validators.py +1 -0
  18. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_query_validators/image_validators.py +1 -0
  19. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_query_validators/include_target_data_validators.py +1 -0
  20. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_query_validators/project_state_validators.py +1 -0
  21. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_requests_mock_server/decorators.py +21 -4
  22. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_requests_mock_server/mock_web_query_api.py +10 -22
  23. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_requests_mock_server/mock_web_services_api.py +79 -91
  24. vws_python_mock-2026.2.22/src/mock_vws/_respx_mock_server/__init__.py +1 -0
  25. vws_python_mock-2026.2.22/src/mock_vws/_respx_mock_server/decorators.py +275 -0
  26. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_services_validators/__init__.py +4 -0
  27. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_services_validators/key_validators.py +1 -0
  28. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/database.py +9 -0
  29. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/target.py +38 -0
  30. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22/src/vws_python_mock.egg-info}/PKG-INFO +28 -2
  31. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/vws_python_mock.egg-info/SOURCES.txt +4 -0
  32. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/vws_python_mock.egg-info/requires.txt +3 -1
  33. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/conftest.py +4 -1
  34. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/fixtures/prepared_requests.py +3 -1
  35. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/fixtures/vuforia_backends.py +1 -0
  36. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_add_target.py +26 -0
  37. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_authorization_header.py +2 -0
  38. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_database_summary.py +9 -1
  39. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_delete_target.py +2 -2
  40. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_docker.py +1 -0
  41. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_flask_app_usage.py +178 -44
  42. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_get_duplicates.py +5 -0
  43. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_get_target.py +4 -0
  44. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_invalid_given_id.py +4 -0
  45. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_query.py +41 -2
  46. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_requests_mock_usage.py +37 -1
  47. vws_python_mock-2026.2.22/tests/mock_vws/test_respx_mock_usage.py +350 -0
  48. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_target_list.py +2 -0
  49. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_target_summary.py +3 -1
  50. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_update_target.py +26 -5
  51. vws_python_mock-2026.2.22/tests/mock_vws/test_vumark_generation_api.py +360 -0
  52. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/utils/__init__.py +5 -0
  53. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/utils/assertions.py +1 -0
  54. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/utils/usage_test_helpers.py +2 -0
  55. vws_python_mock-2026.2.21/docs/source/getting-started.rst +0 -4
  56. vws_python_mock-2026.2.21/tests/mock_vws/test_vumark_generation_api.py +0 -186
  57. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/.git_archival.txt +0 -0
  58. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/.gitattributes +0 -0
  59. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/.github/dependabot.yml +0 -0
  60. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/.github/workflows/dependabot-merge.yml +0 -0
  61. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/.github/workflows/docker-build.yml +0 -0
  62. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/.github/workflows/lint.yml +0 -0
  63. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/.github/workflows/publish-site.yml +0 -0
  64. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/.github/workflows/release.yml +0 -0
  65. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/.github/workflows/test.yml +0 -0
  66. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/.gitignore +0 -0
  67. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/.pre-commit-config.yaml +0 -0
  68. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/.prettierrc +0 -0
  69. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/.vscode/extensions.json +0 -0
  70. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/.vscode/settings.json +0 -0
  71. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/CODE_OF_CONDUCT.rst +0 -0
  72. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/LICENSE +0 -0
  73. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/MANIFEST.in +0 -0
  74. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/admin/__init__.py +0 -0
  75. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/admin/create_secrets_files.py +0 -0
  76. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/admin/vumark_template.svg +0 -0
  77. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/ci/__init__.py +0 -0
  78. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/ci/decrypt_secret.sh +0 -0
  79. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/conftest.py +0 -0
  80. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/docker-bake.hcl +0 -0
  81. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/docs/source/__init__.py +0 -0
  82. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/docs/source/basic-example.rst +0 -0
  83. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/docs/source/changelog.rst +0 -0
  84. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/docs/source/ci-setup.rst +0 -0
  85. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/docs/source/conf.py +0 -0
  86. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/docs/source/contributing.rst +0 -0
  87. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/docs/source/differences-to-vws.rst +0 -0
  88. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/docs/source/docker.rst +0 -0
  89. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/docs/source/installation.rst +0 -0
  90. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/docs/source/release-process.rst +0 -0
  91. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/docs/source/versioning-and-api-stability.rst +0 -0
  92. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/secrets.tar.gpg +0 -0
  93. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/setup.cfg +0 -0
  94. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_base64_decoding.py +0 -0
  95. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_constants.py +0 -0
  96. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_database_matchers.py +0 -0
  97. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_flask_server/Dockerfile +0 -0
  98. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_flask_server/__init__.py +0 -0
  99. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_flask_server/healthcheck.py +0 -0
  100. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_query_tools.py +0 -0
  101. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_query_validators/__init__.py +0 -0
  102. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_query_validators/accept_header_validators.py +0 -0
  103. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_query_validators/auth_validators.py +0 -0
  104. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_query_validators/content_length_validators.py +0 -0
  105. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_query_validators/exceptions.py +0 -0
  106. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_query_validators/fields_validators.py +0 -0
  107. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_query_validators/num_results_validators.py +0 -0
  108. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_requests_mock_server/__init__.py +0 -0
  109. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_services_validators/active_flag_validators.py +0 -0
  110. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_services_validators/auth_validators.py +0 -0
  111. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_services_validators/content_length_validators.py +0 -0
  112. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_services_validators/content_type_validators.py +0 -0
  113. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_services_validators/date_validators.py +0 -0
  114. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_services_validators/exceptions.py +0 -0
  115. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_services_validators/image_validators.py +0 -0
  116. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_services_validators/json_validators.py +0 -0
  117. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_services_validators/metadata_validators.py +0 -0
  118. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_services_validators/name_validators.py +0 -0
  119. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_services_validators/project_state_validators.py +0 -0
  120. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_services_validators/target_validators.py +0 -0
  121. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/_services_validators/width_validators.py +0 -0
  122. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/database_type.py +0 -0
  123. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/image_matchers.py +0 -0
  124. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/py.typed +0 -0
  125. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/states.py +0 -0
  126. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/target_manager.py +0 -0
  127. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/mock_vws/target_raters.py +0 -0
  128. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/vws_python_mock.egg-info/dependency_links.txt +0 -0
  129. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/vws_python_mock.egg-info/not-zip-safe +0 -0
  130. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/src/vws_python_mock.egg-info/top_level.txt +0 -0
  131. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/__init__.py +0 -0
  132. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/__init__.py +0 -0
  133. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/fixtures/__init__.py +0 -0
  134. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/fixtures/credentials.py +0 -0
  135. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_content_length.py +0 -0
  136. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_date_header.py +0 -0
  137. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_invalid_json.py +0 -0
  138. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_target_raters.py +0 -0
  139. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_target_validators.py +0 -0
  140. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/test_unexpected_json.py +0 -0
  141. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/utils/retries.py +0 -0
  142. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/tests/mock_vws/utils/too_many_requests.py +0 -0
  143. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/vuforia_secrets.env.example +0 -0
  144. {vws_python_mock-2026.2.21 → vws_python_mock-2026.2.22}/zizmor.yml +0 -0
@@ -4,6 +4,10 @@ Changelog
4
4
  Next
5
5
  ----
6
6
 
7
+ 2026.02.22
8
+ ----------
9
+
10
+
7
11
  2026.02.21
8
12
  ----------
9
13
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vws-python-mock
3
- Version: 2026.2.21
3
+ Version: 2026.2.22
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
@@ -19,12 +19,14 @@ Description-Content-Type: text/x-rst
19
19
  License-File: LICENSE
20
20
  Requires-Dist: beartype>=0.22.9
21
21
  Requires-Dist: flask>=3.0.3
22
+ Requires-Dist: httpx>=0.27.0
22
23
  Requires-Dist: numpy>=1.26.4
23
24
  Requires-Dist: pillow>=11.0.0
24
25
  Requires-Dist: piq>=0.8.0
25
26
  Requires-Dist: pydantic-settings>=2.6.1
26
27
  Requires-Dist: requests>=2.32.3
27
28
  Requires-Dist: responses>=0.25.3
29
+ Requires-Dist: respx>=0.21.0
28
30
  Requires-Dist: torch>=2.5.1
29
31
  Requires-Dist: torchmetrics>=1.5.1
30
32
  Requires-Dist: torchvision>=0.20.1
@@ -82,7 +84,7 @@ Requires-Dist: types-pyyaml==6.0.12.20250915; extra == "dev"
82
84
  Requires-Dist: types-requests==2.32.4.20260107; extra == "dev"
83
85
  Requires-Dist: urllib3==2.6.3; extra == "dev"
84
86
  Requires-Dist: vulture==2.14; extra == "dev"
85
- Requires-Dist: vws-python==2026.2.15; extra == "dev"
87
+ Requires-Dist: vws-python==2026.2.21; extra == "dev"
86
88
  Requires-Dist: vws-test-fixtures==2023.3.5; extra == "dev"
87
89
  Requires-Dist: vws-web-tools==2026.2.20; extra == "dev"
88
90
  Requires-Dist: yamlfix==1.19.1; extra == "dev"
@@ -131,6 +133,30 @@ By default, an exception will be raised if any requests to unmocked addresses ar
131
133
 
132
134
  .. _requests: https://pypi.org/project/requests/
133
135
 
136
+ Mocking calls made to Vuforia with Python ``httpx``
137
+ ----------------------------------------------------
138
+
139
+ Using the mock redirects requests to Vuforia made with `httpx`_ to an in-memory implementation.
140
+
141
+ .. code-block:: python
142
+
143
+ """Make a request to the Vuforia Web Services API mock."""
144
+
145
+ import httpx
146
+
147
+ from mock_vws import MockVWSForHttpx
148
+ from mock_vws.database import CloudDatabase
149
+
150
+ with MockVWSForHttpx() as mock:
151
+ database = CloudDatabase()
152
+ mock.add_cloud_database(cloud_database=database)
153
+ # This will use the Vuforia mock.
154
+ httpx.get(url="https://vws.vuforia.com/summary", timeout=30)
155
+
156
+ By default, an exception will be raised if any requests to unmocked addresses are made.
157
+
158
+ .. _httpx: https://pypi.org/project/httpx/
159
+
134
160
  Using Docker to mock calls to Vuforia from any language
135
161
  -------------------------------------------------------
136
162
 
@@ -38,6 +38,30 @@ By default, an exception will be raised if any requests to unmocked addresses ar
38
38
 
39
39
  .. _requests: https://pypi.org/project/requests/
40
40
 
41
+ Mocking calls made to Vuforia with Python ``httpx``
42
+ ----------------------------------------------------
43
+
44
+ Using the mock redirects requests to Vuforia made with `httpx`_ to an in-memory implementation.
45
+
46
+ .. code-block:: python
47
+
48
+ """Make a request to the Vuforia Web Services API mock."""
49
+
50
+ import httpx
51
+
52
+ from mock_vws import MockVWSForHttpx
53
+ from mock_vws.database import CloudDatabase
54
+
55
+ with MockVWSForHttpx() as mock:
56
+ database = CloudDatabase()
57
+ mock.add_cloud_database(cloud_database=database)
58
+ # This will use the Vuforia mock.
59
+ httpx.get(url="https://vws.vuforia.com/summary", timeout=30)
60
+
61
+ By default, an exception will be raised if any requests to unmocked addresses are made.
62
+
63
+ .. _httpx: https://pypi.org/project/httpx/
64
+
41
65
  Using Docker to mock calls to Vuforia from any language
42
66
  -------------------------------------------------------
43
67
 
@@ -83,6 +83,7 @@ def test_ci_patterns_valid(request: pytest.FixtureRequest) -> None:
83
83
 
84
84
 
85
85
  def test_tests_collected_once(
86
+ *,
86
87
  capsys: pytest.CaptureFixture[str],
87
88
  request: pytest.FixtureRequest,
88
89
  ) -> None:
@@ -0,0 +1,12 @@
1
+ Getting started
2
+ ---------------
3
+
4
+ Mocking calls made to Vuforia with Python ``requests``
5
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6
+
7
+ .. include:: basic-example.rst
8
+
9
+ Mocking calls made to Vuforia with Python ``httpx``
10
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11
+
12
+ .. include:: httpx-example.rst
@@ -0,0 +1,22 @@
1
+ Using the mock redirects requests to Vuforia made with `httpx`_ to an in-memory implementation.
2
+
3
+ .. code-block:: python
4
+
5
+ """Make a request to the Vuforia Web Services API mock."""
6
+
7
+ import httpx
8
+
9
+ from mock_vws import MockVWSForHttpx
10
+ from mock_vws.database import CloudDatabase
11
+
12
+ with MockVWSForHttpx() as mock:
13
+ database = CloudDatabase()
14
+ mock.add_cloud_database(cloud_database=database)
15
+ # This will use the Vuforia mock.
16
+ httpx.get(url="https://vws.vuforia.com/summary", timeout=30)
17
+
18
+ By default, an exception will be raised if any requests to unmocked addresses are made.
19
+
20
+ See :ref:`mock-api-reference` for details of what can be changed and how.
21
+
22
+ .. _httpx: https://pypi.org/project/httpx/
@@ -12,6 +12,11 @@ This requires Python |minimum-python-version|\+.
12
12
 
13
13
  .. include:: basic-example.rst
14
14
 
15
+ Mocking calls made to Vuforia with Python ``httpx``
16
+ ----------------------------------------------------
17
+
18
+ .. include:: httpx-example.rst
19
+
15
20
  Using Docker to mock calls to Vuforia from any language
16
21
  -------------------------------------------------------
17
22
 
@@ -7,6 +7,10 @@ API Reference
7
7
  :members:
8
8
  :undoc-members:
9
9
 
10
+ .. autoclass:: mock_vws.MockVWSForHttpx
11
+ :members:
12
+ :undoc-members:
13
+
10
14
  .. autoclass:: mock_vws.MissingSchemeError
11
15
  :members:
12
16
  :undoc-members:
@@ -36,12 +36,14 @@ dynamic = [
36
36
  dependencies = [
37
37
  "beartype>=0.22.9",
38
38
  "flask>=3.0.3",
39
+ "httpx>=0.27.0",
39
40
  "numpy>=1.26.4",
40
41
  "pillow>=11.0.0",
41
42
  "piq>=0.8.0",
42
43
  "pydantic-settings>=2.6.1",
43
44
  "requests>=2.32.3",
44
45
  "responses>=0.25.3",
46
+ "respx>=0.21.0",
45
47
  "torch>=2.5.1",
46
48
  "torchmetrics>=1.5.1",
47
49
  "torchvision>=0.20.1",
@@ -103,7 +105,7 @@ optional-dependencies.dev = [
103
105
  "types-requests==2.32.4.20260107",
104
106
  "urllib3==2.6.3",
105
107
  "vulture==2.14",
106
- "vws-python==2026.2.15",
108
+ "vws-python==2026.2.21",
107
109
  "vws-test-fixtures==2023.3.5",
108
110
  "vws-web-tools==2026.2.20",
109
111
  "yamlfix==1.19.1",
@@ -329,18 +331,18 @@ keep_full_version = true
329
331
  max_supported_python = "3.13"
330
332
 
331
333
  [tool.pytest]
332
- ini_options.xfail_strict = true
333
- ini_options.log_cli = true
334
- ini_options.addopts = [
334
+ xfail_strict = true
335
+ log_cli = true
336
+ addopts = [
335
337
  "--strict-markers",
336
338
  ]
337
- ini_options.markers = [
339
+ markers = [
338
340
  "requires_docker_build",
339
341
  ]
340
342
  # Options for pytest-retry.
341
- ini_options.retries = 10
342
- ini_options.retry_delay = 10
343
- ini_options.cumulative_timing = false
343
+ retries = "10"
344
+ retry_delay = "10"
345
+ cumulative_timing = false
344
346
 
345
347
  [tool.coverage]
346
348
  run.branch = true
@@ -45,6 +45,7 @@ hmac
45
45
  html
46
46
  http
47
47
  https
48
+ httpx
48
49
  iff
49
50
  io
50
51
  issuecomment
@@ -98,6 +99,7 @@ reqjsonarr
98
99
  resheader
99
100
  resjson
100
101
  resjsonarr
102
+ respx
101
103
  rfc
102
104
  rgb
103
105
  str
@@ -4,8 +4,10 @@ from mock_vws._requests_mock_server.decorators import (
4
4
  MissingSchemeError,
5
5
  MockVWS,
6
6
  )
7
+ from mock_vws._respx_mock_server.decorators import MockVWSForHttpx
7
8
 
8
9
  __all__ = [
9
10
  "MissingSchemeError",
10
11
  "MockVWS",
12
+ "MockVWSForHttpx",
11
13
  ]
@@ -91,11 +91,15 @@ def delete_vumark_database(database_name: str) -> Response:
91
91
 
92
92
  :status 200: The VuMark database has been deleted.
93
93
  """
94
- (matching_database,) = {
95
- database
96
- for database in TARGET_MANAGER.vumark_databases
97
- if database_name == database.database_name
98
- }
94
+ try:
95
+ (matching_database,) = {
96
+ database
97
+ for database in TARGET_MANAGER.vumark_databases
98
+ if database_name == database.database_name
99
+ }
100
+ except ValueError:
101
+ return Response(response="", status=HTTPStatus.NOT_FOUND)
102
+
99
103
  TARGET_MANAGER.remove_vumark_database(vumark_database=matching_database)
100
104
  return Response(response="", status=HTTPStatus.OK)
101
105
 
@@ -365,6 +369,7 @@ def delete_target(database_name: str, target_id: str) -> Response:
365
369
  rule="/cloud_databases/<string:database_name>/targets/<string:target_id>",
366
370
  methods=[HTTPMethod.PUT],
367
371
  )
372
+ @beartype
368
373
  def update_target(database_name: str, target_id: str) -> Response:
369
374
  """Update a target."""
370
375
  (database,) = (
@@ -113,6 +113,7 @@ def add_response_delay(response: Response) -> Response:
113
113
 
114
114
 
115
115
  @CLOUDRECO_FLASK_APP.errorhandler(code_or_exception=ValidatorError)
116
+ @beartype
116
117
  def handle_exceptions(exc: ValidatorError) -> Response:
117
118
  """Return the error response associated with the given exception."""
118
119
  response = Response(
@@ -127,6 +128,7 @@ def handle_exceptions(exc: ValidatorError) -> Response:
127
128
 
128
129
 
129
130
  @CLOUDRECO_FLASK_APP.route(rule="/v1/query", methods=[HTTPMethod.POST])
131
+ @beartype
130
132
  def query() -> Response:
131
133
  """Perform an image recognition query."""
132
134
  settings = VWQSettings.model_validate(obj={})
@@ -117,6 +117,7 @@ def get_all_vumark_databases() -> set[VuMarkDatabase]:
117
117
 
118
118
 
119
119
  @VWS_FLASK_APP.before_request
120
+ @beartype
120
121
  def set_terminate_wsgi_input() -> None:
121
122
  """We set ``wsgi.input_terminated`` to ``True`` when going through
122
123
  ``requests`` in our tests, so that requests have the given ``Content-
@@ -171,6 +172,7 @@ def add_response_delay(response: Response) -> Response:
171
172
 
172
173
 
173
174
  @VWS_FLASK_APP.errorhandler(code_or_exception=ValidatorError)
175
+ @beartype
174
176
  def handle_exceptions(exc: ValidatorError) -> Response:
175
177
  """Return the error response associated with the given exception."""
176
178
  response = Response(
@@ -319,6 +321,7 @@ def get_target(target_id: str) -> Response:
319
321
  rule="/targets/<string:target_id>",
320
322
  methods=[HTTPMethod.DELETE],
321
323
  )
324
+ @beartype
322
325
  def delete_target(target_id: str) -> Response:
323
326
  """Delete a target.
324
327
 
@@ -395,9 +398,6 @@ def generate_vumark_instance(target_id: str) -> Response:
395
398
  databases=all_databases,
396
399
  )
397
400
 
398
- # ``target_id`` is validated by request validators.
399
- del target_id
400
-
401
401
  database = get_database_matching_server_keys(
402
402
  request_headers=dict(request.headers),
403
403
  request_body=request.data,
@@ -408,6 +408,10 @@ def generate_vumark_instance(target_id: str) -> Response:
408
408
  if not isinstance(database, VuMarkDatabase):
409
409
  raise InvalidTargetTypeError
410
410
 
411
+ target = database.get_vumark_target(target_id=target_id)
412
+ if target.status != TargetStatuses.SUCCESS.value:
413
+ raise TargetStatusNotSuccessError
414
+
411
415
  accept = request.headers.get(key="Accept", default="")
412
416
  valid_accept_types: dict[str, bytes] = {
413
417
  "image/png": VUMARK_PNG,
@@ -499,6 +503,7 @@ def database_summary() -> Response:
499
503
  rule="/summary/<string:target_id>",
500
504
  methods=[HTTPMethod.GET],
501
505
  )
506
+ @beartype
502
507
  def target_summary(target_id: str) -> Response:
503
508
  """Get a summary report for a target.
504
509
 
@@ -616,6 +621,7 @@ def get_duplicates(target_id: str) -> Response:
616
621
 
617
622
 
618
623
  @VWS_FLASK_APP.route(rule="/targets", methods=[HTTPMethod.GET])
624
+ @beartype
619
625
  def target_list() -> Response:
620
626
  """Get a list of all targets.
621
627
 
@@ -658,6 +664,7 @@ def target_list() -> Response:
658
664
  @VWS_FLASK_APP.route(
659
665
  rule="/targets/<string:target_id>", methods=[HTTPMethod.PUT]
660
666
  )
667
+ @beartype
661
668
  def update_target(target_id: str) -> Response:
662
669
  """Update a target.
663
670
 
@@ -1,13 +1,32 @@
1
1
  """Common utilities for creating mock routes."""
2
2
 
3
3
  import json
4
- from collections.abc import Iterable
4
+ from collections.abc import Iterable, Mapping
5
5
  from dataclasses import dataclass
6
6
  from typing import Any
7
7
 
8
8
  from beartype import beartype
9
9
 
10
10
 
11
+ @beartype
12
+ @dataclass(frozen=True)
13
+ class RequestData:
14
+ """A library-agnostic representation of an HTTP request.
15
+
16
+ Args:
17
+ method: The HTTP method of the request.
18
+ path: The path of the request.
19
+ headers: The headers sent with the request.
20
+ body: The body of the request.
21
+ """
22
+
23
+ method: str
24
+ path: str
25
+ headers: Mapping[str, str]
26
+ body: bytes
27
+
28
+
29
+ @beartype
11
30
  @dataclass(frozen=True)
12
31
  class Route:
13
32
  """A representation of a VWS route.
@@ -18,6 +18,7 @@ _LOGGER = logging.getLogger(name=__name__)
18
18
 
19
19
  @beartype
20
20
  def validate_content_type_header(
21
+ *,
21
22
  request_headers: Mapping[str, str],
22
23
  request_body: bytes,
23
24
  ) -> None:
@@ -34,6 +34,7 @@ def validate_date_header_given(*, request_headers: Mapping[str, str]) -> None:
34
34
  raise DateHeaderNotGivenError
35
35
 
36
36
 
37
+ @beartype
37
38
  def _accepted_date_formats() -> set[str]:
38
39
  """Return all known accepted date formats.
39
40
 
@@ -171,6 +171,7 @@ def validate_image_format(
171
171
 
172
172
  @beartype
173
173
  def validate_image_is_image(
174
+ *,
174
175
  request_headers: Mapping[str, str],
175
176
  request_body: bytes,
176
177
  ) -> None:
@@ -15,6 +15,7 @@ _LOGGER = logging.getLogger(name=__name__)
15
15
 
16
16
  @beartype
17
17
  def validate_include_target_data(
18
+ *,
18
19
  request_headers: Mapping[str, str],
19
20
  request_body: bytes,
20
21
  ) -> None:
@@ -15,6 +15,7 @@ _LOGGER = logging.getLogger(name=__name__)
15
15
 
16
16
  @beartype
17
17
  def validate_project_state(
18
+ *,
18
19
  request_path: str,
19
20
  request_headers: Mapping[str, str],
20
21
  request_body: bytes,
@@ -12,6 +12,7 @@ from beartype import BeartypeConf, beartype
12
12
  from requests import PreparedRequest
13
13
  from responses import RequestsMock
14
14
 
15
+ from mock_vws._mock_common import RequestData
15
16
  from mock_vws.database import CloudDatabase, VuMarkDatabase
16
17
  from mock_vws.image_matchers import (
17
18
  ImageMatcher,
@@ -27,12 +28,14 @@ from .mock_web_query_api import MockVuforiaWebQueryAPI
27
28
  from .mock_web_services_api import MockVuforiaWebServicesAPI
28
29
 
29
30
  _ResponseType = tuple[int, Mapping[str, str], str | bytes]
30
- _Callback = Callable[[PreparedRequest], _ResponseType]
31
+ _MockCallback = Callable[[RequestData], _ResponseType]
32
+ _ResponsesCallback = Callable[[PreparedRequest], _ResponseType]
31
33
 
32
34
  _STRUCTURAL_SIMILARITY_MATCHER = StructuralSimilarityMatcher()
33
35
  _BRISQUE_TRACKING_RATER = BrisqueTargetTrackingRater()
34
36
 
35
37
 
38
+ @beartype
36
39
  class MissingSchemeError(Exception):
37
40
  """Raised when a URL is missing a schema."""
38
41
 
@@ -156,10 +159,10 @@ class MockVWS(ContextDecorator):
156
159
 
157
160
  @staticmethod
158
161
  def _wrap_callback(
159
- callback: _Callback,
162
+ callback: _MockCallback,
160
163
  delay_seconds: float,
161
164
  sleep_fn: Callable[[float], None],
162
- ) -> _Callback:
165
+ ) -> _ResponsesCallback:
163
166
  """Wrap a callback to add a response delay."""
164
167
 
165
168
  def wrapped(
@@ -186,7 +189,21 @@ class MockVWS(ContextDecorator):
186
189
  sleep_fn(effective)
187
190
  raise requests.exceptions.Timeout
188
191
 
189
- result = callback(request)
192
+ raw_body = request.body
193
+ if raw_body is None:
194
+ body_bytes = b""
195
+ elif isinstance(raw_body, str):
196
+ body_bytes = raw_body.encode(encoding="utf-8")
197
+ else:
198
+ body_bytes = raw_body
199
+
200
+ request_data = RequestData(
201
+ method=request.method or "",
202
+ path=request.path_url,
203
+ headers=dict(request.headers),
204
+ body=body_bytes,
205
+ )
206
+ result = callback(request_data)
190
207
  sleep_fn(delay_seconds)
191
208
  return result
192
209
 
@@ -10,9 +10,8 @@ from http import HTTPMethod, HTTPStatus
10
10
  from typing import ParamSpec, Protocol, runtime_checkable
11
11
 
12
12
  from beartype import beartype
13
- from requests.models import PreparedRequest
14
13
 
15
- from mock_vws._mock_common import Route
14
+ from mock_vws._mock_common import RequestData, Route
16
15
  from mock_vws._query_tools import (
17
16
  get_query_match_response_text,
18
17
  )
@@ -42,6 +41,7 @@ class _RouteMethod(Protocol[_P]):
42
41
 
43
42
  @beartype
44
43
  def route(
44
+ *,
45
45
  path_pattern: str,
46
46
  http_methods: Iterable[str],
47
47
  ) -> Callable[[_RouteMethod[_P]], _RouteMethod[_P]]:
@@ -78,21 +78,9 @@ def route(
78
78
  return decorator
79
79
 
80
80
 
81
- @beartype
82
- def _body_bytes(request: PreparedRequest) -> bytes:
83
- """Return the body of a request as bytes."""
84
- if request.body is None or isinstance(request.body, str):
85
- return b""
86
-
87
- return request.body
88
-
89
-
90
81
  @beartype
91
82
  class MockVuforiaWebQueryAPI:
92
- """A fake implementation of the Vuforia Web Query API.
93
-
94
- This implementation is tied to the implementation of ``responses``.
95
- """
83
+ """A fake implementation of the Vuforia Web Query API."""
96
84
 
97
85
  def __init__(
98
86
  self,
@@ -114,14 +102,14 @@ class MockVuforiaWebQueryAPI:
114
102
  self._query_match_checker = query_match_checker
115
103
 
116
104
  @route(path_pattern="/v1/query", http_methods={HTTPMethod.POST})
117
- def query(self, request: PreparedRequest) -> _ResponseType:
105
+ def query(self, request: RequestData) -> _ResponseType:
118
106
  """Perform an image recognition query."""
119
107
  try:
120
108
  run_query_validators(
121
- request_path=request.path_url,
109
+ request_path=request.path,
122
110
  request_headers=request.headers,
123
- request_body=_body_bytes(request=request),
124
- request_method=request.method or "",
111
+ request_body=request.body,
112
+ request_method=request.method,
125
113
  databases=self._target_manager.cloud_databases,
126
114
  )
127
115
  except ValidatorError as exc:
@@ -129,9 +117,9 @@ class MockVuforiaWebQueryAPI:
129
117
 
130
118
  response_text = get_query_match_response_text(
131
119
  request_headers=request.headers,
132
- request_body=_body_bytes(request=request),
133
- request_method=request.method or "",
134
- request_path=request.path_url,
120
+ request_body=request.body,
121
+ request_method=request.method,
122
+ request_path=request.path,
135
123
  databases=self._target_manager.cloud_databases,
136
124
  query_match_checker=self._query_match_checker,
137
125
  )