vws-python-mock 2026.8.26__tar.gz → 2026.9.6__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.
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.github/workflows/test.yml +18 -6
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/CHANGELOG.rst +35 -0
- {vws_python_mock-2026.8.26/src/vws_python_mock.egg-info → vws_python_mock-2026.9.6}/PKG-INFO +43 -20
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/README.rst +21 -1
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/conftest.py +13 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/basic-example.rst +2 -1
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/contributing.rst +38 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/differences-to-vws.rst +128 -38
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/docker.rst +11 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/getting-started.rst +2 -0
- vws_python_mock-2026.9.6/docs/source/httpx2-example.rst +20 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/index.rst +3 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/mock-api-reference.rst +8 -0
- vws_python_mock-2026.9.6/docs/source/unverified-behavior.rst +239 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/prek.toml +9 -10
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/pyproject.toml +35 -19
- vws_python_mock-2026.9.6/secrets.tar.gpg +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/spelling_private_dict.txt +12 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/__init__.py +4 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_flask_server/target_manager.py +437 -129
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_flask_server/vws.py +20 -8
- vws_python_mock-2026.9.6/src/mock_vws/_httpx2_mock_server/__init__.py +1 -0
- vws_python_mock-2026.9.6/src/mock_vws/_httpx2_mock_server/decorators.py +396 -0
- vws_python_mock-2026.9.6/src/mock_vws/_matching.py +92 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_query_tools.py +13 -23
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_query_validators/exceptions.py +23 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_query_validators/fields_validators.py +6 -11
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_query_validators/image_validators.py +4 -10
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_query_validators/include_target_data_validators.py +4 -11
- vws_python_mock-2026.9.6/src/mock_vws/_query_validators/multipart.py +109 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_query_validators/num_results_validators.py +4 -11
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_reco_counts_web_api.py +45 -1
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_requests_mock_server/mock_web_query_api.py +1 -1
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_requests_mock_server/mock_web_services_api.py +76 -93
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_respx_mock_server/decorators.py +17 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/__init__.py +22 -35
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/auth_validators.py +7 -4
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/database_id_validators.py +3 -21
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/image_validators.py +7 -1
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/json_validators.py +29 -8
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/name_validators.py +5 -31
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/project_state_validators.py +3 -19
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/request_quota_validators.py +2 -21
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/request_rate_validators.py +3 -15
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/target_quota_validators.py +2 -15
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/target_validators.py +3 -20
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/database.py +11 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/decorators.py +103 -7
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/image_matchers.py +27 -6
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/model_target.py +35 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/reco_counts.py +21 -6
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/target.py +20 -4
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6/src/vws_python_mock.egg-info}/PKG-INFO +43 -20
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/vws_python_mock.egg-info/SOURCES.txt +12 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/vws_python_mock.egg-info/requires.txt +21 -18
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/vws_python_mock.egg-info/scm_file_list.json +12 -0
- vws_python_mock-2026.9.6/src/vws_python_mock.egg-info/scm_version.json +8 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/conftest.py +27 -2
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/fixtures/prepared_requests.py +48 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/fixtures/vuforia_backends.py +71 -4
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_add_target.py +25 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_cloud_query_failure_response.py +15 -4
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_database_summary.py +12 -3
- vws_python_mock-2026.9.6/tests/mock_vws/test_docker.py +685 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_flask_app_usage.py +291 -0
- vws_python_mock-2026.9.6/tests/mock_vws/test_httpx2_mock_usage.py +538 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_invalid_given_id.py +2 -2
- vws_python_mock-2026.9.6/tests/mock_vws/test_invalid_json.py +306 -0
- vws_python_mock-2026.9.6/tests/mock_vws/test_model_target_failure_response.py +210 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_model_target_generation_failure.py +23 -3
- vws_python_mock-2026.9.6/tests/mock_vws/test_model_target_retries.py +219 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_model_target_web_api.py +83 -14
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_query.py +241 -12
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_reco_counts_report.py +72 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_requests_mock_usage.py +457 -3
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_respx_mock_usage.py +25 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_target_summary.py +49 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_target_validators.py +1 -33
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_update_target.py +61 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_vumark_generation_failure.py +18 -4
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/utils/__init__.py +52 -6
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/utils/assertions.py +22 -3
- vws_python_mock-2026.9.6/tests/mock_vws/utils/httpx2_transports.py +156 -0
- vws_python_mock-2026.9.6/tests/mock_vws/utils/model_target_retries.py +161 -0
- vws_python_mock-2026.9.6/tests/mock_vws/utils/recognition_counts.py +70 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/utils/retries.py +17 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/uv.lock +295 -233
- vws_python_mock-2026.8.26/secrets.tar.gpg +0 -0
- vws_python_mock-2026.8.26/src/vws_python_mock.egg-info/scm_version.json +0 -8
- vws_python_mock-2026.8.26/tests/mock_vws/test_docker.py +0 -394
- vws_python_mock-2026.8.26/tests/mock_vws/test_invalid_json.py +0 -194
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.dockerignore +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.git_archival.txt +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.gitattributes +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.github/dependabot.yml +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.github/workflows/autofix.yml +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.github/workflows/dependabot-merge.yml +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.github/workflows/docker-build.yml +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.github/workflows/lint.yml +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.github/workflows/publish-site.yml +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.github/workflows/release.yml +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.gitignore +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.prettierrc +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.vale.ini +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.vscode/extensions.json +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/.vscode/settings.json +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/CODE_OF_CONDUCT.rst +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/LICENSE +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/MANIFEST.in +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/admin/__init__.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/admin/create_secrets_files.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/admin/vumark_template.svg +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/ci/__init__.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/ci/decrypt_secret.sh +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docker-bake.hcl +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/__init__.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/changelog.rst +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/ci-setup.rst +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/conf.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/httpx-example.rst +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/installation.rst +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/release-process.rst +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/unreleased.rst +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/source/versioning-and-api-stability.rst +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/docs/towncrier_template.rst.jinja +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/newsfragments/.gitkeep +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/setup.cfg +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_base64_decoding.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_constants.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_database_matchers.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_flask_server/Dockerfile +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_flask_server/__init__.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_flask_server/healthcheck.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_flask_server/vwq.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_image_opening.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_mock_common.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_model_target_web_api.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_query_validators/__init__.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_query_validators/accept_header_validators.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_query_validators/auth_validators.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_query_validators/content_length_validators.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_query_validators/content_type_validators.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_query_validators/date_validators.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_query_validators/project_state_validators.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_requests_mock_server/__init__.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_respx_mock_server/__init__.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/active_flag_validators.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/content_length_validators.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/content_type_validators.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/date_validators.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/exceptions.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/instance_id_validators.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/key_validators.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/metadata_validators.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/_services_validators/width_validators.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/cloud_query.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/database_type.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/py.typed +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/request_rate_limits.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/states.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/target_manager.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/target_raters.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/mock_vws/vumark.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/vws_python_mock.egg-info/dependency_links.txt +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/vws_python_mock.egg-info/not-zip-safe +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/src/vws_python_mock.egg-info/top_level.txt +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/__init__.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/backend_harness.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/__init__.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/fixtures/__init__.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/fixtures/credentials.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/fixtures/model_target_prepared_requests.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_authorization_header.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_content_length.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_date_header.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_delete_target.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_get_duplicates.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_get_target.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_healthcheck.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_model_target_generation_warning.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_model_target_training_allowance.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_target_list.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_target_raters.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_unexpected_json.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/test_vumark_generation_api.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/utils/too_many_requests.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/tests/mock_vws/utils/usage_test_helpers.py +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/vuforia_secrets.env.example +0 -0
- {vws_python_mock-2026.8.26 → vws_python_mock-2026.9.6}/zizmor.yml +0 -0
|
@@ -46,6 +46,7 @@ jobs:
|
|
|
46
46
|
- tests/mock_vws/test_query.py::TestTargetStatusFailed
|
|
47
47
|
- tests/mock_vws/test_query.py::TestDateFormats
|
|
48
48
|
- tests/mock_vws/test_query.py::TestInactiveProject
|
|
49
|
+
- tests/mock_vws/test_query.py::TestTruncatedBody
|
|
49
50
|
- tests/mock_vws/test_add_target.py::TestContentTypes
|
|
50
51
|
- tests/mock_vws/test_add_target.py::TestMissingData
|
|
51
52
|
- tests/mock_vws/test_add_target.py::TestWidth
|
|
@@ -85,7 +86,10 @@ jobs:
|
|
|
85
86
|
- tests/mock_vws/test_get_duplicates.py
|
|
86
87
|
- tests/mock_vws/test_get_target.py
|
|
87
88
|
- tests/mock_vws/test_invalid_given_id.py
|
|
88
|
-
- tests/mock_vws/test_invalid_json.py::TestInvalidJSON::
|
|
89
|
+
- tests/mock_vws/test_invalid_json.py::TestInvalidJSON::test_not_json
|
|
90
|
+
- tests/mock_vws/test_invalid_json.py::TestInvalidJSON::test_not_an_object
|
|
91
|
+
- tests/mock_vws/test_invalid_json.py::TestInvalidJSON::test_not_utf_8
|
|
92
|
+
- tests/mock_vws/test_invalid_json.py::TestInvalidJSON::test_empty_body
|
|
89
93
|
- tests/mock_vws/test_invalid_json.py::TestInvalidJSON::test_invalid_json_with_skewed_time
|
|
90
94
|
- tests/mock_vws/test_target_list.py
|
|
91
95
|
- tests/mock_vws/test_reco_counts_report.py
|
|
@@ -101,11 +105,13 @@ jobs:
|
|
|
101
105
|
- tests/mock_vws/test_update_target.py::TestImage::test_image_valid
|
|
102
106
|
- tests/mock_vws/test_update_target.py::TestImage::test_bad_image_format_or_color_space
|
|
103
107
|
- tests/mock_vws/test_update_target.py::TestImage::test_corrupted
|
|
108
|
+
- tests/mock_vws/test_update_target.py::TestImage::test_truncated
|
|
104
109
|
- tests/mock_vws/test_update_target.py::TestImage::test_image_too_large
|
|
105
110
|
- tests/mock_vws/test_update_target.py::TestImage::test_not_base64_encoded_processable
|
|
106
111
|
- tests/mock_vws/test_update_target.py::TestImage::test_not_base64_encoded_not_processable
|
|
107
112
|
- tests/mock_vws/test_update_target.py::TestImage::test_not_image
|
|
108
113
|
- tests/mock_vws/test_update_target.py::TestImage::test_invalid_type
|
|
114
|
+
- tests/mock_vws/test_update_target.py::TestImage::test_rating_does_not_return_to_minus_one
|
|
109
115
|
- tests/mock_vws/test_update_target.py::TestImage::test_rating_can_change
|
|
110
116
|
- tests/mock_vws/test_update_target.py::TestTargetName::test_name_valid
|
|
111
117
|
- tests/mock_vws/test_update_target.py::TestTargetName::test_name_invalid
|
|
@@ -115,16 +121,19 @@ jobs:
|
|
|
115
121
|
- tests/mock_vws/test_update_target.py::TestUpdate
|
|
116
122
|
- tests/mock_vws/test_update_target.py::TestWidth
|
|
117
123
|
- tests/mock_vws/test_update_target.py::TestInactiveProject
|
|
118
|
-
- tests/mock_vws/test_requests_mock_usage.py
|
|
119
|
-
- tests/mock_vws/test_respx_mock_usage.py
|
|
120
|
-
- tests/mock_vws/test_flask_app_usage.py
|
|
121
124
|
- tests/mock_vws/test_model_target_generation_failure.py
|
|
122
125
|
- tests/mock_vws/test_model_target_generation_warning.py
|
|
126
|
+
- tests/mock_vws/test_model_target_failure_response.py
|
|
123
127
|
- tests/mock_vws/test_model_target_training_allowance.py
|
|
124
128
|
- tests/mock_vws/test_model_target_web_api.py
|
|
129
|
+
- tests/mock_vws/test_model_target_retries.py
|
|
125
130
|
- tests/mock_vws/test_vumark_generation_api.py
|
|
126
131
|
- tests/mock_vws/test_vumark_generation_failure.py
|
|
127
132
|
- tests/mock_vws/test_target_validators.py
|
|
133
|
+
- tests/mock_vws/test_requests_mock_usage.py
|
|
134
|
+
- tests/mock_vws/test_respx_mock_usage.py
|
|
135
|
+
- tests/mock_vws/test_httpx2_mock_usage.py
|
|
136
|
+
- tests/mock_vws/test_flask_app_usage.py
|
|
128
137
|
- tests/mock_vws/test_healthcheck.py
|
|
129
138
|
- tests/mock_vws/test_docker.py
|
|
130
139
|
- README.rst
|
|
@@ -151,8 +160,11 @@ jobs:
|
|
|
151
160
|
# See the "CI Setup" document for details of how this was set up.
|
|
152
161
|
ci/decrypt_secret.sh
|
|
153
162
|
tar xvf "${HOME}"/secrets/secrets.tar
|
|
154
|
-
# Matrix jobs are 0-based; only secrets 0-99 exist
|
|
155
|
-
#
|
|
163
|
+
# Matrix jobs are 0-based; only secrets 0-99 exist, so patterns
|
|
164
|
+
# past index 99 wrap and share a database with an earlier job.
|
|
165
|
+
# The ci_pattern list is therefore ordered so that every pattern
|
|
166
|
+
# from index 100 on is one which never parametrises over the Real
|
|
167
|
+
# Vuforia backend, and so never uses these credentials.
|
|
156
168
|
SECRET_INDEX=$((JOB_INDEX % 100))
|
|
157
169
|
cp "./ci_secrets/vuforia_secrets_${SECRET_INDEX}.env" ./vuforia_secrets.env
|
|
158
170
|
|
|
@@ -3,6 +3,41 @@ Changelog
|
|
|
3
3
|
|
|
4
4
|
.. towncrier release notes start
|
|
5
5
|
|
|
6
|
+
2026.09.06
|
|
7
|
+
----------
|
|
8
|
+
|
|
9
|
+
- Add ``MockVWS.set_target_recognition_counts`` and a matching target manager endpoint for the Flask and Docker mock, for setting the recognition counts which the target summary report and the reco counts report show for a target.
|
|
10
|
+
A reco counts report now has a row for each target with recognitions in the requested month, and the recognition counts and recognition threshold of a cloud database are kept when the database is created in the Flask and Docker mock.
|
|
11
|
+
|
|
12
|
+
- Order Query API and ``GET /duplicates/{target_id}`` results by match score, with the best match first, as the real Query API does.
|
|
13
|
+
Matches with equal scores keep the existing upload date and then target ID order.
|
|
14
|
+
|
|
15
|
+
Image matchers now return a score, or ``None`` for no match, rather than a ``bool``.
|
|
16
|
+
``StructuralSimilarityMatcher`` returns the images' SSIM score, and ``ExactMatcher`` gives every match the same score.
|
|
17
|
+
A custom ``query_match_checker`` or ``duplicate_match_checker`` which returns a ``bool`` now raises a ``TypeError``; return a score, such as ``1.0``, for a match and ``None`` for no match instead.
|
|
18
|
+
The mock's ranking is its matcher's opinion, not Vuforia's proprietary one, so the mock's order still need not agree with the real Query API's order.
|
|
19
|
+
|
|
20
|
+
- Respond, rather than failing to respond, to a request body which is not UTF-8 and to a query request whose ``multipart/form-data`` body ends before its closing boundary.
|
|
21
|
+
|
|
22
|
+
- Return a ``400`` response which names the offending field and the accepted values, rather than a ``500`` response, when the target manager container is given an invalid cloud database or VuMark database to create.
|
|
23
|
+
Return a ``404`` response, rather than a ``500`` response, when the target manager container is asked to add a target to a database which does not exist.
|
|
24
|
+
|
|
25
|
+
- Say which claims about the real Vuforia Web Services the mock has not had checked against them. :doc:`unverified-behavior` lists each such claim and what would verify it, and ``differences-to-vws`` marks the unverified assumptions among its deliberate differences.
|
|
26
|
+
|
|
27
|
+
- Return a ``BadImage`` response, rather than failing to respond, when an image given to the Target API is truncated before the end of its image data.
|
|
28
|
+
|
|
29
|
+
- ``MockVWS`` now intercepts ``httpx2`` requests, synchronous and asynchronous, alongside ``requests`` and ``httpx``. The ``httpx2`` path uses native ``httpx2`` requests and responses, and does not need ``httpx2.alias_httpx()``.
|
|
30
|
+
|
|
31
|
+
- Fix nested ``MockVWS`` instances on the ``httpx`` backend: an inner mock is now the only one which answers while it is running, matching the ``requests`` and ``httpx2`` backends. Previously the outer mock kept answering and requests to the inner mock's URL were refused.
|
|
32
|
+
|
|
33
|
+
- Return a response, rather than raising an uncaught ``JSONDecodeError``, when an empty body is given to a VWS endpoint which takes a JSON body.
|
|
34
|
+
As real Vuforia does, ``POST /targets`` and ``PUT /targets/<target_id>`` now return a 500 ``Fail`` response, the reco counts report endpoint returns a 400 ``Fail`` response, and the VuMark instance generation endpoint returns a 400 ``BadRequest`` response.
|
|
35
|
+
|
|
36
|
+
2026.08.26.1
|
|
37
|
+
------------
|
|
38
|
+
|
|
39
|
+
- Add configurable Model Target HTTP failure responses for selected dataset request phases.
|
|
40
|
+
|
|
6
41
|
2026.08.26
|
|
7
42
|
----------
|
|
8
43
|
|
{vws_python_mock-2026.8.26/src/vws_python_mock.egg-info → vws_python_mock-2026.9.6}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vws-python-mock
|
|
3
|
-
Version: 2026.
|
|
3
|
+
Version: 2026.9.6
|
|
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
|
|
@@ -20,9 +20,11 @@ License-File: LICENSE
|
|
|
20
20
|
Requires-Dist: beartype>=0.22.9
|
|
21
21
|
Requires-Dist: flask>=3.0.3
|
|
22
22
|
Requires-Dist: httpx>=0.27.0
|
|
23
|
+
Requires-Dist: httpx2>=2.12
|
|
23
24
|
Requires-Dist: numpy>=2.4.4
|
|
24
25
|
Requires-Dist: opencv-contrib-python-headless>=5.0.0.93
|
|
25
26
|
Requires-Dist: pillow>=12.2.0
|
|
27
|
+
Requires-Dist: pydantic>=2.6
|
|
26
28
|
Requires-Dist: pydantic-settings>=2.6.1
|
|
27
29
|
Requires-Dist: pyteenybrisque>=0.1.1
|
|
28
30
|
Requires-Dist: requests>=2.32.3
|
|
@@ -35,37 +37,38 @@ Provides-Extra: dev
|
|
|
35
37
|
Requires-Dist: actionlint-py==1.7.12.24; extra == "dev"
|
|
36
38
|
Requires-Dist: check-manifest==0.51; extra == "dev"
|
|
37
39
|
Requires-Dist: check-wheel-contents==0.6.3; extra == "dev"
|
|
38
|
-
Requires-Dist: coverage==7.
|
|
40
|
+
Requires-Dist: coverage==7.16.0; extra == "dev"
|
|
39
41
|
Requires-Dist: deptry==0.25.1; extra == "dev"
|
|
40
42
|
Requires-Dist: dirty-equals==0.11; extra == "dev"
|
|
41
43
|
Requires-Dist: doc8==2.0.0; extra == "dev"
|
|
42
|
-
Requires-Dist: doccmd==2026.8.
|
|
44
|
+
Requires-Dist: doccmd==2026.8.30.1; extra == "dev"
|
|
43
45
|
Requires-Dist: docker==7.2.0; extra == "dev"
|
|
44
46
|
Requires-Dist: freezegun==1.5.5; extra == "dev"
|
|
45
47
|
Requires-Dist: furo==2025.12.19; extra == "dev"
|
|
48
|
+
Requires-Dist: hadolint-coatl==2.15.1; extra == "dev"
|
|
46
49
|
Requires-Dist: interrogate==1.7.0; extra == "dev"
|
|
47
50
|
Requires-Dist: mypy[faster-cache]==2.3.1; extra == "dev"
|
|
48
|
-
Requires-Dist: mypy-strict-kwargs==2026.
|
|
49
|
-
Requires-Dist: no-defaults==2.
|
|
50
|
-
Requires-Dist: prek==0.
|
|
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"
|
|
51
54
|
Requires-Dist: pydocstringformatter==1.0.0; extra == "dev"
|
|
52
55
|
Requires-Dist: pydocstyle==6.3; extra == "dev"
|
|
53
|
-
Requires-Dist: pylint[spelling]==4.0.
|
|
56
|
+
Requires-Dist: pylint[spelling]==4.0.8; extra == "dev"
|
|
54
57
|
Requires-Dist: pylint-per-file-ignores==3.2.1; extra == "dev"
|
|
55
|
-
Requires-Dist: pyproject-fmt==2.
|
|
58
|
+
Requires-Dist: pyproject-fmt==2.29.2; extra == "dev"
|
|
56
59
|
Requires-Dist: pyrefly==1.2.0; extra == "dev"
|
|
57
60
|
Requires-Dist: pyright==1.1.411; extra == "dev"
|
|
58
61
|
Requires-Dist: pyroma==5.0.1; extra == "dev"
|
|
59
62
|
Requires-Dist: pytest==9.1.1; extra == "dev"
|
|
60
63
|
Requires-Dist: pytest-beartype-tests==2026.8.16; extra == "dev"
|
|
61
|
-
Requires-Dist: pytest-partition-check==2026.8.
|
|
64
|
+
Requires-Dist: pytest-partition-check==2026.8.23; extra == "dev"
|
|
62
65
|
Requires-Dist: pytest-retry==1.7.0; extra == "dev"
|
|
63
66
|
Requires-Dist: pytest-xdist==3.8.0; extra == "dev"
|
|
64
67
|
Requires-Dist: pyyaml==6.0.3; extra == "dev"
|
|
65
68
|
Requires-Dist: requests-mock-flask==2026.8.16; extra == "dev"
|
|
66
|
-
Requires-Dist: ruff==0.16.
|
|
69
|
+
Requires-Dist: ruff==0.16.5; extra == "dev"
|
|
67
70
|
Requires-Dist: shellcheck-py==0.11.0.1; extra == "dev"
|
|
68
|
-
Requires-Dist: shfmt-py==4.
|
|
71
|
+
Requires-Dist: shfmt-py==4.1.0; extra == "dev"
|
|
69
72
|
Requires-Dist: sphinx==9.1.0; extra == "dev"
|
|
70
73
|
Requires-Dist: sphinx-copybutton==0.5.2; extra == "dev"
|
|
71
74
|
Requires-Dist: sphinx-lint==1.0.2; extra == "dev"
|
|
@@ -76,22 +79,22 @@ Requires-Dist: sphinx-toolbox==4.3.0; extra == "dev"
|
|
|
76
79
|
Requires-Dist: sphinxcontrib-httpdomain==2.0.0; extra == "dev"
|
|
77
80
|
Requires-Dist: sphinxcontrib-spelling==8.0.2; extra == "dev"
|
|
78
81
|
Requires-Dist: sphinxcontrib-towncrier==0.5.0a0; extra == "dev"
|
|
79
|
-
Requires-Dist: strict-kwargs==2026.8.
|
|
82
|
+
Requires-Dist: strict-kwargs==2026.8.28.post2; extra == "dev"
|
|
80
83
|
Requires-Dist: sybil==10.1.0; extra == "dev"
|
|
81
84
|
Requires-Dist: tenacity==9.1.4; extra == "dev"
|
|
82
85
|
Requires-Dist: towncrier==25.8.0; extra == "dev"
|
|
83
|
-
Requires-Dist: ty==0.0.
|
|
84
|
-
Requires-Dist: types-docker==7.2.0.
|
|
86
|
+
Requires-Dist: ty==0.0.77; extra == "dev"
|
|
87
|
+
Requires-Dist: types-docker==7.2.0.20260827; extra == "dev"
|
|
85
88
|
Requires-Dist: types-pyyaml==6.0.12.20260815; extra == "dev"
|
|
86
89
|
Requires-Dist: types-requests==2.33.0.20260712; extra == "dev"
|
|
87
90
|
Requires-Dist: urllib3==2.7.0; extra == "dev"
|
|
88
|
-
Requires-Dist: vale==3.
|
|
91
|
+
Requires-Dist: vale==3.19.0.0; extra == "dev"
|
|
89
92
|
Requires-Dist: vulture==2.16; extra == "dev"
|
|
90
|
-
Requires-Dist: vws-python==2026.8.
|
|
91
|
-
Requires-Dist: vws-test-fixtures==2026.8.
|
|
92
|
-
Requires-Dist: vws-web-tools==2026.8.
|
|
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"
|
|
93
96
|
Requires-Dist: yamlfix==1.19.1; extra == "dev"
|
|
94
|
-
Requires-Dist: zizmor==1.
|
|
97
|
+
Requires-Dist: zizmor==1.30.0; extra == "dev"
|
|
95
98
|
Provides-Extra: release
|
|
96
99
|
Requires-Dist: check-wheel-contents==0.6.3; extra == "release"
|
|
97
100
|
Requires-Dist: towncrier==25.8.0; extra == "release"
|
|
@@ -110,7 +113,7 @@ Mock for the Vuforia Web Services (VWS) API, the Vuforia Web Query API, and the
|
|
|
110
113
|
Mocking calls made to Vuforia
|
|
111
114
|
------------------------------
|
|
112
115
|
|
|
113
|
-
``MockVWS`` intercepts requests made with `requests`_ or `
|
|
116
|
+
``MockVWS`` intercepts requests made with `requests`_, `httpx`_ or `HTTPX2`_.
|
|
114
117
|
|
|
115
118
|
.. code-block:: shell
|
|
116
119
|
|
|
@@ -150,10 +153,30 @@ This requires Python |minimum-python-version|\+.
|
|
|
150
153
|
# This will use the Vuforia mock.
|
|
151
154
|
httpx.get(url="https://vws.vuforia.com/summary", timeout=30)
|
|
152
155
|
|
|
156
|
+
``MockVWS`` also intercepts `HTTPX2`_ requests, with no need for ``httpx2.alias_httpx()``:
|
|
157
|
+
|
|
158
|
+
.. code-block:: python
|
|
159
|
+
|
|
160
|
+
"""Make a request to the Vuforia Web Services API mock using httpx2."""
|
|
161
|
+
|
|
162
|
+
import httpx2
|
|
163
|
+
|
|
164
|
+
from mock_vws import MockVWS
|
|
165
|
+
from mock_vws.database import CloudDatabase
|
|
166
|
+
|
|
167
|
+
with MockVWS() as mock:
|
|
168
|
+
database = CloudDatabase()
|
|
169
|
+
mock.add_cloud_database(cloud_database=database)
|
|
170
|
+
# This will use the Vuforia mock.
|
|
171
|
+
httpx2.get(url="https://vws.vuforia.com/summary", timeout=30)
|
|
172
|
+
|
|
173
|
+
Asynchronous ``httpx`` and `HTTPX2`_ clients are intercepted as well.
|
|
174
|
+
|
|
153
175
|
By default, an exception will be raised if any requests to unmocked addresses are made.
|
|
154
176
|
|
|
155
177
|
.. _requests: https://pypi.org/project/requests/
|
|
156
178
|
.. _httpx: https://pypi.org/project/httpx/
|
|
179
|
+
.. _HTTPX2: https://httpx2.pydantic.dev/
|
|
157
180
|
|
|
158
181
|
Using Docker to mock calls to Vuforia from any language
|
|
159
182
|
-------------------------------------------------------
|
|
@@ -11,7 +11,7 @@ Mock for the Vuforia Web Services (VWS) API, the Vuforia Web Query API, and the
|
|
|
11
11
|
Mocking calls made to Vuforia
|
|
12
12
|
------------------------------
|
|
13
13
|
|
|
14
|
-
``MockVWS`` intercepts requests made with `requests`_ or `
|
|
14
|
+
``MockVWS`` intercepts requests made with `requests`_, `httpx`_ or `HTTPX2`_.
|
|
15
15
|
|
|
16
16
|
.. code-block:: shell
|
|
17
17
|
|
|
@@ -51,10 +51,30 @@ This requires Python |minimum-python-version|\+.
|
|
|
51
51
|
# This will use the Vuforia mock.
|
|
52
52
|
httpx.get(url="https://vws.vuforia.com/summary", timeout=30)
|
|
53
53
|
|
|
54
|
+
``MockVWS`` also intercepts `HTTPX2`_ requests, with no need for ``httpx2.alias_httpx()``:
|
|
55
|
+
|
|
56
|
+
.. code-block:: python
|
|
57
|
+
|
|
58
|
+
"""Make a request to the Vuforia Web Services API mock using httpx2."""
|
|
59
|
+
|
|
60
|
+
import httpx2
|
|
61
|
+
|
|
62
|
+
from mock_vws import MockVWS
|
|
63
|
+
from mock_vws.database import CloudDatabase
|
|
64
|
+
|
|
65
|
+
with MockVWS() as mock:
|
|
66
|
+
database = CloudDatabase()
|
|
67
|
+
mock.add_cloud_database(cloud_database=database)
|
|
68
|
+
# This will use the Vuforia mock.
|
|
69
|
+
httpx2.get(url="https://vws.vuforia.com/summary", timeout=30)
|
|
70
|
+
|
|
71
|
+
Asynchronous ``httpx`` and `HTTPX2`_ clients are intercepted as well.
|
|
72
|
+
|
|
54
73
|
By default, an exception will be raised if any requests to unmocked addresses are made.
|
|
55
74
|
|
|
56
75
|
.. _requests: https://pypi.org/project/requests/
|
|
57
76
|
.. _httpx: https://pypi.org/project/httpx/
|
|
77
|
+
.. _HTTPX2: https://httpx2.pydantic.dev/
|
|
58
78
|
|
|
59
79
|
Using Docker to mock calls to Vuforia from any language
|
|
60
80
|
-------------------------------------------------------
|
|
@@ -28,5 +28,18 @@ def pytest_set_filtered_exceptions() -> tuple[type[Exception], ...]:
|
|
|
28
28
|
|
|
29
29
|
This is for ``pytest-retry``.
|
|
30
30
|
The configuration for retries is in ``pyproject.toml``.
|
|
31
|
+
|
|
32
|
+
``pytest-retry`` treats this as an allowlist: a test which fails
|
|
33
|
+
with anything else is not retried, whatever ``retries`` is set to.
|
|
34
|
+
That is deliberate, because widening it to ``AssertionError`` would
|
|
35
|
+
retry every failing assertion in the suite ten times.
|
|
36
|
+
|
|
37
|
+
The consequence is that a test which turns a response into an
|
|
38
|
+
``AssertionError`` itself, rather than letting an exception out of
|
|
39
|
+
the client, is never retried here. The Model Target Web API tests
|
|
40
|
+
assert on ``requests.Response`` objects and so are in that
|
|
41
|
+
position; they get their own, narrower policy, applied before the
|
|
42
|
+
assertion, in
|
|
43
|
+
:py:mod:`tests.mock_vws.utils.model_target_retries`.
|
|
31
44
|
"""
|
|
32
45
|
return TRANSIENT_VWS_EXCEPTIONS
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
``MockVWS`` intercepts requests to Vuforia made with `requests`_ or `
|
|
1
|
+
``MockVWS`` intercepts requests to Vuforia made with `requests`_, `httpx`_ or `HTTPX2`_.
|
|
2
2
|
|
|
3
3
|
.. code-block:: python
|
|
4
4
|
|
|
@@ -47,3 +47,4 @@ See :ref:`mock-api-reference` for details of what can be changed and how.
|
|
|
47
47
|
|
|
48
48
|
.. _requests: https://pypi.org/project/requests/
|
|
49
49
|
.. _httpx: https://pypi.org/project/httpx/
|
|
50
|
+
.. _HTTPX2: https://httpx2.pydantic.dev/
|
|
@@ -124,6 +124,42 @@ Use the following custom ``pytest`` options to skip some tests:
|
|
|
124
124
|
--skip-docker_build_tests
|
|
125
125
|
Skip tests for building Docker images
|
|
126
126
|
|
|
127
|
+
Verifying signed Model Target requests
|
|
128
|
+
--------------------------------------
|
|
129
|
+
|
|
130
|
+
Creating an advanced Model Target dataset with a state-based configuration or a standard dataset with inline CAD data is a "signed" request: the real Vuforia signs the trained dataset, and each signing consumes the account's Model Target training allowance.
|
|
131
|
+
The allowance is small (roughly 20 signings), it is shared by every CI job and every concurrent run, and it cannot be raised or reset.
|
|
132
|
+
Verifying signed requests on every run exhausted the allowance within hours and then made every CI run fail with ``TRAINING_ALLOWANCE_EXCEEDED``.
|
|
133
|
+
|
|
134
|
+
The signed test cases therefore run against the mock backends on every run, but are skipped against the real Vuforia by default.
|
|
135
|
+
To verify them against the real Vuforia, for example after the allowance has recovered, opt in with:
|
|
136
|
+
|
|
137
|
+
.. code-block:: text
|
|
138
|
+
|
|
139
|
+
--verify-model-target-signing
|
|
140
|
+
Run signed Model Target dataset tests against
|
|
141
|
+
the real Vuforia
|
|
142
|
+
|
|
143
|
+
The equivalent unsigned requests (a standard dataset, or an advanced dataset without a state-based configuration) are far cheaper and are verified against the real Vuforia on every run.
|
|
144
|
+
With enough traffic even unsigned dataset creation can be rejected with ``TRAINING_ALLOWANCE_EXCEEDED``; an unexpected allowance rejection is reported as an expected failure rather than a test failure, and the affected tests pass again automatically once the allowance recovers.
|
|
145
|
+
|
|
146
|
+
Retrying transient real Model Target failures
|
|
147
|
+
---------------------------------------------
|
|
148
|
+
|
|
149
|
+
The load balancer in front of the real Model Target Web API sometimes answers a perfectly good request with a gateway error, or drops the connection.
|
|
150
|
+
That is nothing to do with the contract under test, and a rerun of the same job passes, so a small number of retries is applied to requests which are safe to repeat.
|
|
151
|
+
|
|
152
|
+
The policy is in ``tests/mock_vws/utils/model_target_retries.py``.
|
|
153
|
+
It applies only while a test is running against the real Model Target backend, and only to ``GET`` requests: repeating a dataset creation can create a second dataset and can consume the account's Model Target training allowance, so mutating requests are sent exactly once.
|
|
154
|
+
Three attempts are made, with an exponential backoff with jitter of up to ten seconds, using ``tenacity``.
|
|
155
|
+
A request which still fails transiently on the last attempt is not hidden: the response is returned as it is, so the assertion reports the real status and body.
|
|
156
|
+
|
|
157
|
+
The mock backends are unaffected, so tests which configure a mock to answer with a 5xx still get that response immediately.
|
|
158
|
+
|
|
159
|
+
This is separate from the repository-wide ``pytest-retry`` configuration, which retries a test only when it fails with one of the exception types returned by ``pytest_set_filtered_exceptions`` in the top level ``conftest.py``.
|
|
160
|
+
The Model Target tests assert on ``requests.Response`` objects, so a gateway error reaches ``pytest-retry`` as an ``AssertionError`` and is never retried by it.
|
|
161
|
+
Widening that allowlist would retry every failing assertion in the suite, which is why the Model Target policy sits in the request helper instead, before the assertion.
|
|
162
|
+
|
|
127
163
|
Documentation
|
|
128
164
|
-------------
|
|
129
165
|
|
|
@@ -154,6 +190,8 @@ This is because the target goes into a processing state.
|
|
|
154
190
|
``image`` is required for ``POST /targets``, but it is documented as not mandatory.
|
|
155
191
|
|
|
156
192
|
The ``tracking_rating`` returned by ``GET /targets/<target_id>`` can be -1.
|
|
193
|
+
This happens only while a newly uploaded target is processing, and it lasts for about a second.
|
|
194
|
+
Updating a target returns it to the processing state but does not return the rating to -1; the new image's rating is reported straight away.
|
|
157
195
|
|
|
158
196
|
The database summary from ``GET /summary`` has multiple undocumented return fields.
|
|
159
197
|
|
|
@@ -4,12 +4,21 @@ Differences between the mock and the real Vuforia Web Services
|
|
|
4
4
|
The mock attempts to be realistic, but it was built without access to the source code of the original API.
|
|
5
5
|
Please report any issues `here <https://github.com/VWS-Python/vws-python-mock/issues>`__.
|
|
6
6
|
|
|
7
|
+
This document mixes three kinds of statement, and it says which is which:
|
|
8
|
+
|
|
9
|
+
* A deliberate difference, where the mock does something else on purpose.
|
|
10
|
+
The image matchers are one.
|
|
11
|
+
* Behavior which the mock does not implement.
|
|
12
|
+
* An unverified assumption, where the mock follows Vuforia's documentation and nobody has checked that the documentation is accurate.
|
|
13
|
+
Each of these carries a note pointing at its entry in :doc:`unverified-behavior`, which says what would verify it.
|
|
14
|
+
These are the ones which can bite: the mock passes its tests, your tests pass, and the divergence appears in production.
|
|
15
|
+
|
|
7
16
|
Image matching
|
|
8
17
|
--------------
|
|
9
18
|
|
|
10
19
|
Vuforia's image matching is proprietary and we do not intend to accurately copy it.
|
|
11
20
|
Instead, we aim for simple algorithms which are fast and are good enough for testing purposes.
|
|
12
|
-
The image
|
|
21
|
+
The image matchers are configurable, using :paramref:`~mock_vws.MockVWS.query_match_checker` and :paramref:`~mock_vws.MockVWS.duplicate_match_checker`.
|
|
13
22
|
|
|
14
23
|
Speed and summary accuracy
|
|
15
24
|
--------------------------
|
|
@@ -23,7 +32,22 @@ In the real Vuforia Web Services, the processing stage takes varying lengths of
|
|
|
23
32
|
The database summary in the real Vuforia Web Services takes some time to account for images and recognitions.
|
|
24
33
|
Sometimes the real summary skips image states such as the processing state.
|
|
25
34
|
The mock is accurate immediately with regards to images.
|
|
35
|
+
|
|
26
36
|
The mock does not count recognitions.
|
|
37
|
+
Real Vuforia's recognition counts lag behind its queries by far longer than a
|
|
38
|
+
test runs, so a query in the mock does not change any count either.
|
|
39
|
+
Set the counts you want to see instead:
|
|
40
|
+
:paramref:`mock_vws.database.CloudDatabase.total_recos`,
|
|
41
|
+
:paramref:`mock_vws.database.CloudDatabase.current_month_recos`,
|
|
42
|
+
:paramref:`mock_vws.database.CloudDatabase.previous_month_recos` and
|
|
43
|
+
:paramref:`mock_vws.database.CloudDatabase.reco_threshold` for the database
|
|
44
|
+
summary report, and
|
|
45
|
+
:meth:`mock_vws.MockVWS.set_target_recognition_counts` for the counts of a
|
|
46
|
+
target, which the target summary report and the reco counts report show.
|
|
47
|
+
Targets are created by API requests, so their counts are set after the target
|
|
48
|
+
is created.
|
|
49
|
+
The Flask and Docker mock has an equivalent target manager endpoint, described
|
|
50
|
+
in :doc:`docker`.
|
|
27
51
|
|
|
28
52
|
Image quality and ratings
|
|
29
53
|
-------------------------
|
|
@@ -32,6 +56,10 @@ Targets are assigned a rating between 0 and 5 of how good they are for tracking
|
|
|
32
56
|
In the mock this is calculated from the image quality, differently to how Vuforia does this.
|
|
33
57
|
This is customizable with the :paramref:`~mock_vws.MockVWS.target_tracking_rater` parameter.
|
|
34
58
|
|
|
59
|
+
A target which is being processed after an upload reports a rating of -1 for a short time, and then the image's rating, while it is still processing.
|
|
60
|
+
An update does not start a new -1 window: it returns the target to the processing state and reports the new image's rating straight away.
|
|
61
|
+
The mock does the same, in proportion to :paramref:`~mock_vws.MockVWS.processing_time_seconds` rather than to the real timings.
|
|
62
|
+
|
|
35
63
|
Image targets which are not suited to detection are given 'failed' statuses.
|
|
36
64
|
The criteria for these images is not defined by the Vuforia documentation.
|
|
37
65
|
The mock is more forgiving than the real Vuforia Web Services.
|
|
@@ -41,14 +69,14 @@ Result ordering
|
|
|
41
69
|
---------------
|
|
42
70
|
|
|
43
71
|
The real Query API orders results by match score, with the best match first.
|
|
44
|
-
The mock
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Do not rely on the first result of a mock query being the best match.
|
|
72
|
+
The mock does the same, with the score which its image matcher gives each match.
|
|
73
|
+
That matcher is not Vuforia's, so the mock's ranking is not Vuforia's ranking.
|
|
74
|
+
The order decides which results survive ``max_num_results``, and which result gets target data with ``include_target_data=top``.
|
|
48
75
|
|
|
49
|
-
|
|
76
|
+
Matches with the same score, which is every match of :class:`~mock_vws.image_matchers.ExactMatcher`, are ordered by upload date and then by target ID.
|
|
77
|
+
The real Vuforia Web Services give no such guarantee.
|
|
50
78
|
|
|
51
|
-
``GET /
|
|
79
|
+
``GET /duplicates/{target_id}`` is ordered by match score too, and ``GET /targets`` by upload date and then by target ID.
|
|
52
80
|
The real Vuforia Web Services do not document an order for those endpoints.
|
|
53
81
|
|
|
54
82
|
Matching recently deleted targets
|
|
@@ -111,7 +139,11 @@ NGINX Error cases
|
|
|
111
139
|
|
|
112
140
|
Vuforia uses NGINX.
|
|
113
141
|
This has error handling which is not duplicated in the mock.
|
|
114
|
-
For example, Vuforia
|
|
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.
|
|
143
|
+
|
|
144
|
+
.. admonition:: Unverified assumption
|
|
145
|
+
|
|
146
|
+
:ref:`unverified-nginx-oversized-header-or-cookie`
|
|
115
147
|
|
|
116
148
|
Result codes
|
|
117
149
|
------------
|
|
@@ -127,11 +159,12 @@ These are:
|
|
|
127
159
|
Request quota exhaustion
|
|
128
160
|
------------------------
|
|
129
161
|
|
|
130
|
-
The mock returns ``RequestQuotaReached`` when a
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
162
|
+
The mock returns ``RequestQuotaReached`` when a :class:`mock_vws.database.CloudDatabase` is created with ``request_quota=0``.
|
|
163
|
+
This behavior follows the public Vuforia documentation.
|
|
164
|
+
|
|
165
|
+
.. admonition:: Unverified assumption
|
|
166
|
+
|
|
167
|
+
:ref:`unverified-request-quota-exhaustion`
|
|
135
168
|
|
|
136
169
|
Request rate limits
|
|
137
170
|
-------------------
|
|
@@ -141,11 +174,14 @@ endpoints in general, with 45 requests per second for
|
|
|
141
174
|
``GET /targets/{target_id}``, 10 requests per second for
|
|
142
175
|
``GET /duplicates/{target_id}``, and 1 request per minute for ``GET /targets``.
|
|
143
176
|
|
|
144
|
-
The mock models these limits separately for each group of endpoints, but it
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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.
|
|
179
|
+
|
|
180
|
+
.. admonition:: Unverified assumption
|
|
181
|
+
|
|
182
|
+
:ref:`unverified-request-rate-limits`
|
|
183
|
+
|
|
184
|
+
Set ``request_rate_limits`` to
|
|
149
185
|
:data:`mock_vws.request_rate_limits.DOCUMENTED_REQUEST_RATE_LIMITS` to apply
|
|
150
186
|
the documented limits::
|
|
151
187
|
|
|
@@ -167,9 +203,11 @@ the documented limits::
|
|
|
167
203
|
VWS endpoints together, and it is tracked separately from the per-endpoint
|
|
168
204
|
limits.
|
|
169
205
|
|
|
170
|
-
Vuforia also documents that ``GET /targets`` fails for databases with more than
|
|
171
|
-
|
|
172
|
-
|
|
206
|
+
Vuforia also documents that ``GET /targets`` fails for databases with more than 1 million images, which the mock does not implement.
|
|
207
|
+
|
|
208
|
+
.. admonition:: Unverified assumption
|
|
209
|
+
|
|
210
|
+
:ref:`unverified-targets-over-one-million-images`
|
|
173
211
|
|
|
174
212
|
Configurable Cloud Query failures
|
|
175
213
|
---------------------------------
|
|
@@ -178,7 +216,7 @@ The Vuforia Cloud Query API documents failure responses with JSON, arbitrary
|
|
|
178
216
|
content, or no body. Use
|
|
179
217
|
:paramref:`mock_vws.MockVWS.cloud_query_failure_response` to make every Cloud
|
|
180
218
|
Query request return a particular documented failure shape through the
|
|
181
|
-
in-process ``requests`` and ``
|
|
219
|
+
in-process ``requests``, ``httpx`` and ``httpx2`` backends::
|
|
182
220
|
|
|
183
221
|
from mock_vws import CloudQueryFailureResponse, MockVWS
|
|
184
222
|
|
|
@@ -196,11 +234,45 @@ The configured response bypasses normal Cloud Query validation and image
|
|
|
196
234
|
matching. Omitting it preserves the normal successful-query behavior. This
|
|
197
235
|
configuration is not supported by the Flask/Docker backend.
|
|
198
236
|
|
|
237
|
+
Configurable Model Target failures
|
|
238
|
+
----------------------------------
|
|
239
|
+
|
|
240
|
+
Use :paramref:`mock_vws.MockVWS.model_target_failure_response` to return a
|
|
241
|
+
particular HTTP failure from selected Model Target dataset request phases. The
|
|
242
|
+
OAuth2 token request is still handled normally, so this exercises client
|
|
243
|
+
behavior after successful token acquisition::
|
|
244
|
+
|
|
245
|
+
from mock_vws import (
|
|
246
|
+
MockVWS,
|
|
247
|
+
ModelTargetFailureResponse,
|
|
248
|
+
ModelTargetRequest,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
failure = ModelTargetFailureResponse(
|
|
252
|
+
status_code=503,
|
|
253
|
+
headers={"Content-Type": "text/plain", "Retry-After": "10"},
|
|
254
|
+
body=b"Temporarily unavailable",
|
|
255
|
+
requests=frozenset({ModelTargetRequest.STATUS}),
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
with MockVWS(model_target_failure_response=failure):
|
|
259
|
+
# Model Target status calls return the configured response.
|
|
260
|
+
...
|
|
261
|
+
|
|
262
|
+
Omit ``requests`` to affect create, status, download, and delete requests.
|
|
263
|
+
Other phases retain their normal behavior. This configuration works with
|
|
264
|
+
the in-process ``requests``, ``httpx`` and ``httpx2`` backends and is not
|
|
265
|
+
supported by the Flask/Docker backend.
|
|
266
|
+
|
|
199
267
|
Other configurable result codes
|
|
200
268
|
-------------------------------
|
|
201
269
|
|
|
202
|
-
The mock also supports four other result codes which
|
|
203
|
-
|
|
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:
|
|
271
|
+
|
|
272
|
+
.. admonition:: Unverified assumption
|
|
273
|
+
|
|
274
|
+
:ref:`unverified-additional-result-codes`
|
|
275
|
+
|
|
204
276
|
|
|
205
277
|
* ``TargetQuotaReached`` is returned when adding a target to a
|
|
206
278
|
:class:`mock_vws.database.CloudDatabase` which already contains
|
|
@@ -318,14 +390,15 @@ Real Vuforia uses ``userId:<numeric-user-id>`` where the numeric portion is per-
|
|
|
318
390
|
Standard and advanced routes share datasets by UUID. Access to each route
|
|
319
391
|
family is separated by its corresponding OAuth scope.
|
|
320
392
|
|
|
321
|
-
Some Model Target Web API paths remain mock-only in
|
|
322
|
-
``tests/mock_vws/test_model_target_web_api.py::TestAdditionalBehaviors``.
|
|
393
|
+
Some Model Target Web API paths remain mock-only in ``tests/mock_vws/test_model_target_web_api.py::TestAdditionalBehaviors``.
|
|
323
394
|
Downloads of still-processing datasets are mock-only because exercising the path against real Vuforia would require creating a dataset on every test run; the mock drives the processing window deterministically.
|
|
324
|
-
A download request for a dataset which is not ready reports the dataset's
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
395
|
+
A download request for a dataset which is not ready reports the dataset's training status.
|
|
396
|
+
The mock reports ``not-started`` for the whole processing window, as real Vuforia does for a dataset which was just created, and ``failed`` for a dataset whose generation failed.
|
|
397
|
+
|
|
398
|
+
.. admonition:: Unverified assumption
|
|
399
|
+
|
|
400
|
+
:ref:`unverified-model-target-failed-dataset-name`
|
|
401
|
+
|
|
329
402
|
Some malformed State-Based Model Target configuration documents remain
|
|
330
403
|
mock-only because real Vuforia returns an internal server error for them.
|
|
331
404
|
|
|
@@ -334,9 +407,24 @@ Reco counts reports
|
|
|
334
407
|
|
|
335
408
|
The mock does not count recognitions, so a generated reco counts report
|
|
336
409
|
contains only the ``target_id,reco_count`` header row, ending with a carriage
|
|
337
|
-
return and a line feed.
|
|
410
|
+
return and a line feed, until recognition counts are set on targets.
|
|
338
411
|
That is what real Vuforia returns for a database with no recognitions.
|
|
339
|
-
|
|
412
|
+
|
|
413
|
+
A report for the current month has a row for each target with a non-zero
|
|
414
|
+
``current_month_recos``, and a report for the previous month has a row for
|
|
415
|
+
each target with a non-zero ``previous_month_recos``.
|
|
416
|
+
Each row ends with a carriage return and a line feed, as the header row does.
|
|
417
|
+
The mock takes the counts when the report is requested, so counts which are
|
|
418
|
+
set after that are not in that report.
|
|
419
|
+
The mock orders the rows by target ID.
|
|
420
|
+
|
|
421
|
+
.. admonition:: Unverified assumption
|
|
422
|
+
|
|
423
|
+
:ref:`unverified-reco-counts-report-row-order`
|
|
424
|
+
|
|
425
|
+
Setting recognition counts is mock-only, because real Vuforia's counts are
|
|
426
|
+
delayed for longer than a test runs, so the tests for reports with rows in
|
|
427
|
+
``tests/mock_vws/test_reco_counts_report.py`` run against the mocks only.
|
|
340
428
|
As with real Vuforia, the report is served with a ``text/plain`` content type
|
|
341
429
|
rather than a CSV one.
|
|
342
430
|
|
|
@@ -375,8 +463,10 @@ The mock's URL returns a 404 response until the report is ready, and requires
|
|
|
375
463
|
no authorization.
|
|
376
464
|
The lack of authorization matches real Vuforia, whose URL carries its own
|
|
377
465
|
signature.
|
|
378
|
-
|
|
379
|
-
|
|
466
|
+
|
|
467
|
+
.. admonition:: Unverified assumption
|
|
468
|
+
|
|
469
|
+
:ref:`unverified-reco-counts-report-not-ready`
|
|
380
470
|
|
|
381
471
|
Paths which the mock does not serve
|
|
382
472
|
-----------------------------------
|
|
@@ -389,9 +479,9 @@ For any other request which it does not serve, such as ``DELETE /summary`` or
|
|
|
389
479
|
and the path of the request.
|
|
390
480
|
The Flask and Docker mock reproduces both response shapes.
|
|
391
481
|
|
|
392
|
-
The ``requests`` and ``
|
|
393
|
-
serves, so a request to any other path raises a connection error
|
|
394
|
-
giving the 404 response which real Vuforia gives.
|
|
482
|
+
The ``requests``, ``httpx`` and ``httpx2`` backends mock only the paths which
|
|
483
|
+
the mock serves, so a request to any other path raises a connection error
|
|
484
|
+
rather than giving the 404 response which real Vuforia gives.
|
|
395
485
|
|
|
396
486
|
Header cases
|
|
397
487
|
------------
|