Skip to content

Commit 3df0281

Browse files
Make MockVWS intercept both requests and httpx (#2998)
* Make MockVWS intercept both requests and httpx MockVWS now starts both responses (for requests) and respx (for httpx) mocks simultaneously, eliminating the need for a separate MockVWSForHttpx class. Removes MockVWSForHttpx entirely. Updates all tests and docs to reflect this change. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * Fix pylint C0413 and C0402 in respx decorators Move TYPE_CHECKING guard to after all imports to fix wrong-import-position. Replace "MockRouter" in docstrings with "respx router" to fix spelling warning. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Suppress pyrefly false positive with inline ignore comments pyrefly's dual search path ("." and "src") causes the same class to be seen under two module paths, triggering a spurious bad-argument-type error when passing API objects to start_respx_router. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Replace concrete type imports with Protocol to fix pyrefly false positive Define _APIHandler Protocol in _respx_mock_server/decorators.py so it no longer imports concrete classes from _requests_mock_server/. This removes the cross-module dependency that caused pyrefly to see the same class under two module paths (mock_vws.* vs src.mock_vws.*) and report a spurious bad-argument-type error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent aa5741a commit 3df0281

13 files changed

Lines changed: 258 additions & 434 deletions

File tree

CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ Changelog
44
Next
55
----
66

7+
- ``MockVWS`` now intercepts both ``requests`` (via ``responses``) and ``httpx`` (via ``respx``) simultaneously.
8+
``MockVWSForHttpx`` has been removed — ``MockVWS`` handles both HTTP libraries.
9+
710
2026.02.22.2
811
------------
912

README.rst

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ VWS Mock
88

99
Mock for the Vuforia Web Services (VWS) API and the Vuforia Web Query API.
1010

11-
Mocking calls made to Vuforia with Python ``requests``
12-
------------------------------------------------------
11+
Mocking calls made to Vuforia
12+
------------------------------
1313

14-
Using the mock redirects requests to Vuforia made with `requests`_ to an in-memory implementation.
14+
``MockVWS`` intercepts requests made with `requests`_ or `httpx`_.
1515

1616
.. code-block:: shell
1717
@@ -34,32 +34,26 @@ This requires Python |minimum-python-version|\+.
3434
# This will use the Vuforia mock.
3535
requests.get(url="https://vws.vuforia.com/summary", timeout=30)
3636
37-
By default, an exception will be raised if any requests to unmocked addresses are made.
38-
39-
.. _requests: https://pypi.org/project/requests/
40-
41-
Mocking calls made to Vuforia with Python ``httpx``
42-
----------------------------------------------------
43-
44-
Using the mock redirects requests to Vuforia made with `httpx`_ to an in-memory implementation.
37+
``MockVWS`` also intercepts `httpx`_ requests:
4538

4639
.. code-block:: python
4740
48-
"""Make a request to the Vuforia Web Services API mock."""
41+
"""Make a request to the Vuforia Web Services API mock using httpx."""
4942
5043
import httpx
5144
52-
from mock_vws import MockVWSForHttpx
45+
from mock_vws import MockVWS
5346
from mock_vws.database import CloudDatabase
5447
55-
with MockVWSForHttpx() as mock:
48+
with MockVWS() as mock:
5649
database = CloudDatabase()
5750
mock.add_cloud_database(cloud_database=database)
5851
# This will use the Vuforia mock.
5952
httpx.get(url="https://vws.vuforia.com/summary", timeout=30)
6053
6154
By default, an exception will be raised if any requests to unmocked addresses are made.
6255

56+
.. _requests: https://pypi.org/project/requests/
6357
.. _httpx: https://pypi.org/project/httpx/
6458

6559
Using Docker to mock calls to Vuforia from any language

docs/source/basic-example.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Using the mock redirects requests to Vuforia made with `requests`_ to an in-memory implementation.
1+
``MockVWS`` intercepts requests to Vuforia made with `requests`_ or `httpx`_.
22

33
.. code-block:: python
44
@@ -20,3 +20,4 @@ By default, an exception will be raised if any requests to unmocked addresses ar
2020
See :ref:`mock-api-reference` for details of what can be changed and how.
2121

2222
.. _requests: https://pypi.org/project/requests/
23+
.. _httpx: https://pypi.org/project/httpx/

docs/source/getting-started.rst

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
Getting started
22
---------------
33

4-
Mocking calls made to Vuforia with Python ``requests``
5-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4+
Mocking calls made to Vuforia
5+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
66

77
.. include:: basic-example.rst
88

9-
Mocking calls made to Vuforia with Python ``httpx``
10-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11-
129
.. include:: httpx-example.rst

docs/source/httpx-example.rst

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
1-
Using the mock redirects requests to Vuforia made with `httpx`_ to an in-memory implementation.
1+
``MockVWS`` also intercepts requests made with `httpx`_.
22

33
.. code-block:: python
44
5-
"""Make a request to the Vuforia Web Services API mock."""
5+
"""Make a request to the Vuforia Web Services API mock using httpx."""
66
77
import httpx
88
9-
from mock_vws import MockVWSForHttpx
9+
from mock_vws import MockVWS
1010
from mock_vws.database import CloudDatabase
1111
12-
with MockVWSForHttpx() as mock:
12+
with MockVWS() as mock:
1313
database = CloudDatabase()
1414
mock.add_cloud_database(cloud_database=database)
1515
# This will use the Vuforia mock.
1616
httpx.get(url="https://vws.vuforia.com/summary", timeout=30)
1717
18-
By default, an exception will be raised if any requests to unmocked addresses are made.
19-
20-
See :ref:`mock-api-reference` for details of what can be changed and how.
21-
2218
.. _httpx: https://pypi.org/project/httpx/

docs/source/index.rst

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
|project|
22
=========
33

4-
Mocking calls made to Vuforia with Python ``requests``
5-
------------------------------------------------------
4+
Mocking calls made to Vuforia
5+
------------------------------
66

77
.. code-block:: console
88
@@ -12,9 +12,6 @@ This requires Python |minimum-python-version|\+.
1212

1313
.. include:: basic-example.rst
1414

15-
Mocking calls made to Vuforia with Python ``httpx``
16-
----------------------------------------------------
17-
1815
.. include:: httpx-example.rst
1916

2017
Using Docker to mock calls to Vuforia from any language

docs/source/mock-api-reference.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ API Reference
77
:members:
88
:undoc-members:
99

10-
.. autoclass:: mock_vws.MockVWSForHttpx
11-
:members:
12-
:undoc-members:
13-
1410
.. autoclass:: mock_vws.MissingSchemeError
1511
:members:
1612
:undoc-members:

src/mock_vws/__init__.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
"""Tools for using a fake implementation of Vuforia."""
22

3-
from mock_vws._requests_mock_server.decorators import (
4-
MissingSchemeError,
5-
MockVWS,
6-
)
7-
from mock_vws._respx_mock_server.decorators import MockVWSForHttpx
3+
from mock_vws._mock_common import MissingSchemeError
4+
from mock_vws._requests_mock_server.decorators import MockVWS
85

96
__all__ = [
107
"MissingSchemeError",
118
"MockVWS",
12-
"MockVWSForHttpx",
139
]

src/mock_vws/_mock_common.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,29 @@
88
from beartype import beartype
99

1010

11+
@beartype
12+
class MissingSchemeError(Exception):
13+
"""Raised when a URL is missing a schema."""
14+
15+
def __init__(self, url: str) -> None:
16+
"""
17+
Args:
18+
url: The URL which is missing a scheme.
19+
"""
20+
super().__init__()
21+
self.url = url
22+
23+
def __str__(self) -> str:
24+
"""
25+
Give a string representation of this error with a
26+
suggestion.
27+
"""
28+
return (
29+
f'Invalid URL "{self.url}": No scheme supplied. '
30+
f'Perhaps you meant "https://{self.url}".'
31+
)
32+
33+
1134
@beartype
1235
@dataclass(frozen=True)
1336
class RequestData:

src/mock_vws/_requests_mock_server/decorators.py

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44
import time
55
from collections.abc import Callable, Mapping
66
from contextlib import ContextDecorator
7-
from typing import Any, Literal, Self
7+
from typing import TYPE_CHECKING, Any, Literal, Self
88
from urllib.parse import urlparse
99

1010
import requests
1111
from beartype import BeartypeConf, beartype
1212
from requests import PreparedRequest
1313
from responses import RequestsMock
1414

15-
from mock_vws._mock_common import RequestData
15+
from mock_vws._mock_common import MissingSchemeError, RequestData
16+
from mock_vws._respx_mock_server.decorators import start_respx_router
1617
from mock_vws.database import CloudDatabase, VuMarkDatabase
1718
from mock_vws.image_matchers import (
1819
ImageMatcher,
@@ -27,6 +28,9 @@
2728
from .mock_web_query_api import MockVuforiaWebQueryAPI
2829
from .mock_web_services_api import MockVuforiaWebServicesAPI
2930

31+
if TYPE_CHECKING:
32+
import respx
33+
3034
_ResponseType = tuple[int, Mapping[str, str], str | bytes]
3135
_MockCallback = Callable[[RequestData], _ResponseType]
3236
_ResponsesCallback = Callable[[PreparedRequest], _ResponseType]
@@ -35,32 +39,12 @@
3539
_BRISQUE_TRACKING_RATER = BrisqueTargetTrackingRater()
3640

3741

38-
@beartype
39-
class MissingSchemeError(Exception):
40-
"""Raised when a URL is missing a schema."""
41-
42-
def __init__(self, url: str) -> None:
43-
"""
44-
Args:
45-
url: The URL which is missing a scheme.
46-
"""
47-
super().__init__()
48-
self.url = url
49-
50-
def __str__(self) -> str:
51-
"""
52-
Give a string representation of this error with a
53-
suggestion.
54-
"""
55-
return (
56-
f'Invalid URL "{self.url}": No scheme supplied. '
57-
f'Perhaps you meant "https://{self.url}".'
58-
)
59-
60-
6142
@beartype(conf=BeartypeConf(is_pep484_tower=True))
6243
class MockVWS(ContextDecorator):
63-
"""Route requests to Vuforia's Web Service APIs to fakes of those APIs."""
44+
"""Route requests to Vuforia's Web Service APIs to fakes of those APIs.
45+
46+
Works with both ``requests`` and ``httpx``.
47+
"""
6448

6549
def __init__(
6650
self,
@@ -78,6 +62,8 @@ def __init__(
7862
"""Route requests to Vuforia's Web Service APIs to fakes of those
7963
APIs.
8064
65+
Works with both ``requests`` and ``httpx``.
66+
8167
Args:
8268
real_http: Whether or not to forward requests to the real
8369
server if they are not handled by the mock.
@@ -108,6 +94,7 @@ def __init__(
10894
self._response_delay_seconds = response_delay_seconds
10995
self._sleep_fn = sleep_fn
11096
self._mock: RequestsMock
97+
self._router: respx.MockRouter
11198
self._target_manager = TargetManager()
11299

113100
self._base_vws_url = base_vws_url
@@ -252,6 +239,16 @@ def __enter__(self) -> Self:
252239
self._mock = mock
253240
self._mock.start()
254241

242+
self._router = start_respx_router(
243+
mock_vws_api=self._mock_vws_api,
244+
mock_vwq_api=self._mock_vwq_api,
245+
base_vws_url=self._base_vws_url,
246+
base_vwq_url=self._base_vwq_url,
247+
response_delay_seconds=self._response_delay_seconds,
248+
sleep_fn=self._sleep_fn,
249+
real_http=self._real_http,
250+
)
251+
255252
return self
256253

257254
def __exit__(self, *exc: object) -> Literal[False]:
@@ -265,4 +262,5 @@ def __exit__(self, *exc: object) -> Literal[False]:
265262
del exc
266263

267264
self._mock.stop()
265+
self._router.stop()
268266
return False

0 commit comments

Comments
 (0)