From 17fd151362a09db9c367281aef4e69ef008fc42a Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sat, 19 Oct 2024 18:01:21 +0200 Subject: [PATCH 1/5] Test the plugin for `pook` separately (#256) * Test the plugin for `pook` separately. * Better Makefile. --- Makefile | 4 ++- mocket/mocket.py | 12 -------- mocket/plugins/pook_mock_engine.py | 29 ++++++++++--------- pyproject.toml | 5 ++-- tests/test_asyncio.py | 3 +- tests/test_pook.py | 46 ++++++++++++++++-------------- 6 files changed, 47 insertions(+), 52 deletions(-) diff --git a/Makefile b/Makefile index f344591f..62c52dc7 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,9 @@ types: test: types @echo "Running Python tests" + uv pip uninstall pook || true export VIRTUAL_ENV=.venv; .venv/bin/wait-for-it --service httpbin.local:443 --service localhost:6379 --timeout 5 -- .venv/bin/pytest + uv pip install pook && .venv/bin/pytest tests/test_pook.py && uv pip uninstall pook @echo "" safetest: @@ -41,7 +43,7 @@ publish: clean install-test-requirements uv run twine upload --repository mocket dist/*.tar.gz clean: - rm -rf *.egg-info dist/ requirements.txt uv.lock || true + rm -rf .coverage *.egg-info dist/ requirements.txt uv.lock || true find . -type d -name __pycache__ -exec rm -rf {} \; || true .PHONY: clean publish safetest test setup develop lint-python test-python _services-up diff --git a/mocket/mocket.py b/mocket/mocket.py index daa0e608..cbd42ca9 100644 --- a/mocket/mocket.py +++ b/mocket/mocket.py @@ -48,14 +48,6 @@ except ImportError: pyopenssl_override = False -try: # pragma: no cover - from aiohttp import TCPConnector - - aiohttp_make_ssl_context_cache_clear = TCPConnector._make_ssl_context.cache_clear -except (ImportError, AttributeError): - aiohttp_make_ssl_context_cache_clear = None - - true_socket = socket.socket true_create_connection = socket.create_connection true_gethostbyname = socket.gethostbyname @@ -566,8 +558,6 @@ def enable(namespace=None, truesocket_recording_dir=None): if pyopenssl_override: # pragma: no cover # Take out the pyopenssl version - use the default implementation extract_from_urllib3() - if aiohttp_make_ssl_context_cache_clear: # pragma: no cover - aiohttp_make_ssl_context_cache_clear() @staticmethod def disable(): @@ -604,8 +594,6 @@ def disable(): if pyopenssl_override: # pragma: no cover # Put the pyopenssl version back in place inject_into_urllib3() - if aiohttp_make_ssl_context_cache_clear: # pragma: no cover - aiohttp_make_ssl_context_cache_clear() @classmethod def get_namespace(cls): diff --git a/mocket/plugins/pook_mock_engine.py b/mocket/plugins/pook_mock_engine.py index 99cb07ec..549f5509 100644 --- a/mocket/plugins/pook_mock_engine.py +++ b/mocket/plugins/pook_mock_engine.py @@ -1,5 +1,7 @@ -from pook.engine import MockEngine -from pook.interceptors.base import BaseInterceptor +try: + from pook.engine import MockEngine +except ModuleNotFoundError: + MockEngine = object from mocket.mocket import Mocket from mocket.mockhttp import Entry, Response @@ -37,17 +39,6 @@ def single_register( return entry -class MocketInterceptor(BaseInterceptor): - @staticmethod - def activate(): - Mocket.disable() - Mocket.enable() - - @staticmethod - def disable(): - Mocket.disable() - - class MocketEngine(MockEngine): def __init__(self, engine): def mocket_mock_fun(*args, **kwargs): @@ -68,6 +59,18 @@ def mocket_mock_fun(*args, **kwargs): return mock + from pook.interceptors.base import BaseInterceptor + + class MocketInterceptor(BaseInterceptor): + @staticmethod + def activate(): + Mocket.disable() + Mocket.enable() + + @staticmethod + def disable(): + Mocket.disable() + # Store plugins engine self.engine = engine # Store HTTP client interceptors diff --git a/pyproject.toml b/pyproject.toml index e3b7d866..77d1f5d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,6 @@ test = [ "redis", "gevent", "sure", - "pook", "flake8>5", "xxhash", "httpx", @@ -54,7 +53,7 @@ test = [ "build", "twine", "fastapi", - "aiohttp<3.10.6", + "aiohttp", "wait-for-it", "mypy", "types-decorator", @@ -89,7 +88,7 @@ exclude = [ testpaths = [ "tests", "mocket", ] -addopts = "--doctest-modules --cov=mocket --cov-report=term-missing -v -x" +addopts = "--doctest-modules --cov=mocket --cov-report=term-missing --cov-append -v -x" [tool.ruff] src = ["mocket", "tests"] diff --git a/tests/test_asyncio.py b/tests/test_asyncio.py index 0f9a7d17..59dd474e 100644 --- a/tests/test_asyncio.py +++ b/tests/test_asyncio.py @@ -4,7 +4,6 @@ import socket import tempfile -import aiohttp import pytest from mocket import Mocketizer, async_mocketize @@ -46,6 +45,8 @@ async def test_asyncio_connection(): @pytest.mark.asyncio @async_mocketize async def test_aiohttp(): + import aiohttp + url = "https://bar.foo/" data = {"message": "Hello"} diff --git a/tests/test_pook.py b/tests/test_pook.py index f398672e..56721b5f 100644 --- a/tests/test_pook.py +++ b/tests/test_pook.py @@ -1,29 +1,31 @@ -import pook -import requests +import contextlib -from mocket.plugins.pook_mock_engine import MocketEngine +with contextlib.suppress(ModuleNotFoundError): + import pook + import requests -pook.set_mock_engine(MocketEngine) + from mocket.plugins.pook_mock_engine import MocketEngine + pook.set_mock_engine(MocketEngine) -@pook.on -def test_pook_engine(): - url = "http://twitter.com/api/1/foobar" - status = 404 - response_json = {"error": "foo"} + @pook.on + def test_pook_engine(): + url = "http://twitter.com/api/1/foobar" + status = 404 + response_json = {"error": "foo"} - mock = pook.get( - url, - headers={"content-type": "application/json"}, - reply=status, - response_json=response_json, - ) - mock.persist() + mock = pook.get( + url, + headers={"content-type": "application/json"}, + reply=status, + response_json=response_json, + ) + mock.persist() - requests.get(url) - assert mock.calls == 1 + requests.get(url) + assert mock.calls == 1 - resp = requests.get(url) - assert resp.status_code == status - assert resp.json() == response_json - assert mock.calls == 2 + resp = requests.get(url) + assert resp.status_code == status + assert resp.json() == response_json + assert mock.calls == 2 From ea5ad794760e9b562f788be90954a49f02916d42 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sun, 20 Oct 2024 11:09:03 +0200 Subject: [PATCH 2/5] Refactoring FakeSSLContext. (#257) --- mocket/mocket.py | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/mocket/mocket.py b/mocket/mocket.py index cbd42ca9..570150a8 100644 --- a/mocket/mocket.py +++ b/mocket/mocket.py @@ -25,7 +25,6 @@ from .compat import basestring, byte_type, decode_from_bytes, encode_to_bytes, text_type from .utils import ( - SSL_PROTOCOL, MocketMode, MocketSocketCore, get_mocketize, @@ -98,20 +97,9 @@ def check_hostname(self): def check_hostname(self, _): self._check_hostname = False - def __init__(self, sock=None, server_hostname=None, _context=None, *args, **kwargs): + def __init__(self, *args, **kwargs): self._set_dummy_methods() - if isinstance(sock, MocketSocket): - self.sock = sock - self.sock._host = server_hostname - self.sock.true_socket = true_ssl_socket( - sock=self.sock.true_socket, - server_hostname=server_hostname, - _context=true_ssl_context(protocol=SSL_PROTOCOL), - ) - elif isinstance(sock, int) and true_ssl_context: - self.context = true_ssl_context(sock) - def _set_dummy_methods(self): def dummy_method(*args, **kwargs): pass @@ -120,7 +108,7 @@ def dummy_method(*args, **kwargs): setattr(self, m, dummy_method) @staticmethod - def wrap_socket(sock=sock, *args, **kwargs): + def wrap_socket(sock, *args, **kwargs): sock.kwargs = kwargs sock._secure_socket = True return sock @@ -131,10 +119,6 @@ def wrap_bio(incoming, outcoming, *args, **kwargs): ssl_obj._host = kwargs["server_hostname"] return ssl_obj - def __getattr__(self, name): - if self.sock is not None: - return getattr(self.sock, name) - def create_connection(address, timeout=None, source_address=None): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) From d0b43a1661b2a90b20351d97ea1c79b7255eb2d0 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sun, 20 Oct 2024 12:52:51 +0200 Subject: [PATCH 3/5] Increasing readability of Mocket core. (#258) --- mocket/mocket.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/mocket/mocket.py b/mocket/mocket.py index 570150a8..dcdab533 100644 --- a/mocket/mocket.py +++ b/mocket/mocket.py @@ -370,18 +370,15 @@ def true_sendall(self, data, *args, **kwargs): self.true_socket.connect((host, port)) self.true_socket.sendall(data, *args, **kwargs) encoded_response = b"" - # https://github.com/kennethreitz/requests/blob/master/tests/testserver/server.py#L13 + # https://github.com/kennethreitz/requests/blob/master/tests/testserver/server.py#L12 while True: - if ( - not select.select([self.true_socket], [], [], 0.1)[0] - and encoded_response - ): + more_to_read = select.select([self.true_socket], [], [], 0.1)[0] + if not more_to_read and encoded_response: break - recv = self.true_socket.recv(self._buflen) - - if not recv and encoded_response: + new_content = self.true_socket.recv(self._buflen) + if not new_content: break - encoded_response += recv + encoded_response += new_content # dump the resulting dictionary to a JSON file if Mocket.get_truesocket_recording_dir(): From 8eae0c7fc5aa99b93252e218f6244283db348ef8 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sun, 20 Oct 2024 13:06:03 +0200 Subject: [PATCH 4/5] `aiohttp` reuses SSLContext instances created at import-time (#259) * `aiohttp` reuses SSLContext instances created at import-time. --- mocket/__init__.py | 11 +++++++++-- mocket/plugins/aiohttp_connector.py | 18 ++++++++++++++++++ tests/test_asyncio.py | 9 +++++++-- 3 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 mocket/plugins/aiohttp_connector.py diff --git a/mocket/__init__.py b/mocket/__init__.py index 31cd56fc..8371d554 100644 --- a/mocket/__init__.py +++ b/mocket/__init__.py @@ -1,6 +1,13 @@ from .async_mocket import async_mocketize -from .mocket import Mocket, MocketEntry, Mocketizer, mocketize +from .mocket import FakeSSLContext, Mocket, MocketEntry, Mocketizer, mocketize -__all__ = ("async_mocketize", "mocketize", "Mocket", "MocketEntry", "Mocketizer") +__all__ = ( + "async_mocketize", + "mocketize", + "Mocket", + "MocketEntry", + "Mocketizer", + "FakeSSLContext", +) __version__ = "3.13.1" diff --git a/mocket/plugins/aiohttp_connector.py b/mocket/plugins/aiohttp_connector.py new file mode 100644 index 00000000..353c3af7 --- /dev/null +++ b/mocket/plugins/aiohttp_connector.py @@ -0,0 +1,18 @@ +import contextlib + +from mocket import FakeSSLContext + +with contextlib.suppress(ModuleNotFoundError): + from aiohttp import ClientRequest + from aiohttp.connector import TCPConnector + + class MocketTCPConnector(TCPConnector): + """ + `aiohttp` reuses SSLContext instances created at import-time, + making it more difficult for Mocket to do its job. + This is an attempt to make things smoother, at the cost of + slightly patching the `ClientSession` while testing. + """ + + def _get_ssl_context(self, req: ClientRequest) -> FakeSSLContext: + return FakeSSLContext() diff --git a/tests/test_asyncio.py b/tests/test_asyncio.py index 59dd474e..bef53009 100644 --- a/tests/test_asyncio.py +++ b/tests/test_asyncio.py @@ -4,10 +4,12 @@ import socket import tempfile +import aiohttp import pytest from mocket import Mocketizer, async_mocketize from mocket.mockhttp import Entry +from mocket.plugins.aiohttp_connector import MocketTCPConnector def test_asyncio_record_replay(event_loop): @@ -45,7 +47,10 @@ async def test_asyncio_connection(): @pytest.mark.asyncio @async_mocketize async def test_aiohttp(): - import aiohttp + """ + The alternative to using the custom `connector` would be importing + `aiohttp` when Mocket is already in control (inside the decorated test). + """ url = "https://bar.foo/" data = {"message": "Hello"} @@ -58,7 +63,7 @@ async def test_aiohttp(): ) async with aiohttp.ClientSession( - timeout=aiohttp.ClientTimeout(total=3) + timeout=aiohttp.ClientTimeout(total=3), connector=MocketTCPConnector() ) as session, session.get(url) as response: response = await response.json() assert response == data From 28662a7f277ec3a93abab9778ef29ec22570d6b5 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sun, 20 Oct 2024 13:07:49 +0200 Subject: [PATCH 5/5] Bump version --- mocket/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mocket/__init__.py b/mocket/__init__.py index 8371d554..fb0434e9 100644 --- a/mocket/__init__.py +++ b/mocket/__init__.py @@ -10,4 +10,4 @@ "FakeSSLContext", ) -__version__ = "3.13.1" +__version__ = "3.13.2" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy