Skip to content

Commit 781ecc6

Browse files
committed
Merge branch 'switch-to-responses' into more-beartype
2 parents da3eba7 + 142bcb6 commit 781ecc6

File tree

11 files changed

+203
-197
lines changed

11 files changed

+203
-197
lines changed

docs/source/docker.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ VWS container
136136

137137
The number of seconds to process each image for.
138138

139-
Default: ``2``
139+
Default: ``2.0``
140140

141141
.. envvar:: DUPLICATES_IMAGE_MATCHER
142142

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ dependencies = [
4545
"piq",
4646
"pydantic-settings",
4747
"requests",
48-
"requests-mock",
48+
"responses",
4949
"torch",
5050
"torchmetrics",
5151
"tzdata; sys_platform=='win32'",

src/mock_vws/_flask_server/vws.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import uuid
1313
from enum import StrEnum, auto
1414
from http import HTTPMethod, HTTPStatus
15-
from typing import SupportsFloat
1615

1716
import requests
1817
from beartype import beartype
@@ -68,7 +67,7 @@ class VWSSettings(BaseSettings):
6867
"""Settings for the VWS Flask app."""
6968

7069
target_manager_base_url: str
71-
processing_time_seconds: SupportsFloat = 2
70+
processing_time_seconds: float = 2.0
7271
vws_host: str = ""
7372
duplicates_image_matcher: _ImageMatcherChoice = (
7473
_ImageMatcherChoice.STRUCTURAL_SIMILARITY
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""
2-
An interface to the mock Vuforia which uses ``requests_mock``.
2+
An interface to the mock Vuforia which uses ``responses``.
33
"""

src/mock_vws/_requests_mock_server/decorators.py

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
import re
66
from contextlib import ContextDecorator
7-
from typing import Literal, Self, SupportsFloat
7+
from typing import Literal, Self
88
from urllib.parse import urljoin, urlparse
99

1010
import requests
11-
from requests_mock.mocker import Mocker
11+
from responses import RequestsMock
1212

1313
from mock_vws.database import VuforiaDatabase
1414
from mock_vws.image_matchers import (
@@ -39,7 +39,7 @@ def __init__(
3939
base_vwq_url: str = "https://cloudreco.vuforia.com",
4040
duplicate_match_checker: ImageMatcher = _STRUCTURAL_SIMILARITY_MATCHER,
4141
query_match_checker: ImageMatcher = _STRUCTURAL_SIMILARITY_MATCHER,
42-
processing_time_seconds: SupportsFloat = 2,
42+
processing_time_seconds: float = 2.0,
4343
target_tracking_rater: TargetTrackingRater = _BRISQUE_TRACKING_RATER,
4444
*,
4545
real_http: bool = False,
@@ -69,7 +69,7 @@ def __init__(
6969
"""
7070
super().__init__()
7171
self._real_http = real_http
72-
self._mock: Mocker
72+
self._mock: RequestsMock
7373
self._target_manager = TargetManager()
7474

7575
self._base_vws_url = base_vws_url
@@ -116,32 +116,48 @@ def __enter__(self) -> Self:
116116
Returns:
117117
``self``.
118118
"""
119-
with Mocker(real_http=self._real_http) as mock:
120-
for vws_route in self._mock_vws_api.routes:
121-
url_pattern = urljoin(
122-
base=self._base_vws_url,
123-
url=f"{vws_route.path_pattern}$",
119+
compiled_url_patterns: set[re.Pattern[str]] = set()
120+
121+
mock = RequestsMock(assert_all_requests_are_fired=False)
122+
for vws_route in self._mock_vws_api.routes:
123+
url_pattern = urljoin(
124+
base=self._base_vws_url,
125+
url=f"{vws_route.path_pattern}$",
126+
)
127+
compiled_url_pattern = re.compile(pattern=url_pattern)
128+
compiled_url_patterns.add(compiled_url_pattern)
129+
130+
for vws_http_method in vws_route.http_methods:
131+
mock.add_callback(
132+
method=vws_http_method,
133+
url=compiled_url_pattern,
134+
callback=getattr(self._mock_vws_api, vws_route.route_name),
135+
content_type=None,
124136
)
125137

126-
for vws_http_method in vws_route.http_methods:
127-
mock.register_uri(
128-
method=vws_http_method,
129-
url=re.compile(url_pattern),
130-
text=getattr(self._mock_vws_api, vws_route.route_name),
131-
)
132-
133-
for vwq_route in self._mock_vwq_api.routes:
134-
url_pattern = urljoin(
135-
base=self._base_vwq_url,
136-
url=f"{vwq_route.path_pattern}$",
138+
for vwq_route in self._mock_vwq_api.routes:
139+
url_pattern = urljoin(
140+
base=self._base_vwq_url,
141+
url=f"{vwq_route.path_pattern}$",
142+
)
143+
compiled_url_pattern = re.compile(pattern=url_pattern)
144+
compiled_url_patterns.add(compiled_url_pattern)
145+
146+
for vwq_http_method in vwq_route.http_methods:
147+
mock.add_callback(
148+
method=vwq_http_method,
149+
url=compiled_url_pattern,
150+
callback=getattr(self._mock_vwq_api, vwq_route.route_name),
151+
content_type=None,
137152
)
138153

139-
for vwq_http_method in vwq_route.http_methods:
140-
mock.register_uri(
141-
method=vwq_http_method,
142-
url=re.compile(url_pattern),
143-
text=getattr(self._mock_vwq_api, vwq_route.route_name),
144-
)
154+
if self._real_http:
155+
combined_pattern = "|".join(
156+
f"(?:{pattern.pattern})" for pattern in compiled_url_patterns
157+
)
158+
negated_pattern = f"(?!{combined_pattern})."
159+
compiled_negated_pattern = re.compile(pattern=negated_pattern)
160+
mock.add_passthru(prefix=compiled_negated_pattern)
145161

146162
self._mock = mock
147163
self._mock.start()

src/mock_vws/_requests_mock_server/mock_web_query_api.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77

88
import email.utils
99
from collections.abc import Callable
10-
from http import HTTPMethod
11-
from typing import TYPE_CHECKING
10+
from http import HTTPMethod, HTTPStatus
11+
12+
from requests.models import PreparedRequest
1213

1314
from mock_vws._mock_common import Route
1415
from mock_vws._query_tools import (
@@ -21,18 +22,15 @@
2122
from mock_vws.image_matchers import ImageMatcher
2223
from mock_vws.target_manager import TargetManager
2324

24-
if TYPE_CHECKING:
25-
from requests_mock.request import Request
26-
from requests_mock.response import Context
27-
28-
2925
_ROUTES: set[Route] = set()
3026

27+
_ResponseType = tuple[int, dict[str, str], str]
28+
3129

3230
def route(
3331
path_pattern: str,
3432
http_methods: set[str],
35-
) -> Callable[[Callable[..., str]], Callable[..., str]]:
33+
) -> Callable[[Callable[..., _ResponseType]], Callable[..., _ResponseType]]:
3634
"""
3735
Register a decorated method so that it can be recognized as a route.
3836
@@ -45,7 +43,9 @@ def route(
4543
A decorator which takes methods and makes them recognizable as routes.
4644
"""
4745

48-
def decorator(method: Callable[..., str]) -> Callable[..., str]:
46+
def decorator(
47+
method: Callable[..., _ResponseType],
48+
) -> Callable[..., _ResponseType]:
4949
"""
5050
Register a decorated method so that it can be recognized as a route.
5151
@@ -66,18 +66,22 @@ def decorator(method: Callable[..., str]) -> Callable[..., str]:
6666
return decorator
6767

6868

69-
def _body_bytes(request: "Request") -> bytes:
69+
def _body_bytes(request: PreparedRequest) -> bytes:
7070
"""
7171
Return the body of a request as bytes.
7272
"""
73-
return request.body or b""
73+
if request.body is None:
74+
return b""
75+
76+
assert isinstance(request.body, bytes)
77+
return request.body
7478

7579

7680
class MockVuforiaWebQueryAPI:
7781
"""
7882
A fake implementation of the Vuforia Web Query API.
7983
80-
This implementation is tied to the implementation of `requests_mock`.
84+
This implementation is tied to the implementation of ``responses``.
8185
"""
8286

8387
def __init__(
@@ -99,28 +103,26 @@ def __init__(
99103
self._query_match_checker = query_match_checker
100104

101105
@route(path_pattern="/v1/query", http_methods={HTTPMethod.POST})
102-
def query(self, request: "Request", context: "Context") -> str:
106+
def query(self, request: PreparedRequest) -> _ResponseType:
103107
"""
104108
Perform an image recognition query.
105109
"""
106110
try:
107111
run_query_validators(
108-
request_path=request.path,
112+
request_path=request.path_url,
109113
request_headers=request.headers,
110114
request_body=_body_bytes(request=request),
111-
request_method=request.method,
115+
request_method=request.method or "",
112116
databases=self._target_manager.databases,
113117
)
114118
except ValidatorError as exc:
115-
context.headers = exc.headers
116-
context.status_code = exc.status_code
117-
return exc.response_text
119+
return exc.status_code, exc.headers, exc.response_text
118120

119121
response_text = get_query_match_response_text(
120122
request_headers=request.headers,
121123
request_body=_body_bytes(request=request),
122-
request_method=request.method,
123-
request_path=request.path,
124+
request_method=request.method or "",
125+
request_path=request.path_url,
124126
databases=self._target_manager.databases,
125127
query_match_checker=self._query_match_checker,
126128
)
@@ -130,11 +132,11 @@ def query(self, request: "Request", context: "Context") -> str:
130132
localtime=False,
131133
usegmt=True,
132134
)
133-
context.headers = {
135+
headers = {
134136
"Connection": "keep-alive",
135137
"Content-Type": "application/json",
136138
"Server": "nginx",
137139
"Date": date,
138140
"Content-Length": str(len(response_text)),
139141
}
140-
return response_text
142+
return HTTPStatus.OK, headers, response_text

0 commit comments

Comments
 (0)