vws-python-mock 2026.8.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. mock_vws/__init__.py +9 -0
  2. mock_vws/_base64_decoding.py +35 -0
  3. mock_vws/_constants.py +84 -0
  4. mock_vws/_database_matchers.py +107 -0
  5. mock_vws/_flask_server/Dockerfile +32 -0
  6. mock_vws/_flask_server/__init__.py +1 -0
  7. mock_vws/_flask_server/healthcheck.py +31 -0
  8. mock_vws/_flask_server/target_manager.py +447 -0
  9. mock_vws/_flask_server/vwq.py +173 -0
  10. mock_vws/_flask_server/vws.py +954 -0
  11. mock_vws/_mock_common.py +75 -0
  12. mock_vws/_model_target_web_api.py +486 -0
  13. mock_vws/_query_tools.py +136 -0
  14. mock_vws/_query_validators/__init__.py +128 -0
  15. mock_vws/_query_validators/accept_header_validators.py +31 -0
  16. mock_vws/_query_validators/auth_validators.py +143 -0
  17. mock_vws/_query_validators/content_length_validators.py +90 -0
  18. mock_vws/_query_validators/content_type_validators.py +65 -0
  19. mock_vws/_query_validators/date_validators.py +110 -0
  20. mock_vws/_query_validators/exceptions.py +769 -0
  21. mock_vws/_query_validators/fields_validators.py +47 -0
  22. mock_vws/_query_validators/image_validators.py +197 -0
  23. mock_vws/_query_validators/include_target_data_validators.py +51 -0
  24. mock_vws/_query_validators/num_results_validators.py +64 -0
  25. mock_vws/_query_validators/project_state_validators.py +49 -0
  26. mock_vws/_requests_mock_server/__init__.py +1 -0
  27. mock_vws/_requests_mock_server/decorators.py +275 -0
  28. mock_vws/_requests_mock_server/mock_web_query_api.py +139 -0
  29. mock_vws/_requests_mock_server/mock_web_services_api.py +954 -0
  30. mock_vws/_respx_mock_server/__init__.py +1 -0
  31. mock_vws/_respx_mock_server/decorators.py +186 -0
  32. mock_vws/_services_validators/__init__.py +186 -0
  33. mock_vws/_services_validators/active_flag_validators.py +43 -0
  34. mock_vws/_services_validators/auth_validators.py +124 -0
  35. mock_vws/_services_validators/content_length_validators.py +102 -0
  36. mock_vws/_services_validators/content_type_validators.py +42 -0
  37. mock_vws/_services_validators/date_validators.py +78 -0
  38. mock_vws/_services_validators/exceptions.py +858 -0
  39. mock_vws/_services_validators/image_validators.py +220 -0
  40. mock_vws/_services_validators/json_validators.py +69 -0
  41. mock_vws/_services_validators/key_validators.py +171 -0
  42. mock_vws/_services_validators/metadata_validators.py +106 -0
  43. mock_vws/_services_validators/name_validators.py +235 -0
  44. mock_vws/_services_validators/project_state_validators.py +76 -0
  45. mock_vws/_services_validators/request_quota_validators.py +42 -0
  46. mock_vws/_services_validators/target_quota_validators.py +41 -0
  47. mock_vws/_services_validators/target_validators.py +69 -0
  48. mock_vws/_services_validators/width_validators.py +38 -0
  49. mock_vws/database.py +257 -0
  50. mock_vws/database_type.py +13 -0
  51. mock_vws/image_matchers.py +124 -0
  52. mock_vws/model_target.py +79 -0
  53. mock_vws/py.typed +0 -0
  54. mock_vws/states.py +20 -0
  55. mock_vws/target.py +285 -0
  56. mock_vws/target_manager.py +178 -0
  57. mock_vws/target_raters.py +109 -0
  58. vws_python_mock-2026.8.4.dist-info/METADATA +177 -0
  59. vws_python_mock-2026.8.4.dist-info/RECORD +62 -0
  60. vws_python_mock-2026.8.4.dist-info/WHEEL +5 -0
  61. vws_python_mock-2026.8.4.dist-info/licenses/LICENSE +21 -0
  62. vws_python_mock-2026.8.4.dist-info/top_level.txt +1 -0
