From f4144359f695f089480ef16b320fd6e993d47337 Mon Sep 17 00:00:00 2001 From: Mohammad Razavi Date: Mon, 7 Aug 2023 08:42:58 +0200 Subject: [PATCH 01/25] Fix ResourceWarning unclosed socket This PR fixes issue #710 by properly closing the underlying socket. It first uses `pool._put_conn` to keep the connection in the pool, and later removes and closes it when the context manager exits. I was unsure about the exact purpose of the `ConnectonRemove` class, so I made minimal changes to minimize the risk of breaking the code and there may be better solutions for fixing this issue. For example, the `urllib3.connectionpool.HTTPConnectionPool` will utilize a weakref to terminate pool connections. By appending our connection to it, it will also take care of closing our connection. So another solution could be to modify the `__exit__` in `patch.ConnectionRemover` method and add our connection to the pool: ```py class ConnectionRemover: ... def __exit__(self, *args): for pool, connections in self._connection_pool_to_connections.items(): for connection in connections: if isinstance(connection, self._connection_class): pool._put_conn(connection) ``` --- vcr/patch.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vcr/patch.py b/vcr/patch.py index afcaab571..0ea911014 100644 --- a/vcr/patch.py +++ b/vcr/patch.py @@ -367,6 +367,7 @@ def __init__(self, connection_class): def add_connection_to_pool_entry(self, pool, connection): if isinstance(connection, self._connection_class): self._connection_pool_to_connections.setdefault(pool, set()).add(connection) + pool._put_conn(connection) def remove_connection_to_pool_entry(self, pool, connection): if isinstance(connection, self._connection_class): @@ -382,6 +383,7 @@ def __exit__(self, *args): connection = pool.pool.get() if isinstance(connection, self._connection_class): connections.remove(connection) + connection.close() else: readd_connections.append(connection) for connection in readd_connections: From 556fd0166c919eb5bae4e125d5a7339b689caaca Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 11:07:40 +0000 Subject: [PATCH 02/25] enable filterwarnings=error --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 7dec86163..9c92396a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,12 @@ ignore-regex = "\\\\[fnrstv]" # ignore-words-list = '' [tool.pytest.ini_options] +addopts = [ + "--strict-config", + "--strict-markers", +] markers = ["online"] +filterwarnings = ["error"] [tool.ruff] select = [ From fa789e975b08ba1a40fc7163fc72a0b26a353ad3 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 11:07:56 +0000 Subject: [PATCH 03/25] fix a KeyError --- vcr/patch.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vcr/patch.py b/vcr/patch.py index d1527a506..d6dd9b7a3 100644 --- a/vcr/patch.py +++ b/vcr/patch.py @@ -386,7 +386,10 @@ def __exit__(self, *args): while pool.pool and not pool.pool.empty() and connections: connection = pool.pool.get() if isinstance(connection, self._connection_class): - connections.remove(connection) + try: + connections.remove(connection) + except KeyError: + pass connection.close() else: readd_connections.append(connection) From bddec2e62a7f61db70e351684ac51e9cf6b374ff Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 11:08:31 +0000 Subject: [PATCH 04/25] use socketserver.ThreadingTCPServer as a contextmanager --- tests/integration/test_proxy.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/integration/test_proxy.py b/tests/integration/test_proxy.py index 7366d33e6..c49ea6b0d 100644 --- a/tests/integration/test_proxy.py +++ b/tests/integration/test_proxy.py @@ -39,12 +39,12 @@ def do_GET(self): @pytest.fixture(scope="session") def proxy_server(): - httpd = socketserver.ThreadingTCPServer(("", 0), Proxy) - proxy_process = threading.Thread(target=httpd.serve_forever) - proxy_process.start() - yield "http://{}:{}".format(*httpd.server_address) - httpd.shutdown() - proxy_process.join() + with socketserver.ThreadingTCPServer(("", 0), Proxy) as httpd: + proxy_process = threading.Thread(target=httpd.serve_forever) + proxy_process.start() + yield "http://{}:{}".format(*httpd.server_address) + httpd.shutdown() + proxy_process.join() def test_use_proxy(tmpdir, httpbin, proxy_server): From 3919cb2573387799792020a6a37795ef084d3332 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 11:08:49 +0000 Subject: [PATCH 05/25] remember to close the VCRHTTPSConnection --- tests/unit/test_stubs.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_stubs.py b/tests/unit/test_stubs.py index efb6ce8cb..e9e5aaddb 100644 --- a/tests/unit/test_stubs.py +++ b/tests/unit/test_stubs.py @@ -1,3 +1,4 @@ +import contextlib from unittest import mock from pytest import mark @@ -16,7 +17,7 @@ def test_setting_of_attributes_get_propagated_to_real_connection(self): @mark.online @mock.patch("vcr.cassette.Cassette.can_play_response_for", return_value=False) def testing_connect(*args): - vcr_connection = VCRHTTPSConnection("www.google.com") - vcr_connection.cassette = Cassette("test", record_mode=mode.ALL) - vcr_connection.real_connection.connect() - assert vcr_connection.real_connection.sock is not None + with contextlib.closing(VCRHTTPSConnection("www.google.com")) as vcr_connection: + vcr_connection.cassette = Cassette("test", record_mode=mode.ALL) + vcr_connection.real_connection.connect() + assert vcr_connection.real_connection.sock is not None From f075c8b0b498faa08f015488abd292dc3ae7a32f Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 11:39:16 +0000 Subject: [PATCH 06/25] close aiohttp session on errors --- tests/integration/aiohttp_utils.py | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/integration/aiohttp_utils.py b/tests/integration/aiohttp_utils.py index 71b59e879..7c650913a 100644 --- a/tests/integration/aiohttp_utils.py +++ b/tests/integration/aiohttp_utils.py @@ -5,24 +5,24 @@ async def aiohttp_request(loop, method, url, output="text", encoding="utf-8", content_type=None, **kwargs): - session = aiohttp.ClientSession(loop=loop) - response_ctx = session.request(method, url, **kwargs) - - response = await response_ctx.__aenter__() - if output == "text": - content = await response.text() - elif output == "json": - content_type = content_type or "application/json" - content = await response.json(encoding=encoding, content_type=content_type) - elif output == "raw": - content = await response.read() - elif output == "stream": - content = await response.content.read() - - response_ctx._resp.close() - await session.close() - - return response, content + async with aiohttp.ClientSession(loop=loop) as session: + response_ctx = session.request(method, url, **kwargs) + + response = await response_ctx.__aenter__() + if output == "text": + content = await response.text() + elif output == "json": + content_type = content_type or "application/json" + content = await response.json(encoding=encoding, content_type=content_type) + elif output == "raw": + content = await response.read() + elif output == "stream": + content = await response.content.read() + + response_ctx._resp.close() + await session.close() + + return response, content def aiohttp_app(): From 895ae205caf06cc066d40c93b4386252d97fd414 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 11:39:51 +0000 Subject: [PATCH 07/25] use asyncio.run to run coroutines --- tests/integration/test_aiohttp.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_aiohttp.py b/tests/integration/test_aiohttp.py index a740ef213..0af2d498d 100644 --- a/tests/integration/test_aiohttp.py +++ b/tests/integration/test_aiohttp.py @@ -14,10 +14,10 @@ def run_in_loop(fn): - with contextlib.closing(asyncio.new_event_loop()) as loop: - asyncio.set_event_loop(loop) - task = loop.create_task(fn(loop)) - return loop.run_until_complete(task) + async def wrapper(): + return await fn(asyncio.get_running_loop()) + + return asyncio.run(wrapper()) def request(method, url, output="text", **kwargs): From 97de8a0fce6e1b49d60754c03424a4799f1b19c5 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 13:09:13 +0000 Subject: [PATCH 08/25] ignore warning from dateutil --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9c92396a4..a30f43aa3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,10 @@ addopts = [ "--strict-markers", ] markers = ["online"] -filterwarnings = ["error"] +filterwarnings = [ + "error", + '''ignore:datetime\.datetime\.utcfromtimestamp\(\) is deprecated and scheduled for removal in a future version.*:DeprecationWarning''' +] [tool.ruff] select = [ From 73d11e80eb064b98030aa041a37aca31460db9ae Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 13:34:52 +0000 Subject: [PATCH 09/25] fix httpx resource warnings --- tests/integration/test_httpx.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/integration/test_httpx.py b/tests/integration/test_httpx.py index 723daed7b..d81b06530 100644 --- a/tests/integration/test_httpx.py +++ b/tests/integration/test_httpx.py @@ -28,21 +28,27 @@ class DoSyncRequest(BaseDoRequest): _client_class = httpx.Client def __enter__(self): + self._client = self._make_client() return self def __exit__(self, *args): - pass + self._client.close() + del self._client @property def client(self): try: return self._client - except AttributeError: - self._client = self._make_client() - return self._client + except AttributeError as e: + raise ValueError('To access sync client, use "with do_request() as client"') from e def __call__(self, *args, **kwargs): - return self.client.request(*args, timeout=60, **kwargs) + if hasattr(self, "_client"): + return self.client.request(*args, timeout=60, **kwargs) + + # Use one-time context and dispose of the client afterwards + with self: + return self.client.request(*args, timeout=60, **kwargs) def stream(self, *args, **kwargs): with self.client.stream(*args, **kwargs) as response: From cf765928aca3c3696d6eea054bd68b1b6c159985 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 13:35:31 +0000 Subject: [PATCH 10/25] remove redundant contextlib import --- tests/integration/test_aiohttp.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/test_aiohttp.py b/tests/integration/test_aiohttp.py index 0af2d498d..e560901c2 100644 --- a/tests/integration/test_aiohttp.py +++ b/tests/integration/test_aiohttp.py @@ -1,4 +1,3 @@ -import contextlib import logging import urllib.parse From 356ff4122ca97722022bcd79f4b774b150b04966 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 13:40:06 +0000 Subject: [PATCH 11/25] fix sync do_request().stream --- tests/integration/test_httpx.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_httpx.py b/tests/integration/test_httpx.py index d81b06530..39d613114 100644 --- a/tests/integration/test_httpx.py +++ b/tests/integration/test_httpx.py @@ -51,8 +51,14 @@ def __call__(self, *args, **kwargs): return self.client.request(*args, timeout=60, **kwargs) def stream(self, *args, **kwargs): - with self.client.stream(*args, **kwargs) as response: - return b"".join(response.iter_bytes()) + if hasattr(self, "_client"): + with self.client.stream(*args, **kwargs) as response: + return b"".join(response.iter_bytes()) + + # Use one-time context and dispose of the client afterwards + with self: + with self.client.stream(*args, **kwargs) as response: + return b"".join(response.iter_bytes()) class DoAsyncRequest(BaseDoRequest): From 80614dbd005d6fbcb76b2a975692125338811517 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 13:51:52 +0000 Subject: [PATCH 12/25] fix resource warning due to pytest-asyncio --- tests/integration/test_aiohttp.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/integration/test_aiohttp.py b/tests/integration/test_aiohttp.py index e560901c2..7bf5d7711 100644 --- a/tests/integration/test_aiohttp.py +++ b/tests/integration/test_aiohttp.py @@ -259,6 +259,12 @@ def test_aiohttp_test_client_json(aiohttp_client, tmpdir): assert cassette.play_count == 1 +def test_cleanup_from_pytest_asyncio(): + # work around https://github.com/pytest-dev/pytest-asyncio/issues/724 + asyncio.get_event_loop().close() + asyncio.set_event_loop(None) + + @pytest.mark.online def test_redirect(tmpdir, httpbin): url = httpbin.url + "/redirect/2" From 5cff354ec8d7a83212426e3730b053685ff642cf Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 14:01:10 +0000 Subject: [PATCH 13/25] Revert "fix a KeyError" This reverts commit fa789e975b08ba1a40fc7163fc72a0b26a353ad3. --- vcr/patch.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/vcr/patch.py b/vcr/patch.py index d6dd9b7a3..d1527a506 100644 --- a/vcr/patch.py +++ b/vcr/patch.py @@ -386,10 +386,7 @@ def __exit__(self, *args): while pool.pool and not pool.pool.empty() and connections: connection = pool.pool.get() if isinstance(connection, self._connection_class): - try: - connections.remove(connection) - except KeyError: - pass + connections.remove(connection) connection.close() else: readd_connections.append(connection) From d76c243513c1c88cdad87d7c9ae4aa32f3417bad Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 14:01:14 +0000 Subject: [PATCH 14/25] Revert "Fix ResourceWarning unclosed socket" This reverts commit f4144359f695f089480ef16b320fd6e993d47337. --- vcr/patch.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/vcr/patch.py b/vcr/patch.py index d1527a506..a9e86bd4a 100644 --- a/vcr/patch.py +++ b/vcr/patch.py @@ -371,7 +371,6 @@ def __init__(self, connection_class): def add_connection_to_pool_entry(self, pool, connection): if isinstance(connection, self._connection_class): self._connection_pool_to_connections.setdefault(pool, set()).add(connection) - pool._put_conn(connection) def remove_connection_to_pool_entry(self, pool, connection): if isinstance(connection, self._connection_class): @@ -387,7 +386,6 @@ def __exit__(self, *args): connection = pool.pool.get() if isinstance(connection, self._connection_class): connections.remove(connection) - connection.close() else: readd_connections.append(connection) for connection in readd_connections: From d39c26b358544d96520c024a336b04fc2fad436a Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 14:26:44 +0000 Subject: [PATCH 15/25] remember to close removed connections --- vcr/patch.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/vcr/patch.py b/vcr/patch.py index a9e86bd4a..68701a380 100644 --- a/vcr/patch.py +++ b/vcr/patch.py @@ -372,10 +372,6 @@ def add_connection_to_pool_entry(self, pool, connection): if isinstance(connection, self._connection_class): self._connection_pool_to_connections.setdefault(pool, set()).add(connection) - def remove_connection_to_pool_entry(self, pool, connection): - if isinstance(connection, self._connection_class): - self._connection_pool_to_connections[self._connection_class].remove(connection) - def __enter__(self): return self @@ -386,6 +382,7 @@ def __exit__(self, *args): connection = pool.pool.get() if isinstance(connection, self._connection_class): connections.remove(connection) + connection.close() else: readd_connections.append(connection) for connection in readd_connections: From 8e13af2ee9c1815f128b3a654310a0e92a20afda Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 18:38:56 +0000 Subject: [PATCH 16/25] use context manager for requests.Session --- tests/integration/test_wild.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/integration/test_wild.py b/tests/integration/test_wild.py index 6e0df0877..2db77442c 100644 --- a/tests/integration/test_wild.py +++ b/tests/integration/test_wild.py @@ -63,12 +63,12 @@ def test_flickr_should_respond_with_200(tmpdir): def test_cookies(tmpdir, httpbin): testfile = str(tmpdir.join("cookies.yml")) with vcr.use_cassette(testfile): - s = requests.Session() - s.get(httpbin.url + "/cookies/set?k1=v1&k2=v2") - assert s.cookies.keys() == ["k1", "k2"] + with requests.Session() as s: + s.get(httpbin.url + "/cookies/set?k1=v1&k2=v2") + assert s.cookies.keys() == ["k1", "k2"] - r2 = s.get(httpbin.url + "/cookies") - assert sorted(r2.json()["cookies"].keys()) == ["k1", "k2"] + r2 = s.get(httpbin.url + "/cookies") + assert sorted(r2.json()["cookies"].keys()) == ["k1", "k2"] @pytest.mark.online From cc4d03c62eb12d5bdfde813f8a09c6d355670a52 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 15 Dec 2023 19:11:25 +0000 Subject: [PATCH 17/25] close unremoved connections (pool already removed the connection) --- vcr/patch.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vcr/patch.py b/vcr/patch.py index 68701a380..c373641b0 100644 --- a/vcr/patch.py +++ b/vcr/patch.py @@ -387,6 +387,8 @@ def __exit__(self, *args): readd_connections.append(connection) for connection in readd_connections: pool._put_conn(connection) + for connection in connections: + connection.close() def reset_patchers(): From 666686b5420508b84f9811cec4e0e5927172dee2 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 23 Jan 2024 11:12:02 +0000 Subject: [PATCH 18/25] restore pytest-tornado --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 7a49061d3..31e06c322 100644 --- a/setup.py +++ b/setup.py @@ -67,6 +67,7 @@ def run_tests(self): "pytest-asyncio", "pytest-cov", "pytest-httpbin", + "pytest-tornado", "pytest", "requests>=2.22.0", "tornado", From a093fb177d960e1f74ef52fd91d753b0dfed5476 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 23 Jan 2024 12:09:39 +0000 Subject: [PATCH 19/25] add new deprecation warnings for tornado tests --- pyproject.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a30f43aa3..46a533334 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,11 @@ addopts = [ markers = ["online"] filterwarnings = [ "error", - '''ignore:datetime\.datetime\.utcfromtimestamp\(\) is deprecated and scheduled for removal in a future version.*:DeprecationWarning''' + '''ignore:datetime\.datetime\.utcfromtimestamp\(\) is deprecated and scheduled for removal in a future version.*:DeprecationWarning''', + '''ignore:There is no current event loop:DeprecationWarning''', + '''ignore:make_current is deprecated; start the event loop first:DeprecationWarning''', + '''ignore:clear_current is deprecated:DeprecationWarning''', + '''ignore:the \(type, exc, tb\) signature of throw\(\) is deprecated, use the single-arg signature instead.:DeprecationWarning''', ] [tool.ruff] From c6667ac56caa189c40bb8632e2c0b1aef4b6fce5 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 23 Jan 2024 12:12:05 +0000 Subject: [PATCH 20/25] restore scheme fixture for tests --- tests/integration/test_tornado.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/integration/test_tornado.py b/tests/integration/test_tornado.py index 0ba489739..eed090b9d 100644 --- a/tests/integration/test_tornado.py +++ b/tests/integration/test_tornado.py @@ -17,6 +17,12 @@ supports_raise_error = tornado.version_info >= (4,) +@pytest.fixture(params=["https", "http"]) +def scheme(request): + """Fixture that returns both http and https.""" + return request.param + + @pytest.fixture(params=["simple", "curl", "default"]) def get_client(request): if request.param == "simple": From db1f5b0dee115b37366e9eb6a577cc9970e23139 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 23 Jan 2024 12:12:52 +0000 Subject: [PATCH 21/25] tornado 6 changes raise_error behaviour --- tests/integration/test_tornado.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/integration/test_tornado.py b/tests/integration/test_tornado.py index eed090b9d..579734354 100644 --- a/tests/integration/test_tornado.py +++ b/tests/integration/test_tornado.py @@ -15,6 +15,7 @@ # whether the current version of Tornado supports the raise_error argument for # fetch(). supports_raise_error = tornado.version_info >= (4,) +raise_error_for_response_code_only = tornado.version_info >= (6,) @pytest.fixture(params=["https", "http"]) @@ -239,6 +240,10 @@ def callback(chunk): @pytest.mark.skipif(not supports_raise_error, reason="raise_error unavailable in tornado <= 3") +@pytest.mark.skipif( + raise_error_for_response_code_only, + reason="raise_error only ignores HTTPErrors due to response code", +) @pytest.mark.gen_test def test_unsupported_features_raise_error_disabled(get_client, tmpdir): """Ensure that the exception for an AsyncHTTPClient feature not being @@ -274,6 +279,10 @@ def test_cannot_overwrite_cassette_raises_in_future(get_client, tmpdir): @pytest.mark.skipif(not supports_raise_error, reason="raise_error unavailable in tornado <= 3") +@pytest.mark.skipif( + raise_error_for_response_code_only, + reason="raise_error only ignores HTTPErrors due to response code", +) @pytest.mark.gen_test def test_cannot_overwrite_cassette_raise_error_disabled(get_client, tmpdir): """Ensure that CannotOverwriteExistingCassetteException is not raised if From 6d7a842a333ac641300ec7cc384424178e72db56 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 23 Jan 2024 12:14:58 +0000 Subject: [PATCH 22/25] fix test_tornado_exception_can_be_caught RuntimeError: generator raised StopIteration --- vcr/cassette.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/vcr/cassette.py b/vcr/cassette.py index 1b8b1f2ab..fad0d25d2 100644 --- a/vcr/cassette.py +++ b/vcr/cassette.py @@ -3,7 +3,6 @@ import copy import inspect import logging -import sys from asyncio import iscoroutinefunction import wrapt @@ -126,20 +125,7 @@ def _handle_generator(self, fn): duration of the generator. """ with self as cassette: - coroutine = fn(cassette) - # We don't need to catch StopIteration. The caller (Tornado's - # gen.coroutine, for example) will handle that. - to_yield = next(coroutine) - while True: - try: - to_send = yield to_yield - except Exception: - to_yield = coroutine.throw(*sys.exc_info()) - else: - try: - to_yield = coroutine.send(to_send) - except StopIteration: - break + yield from fn(cassette) def _handle_function(self, fn): with self as cassette: From b7f6c2fce2c7b6e1ea24cdda5d5f041ba6cc1a01 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 23 Jan 2024 12:24:07 +0000 Subject: [PATCH 23/25] mark tornado tests as online --- tests/integration/test_tornado.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/integration/test_tornado.py b/tests/integration/test_tornado.py index 579734354..eeb326f56 100644 --- a/tests/integration/test_tornado.py +++ b/tests/integration/test_tornado.py @@ -51,6 +51,7 @@ def post(client, url, data=None, **kwargs): return client.fetch(http.HTTPRequest(url, method="POST", **kwargs)) +@pytest.mark.online @pytest.mark.gen_test def test_status_code(get_client, scheme, tmpdir): """Ensure that we can read the status code""" @@ -63,6 +64,7 @@ def test_status_code(get_client, scheme, tmpdir): assert 1 == cass.play_count +@pytest.mark.online @pytest.mark.gen_test def test_headers(get_client, scheme, tmpdir): """Ensure that we can read the headers back""" @@ -75,6 +77,7 @@ def test_headers(get_client, scheme, tmpdir): assert 1 == cass.play_count +@pytest.mark.online @pytest.mark.gen_test def test_body(get_client, tmpdir, scheme): """Ensure the responses are all identical enough""" @@ -101,6 +104,7 @@ def test_effective_url(get_client, tmpdir, httpbin): assert 1 == cass.play_count +@pytest.mark.online @pytest.mark.gen_test def test_auth(get_client, tmpdir, scheme): """Ensure that we can handle basic auth""" @@ -116,6 +120,7 @@ def test_auth(get_client, tmpdir, scheme): assert 1 == cass.play_count +@pytest.mark.online @pytest.mark.gen_test def test_auth_failed(get_client, tmpdir, scheme): """Ensure that we can save failed auth statuses""" @@ -139,6 +144,7 @@ def test_auth_failed(get_client, tmpdir, scheme): assert 1 == cass.play_count +@pytest.mark.online @pytest.mark.gen_test def test_post(get_client, tmpdir, scheme): """Ensure that we can post and cache the results""" @@ -154,6 +160,7 @@ def test_post(get_client, tmpdir, scheme): assert 1 == cass.play_count +@pytest.mark.online @pytest.mark.gen_test def test_redirects(get_client, tmpdir, scheme): """Ensure that we can handle redirects""" @@ -166,6 +173,7 @@ def test_redirects(get_client, tmpdir, scheme): assert cass.play_count == 1 +@pytest.mark.online @pytest.mark.gen_test def test_cross_scheme(get_client, tmpdir, scheme): """Ensure that requests between schemes are treated separately""" @@ -185,6 +193,7 @@ def test_cross_scheme(get_client, tmpdir, scheme): assert cass.play_count == 2 +@pytest.mark.online @pytest.mark.gen_test def test_gzip(get_client, tmpdir, scheme): """ @@ -210,6 +219,7 @@ def test_gzip(get_client, tmpdir, scheme): assert 1 == cass.play_count +@pytest.mark.online @pytest.mark.gen_test def test_https_with_cert_validation_disabled(get_client, tmpdir): cass_path = str(tmpdir.join("cert_validation_disabled.yaml")) @@ -263,6 +273,7 @@ def callback(chunk): assert "not yet supported by VCR" in str(response.error) +@pytest.mark.online @pytest.mark.gen_test def test_cannot_overwrite_cassette_raises_in_future(get_client, tmpdir): """Ensure that CannotOverwriteExistingCassetteException is raised inside @@ -318,6 +329,7 @@ def test_tornado_exception_can_be_caught(get_client): assert e.code == 404 +@pytest.mark.online @pytest.mark.gen_test def test_existing_references_get_patched(tmpdir): from tornado.httpclient import AsyncHTTPClient @@ -331,6 +343,7 @@ def test_existing_references_get_patched(tmpdir): assert cass.play_count == 1 +@pytest.mark.online @pytest.mark.gen_test def test_existing_instances_get_patched(get_client, tmpdir): """Ensure that existing instances of AsyncHTTPClient get patched upon @@ -346,6 +359,7 @@ def test_existing_instances_get_patched(get_client, tmpdir): assert cass.play_count == 1 +@pytest.mark.online @pytest.mark.gen_test def test_request_time_is_set(get_client, tmpdir): """Ensures that the request_time on HTTPResponses is set.""" From 42b4a5d2fafcf15b9a50b25180415c9ea66dfccd Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 23 Jan 2024 12:33:15 +0000 Subject: [PATCH 24/25] move off of mockbin on tornado tests also --- tests/integration/test_tornado.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_tornado.py b/tests/integration/test_tornado.py index eeb326f56..16455a3ab 100644 --- a/tests/integration/test_tornado.py +++ b/tests/integration/test_tornado.py @@ -162,9 +162,9 @@ def test_post(get_client, tmpdir, scheme): @pytest.mark.online @pytest.mark.gen_test -def test_redirects(get_client, tmpdir, scheme): +def test_redirects(get_client, tmpdir, httpbin): """Ensure that we can handle redirects""" - url = scheme + "://mockbin.org/redirect/301?url=bytes/1024" + url = httpbin + "/redirect-to?url=bytes/1024&status_code=301" with vcr.use_cassette(str(tmpdir.join("requests.yaml"))): content = (yield get(get_client(), url)).body From 784b2dcb296740bf282adbfecffccc5b421adf4f Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 23 Jan 2024 12:33:45 +0000 Subject: [PATCH 25/25] tornado test_redirects is no longer an online test --- tests/integration/test_tornado.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/test_tornado.py b/tests/integration/test_tornado.py index 16455a3ab..dc1bb8b42 100644 --- a/tests/integration/test_tornado.py +++ b/tests/integration/test_tornado.py @@ -160,7 +160,6 @@ def test_post(get_client, tmpdir, scheme): assert 1 == cass.play_count -@pytest.mark.online @pytest.mark.gen_test def test_redirects(get_client, tmpdir, httpbin): """Ensure that we can handle redirects"""