Skip to content

Commit 05c1146

Browse files
Merge pull request #666 from adamtheturtle/max-image-size-png
Add validation for PNG maximum validation size
2 parents 1e2bd59 + 9637d9d commit 05c1146

File tree

2 files changed

+112
-3
lines changed

2 files changed

+112
-3
lines changed

src/mock_vws/_mock_web_query_api.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from typing import Any, Callable, Dict, List, Set, Tuple, Union
1414

1515
import pytz
16+
import requests
1617
import wrapt
1718
from PIL import Image
1819
from requests import codes
@@ -69,6 +70,53 @@ def validate_project_state(
6970
)
7071

7172

73+
@wrapt.decorator
74+
def validate_image_file_size(
75+
wrapped: Callable[..., str],
76+
instance: Any, # pylint: disable=unused-argument
77+
args: Tuple[_RequestObjectProxy, _Context],
78+
kwargs: Dict,
79+
) -> str:
80+
"""
81+
Validate the file size of the image given to the query endpoint.
82+
83+
Args:
84+
wrapped: An endpoint function for `requests_mock`.
85+
instance: The class that the endpoint function is in.
86+
args: The arguments given to the endpoint function.
87+
kwargs: The keyword arguments given to the endpoint function.
88+
89+
Returns:
90+
The result of calling the endpoint.
91+
92+
Raises:
93+
requests.exceptions.ConnectionError: The image file size is too large.
94+
"""
95+
request, _ = args
96+
body_file = io.BytesIO(request.body)
97+
98+
_, pdict = cgi.parse_header(request.headers['Content-Type'])
99+
parsed = cgi.parse_multipart(
100+
fp=body_file,
101+
pdict={
102+
'boundary': pdict['boundary'].encode(),
103+
},
104+
)
105+
106+
[image] = parsed['image']
107+
108+
image_file = io.BytesIO(image)
109+
pil_image = Image.open(image_file)
110+
111+
if pil_image.format != 'PNG':
112+
return wrapped(*args, **kwargs)
113+
114+
documented_max_png_bytes = 2 * 1024 * 1024
115+
if len(image) > documented_max_png_bytes:
116+
raise requests.exceptions.ConnectionError
117+
return wrapped(*args, **kwargs)
118+
119+
72120
@wrapt.decorator
73121
def validate_image_format(
74122
wrapped: Callable[..., str],
@@ -626,6 +674,7 @@ def decorator(method: Callable[..., str]) -> Callable[..., str]:
626674
validate_date_header_given,
627675
validate_include_target_data,
628676
validate_max_num_results,
677+
validate_image_file_size,
629678
validate_image_file_contents,
630679
validate_image_format,
631680
validate_image_field_given,

tests/mock_vws/test_query.py

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
add_target_to_vws,
2626
delete_target,
2727
get_vws_target,
28+
make_image_file,
2829
query,
2930
update_target,
3031
wait_for_target_processed,
@@ -1065,11 +1066,70 @@ class TestMaximumImageSize:
10651066
Tests for maximum image sizes.
10661067
"""
10671068

1068-
def test_png(self) -> None:
1069+
def test_png(
1070+
self,
1071+
vuforia_database_keys: VuforiaDatabaseKeys,
1072+
) -> None:
10691073
"""
1070-
See https://github.com/adamtheturtle/vws-python/issues/357 for
1071-
implementing this test.
1074+
According to
1075+
https://library.vuforia.com/articles/Solution/How-To-Perform-an-Image-Recognition-Query.
1076+
the maximum file size is "2MiB for PNG".
1077+
1078+
Above this limit, a ``ConnectionError`` is raised.
1079+
We do not test exactly at this limit, but that may be beneficial in the
1080+
future.
10721081
"""
1082+
documented_max_bytes = 2 * 1024 * 1024
1083+
width = height = 835
1084+
png_not_too_large = make_image_file(
1085+
file_format='PNG',
1086+
color_space='RGB',
1087+
width=width,
1088+
height=height,
1089+
)
1090+
1091+
image_content = png_not_too_large.getvalue()
1092+
body = {'image': ('image.jpeg', image_content, 'image/jpeg')}
1093+
1094+
image_content_size = len(image_content)
1095+
# We check that the image we created is just slightly smaller than the
1096+
# maximum file size.
1097+
#
1098+
# This is just because of the implementation details of ``image_file``.
1099+
assert image_content_size < documented_max_bytes
1100+
assert (image_content_size * 1.05) > documented_max_bytes
1101+
1102+
response = query(
1103+
vuforia_database_keys=vuforia_database_keys,
1104+
body=body,
1105+
)
1106+
1107+
assert_query_success(response=response)
1108+
assert response.json()['results'] == []
1109+
1110+
width = height = 836
1111+
png_not_too_large = make_image_file(
1112+
file_format='PNG',
1113+
color_space='RGB',
1114+
width=width,
1115+
height=height,
1116+
)
1117+
1118+
image_content = png_not_too_large.getvalue()
1119+
body = {'image': ('image.jpeg', image_content, 'image/jpeg')}
1120+
image_content_size = len(image_content)
1121+
# We check that the image we created is just slightly larger than the
1122+
# maximum file size.
1123+
#
1124+
# This is just because of the implementation details of ``image_file``.
1125+
assert image_content_size > documented_max_bytes
1126+
assert (image_content_size * 0.95) < documented_max_bytes
1127+
1128+
with pytest.raises(requests.exceptions.ConnectionError):
1129+
query(
1130+
vuforia_database_keys=vuforia_database_keys,
1131+
body=body,
1132+
)
10731133

10741134
def test_jpeg(self) -> None:
10751135
"""

0 commit comments

Comments
 (0)