Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Changelog
Next
----

- ``MockVWS`` now intercepts both ``requests`` (via ``responses``) and ``httpx`` (via ``respx``) simultaneously.
``MockVWSForHttpx`` has been removed — ``MockVWS`` handles both HTTP libraries.

2026.02.22.2
------------

Expand Down
22 changes: 8 additions & 14 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ VWS Mock

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

Mocking calls made to Vuforia with Python ``requests``
------------------------------------------------------
Mocking calls made to Vuforia
------------------------------

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

.. code-block:: shell

Expand All @@ -34,32 +34,26 @@ This requires Python |minimum-python-version|\+.
# This will use the Vuforia mock.
requests.get(url="https://vws.vuforia.com/summary", timeout=30)

By default, an exception will be raised if any requests to unmocked addresses are made.

.. _requests: https://pypi.org/project/requests/

Mocking calls made to Vuforia with Python ``httpx``
----------------------------------------------------

Using the mock redirects requests to Vuforia made with `httpx`_ to an in-memory implementation.
``MockVWS`` also intercepts `httpx`_ requests:

.. code-block:: python

"""Make a request to the Vuforia Web Services API mock."""
"""Make a request to the Vuforia Web Services API mock using httpx."""

import httpx

from mock_vws import MockVWSForHttpx
from mock_vws import MockVWS
from mock_vws.database import CloudDatabase

with MockVWSForHttpx() as mock:
with MockVWS() as mock:
database = CloudDatabase()
mock.add_cloud_database(cloud_database=database)
# This will use the Vuforia mock.
httpx.get(url="https://vws.vuforia.com/summary", timeout=30)

By default, an exception will be raised if any requests to unmocked addresses are made.

.. _requests: https://pypi.org/project/requests/
.. _httpx: https://pypi.org/project/httpx/

Using Docker to mock calls to Vuforia from any language
Expand Down
3 changes: 2 additions & 1 deletion docs/source/basic-example.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Using the mock redirects requests to Vuforia made with `requests`_ to an in-memory implementation.
``MockVWS`` intercepts requests to Vuforia made with `requests`_ or `httpx`_.

.. code-block:: python

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

.. _requests: https://pypi.org/project/requests/
.. _httpx: https://pypi.org/project/httpx/
7 changes: 2 additions & 5 deletions docs/source/getting-started.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
Getting started
---------------

Mocking calls made to Vuforia with Python ``requests``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Mocking calls made to Vuforia
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. include:: basic-example.rst

Mocking calls made to Vuforia with Python ``httpx``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. include:: httpx-example.rst
12 changes: 4 additions & 8 deletions docs/source/httpx-example.rst
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
Using the mock redirects requests to Vuforia made with `httpx`_ to an in-memory implementation.
``MockVWS`` also intercepts requests made with `httpx`_.

.. code-block:: python

"""Make a request to the Vuforia Web Services API mock."""
"""Make a request to the Vuforia Web Services API mock using httpx."""

import httpx

from mock_vws import MockVWSForHttpx
from mock_vws import MockVWS
from mock_vws.database import CloudDatabase

with MockVWSForHttpx() as mock:
with MockVWS() as mock:
database = CloudDatabase()
mock.add_cloud_database(cloud_database=database)
# This will use the Vuforia mock.
httpx.get(url="https://vws.vuforia.com/summary", timeout=30)

By default, an exception will be raised if any requests to unmocked addresses are made.

See :ref:`mock-api-reference` for details of what can be changed and how.

.. _httpx: https://pypi.org/project/httpx/
7 changes: 2 additions & 5 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
|project|
=========

Mocking calls made to Vuforia with Python ``requests``
------------------------------------------------------
Mocking calls made to Vuforia
------------------------------

.. code-block:: console

Expand All @@ -12,9 +12,6 @@ This requires Python |minimum-python-version|\+.

.. include:: basic-example.rst

Mocking calls made to Vuforia with Python ``httpx``
----------------------------------------------------

.. include:: httpx-example.rst

Using Docker to mock calls to Vuforia from any language
Expand Down
4 changes: 0 additions & 4 deletions docs/source/mock-api-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ API Reference
:members:
:undoc-members:

.. autoclass:: mock_vws.MockVWSForHttpx
:members:
:undoc-members:

.. autoclass:: mock_vws.MissingSchemeError
:members:
:undoc-members:
Expand Down
8 changes: 2 additions & 6 deletions src/mock_vws/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
"""Tools for using a fake implementation of Vuforia."""

from mock_vws._requests_mock_server.decorators import (
MissingSchemeError,
MockVWS,
)
from mock_vws._respx_mock_server.decorators import MockVWSForHttpx
from mock_vws._mock_common import MissingSchemeError
from mock_vws._requests_mock_server.decorators import MockVWS

