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.
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/workflows/publish-site.yml +1 -1
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/workflows/release.yml +3 -3
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/workflows/test.yml +15 -12
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/CHANGELOG.rst +10 -0
- {vws_python_mock-2026.9.6/src/vws_python_mock.egg-info → vws_python_mock-2026.9.8}/PKG-INFO +5 -69
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/README.rst +3 -3
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/admin/create_secrets_files.py +10 -10
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/basic-example.rst +2 -2
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/contributing.rst +17 -9
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/differences-to-vws.rst +48 -15
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/httpx-example.rst +1 -1
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/httpx2-example.rst +1 -1
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/unverified-behavior.rst +16 -21
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/prek.toml +42 -42
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/pyproject.toml +38 -20
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/spelling_private_dict.txt +1 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_flask_server/Dockerfile +2 -2
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_flask_server/target_manager.py +30 -33
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_flask_server/vwq.py +3 -7
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_flask_server/vws.py +30 -30
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_httpx2_mock_server/decorators.py +65 -65
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_matching.py +1 -1
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_mock_common.py +43 -6
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_model_target_web_api.py +74 -60
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_tools.py +12 -38
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/__init__.py +54 -38
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/auth_validators.py +7 -4
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/content_length_validators.py +1 -1
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/content_type_validators.py +1 -1
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/date_validators.py +3 -1
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/exceptions.py +37 -0
- vws_python_mock-2026.9.8/src/mock_vws/_query_validators/fields_validators.py +30 -0
- vws_python_mock-2026.9.8/src/mock_vws/_query_validators/header_size_validators.py +32 -0
- vws_python_mock-2026.9.8/src/mock_vws/_query_validators/image_validators.py +119 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/include_target_data_validators.py +4 -14
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/multipart.py +32 -3
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/num_results_validators.py +4 -14
- vws_python_mock-2026.9.8/src/mock_vws/_query_validators/project_state_validators.py +28 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_reco_counts_web_api.py +6 -4
- {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
- {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
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_respx_mock_server/decorators.py +11 -21
- vws_python_mock-2026.9.8/src/mock_vws/_services_validators/__init__.py +95 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/active_flag_validators.py +6 -9
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/auth_validators.py +10 -5
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/content_length_validators.py +29 -36
- vws_python_mock-2026.9.8/src/mock_vws/_services_validators/content_type_validators.py +27 -0
- vws_python_mock-2026.9.8/src/mock_vws/_services_validators/context.py +105 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/database_id_validators.py +8 -19
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/date_validators.py +14 -13
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/exceptions.py +53 -7
- vws_python_mock-2026.9.8/src/mock_vws/_services_validators/header_size_validators.py +32 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/image_validators.py +85 -135
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/instance_id_validators.py +7 -21
- vws_python_mock-2026.9.8/src/mock_vws/_services_validators/json_validators.py +133 -0
- vws_python_mock-2026.9.8/src/mock_vws/_services_validators/key_validators.py +36 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/metadata_validators.py +12 -27
- vws_python_mock-2026.9.8/src/mock_vws/_services_validators/name_validators.py +229 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/project_state_validators.py +9 -17
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/request_quota_validators.py +12 -3
- 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
- vws_python_mock-2026.9.8/src/mock_vws/_services_validators/routes.py +391 -0
- vws_python_mock-2026.9.8/src/mock_vws/_services_validators/target_quota_validators.py +27 -0
- vws_python_mock-2026.9.8/src/mock_vws/_services_validators/target_validators.py +57 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_services_validators/width_validators.py +6 -9
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/database.py +4 -2
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/decorators.py +20 -48
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/model_target.py +7 -7
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/request_rate_limits.py +10 -4
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/target.py +6 -6
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/target_manager.py +1 -1
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8/src/vws_python_mock.egg-info}/PKG-INFO +5 -69
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/vws_python_mock.egg-info/SOURCES.txt +6 -3
- vws_python_mock-2026.9.8/src/vws_python_mock.egg-info/requires.txt +19 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/vws_python_mock.egg-info/scm_file_list.json +6 -3
- vws_python_mock-2026.9.8/src/vws_python_mock.egg-info/scm_version.json +8 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/conftest.py +6 -6
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/fixtures/model_target_prepared_requests.py +2 -2
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/fixtures/prepared_requests.py +1 -1
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/fixtures/vuforia_backends.py +60 -54
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_add_target.py +40 -40
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_authorization_header.py +9 -9
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_content_length.py +5 -5
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_database_summary.py +5 -5
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_date_header.py +1 -1
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_delete_target.py +1 -1
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_docker.py +22 -22
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_flask_app_usage.py +75 -47
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_get_duplicates.py +1 -1
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_get_target.py +3 -1
- vws_python_mock-2026.9.8/tests/mock_vws/test_header_size.py +189 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_healthcheck.py +1 -1
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_httpx2_mock_usage.py +12 -15
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_invalid_given_id.py +1 -1
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_invalid_json.py +2 -2
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_model_target_failure_response.py +11 -10
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_model_target_generation_failure.py +7 -7
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_model_target_generation_warning.py +7 -7
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_model_target_retries.py +9 -7
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_model_target_training_allowance.py +1 -1
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_model_target_web_api.py +36 -36
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_query.py +16 -16
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_reco_counts_report.py +7 -7
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_requests_mock_usage.py +109 -83
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_respx_mock_usage.py +5 -5
- vws_python_mock-2026.9.8/tests/mock_vws/test_target_list.py +181 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_target_summary.py +65 -8
- vws_python_mock-2026.9.8/tests/mock_vws/test_target_validators.py +23 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_unexpected_json.py +2 -2
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_update_target.py +11 -11
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_vumark_generation_api.py +3 -3
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/utils/__init__.py +2 -2
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/utils/assertions.py +50 -8
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/utils/model_target_retries.py +1 -1
- vws_python_mock-2026.9.8/tests/mock_vws/utils/retries.py +75 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/uv.lock +230 -198
- vws_python_mock-2026.9.6/src/mock_vws/_query_validators/fields_validators.py +0 -42
- vws_python_mock-2026.9.6/src/mock_vws/_query_validators/image_validators.py +0 -191
- vws_python_mock-2026.9.6/src/mock_vws/_query_validators/project_state_validators.py +0 -49
- vws_python_mock-2026.9.6/src/mock_vws/_services_validators/__init__.py +0 -203
- vws_python_mock-2026.9.6/src/mock_vws/_services_validators/content_type_validators.py +0 -42
- vws_python_mock-2026.9.6/src/mock_vws/_services_validators/json_validators.py +0 -96
- vws_python_mock-2026.9.6/src/mock_vws/_services_validators/key_validators.py +0 -174
- vws_python_mock-2026.9.6/src/mock_vws/_services_validators/name_validators.py +0 -209
- vws_python_mock-2026.9.6/src/mock_vws/_services_validators/target_quota_validators.py +0 -28
- vws_python_mock-2026.9.6/src/mock_vws/_services_validators/target_validators.py +0 -60
- vws_python_mock-2026.9.6/src/vws_python_mock.egg-info/requires.txt +0 -85
- vws_python_mock-2026.9.6/src/vws_python_mock.egg-info/scm_version.json +0 -8
- vws_python_mock-2026.9.6/tests/backend_harness.py +0 -159
- vws_python_mock-2026.9.6/tests/mock_vws/test_target_list.py +0 -72
- vws_python_mock-2026.9.6/tests/mock_vws/test_target_validators.py +0 -54
- vws_python_mock-2026.9.6/tests/mock_vws/utils/httpx2_transports.py +0 -156
- vws_python_mock-2026.9.6/tests/mock_vws/utils/retries.py +0 -41
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.dockerignore +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.git_archival.txt +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.gitattributes +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/dependabot.yml +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/workflows/autofix.yml +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/workflows/dependabot-merge.yml +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/workflows/docker-build.yml +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.github/workflows/lint.yml +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.gitignore +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.prettierrc +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.vale.ini +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.vscode/extensions.json +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/.vscode/settings.json +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/CODE_OF_CONDUCT.rst +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/LICENSE +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/MANIFEST.in +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/admin/__init__.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/admin/vumark_template.svg +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/ci/__init__.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/ci/decrypt_secret.sh +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/conftest.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docker-bake.hcl +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/__init__.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/changelog.rst +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/ci-setup.rst +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/conf.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/docker.rst +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/getting-started.rst +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/index.rst +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/installation.rst +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/mock-api-reference.rst +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/release-process.rst +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/unreleased.rst +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/source/versioning-and-api-stability.rst +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/docs/towncrier_template.rst.jinja +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/newsfragments/.gitkeep +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/secrets.tar.gpg +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/setup.cfg +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/__init__.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_base64_decoding.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_constants.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_database_matchers.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_flask_server/__init__.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_flask_server/healthcheck.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_httpx2_mock_server/__init__.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_image_opening.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_query_validators/accept_header_validators.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_requests_mock_server/__init__.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/_respx_mock_server/__init__.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/cloud_query.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/database_type.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/image_matchers.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/py.typed +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/reco_counts.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/states.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/target_raters.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/mock_vws/vumark.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/vws_python_mock.egg-info/dependency_links.txt +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/vws_python_mock.egg-info/not-zip-safe +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/src/vws_python_mock.egg-info/top_level.txt +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/__init__.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/__init__.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/fixtures/__init__.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/fixtures/credentials.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_cloud_query_failure_response.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_target_raters.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/test_vumark_generation_failure.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/utils/recognition_counts.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/utils/too_many_requests.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/tests/mock_vws/utils/usage_test_helpers.py +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/vuforia_secrets.env.example +0 -0
- {vws_python_mock-2026.9.6 → vws_python_mock-2026.9.8}/zizmor.yml +0 -0
|
@@ -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 --
|
|
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 --
|
|
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 --
|
|
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 --
|
|
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 --
|
|
240
|
+
uv run --group=dev \
|
|
238
241
|
coverage run -m pytest \
|
|
239
|
-
--skip-
|
|
240
|
-
--skip-
|
|
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 --
|
|
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 --
|
|
348
|
-
uv run --
|
|
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 --
|
|
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 --
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
121
|
-
--skip-mock
|
|
122
|
-
--skip-
|
|
123
|
-
Skip tests
|
|
124
|
-
|
|
125
|
-
|
|
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 --
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
199
|
-
#
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
41
|
+
.. _unverified-project-suspended:
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
A suspended database
|
|
44
|
+
--------------------
|
|
45
45
|
|
|
46
|
-
:Category:
|
|
46
|
+
:Category: temporarily-unverifiable
|
|
47
47
|
:API: VWS Target API
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
``
|
|
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
|