Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix httpx incorrectly named method on interceptor subclass #126

Merged
merged 5 commits into from
Feb 23, 2024
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
5 changes: 5 additions & 0 deletions History.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
History
=======

v1.4.3 / 2024-02-23
-------------------

* Fix httpx incorrectly named method on interceptor subclass by @sarayourfriend in https://github.com/h2non/pook/pull/126

v1.4.2 / 2024-02-15
-------------------

Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ extra-dependencies = [
"pytest~=7.4",
"pytest-asyncio~=0.20.3",
"pytest-pook==0.1.0b0",
"pytest-httpbin==2.0.0",

"requests~=2.20",
"urllib3~=1.24",
Expand All @@ -62,8 +63,8 @@ extra-dependencies = [
# aiohttp depends on multidict, so we can't test aiohttp until
# https://github.com/aio-libs/multidict/issues/887 is resolved
# async-timeout is only used for testing aiohttp
"aiohttp~=3.8; python_version < '3.12'",
"async-timeout~=4.0.3; python_version < '3.12'",
"aiohttp~=3.8",
"async-timeout~=4.0.3",

# mocket relies on httptools which does not support PyPy
"mocket[pook]~=3.12.2; platform_python_implementation != 'PyPy'",
Expand Down
2 changes: 1 addition & 1 deletion src/pook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
__license__ = "MIT"

# Current version
__version__ = "1.4.2"
__version__ = "1.4.3"
4 changes: 2 additions & 2 deletions src/pook/interceptors/_httpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def handler(client, *_):
def activate(self):
[self._patch(path) for path in PATCHES]

def deactivate(self):
def disable(self):
[patch.stop() for patch in self.patchers]


Expand Down Expand Up @@ -96,7 +96,7 @@ async def handle_async_request(self, request):
mock = self._interceptor.engine.match(pook_request)

if not mock:
transport = self._original_transport_for_url(self._client, self.request.url)
transport = self._original_transport_for_url(self._client, request.url)
return await transport.handle_async_request(request)

if mock._delay:
Expand Down
8 changes: 4 additions & 4 deletions src/pook/interceptors/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from abc import abstractmethod, ABCMeta


class BaseInterceptor(object):
class BaseInterceptor:
"""
BaseInterceptor provides a base class for HTTP traffic
interceptors implementations.
Expand All @@ -14,7 +14,7 @@ def __init__(self, engine):
self.engine = engine

@property
def name(self):
def name(self) -> str:
"""
Exposes the interceptor class name.
"""
Expand All @@ -26,12 +26,12 @@ def activate(self):
Activates the traffic interceptor.
This method must be implemented by any interceptor.
"""
pass
raise NotImplementedError("Sub-classes must implement `activate`")

@abstractmethod
def disable(self):
"""
Disables the traffic interceptor.
This method must be implemented by any interceptor.
"""
pass
raise NotImplementedError("Sub-classes must implement `disable`")
49 changes: 28 additions & 21 deletions tests/unit/interceptors/aiohttp_test.py
Original file line number Diff line number Diff line change
@@ -1,53 +1,60 @@
import sys
from pathlib import Path

import pook
import pytest
import aiohttp

from pathlib import Path
import pook

SUPPORTED = sys.version_info < (3, 12)
if SUPPORTED:
# See pyproject.toml comment
import aiohttp
from tests.unit.interceptors.base import StandardTests


pytestmark = [
pytest.mark.pook,
pytest.mark.asyncio,
pytest.mark.skipif(
not SUPPORTED, reason="See pyproject.toml comment on aiohttp dependency"
),
]
pytestmark = [pytest.mark.pook]

URL = "https://httpbin.org/status/404"

class TestStandardAiohttp(StandardTests):
is_async = True

async def amake_request(self, method, url):
async with aiohttp.ClientSession(loop=self.loop) as session:
req = await session.request(method=method, url=url)
content = await req.read()
return req.status, content.decode("utf-8")


binary_file = (Path(__file__).parents[1] / "fixtures" / "nothing.bin").read_bytes()


def _pook_url():
def _pook_url(URL):
return pook.head(URL).reply(200).mock


async def test_async_with_request():
mock = _pook_url()
@pytest.fixture
def URL(httpbin):
return f"{httpbin.url}/status/404"


@pytest.mark.asyncio
async def test_async_with_request(URL):
mock = _pook_url(URL)
async with aiohttp.ClientSession() as session:
async with session.head(URL) as req:
assert req.status == 200

assert len(mock.matches) == 1


async def test_await_request():
mock = _pook_url()
@pytest.mark.asyncio
async def test_await_request(URL):
mock = _pook_url(URL)
async with aiohttp.ClientSession() as session:
req = await session.head(URL)
assert req.status == 200

assert len(mock.matches) == 1


async def test_binary_body():
@pytest.mark.asyncio
async def test_binary_body(URL):
pook.get(URL).reply(200).body(binary_file, binary=True)
async with aiohttp.ClientSession() as session:
req = await session.get(URL)
Expand Down
58 changes: 58 additions & 0 deletions tests/unit/interceptors/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import asyncio
from typing import Optional, Tuple

import pytest

import pook


class StandardTests:
is_async: bool = False

async def amake_request(self, method: str, url: str) -> Tuple[int, Optional[str]]:
raise NotImplementedError(
"Sub-classes for async transports must implement `amake_request`"
)

def make_request(self, method: str, url: str) -> Tuple[int, Optional[str]]:
if self.is_async:
return self.loop.run_until_complete(self.amake_request(method, url))

raise NotImplementedError("Sub-classes must implement `make_request`")

@pytest.fixture(autouse=True, scope="class")
def _loop(self, request):
if self.is_async:
request.cls.loop = asyncio.new_event_loop()
yield
request.cls.loop.close()
else:
yield

@pytest.mark.pook
def test_activate_deactivate(self, httpbin):
url = f"{httpbin.url}/status/404"
pook.get(url).reply(200).body("hello from pook")

status, body = self.make_request("GET", url)

assert status == 200
assert body == "hello from pook"

pook.disable()

status, body = self.make_request("GET", url)

assert status == 404

@pytest.mark.pook(allow_pending_mocks=True)
def test_network_mode(self, httpbin):
upstream_url = f"{httpbin.url}/status/500"
mocked_url = f"{httpbin.url}/status/404"
pook.get(mocked_url).reply(200).body("hello from pook")
pook.enable_network()

# Avoid matching the mocks
status, body = self.make_request("POST", upstream_url)

assert status == 500
44 changes: 29 additions & 15 deletions tests/unit/interceptors/httpx_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,43 @@

from itertools import zip_longest


URL = "https://httpbin.org/status/404"
from tests.unit.interceptors.base import StandardTests


pytestmark = [pytest.mark.pook]


def test_sync():
class TestStandardAsyncHttpx(StandardTests):
is_async = True

async def amake_request(self, method, url):
async with httpx.AsyncClient() as client:
response = await client.request(method=method, url=url)
content = await response.aread()
return response.status_code, content.decode("utf-8")


class TestStandardSyncHttpx(StandardTests):
def make_request(self, method, url):
response = httpx.request(method=method, url=url)
content = response.read()
return response.status_code, content.decode("utf-8")


@pytest.fixture
def URL(httpbin):
return f"{httpbin.url}/status/404"


def test_sync(URL):
pook.get(URL).times(1).reply(200).body("123")

response = httpx.get(URL)

assert response.status_code == 200


async def test_async():
async def test_async(URL):
pook.get(URL).times(1).reply(200).body(b"async_body", binary=True).mock

async with httpx.AsyncClient() as client:
Expand All @@ -29,7 +50,7 @@ async def test_async():
assert (await response.aread()) == b"async_body"


def test_json():
def test_json(URL):
(
pook.post(URL)
.times(1)
Expand All @@ -44,7 +65,8 @@ def test_json():
assert response.json() == {"title": "123abc title"}


def _check_streaming_via(response_method):
@pytest.mark.parametrize("response_method", ("iter_bytes", "iter_raw"))
def test_streaming(URL, response_method):
streamed_response = b"streamed response"
pook.get(URL).times(1).reply(200).body(streamed_response).mock

Expand All @@ -55,15 +77,7 @@ def _check_streaming_via(response_method):
assert bytes().join(read_bytes) == streamed_response


def test_streaming_via_iter_bytes():
_check_streaming_via("iter_bytes")


def test_streaming_via_iter_raw():
_check_streaming_via("iter_raw")


def test_redirect_following():
def test_redirect_following(URL):
urls = [URL, f"{URL}/redirected", f"{URL}/redirected_again"]
for req, dest in zip_longest(urls, urls[1:], fillvalue=None):
if not dest:
Expand Down
6 changes: 5 additions & 1 deletion tests/unit/interceptors/module_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@


class CustomInterceptor(interceptors.BaseInterceptor):
pass
def activate(self):
...

def disable(self):
...


def test_add_custom_interceptor():
Expand Down
Loading
Loading