From 81611cdd586828179fe4ba64f56ef0fe37491b8e Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 13 Sep 2015 01:59:28 +0300 Subject: [PATCH 01/17] Support async with client_response --- aiohttp/client_reqrep.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index f3d2248965b..b3923540f51 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -743,3 +743,14 @@ def json(self, *, encoding=None, loads=json.loads): encoding = self._get_encoding() return loads(self._content.decode(encoding)) + + @asyncio.coroutine + def __aenter__(self): + return self + + @asyncio.coroutine + def __aexit__(self, exc_type, exc_val, exc_tb): + if exc_type is None: + yield from self.release() + else: + self.close() From 3a120dcf86f7051e76ea0d443f0bd76e58c59357 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 27 Sep 2015 15:51:05 +0300 Subject: [PATCH 02/17] Add test for await in client api --- tests/conftest.py | 45 +++++++++++++++++++++++++++++++----- tests/test_py35/test_resp.py | 17 ++++++++++++++ 2 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 tests/test_py35/test_resp.py diff --git a/tests/conftest.py b/tests/conftest.py index aec3f40e0b6..e3978904889 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,27 +3,60 @@ import socket import sys +from aiohttp import web + @pytest.fixture def unused_port(): def f(): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('127.0.0.1', 0)) return s.getsockname()[1] return f -@pytest.fixture +@pytest.yield_fixture def loop(request): - old_loop = asyncio.get_event_loop() + try: + old_loop = asyncio.get_event_loop() + except RuntimeError: + old_loop = None loop = asyncio.new_event_loop() asyncio.set_event_loop(None) - def fin(): - loop.close() + yield loop + + loop.close() + if old_loop: asyncio.set_event_loop(old_loop) - request.addfinalizer(fin) - return loop + +@pytest.yield_fixture +def create_server(loop, unused_port): + app = handler = srv = None + + @asyncio.coroutine + def create(*, debug=False, ssl_ctx=None): + nonlocal app, handler, srv + app = web.Application(loop=loop) + port = unused_port() + handler = app.make_handler(debug=debug, keep_alive_on=False) + srv = yield from loop.create_server(handler, '127.0.0.1', port, + ssl=ssl_ctx) + proto = "https" if ssl_ctx else "http" + url = "{}://127.0.0.1:{}".format(proto, port) + return app, url + + yield create + + @asyncio.coroutine + def finish(): + yield from handler.finish_connections() + yield from app.finish() + srv.close() + yield from srv.wait_closed() + + loop.run_until_complete(finish()) @pytest.mark.tryfirst diff --git a/tests/test_py35/test_resp.py b/tests/test_py35/test_resp.py new file mode 100644 index 00000000000..5437b42c362 --- /dev/null +++ b/tests/test_py35/test_resp.py @@ -0,0 +1,17 @@ +import pytest + +import aiohttp +from aiohttp import web + + +@pytest.mark.run_loop +async def test_await(create_server, loop): + + async def handler(request): + return web.HTTPOk() + + app, url = await create_server() + app.router.add_route('GET', '/', handler) + resp = await aiohttp.get(url+'/', loop=loop) + assert resp.status == 200 + await resp.release() From 4fd1e91ab0c7c070f070b80016b236ccbba64181 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 27 Sep 2015 16:12:31 +0300 Subject: [PATCH 03/17] Add test for async with --- tests/test_py35/test_resp.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_py35/test_resp.py b/tests/test_py35/test_resp.py index 5437b42c362..9c0e9a14d33 100644 --- a/tests/test_py35/test_resp.py +++ b/tests/test_py35/test_resp.py @@ -14,4 +14,21 @@ async def handler(request): app.router.add_route('GET', '/', handler) resp = await aiohttp.get(url+'/', loop=loop) assert resp.status == 200 + assert resp.connection is not None await resp.release() + assert resp.connection is None + + +@pytest.mark.run_loop +async def test_response_context_manager(create_server, loop): + + async def handler(request): + return web.HTTPOk() + + app, url = await create_server() + app.router.add_route('GET', '/', handler) + resp = await aiohttp.get(url+'/', loop=loop) + async with resp: + assert resp.status == 200 + assert resp.connection is not None + assert resp.connection is None From ed18f26b4e5923cbcec958a471281f2616a84cd9 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 27 Sep 2015 16:16:59 +0300 Subject: [PATCH 04/17] Update doc --- docs/client_reference.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/client_reference.rst b/docs/client_reference.rst index 501c3d2f44a..b6d495350c6 100644 --- a/docs/client_reference.rst +++ b/docs/client_reference.rst @@ -884,6 +884,19 @@ Response object User never creates the instance of ClientResponse class but gets it from API calls. + :class:`ClientResponse` supports async context manager protocol, e.g.:: + + resp = await client_session.get(url) + async with resp: + assert resp.status == 200 + + After exiting from ``async with`` block response object will be + *released* (see :meth:`release` coroutine). + + .. versionadded:: 0.18 + + Support for ``async with``. + .. attribute:: version Response's version, :class:`HttpVersion` instance. From d56471a35a80e987a2a75a57d38330c5f6431be4 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 27 Sep 2015 16:21:08 +0300 Subject: [PATCH 05/17] Add __aenter__/__aexit__ for 3.5+ only --- aiohttp/client_reqrep.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index 516890c80bb..06f45d11f53 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -24,6 +24,7 @@ from .protocol import HttpMessage PY_341 = sys.version_info >= (3, 4, 1) +PY_35 = sys.version_info >= (3, 5) HTTP_PORT = 80 HTTPS_PORT = 443 @@ -750,13 +751,14 @@ def json(self, *, encoding=None, loads=json.loads): return loads(self._content.decode(encoding)) - @asyncio.coroutine - def __aenter__(self): - return self + if PY_35: + @asyncio.coroutine + def __aenter__(self): + return self - @asyncio.coroutine - def __aexit__(self, exc_type, exc_val, exc_tb): - if exc_type is None: - yield from self.release() - else: - self.close() + @asyncio.coroutine + def __aexit__(self, exc_type, exc_val, exc_tb): + if exc_type is None: + yield from self.release() + else: + self.close() From b0fef4ed92cde72305a2d85f3e96adde93f82547 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 27 Sep 2015 18:59:28 +0300 Subject: [PATCH 06/17] Add test for context manager --- tests/test_py35/test_resp.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_py35/test_resp.py b/tests/test_py35/test_resp.py index 9c0e9a14d33..fa2f9a2ed44 100644 --- a/tests/test_py35/test_resp.py +++ b/tests/test_py35/test_resp.py @@ -32,3 +32,18 @@ async def handler(request): assert resp.status == 200 assert resp.connection is not None assert resp.connection is None + + +@pytest.mark.run_loop +async def test_client_api_context_manager(create_server, loop): + + async def handler(request): + return web.HTTPOk() + + app, url = await create_server() + app.router.add_route('GET', '/', handler) + + async with aiohttp.get(url+'/', loop=loop) as resp: + assert resp.status == 200 + assert resp.connection is not None + assert resp.connection is None From ab491684f73f75d27d46939bb08f8ea37c969a49 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 27 Sep 2015 19:15:12 +0300 Subject: [PATCH 07/17] Don't do flake8 check for tests and examples for python < 3.5 --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b53b408eda3..93bbcffe882 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,10 @@ install: - python setup.py develop script: - - flake8 aiohttp examples tests + - flake8 aiohttp + - if python -c "import sys; sys.exit(sys.version_info >= (3,5)"; then + flake8 examples tests; + fi - coverage run --source=aiohttp setup.py test - python setup.py check -rm From a1d8fc94ae995dda39aff00eac7e698b53a09df8 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 27 Sep 2015 23:26:47 +0300 Subject: [PATCH 08/17] Work on async with --- aiohttp/client.py | 232 ++++++++++++++++++++++++----------- tests/test_py35/test_resp.py | 7 ++ 2 files changed, 164 insertions(+), 75 deletions(-) diff --git a/aiohttp/client.py b/aiohttp/client.py index a158496c3d3..cb871120393 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -23,6 +23,7 @@ 'delete', 'post', 'put', 'patch') PY_341 = sys.version_info >= (3, 4, 1) +PY_35 = sys.version_info >= (3, 5) class ClientSession: @@ -89,7 +90,6 @@ def __del__(self, _warnings=warnings): context['source_traceback'] = self._source_traceback self._loop.call_exception_handler(context) - @asyncio.coroutine def request(self, method, url, *, params=None, data=None, @@ -106,6 +106,41 @@ def request(self, method, url, *, expect100=False, read_until_eof=True): """Perform HTTP request.""" + return _RequestContextManager( + self._request( + method, + url, + params=params, + data=data, + headers=headers, + skip_auto_headers=skip_auto_headers, + files=files, + auth=auth, + allow_redirects=allow_redirects, + max_redirects=max_redirects, + encoding=encoding, + version=version, + compress=compress, + chunked=chunked, + expect100=expect100, + read_until_eof=read_until_eof)) + + @asyncio.coroutine + def _request(self, method, url, *, + params=None, + data=None, + headers=None, + skip_auto_headers=None, + files=None, + auth=None, + allow_redirects=True, + max_redirects=10, + encoding='utf-8', + version=aiohttp.HttpVersion11, + compress=None, + chunked=None, + expect100=False, + read_until_eof=True): if self.closed: raise RuntimeError('Session is closed') @@ -289,60 +324,53 @@ def _prepare_headers(self, headers): added_names.add(key) return result - @asyncio.coroutine def get(self, url, *, allow_redirects=True, **kwargs): """Perform HTTP GET request.""" - resp = yield from self.request(hdrs.METH_GET, url, - allow_redirects=allow_redirects, - **kwargs) - return resp + return _RequestContextManager( + self._request(hdrs.METH_GET, url, + allow_redirects=allow_redirects, + **kwargs)) - @asyncio.coroutine def options(self, url, *, allow_redirects=True, **kwargs): """Perform HTTP OPTIONS request.""" - resp = yield from self.request(hdrs.METH_OPTIONS, url, - allow_redirects=allow_redirects, - **kwargs) - return resp + return _RequestContextManager( + self._request(hdrs.METH_OPTIONS, url, + allow_redirects=allow_redirects, + **kwargs)) - @asyncio.coroutine def head(self, url, *, allow_redirects=False, **kwargs): """Perform HTTP HEAD request.""" - resp = yield from self.request(hdrs.METH_HEAD, url, - allow_redirects=allow_redirects, - **kwargs) - return resp + return _RequestContextManager( + self._request(hdrs.METH_HEAD, url, + allow_redirects=allow_redirects, + **kwargs)) - @asyncio.coroutine def post(self, url, *, data=None, **kwargs): """Perform HTTP POST request.""" - resp = yield from self.request(hdrs.METH_POST, url, - data=data, - **kwargs) - return resp + return _RequestContextManager( + self._request(hdrs.METH_POST, url, + data=data, + **kwargs)) - @asyncio.coroutine def put(self, url, *, data=None, **kwargs): """Perform HTTP PUT request.""" - resp = yield from self.request(hdrs.METH_PUT, url, - data=data, - **kwargs) - return resp + return _RequestContextManager( + self._request(hdrs.METH_PUT, url, + data=data, + **kwargs)) - @asyncio.coroutine def patch(self, url, *, data=None, **kwargs): """Perform HTTP PATCH request.""" - resp = yield from self.request(hdrs.METH_PATCH, url, - data=data, - **kwargs) - return resp + return _RequestContextManager( + self._request(hdrs.METH_PATCH, url, + data=data, + **kwargs)) - @asyncio.coroutine def delete(self, url, **kwargs): """Perform HTTP DELETE request.""" - resp = yield from self.request(hdrs.METH_DELETE, url, - **kwargs) - return resp + return _RequestContextManager( + self._request(hdrs.METH_DELETE, url, + **kwargs)) def close(self): """Close underlying connector. @@ -384,12 +412,81 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): self.close() +if PY_35: + from collections.abc import Coroutine + base = Coroutine +else: + base = object + + +class _RequestContextManager(base): + + __slots__ = ('_coro', '_resp') + + def __init__(self, coro): + self._coro = coro + self._resp = None + + def send(self, value): + return self._coro.send(value) + + def throw(self, typ, val=None, tb=None): + return self._coro.throw(typ, val, tb) + + def close(self): + return self._coro.close() + + @asyncio.coroutine + def __iter__(self): + resp = yield from self._coro + return resp + + if PY_35: + def __await__(self): + resp = yield from self._coro + return resp + + @asyncio.coroutine + def __aenter__(self): + self._resp = yield from self._coro + return self._resp + + @asyncio.coroutine + def __aexit__(self, exc_type, exc, tb): + yield from self._resp.release() + + +class _DetachedRequestContextManager(_RequestContextManager): + + __slots__ = _RequestContextManager.__slots__ + ('_session', ) + + def __init__(self, coro, session): + super().__init__(coro) + self._session = session + + @asyncio.coroutine + def __iter__(self): + resp = yield from self._coro + self._session.detach() + return resp + + if PY_35: + def __await__(self): + resp = yield from self._coro + self._session.detach() + return resp + + @asyncio.coroutine + def __aexit__(self, exc_type, exc, tb): + yield from self._resp.release() + self._session.detach() + -@asyncio.coroutine def request(method, url, *, params=None, data=None, headers=None, + skip_auto_headers=None, cookies=None, files=None, auth=None, @@ -460,63 +557,48 @@ def request(method, url, *, cookies=cookies, connector=connector, **kwargs) - try: - resp = yield from session.request(method, url, - params=params, - data=data, - headers=headers, - files=files, - auth=auth, - allow_redirects=allow_redirects, - max_redirects=max_redirects, - encoding=encoding, - version=version, - compress=compress, - chunked=chunked, - expect100=expect100, - read_until_eof=read_until_eof) - return resp - finally: - session.detach() + return _DetachedRequestContextManager( + session._request(method, url, + params=params, + data=data, + headers=headers, + skip_auto_headers=skip_auto_headers, + files=files, + auth=auth, + allow_redirects=allow_redirects, + max_redirects=max_redirects, + encoding=encoding, + version=version, + compress=compress, + chunked=chunked, + expect100=expect100, + read_until_eof=read_until_eof), + session=session) -@asyncio.coroutine def get(url, **kwargs): - ret = yield from request(hdrs.METH_GET, url, **kwargs) - return ret + return request(hdrs.METH_GET, url, **kwargs) -@asyncio.coroutine def options(url, **kwargs): - ret = yield from request(hdrs.METH_OPTIONS, url, **kwargs) - return ret + return request(hdrs.METH_OPTIONS, url, **kwargs) -@asyncio.coroutine def head(url, **kwargs): - ret = yield from request(hdrs.METH_HEAD, url, **kwargs) - return ret + return request(hdrs.METH_HEAD, url, **kwargs) -@asyncio.coroutine def post(url, **kwargs): - ret = yield from request(hdrs.METH_POST, url, **kwargs) - return ret + return request(hdrs.METH_POST, url, **kwargs) -@asyncio.coroutine def put(url, **kwargs): - ret = yield from request(hdrs.METH_PUT, url, **kwargs) - return ret + return request(hdrs.METH_PUT, url, **kwargs) -@asyncio.coroutine def patch(url, **kwargs): - ret = yield from request(hdrs.METH_PATCH, url, **kwargs) - return ret + return request(hdrs.METH_PATCH, url, **kwargs) -@asyncio.coroutine def delete(url, **kwargs): - ret = yield from request(hdrs.METH_DELETE, url, **kwargs) - return ret + return request(hdrs.METH_DELETE, url, **kwargs) diff --git a/tests/test_py35/test_resp.py b/tests/test_py35/test_resp.py index fa2f9a2ed44..34c6187820b 100644 --- a/tests/test_py35/test_resp.py +++ b/tests/test_py35/test_resp.py @@ -1,7 +1,10 @@ import pytest import aiohttp +import asyncio from aiohttp import web +from aiohttp.client import _RequestContextManager +from collections.abc import Coroutine @pytest.mark.run_loop @@ -47,3 +50,7 @@ async def handler(request): assert resp.status == 200 assert resp.connection is not None assert resp.connection is None + + +def test_ctx_manager_is_coroutine(): + assert issubclass(_RequestContextManager, Coroutine) From 784d82df9cb4506291e6b54ccd1409374f5aa71e Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Mon, 28 Sep 2015 21:14:08 +0300 Subject: [PATCH 09/17] Fix more tests --- tests/test_client_session.py | 62 ++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/tests/test_client_session.py b/tests/test_client_session.py index 8e4223bd418..974331ad64a 100644 --- a/tests/test_client_session.py +++ b/tests/test_client_session.py @@ -182,14 +182,14 @@ def _make_one(self, **kwargs): read_until_eof=False) return session, params - @mock.patch("aiohttp.client.ClientSession.request") + @mock.patch("aiohttp.client.ClientSession._request") def test_http_GET(self, patched): session, params = self._make_one() - self.run(session.get( + session.get( "http://test.example.com", params={"x": 1}, - **params)) - self.assertTrue(patched.called, "`ClientSession.request` not called") + **params) + self.assertTrue(patched.called, "`ClientSession._request` not called") self.assertEqual( list(patched.call_args), [("GET", "http://test.example.com",), @@ -199,14 +199,14 @@ def test_http_GET(self, patched): **params)]) session.close() - @mock.patch("aiohttp.client.ClientSession.request") + @mock.patch("aiohttp.client.ClientSession._request") def test_http_OPTIONS(self, patched): session, params = self._make_one() - self.run(session.options( + session.options( "http://opt.example.com", params={"x": 2}, - **params)) - self.assertTrue(patched.called, "`ClientSession.request` not called") + **params) + self.assertTrue(patched.called, "`ClientSession._request` not called") self.assertEqual( list(patched.call_args), [("OPTIONS", "http://opt.example.com",), @@ -216,14 +216,14 @@ def test_http_OPTIONS(self, patched): **params)]) session.close() - @mock.patch("aiohttp.client.ClientSession.request") + @mock.patch("aiohttp.client.ClientSession._request") def test_http_HEAD(self, patched): session, params = self._make_one() - self.run(session.head( + session.head( "http://head.example.com", params={"x": 2}, - **params)) - self.assertTrue(patched.called, "`ClientSession.request` not called") + **params) + self.assertTrue(patched.called, "`ClientSession._request` not called") self.assertEqual( list(patched.call_args), [("HEAD", "http://head.example.com",), @@ -233,74 +233,68 @@ def test_http_HEAD(self, patched): **params)]) session.close() - @mock.patch("aiohttp.client.ClientSession.request") + @mock.patch("aiohttp.client.ClientSession._request") def test_http_POST(self, patched): session, params = self._make_one() - self.run(session.post( + session.post( "http://post.example.com", params={"x": 2}, data="Some_data", - files={"x": '1'}, - **params)) - self.assertTrue(patched.called, "`ClientSession.request` not called") + **params) + self.assertTrue(patched.called, "`ClientSession._request` not called") self.assertEqual( list(patched.call_args), [("POST", "http://post.example.com",), dict( params={"x": 2}, data="Some_data", - files={"x": '1'}, **params)]) session.close() - @mock.patch("aiohttp.client.ClientSession.request") + @mock.patch("aiohttp.client.ClientSession._request") def test_http_PUT(self, patched): session, params = self._make_one() - self.run(session.put( + session.put( "http://put.example.com", params={"x": 2}, data="Some_data", - files={"x": '1'}, - **params)) - self.assertTrue(patched.called, "`ClientSession.request` not called") + **params) + self.assertTrue(patched.called, "`ClientSession._request` not called") self.assertEqual( list(patched.call_args), [("PUT", "http://put.example.com",), dict( params={"x": 2}, data="Some_data", - files={"x": '1'}, **params)]) session.close() - @mock.patch("aiohttp.client.ClientSession.request") + @mock.patch("aiohttp.client.ClientSession._request") def test_http_PATCH(self, patched): session, params = self._make_one() - self.run(session.patch( + session.patch( "http://patch.example.com", params={"x": 2}, data="Some_data", - files={"x": '1'}, - **params)) - self.assertTrue(patched.called, "`ClientSession.request` not called") + **params) + self.assertTrue(patched.called, "`ClientSession._request` not called") self.assertEqual( list(patched.call_args), [("PATCH", "http://patch.example.com",), dict( params={"x": 2}, data="Some_data", - files={"x": '1'}, **params)]) session.close() - @mock.patch("aiohttp.client.ClientSession.request") + @mock.patch("aiohttp.client.ClientSession._request") def test_http_DELETE(self, patched): session, params = self._make_one() - self.run(session.delete( + session.delete( "http://delete.example.com", params={"x": 2}, - **params)) - self.assertTrue(patched.called, "`ClientSession.request` not called") + **params) + self.assertTrue(patched.called, "`ClientSession._request` not called") self.assertEqual( list(patched.call_args), [("DELETE", "http://delete.example.com",), From 0a6e938bbd59e0cd16f3d2f9e477b4bb158116cc Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Mon, 28 Sep 2015 21:18:33 +0300 Subject: [PATCH 10/17] Drop extra spaces --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 93bbcffe882..1cdd2ef436a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ install: - python setup.py develop script: - - flake8 aiohttp + - flake8 aiohttp - if python -c "import sys; sys.exit(sys.version_info >= (3,5)"; then flake8 examples tests; fi From 75d849fa6ae6628ca09ba8f58b0f246204c61996 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Mon, 28 Sep 2015 23:25:45 +0300 Subject: [PATCH 11/17] Convert to super() --- aiohttp/client.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/aiohttp/client.py b/aiohttp/client.py index cb871120393..fe4b0052608 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -453,7 +453,10 @@ def __aenter__(self): @asyncio.coroutine def __aexit__(self, exc_type, exc, tb): - yield from self._resp.release() + if exc_type is not None: + self._resp.close() + else: + yield from self._resp.release() class _DetachedRequestContextManager(_RequestContextManager): @@ -466,19 +469,19 @@ def __init__(self, coro, session): @asyncio.coroutine def __iter__(self): - resp = yield from self._coro + resp = yield from super().__iter__() self._session.detach() return resp if PY_35: def __await__(self): - resp = yield from self._coro + resp = yield from super().__await__() self._session.detach() return resp @asyncio.coroutine def __aexit__(self, exc_type, exc, tb): - yield from self._resp.release() + yield from super().__aexit__(exc_type, exc, tb) self._session.detach() From 327b5879468ee2cfad449c551f8eab9c89ac1943 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 30 Sep 2015 19:46:08 +0300 Subject: [PATCH 12/17] Fix bare .request() and family --- aiohttp/client.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/aiohttp/client.py b/aiohttp/client.py index fe4b0052608..3165d5aa84e 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -467,21 +467,8 @@ def __init__(self, coro, session): super().__init__(coro) self._session = session - @asyncio.coroutine - def __iter__(self): - resp = yield from super().__iter__() - self._session.detach() - return resp - - if PY_35: - def __await__(self): - resp = yield from super().__await__() - self._session.detach() - return resp - - @asyncio.coroutine - def __aexit__(self, exc_type, exc, tb): - yield from super().__aexit__(exc_type, exc, tb) + if PY_341: + def __del__(self): self._session.detach() From 4253b1d75f0365948bac7abfc3f02a688a31c017 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 30 Sep 2015 19:51:54 +0300 Subject: [PATCH 13/17] Fix flake8 error --- tests/test_py35/test_resp.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_py35/test_resp.py b/tests/test_py35/test_resp.py index 34c6187820b..ff6b7d0e096 100644 --- a/tests/test_py35/test_resp.py +++ b/tests/test_py35/test_resp.py @@ -1,7 +1,6 @@ import pytest import aiohttp -import asyncio from aiohttp import web from aiohttp.client import _RequestContextManager from collections.abc import Coroutine From 2e2fa2e77dd6b84e470e72fb3ce241fc820a2f4c Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 30 Sep 2015 20:19:25 +0300 Subject: [PATCH 14/17] Fix .throw() method --- aiohttp/client.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/aiohttp/client.py b/aiohttp/client.py index 3165d5aa84e..0ee7a0e748f 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -431,7 +431,12 @@ def send(self, value): return self._coro.send(value) def throw(self, typ, val=None, tb=None): - return self._coro.throw(typ, val, tb) + if val is None: + return self._coro.throw(typ) + elif tb is None: + return self._coro.throw(typ, val) + else: + return self._coro.throw(typ, val, tb) def close(self): return self._coro.close() From 52b548a5870d1fb4230cf605884b8261fa38890e Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 1 Oct 2015 08:38:34 +0300 Subject: [PATCH 15/17] Add _RequestContextManager to _COROUTINE_TYPES --- aiohttp/client.py | 7 +++++++ tox.ini | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/aiohttp/client.py b/aiohttp/client.py index 0ee7a0e748f..8b823fc4e9b 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -464,6 +464,13 @@ def __aexit__(self, exc_type, exc, tb): yield from self._resp.release() +try: + from asyncio import coroutines + coroutines._COROUTINE_TYPES += (_RequestContextManager) +except: + pass + + class _DetachedRequestContextManager(_RequestContextManager): __slots__ = _RequestContextManager.__slots__ + ('_session', ) diff --git a/tox.ini b/tox.ini index 9f85eb30230..53c86d3dc92 100644 --- a/tox.ini +++ b/tox.ini @@ -17,7 +17,7 @@ deps = commands = # --cov={envsitepackagesdir}/tests # py.test --cov={envsitepackagesdir}/aiohttp tests {posargs} - coverage run -m pytest {posargs} + coverage run -m pytest tests {posargs} mv .coverage .coverage.{envname} setenv = From a541cbe9d9536de3abffe24b43e69809fbf39ab5 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 1 Oct 2015 08:58:47 +0300 Subject: [PATCH 16/17] Fix typo --- aiohttp/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiohttp/client.py b/aiohttp/client.py index 8b823fc4e9b..3a9313a6761 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -466,7 +466,7 @@ def __aexit__(self, exc_type, exc, tb): try: from asyncio import coroutines - coroutines._COROUTINE_TYPES += (_RequestContextManager) + coroutines._COROUTINE_TYPES += (_RequestContextManager,) except: pass From ebf366b67c450a65d945bb47181557b89b8ab362 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 1 Oct 2015 09:02:16 +0300 Subject: [PATCH 17/17] Perform dirty things for python < 3.5 only --- aiohttp/client.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/aiohttp/client.py b/aiohttp/client.py index 3a9313a6761..ae8e9cedac2 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -464,11 +464,12 @@ def __aexit__(self, exc_type, exc, tb): yield from self._resp.release() -try: - from asyncio import coroutines - coroutines._COROUTINE_TYPES += (_RequestContextManager,) -except: - pass +if not PY_35: + try: + from asyncio import coroutines + coroutines._COROUTINE_TYPES += (_RequestContextManager,) + except: + pass class _DetachedRequestContextManager(_RequestContextManager):