mock_vws/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ """Tools for using a fake implementation of Vuforia."""
2
+
3
+ from mock_vws._mock_common import MissingSchemeError
4
+ from mock_vws._requests_mock_server.decorators import MockVWS
5
+
6
+ __all__ = [
7
+ "MissingSchemeError",
8
+ "MockVWS",
9
+ ]
@@ -0,0 +1,35 @@
1
+ """Helpers for handling Base64 like Vuforia does."""
2
+
3
+ import base64
4
+ import binascii
5
+ import string
6
+
7
+ from beartype import beartype
8
+
9
+
10
+ @beartype
11
+ def decode_base64(encoded_data: str) -> bytes:
12
+ """Decode base64 somewhat like Vuforia does.
13
+
14
+ Raises:
15
+ binascii.Error: Vuforia would consider this encoded data as an
16
+ "UNPROCESSABLE_ENTITY".
17
+
18
+ Returns:
19
+ The given data, decoded as base64.
20
+ """
21
+ acceptable_characters = string.ascii_letters + string.digits + "+/="
22
+ for character in encoded_data:
23
+ if character not in acceptable_characters:
24
+ raise binascii.Error
25
+
26
+ mod_four_result_to_modified_encoded_data = {
27
+ 0: encoded_data,
28
+ 1: encoded_data[:-1],
29
+ 2: f"{encoded_data}==",
30
+ 3: f"{encoded_data}=",
31
+ }
32
+ modified_encoded_data = mod_four_result_to_modified_encoded_data[
33
+ len(encoded_data) % 4
34
+ ]
35
+ return base64.b64decode(s=modified_encoded_data)
mock_vws/_constants.py ADDED
@@ -0,0 +1,84 @@
1
+ """Constants used to make the VWS mock."""
2
+
3
+ from enum import Enum, unique
4
+
5
+ from beartype import beartype
6
+
7
+ VUMARK_PNG = (
8
+ b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00"
9
+ b"\x01\x08\x04\x00\x00\x00\xb5\x1c\x0c\x02\x00\x00\x00\x0bIDATx\xdac"
10
+ b"\xfc\xff\x1f\x00\x03\x03\x02\x00\xee\xd9\x97\xa9\x00\x00\x00\x00IEND"
11
+ b"\xaeB`\x82"
12
+ )
13
+
14
+ VUMARK_SVG = (
15
+ b'<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"></svg>'
16
+ )
17
+
18
+ VUMARK_PDF = (
19
+ b"%PDF-1.4\n"
20
+ b"1 0 obj<</Type /Catalog /Pages 2 0 R>>endobj\n"
21
+ b"2 0 obj<</Type /Pages /Kids [3 0 R] /Count 1>>endobj\n"
22
+ b"3 0 obj<</Type /Page /MediaBox [0 0 100 100]>>endobj\n"
23
+ b"xref\n0 4\n"
24
+ b"0000000000 65535 f \n"
25
+ b"trailer<</Size 4/Root 1 0 R>>\n"
26
+ b"startxref\n9\n%%EOF"
27
+ )
28
+
29
+
30
+ @beartype
31
+ @unique
32
+ class ResultCodes(Enum):
33
+ """Constants representing various VWS result codes.
34
+
35
+ See
36
+ https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#result-codes.
37
+
38
+ Some codes here are not documented in the above link.
39
+ """
40
+
41
+ SUCCESS = "Success"
42
+ TARGET_CREATED = "TargetCreated"
43
+ AUTHENTICATION_FAILURE = "AuthenticationFailure"
44
+ REQUEST_TIME_TOO_SKEWED = "RequestTimeTooSkewed"
45
+ TARGET_NAME_EXIST = "TargetNameExist"
46
+ UNKNOWN_TARGET = "UnknownTarget"
47
+ BAD_IMAGE = "BadImage"
48
+ IMAGE_TOO_LARGE = "ImageTooLarge"
49
+ METADATA_TOO_LARGE = "MetadataTooLarge"
50
+ # The documentation says "Start date is after the end date" but, at the
51
+ # time of writing, I do not know how to trigger that, therefore this is not
52
+ # tested.
53
+ DATE_RANGE_ERROR = "DateRangeError"
54
+ FAIL = "Fail"
55
+ TARGET_STATUS_PROCESSING = "TargetStatusProcessing"
56
+ # This is tested only against the mock. We do not deliberately exhaust the
57
+ # real test database's quota because that would stop the verified-fake test
58
+ # suite from using it.
59
+ REQUEST_QUOTA_REACHED = "RequestQuotaReached"
60
+ TARGET_STATUS_NOT_SUCCESS = "TargetStatusNotSuccess"
61
+ TARGET_QUOTA_REACHED = "TargetQuotaReached"
62
+ PROJECT_SUSPENDED = "ProjectSuspended"
63
+ PROJECT_INACTIVE = "ProjectInactive"
64
+ PROJECT_HAS_NO_API_ACCESS = "ProjectHasNoAPIAccess"
65
+ INACTIVE_PROJECT = "InactiveProject"
66
+ TOO_MANY_REQUESTS = "TooManyRequests"
67
+ INVALID_ACCEPT_HEADER = "InvalidAcceptHeader"
68
+ INVALID_INSTANCE_ID = "InvalidInstanceId"
69
+ BAD_REQUEST = "BadRequest"
70
+ INVALID_TARGET_TYPE = "InvalidTargetType"
71
+
72
+
73
+ @beartype
74
+ @unique
75
+ class TargetStatuses(Enum):
76
+ """Constants representing VWS target statuses.
77
+
78
+ See the 'status' field in
79
+ https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#target-record
80
+ """
81
+
82
+ PROCESSING = "processing"
83
+ SUCCESS = "success"
84
+ FAILED = "failed"
@@ -0,0 +1,107 @@
1
+ """Helpers for getting databases which match keys given in requests."""
2
+
3
+ from collections.abc import Iterable, Mapping
4
+
5
+ from beartype import beartype
6
+ from vws_auth_tools import authorization_header
7
+
8
+ from mock_vws.database import CloudDatabase, VuMarkDatabase
9
+
10
+ AnyDatabase = CloudDatabase | VuMarkDatabase
11
+
12
+
13
+ @beartype
14
+ def get_database_matching_client_keys(
15
+ *,
16
+ request_headers: Mapping[str, str],
17
+ request_body: bytes | None,
18
+ request_method: str,
19
+ request_path: str,
20
+ databases: Iterable[CloudDatabase],
21
+ ) -> CloudDatabase:
22
+ """Return the first of the given databases which is being accessed by
23
+ the
24
+ given client request.
25
+
26
+ Args:
27
+ request_headers: The headers sent with the request.
28
+ request_body: The request body.
29
+ request_method: The HTTP method of the request.
30
+ request_path: The path of the request.
31
+ databases: The databases to check for matches.
32
+
33
+ Returns:
34
+ The database which is being accessed by the given client request.
35
+
36
+ Raises:
37
+ ValueError: No database matches the given request.
38
+ """
39
+ request_headers_dict = dict(request_headers)
40
+ content_type = request_headers_dict.get("Content-Type", "").split(sep=";")[
41
+ 0
42
+ ]
43
+ auth_header = request_headers_dict.get("Authorization")
44
+ date = request_headers_dict.get("Date", "")
45
+
46
+ for database in databases:
47
+ expected_authorization_header = authorization_header(
48
+ access_key=database.client_access_key,
49
+ secret_key=database.client_secret_key,
50
+ method=request_method,
51
+ content=request_body,
52
+ content_type=content_type,
53
+ date=date,
54
+ request_path=request_path,
55
+ )
56
+
57
+ if auth_header == expected_authorization_header:
58
+ return database
59
+ raise ValueError
60
+
61
+
62
+ @beartype
63
+ def get_database_matching_server_keys[DatabaseT: AnyDatabase](
64
+ *,
65
+ request_headers: Mapping[str, str],
66
+ request_body: bytes | None,
67
+ request_method: str,
68
+ request_path: str,
69
+ databases: Iterable[DatabaseT],
70
+ ) -> DatabaseT:
71
+ """Return the first of the given databases which is being accessed by
72
+ the
73
+ given server request.
74
+
75
+ Args:
76
+ request_headers: The headers sent with the request.
77
+ request_body: The request body.
78
+ request_method: The HTTP method of the request.
79
+ request_path: The path of the request.
80
+ databases: The databases to check for matches.
81
+
82
+ Returns:
83
+ The database being accessed by the given server request.
84
+
85
+ Raises:
86
+ ValueError: No database matches the given request.
87
+ """
88
+ request_headers_dict = dict(request_headers)
89
+ content_type_header = request_headers_dict.get("Content-Type", "")
90
+ content_type = content_type_header.split(sep=";")[0]
91
+ auth_header = request_headers_dict.get("Authorization")
92
+ date = request_headers_dict.get("Date", "")
93
+
94
+ for database in databases:
95
+ expected_authorization_header = authorization_header(
96
+ access_key=database.server_access_key,
97
+ secret_key=database.server_secret_key,
98
+ method=request_method,
99
+ content=request_body,
100
+ content_type=content_type,
101
+ date=date,
102
+ request_path=request_path,
103
+ )
104
+
105
+ if auth_header == expected_authorization_header:
106
+ return database
107
+ raise ValueError
@@ -0,0 +1,32 @@
1
+ FROM ghcr.io/astral-sh/uv:0.11.7-python3.14-trixie-slim AS base
2
+ # We set this pretend version as we do not have Git in our path, and we do
3
+ # not care enough about having the version correct inside the Docker container
4
+ # to install it.
5
+ ENV SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0
6
+ # Avoid using root user.
7
+ RUN useradd -ms /bin/bash myuser
8
+ USER myuser
9
+ COPY --chown=myuser:myuser . /app
10
+
11
+ # See https://pythonspeed.com/articles/activate-virtualenv-dockerfile/
12
+ # For why we use this method of activating the virtual environment.
13
+ ENV UV_PROJECT_ENVIRONMENT=/app/docker_venvs/.venv
14
+ ENV PATH="$UV_PROJECT_ENVIRONMENT/bin:$PATH"
15
+
16
+ WORKDIR /app
17
+ RUN uv sync --no-cache
18
+ EXPOSE 5000
19
+ ENTRYPOINT ["python"]
20
+ HEALTHCHECK --interval=1s --timeout=10s --start-period=5s --retries=3 CMD ["python", "/app/src/mock_vws/_flask_server/healthcheck.py"]
21
+
22
+ FROM base AS vws
23
+ ENV VWS_HOST=0.0.0.0
24
+ CMD ["src/mock_vws/_flask_server/vws.py"]
25
+
26
+ FROM base AS vwq
27
+ ENV VWQ_HOST=0.0.0.0
28
+ CMD ["src/mock_vws/_flask_server/vwq.py"]
29
+
30
+ FROM base AS target-manager
31
+ ENV TARGET_MANAGER_HOST=0.0.0.0
32
+ CMD ["src/mock_vws/_flask_server/target_manager.py"]
@@ -0,0 +1 @@
1
+ """Flask server for the mock Vuforia web service."""
@@ -0,0 +1,31 @@
1
+ """Health check for the Flask server."""
2
+
3
+ import http.client
4
+ import socket
5
+ import sys
6
+ from http import HTTPStatus
7
+
8
+ from beartype import beartype
9
+
10
+
11
+ @beartype
12
+ def flask_app_healthy(port: int) -> bool:
13
+ """Check if the Flask app is healthy."""
14
+ conn = http.client.HTTPConnection(host="localhost", port=port)
15
+ try:
16
+ conn.request(method="GET", url="/some-random-endpoint")
17
+ response = conn.getresponse()
18
+ except TimeoutError, http.client.HTTPException, socket.gaierror:
19
+ return False
20
+ finally:
21
+ conn.close()
22
+
23
+ return response.status in {
24
+ HTTPStatus.NOT_FOUND,
25
+ HTTPStatus.UNAUTHORIZED,
26
+ HTTPStatus.FORBIDDEN,
27
+ }
28
+
29
+
30
+ if __name__ == "__main__":
31
+ sys.exit(int(not flask_app_healthy(port=5000)))