__all__ = [
"MissingSchemeError",
"MockVWS",
"MockVWSForHttpx",
]
23 changes: 23 additions & 0 deletions src/mock_vws/_mock_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,29 @@
from beartype import beartype


@beartype
class MissingSchemeError(Exception):
"""Raised when a URL is missing a schema."""

def __init__(self, url: str) -> None:
"""
Args:
url: The URL which is missing a scheme.
"""
super().__init__()
self.url = url

def __str__(self) -> str:
"""
Give a string representation of this error with a
suggestion.
"""
return (
f'Invalid URL "{self.url}": No scheme supplied. '
f'Perhaps you meant "https://{self.url}".'
)


@beartype
@dataclass(frozen=True)
class RequestData:
Expand Down
50 changes: 24 additions & 26 deletions src/mock_vws/_requests_mock_server/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
import time
from collections.abc import Callable, Mapping
from contextlib import ContextDecorator
from typing import Any, Literal, Self
from typing import TYPE_CHECKING, Any, Literal, Self
from urllib.parse import urlparse

import requests
from beartype import BeartypeConf, beartype
from requests import PreparedRequest
from responses import RequestsMock

from mock_vws._mock_common import RequestData
from mock_vws._mock_common import MissingSchemeError, RequestData
from mock_vws._respx_mock_server.decorators import start_respx_router
from mock_vws.database import CloudDatabase, VuMarkDatabase
from mock_vws.image_matchers import (
ImageMatcher,
Expand All @@ -27,6 +28,9 @@
from .mock_web_query_api import MockVuforiaWebQueryAPI
from .mock_web_services_api import MockVuforiaWebServicesAPI

if TYPE_CHECKING:
import respx

_ResponseType = tuple[int, Mapping[str, str], str | bytes]
_MockCallback = Callable[[RequestData], _ResponseType]
_ResponsesCallback = Callable[[PreparedRequest], _ResponseType]
Expand All @@ -35,32 +39,12 @@
_BRISQUE_TRACKING_RATER = BrisqueTargetTrackingRater()


@beartype
class MissingSchemeError(Exception):
"""Raised when a URL is missing a schema."""

def __init__(self, url: str) -> None:
"""
Args:
url: The URL which is missing a scheme.
"""
super().__init__()
self.url = url

def __str__(self) -> str:
"""
Give a string representation of this error with a
suggestion.
"""
return (
f'Invalid URL "{self.url}": No scheme supplied. '
f'Perhaps you meant "https://{self.url}".'
)


@beartype(conf=BeartypeConf(is_pep484_tower=True))
class MockVWS(ContextDecorator):
"""Route requests to Vuforia's Web Service APIs to fakes of those APIs."""
"""Route requests to Vuforia's Web Service APIs to fakes of those APIs.

Works with both ``requests`` and ``httpx``.
"""

def __init__(
self,
Expand All @@ -78,6 +62,8 @@ def __init__(
"""Route requests to Vuforia's Web Service APIs to fakes of those
APIs.

Works with both ``requests`` and ``httpx``.

Args:
real_http: Whether or not to forward requests to the real
server if they are not handled by the mock.
Expand Down Expand Up @@ -108,6 +94,7 @@ def __init__(
self._response_delay_seconds = response_delay_seconds
self._sleep_fn = sleep_fn
self._mock: RequestsMock
self._router: respx.MockRouter
self._target_manager = TargetManager()

self._base_vws_url = base_vws_url
Expand Down Expand Up @@ -252,6 +239,16 @@ def __enter__(self) -> Self:
self._mock = mock
self._mock.start()

self._router = start_respx_router(
mock_vws_api=self._mock_vws_api,
mock_vwq_api=self._mock_vwq_api,
base_vws_url=self._base_vws_url,
base_vwq_url=self._base_vwq_url,
response_delay_seconds=self._response_delay_seconds,
sleep_fn=self._sleep_fn,
real_http=self._real_http,
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Responses mock leaked if respx router setup fails

Medium Severity

In __enter__, self._mock.start() is called before start_respx_router(). If start_respx_router raises an exception, __exit__ never runs (the context manager wasn't successfully entered), so the responses mock remains active and silently intercepts all subsequent requests calls. Similarly in __exit__, if self._mock.stop() raises, self._router.stop() is skipped and the respx router stays active. Both resources need try/finally protection since neither cleanup is guaranteed to run if the other fails.

Additional Locations (1)

Fix in Cursor Fix in Web


return self

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

self._mock.stop()
self._router.stop()
return False
Loading