From a1b907bf414ea33529454dfbff9285a78f81024c Mon Sep 17 00:00:00 2001 From: Anton Kasyanov Date: Sat, 23 Jul 2016 15:04:41 +0200 Subject: [PATCH 01/13] Added the note about ipdb to CONTRIBUTING.rst (#993) --- CONTRIBUTING.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index f3131855663..826a700d092 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -60,6 +60,13 @@ After that please install libraries required for development:: We also recommend to install *ipdb* but it's on your own:: $ pip install ipdb + +.. note:: + If you plan to use ``ipdb`` within the test suite, use:: + + $ make vtest + + to run the tests. Congratulations, you are ready to run the test suite From 52a02166f8ed4c2f5bc3bdc7aaf5291530bf799f Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 23 Jul 2016 15:05:11 +0200 Subject: [PATCH 02/13] Add unsafe option for cookie jar --- CHANGES.txt | 7 +++++++ aiohttp/helpers.py | 7 ++++--- aiohttp/test_utils.py | 5 ++++- docs/client.rst | 22 ++++++++++++++++++++-- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 62a234e0989..e78169d8db4 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,6 +8,13 @@ CHANGES - Don't expose `aiohttp.__version__` +- Add unsafe parameter to CookieJar #968 + +- Use unsafe cookie jar in test client tools + +- Expose aiohttp.CookieJar name + + 0.22.1 (08-16-2016) ------------------- diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py index d93d6c5bb21..25758ea4a3d 100644 --- a/aiohttp/helpers.py +++ b/aiohttp/helpers.py @@ -27,7 +27,7 @@ __all__ = ('BasicAuth', 'create_future', 'FormData', 'parse_mimetype', - 'Timeout') + 'Timeout', 'CookieJar') class BasicAuth(namedtuple('BasicAuth', ['login', 'password', 'encoding'])): @@ -587,9 +587,10 @@ class CookieJar(AbstractCookieJar): DATE_YEAR_RE = re.compile("(\d{2,4})") - def __init__(self, *, loop=None): + def __init__(self, *, unsafe=False, loop=None): super().__init__(loop=loop) self._host_only_cookies = set() + self._unsafe = unsafe def _expire_cookie(self, when, name, DAY=24*3600): now = self._loop.time() @@ -608,7 +609,7 @@ def update_cookies(self, cookies, response_url=None): url_parsed = urlsplit(response_url or "") hostname = url_parsed.hostname - if is_ip_address(hostname): + if not self._unsafe and is_ip_address(hostname): # Don't accept cookies from IPs return diff --git a/aiohttp/test_utils.py b/aiohttp/test_utils.py index bb9e784eef4..2c6aa17a684 100644 --- a/aiohttp/test_utils.py +++ b/aiohttp/test_utils.py @@ -349,7 +349,10 @@ def __init__(self, app, protocol="http"): self._server = None if not loop.is_running(): loop.run_until_complete(self.start_server()) - self._session = ClientSession(loop=self._loop) + self._session = ClientSession( + loop=self._loop, + cookie_jar=aiohttp.CookieJar(unsafe=True, + loop=self._loop)) self._root = '{}://{}:{}'.format(protocol, self._address, self.port) self._closed = False diff --git a/docs/client.rst b/docs/client.rst index 7361d2f80d8..f30503b9525 100644 --- a/docs/client.rst +++ b/docs/client.rst @@ -386,6 +386,24 @@ You also can set default headers for all session requests:: :class:`~aiohttp.ClientSession` supports keep-alive requests and connection pooling out-of-the-box. +.. _aiohttp-client-cookie-safety: + +Cookie safety +------------- + +By default :class:`~aiohttp.ClientSession` uses strict version of +:class:`~aiohttp.CookieJar`. :rfc:`2109` explicitly forbids cookie +accepting from URLs with IP address instead of DNS name +(e.g. `http://127.0.0.1:80/cookie`). + +It's good but sometimes for testing we need to enable support for such +cookies. It should be done by passing `usafe=True` to +:class:`~aiohttp.CookieJar` constructor:: + + + jar = aiohttp.CookieJar(unsafe=True) + session = aiohttp.ClientSession(cookie_jar=jar) + Connectors ---------- @@ -421,8 +439,8 @@ In order to specify the nameservers to when resolving the hostnames, aiodns is required. from aiohttp.resolver import AsyncResolver - - + + resolver = AsyncResolver(nameservers=["8.8.8.8", "8.8.4.4"]) conn = aiohttp.TCPConnector(resolver=resolver) From f9c27f048f6e09634bdfc78780fcc1f3e5ce87a6 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 23 Jul 2016 15:06:02 +0200 Subject: [PATCH 03/13] Bump to 0.22.2 --- CHANGES.txt | 6 +++--- aiohttp/__init__.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e78169d8db4..4a66afd8a19 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,7 @@ CHANGES ======= -0.22.2 (XX-XX-XXXX) +0.22.2 (07-23-2016) ------------------- - Suppress CancelledError when Timeout raises TimeoutError #970 @@ -15,14 +15,14 @@ CHANGES - Expose aiohttp.CookieJar name -0.22.1 (08-16-2016) +0.22.1 (07-16-2016) ------------------- - Large cookie expiration/max-age doesn't break an event loop from now (fixes #967) -0.22.0 (08-15-2016) +0.22.0 (07-15-2016) ------------------- - Fix bug in serving static directory #803 diff --git a/aiohttp/__init__.py b/aiohttp/__init__.py index 9da01d1b2b9..337c04a391e 100644 --- a/aiohttp/__init__.py +++ b/aiohttp/__init__.py @@ -1,6 +1,6 @@ # This relies on each of the submodules having an __all__ variable. -__version__ = '0.22.2b0' +__version__ = '0.22.2' import multidict # noqa From 9942012769c3853193dc4cef422d8b966dab0c1d Mon Sep 17 00:00:00 2001 From: Mariano Anaya Date: Sat, 23 Jul 2016 15:46:25 +0200 Subject: [PATCH 04/13] Issue997 default connector limit (#991) * Git ignore `*.swp` files * Allow `BaseConnector` to be used as a context manager * Set default limit connection to 20 The default was changed from `None` to 20. `None` used to mean that there were no limits at all, so all number of connections were allowed to the same endpoint. Not, when the user does not specify, the default is 20, but it is still possible to set `None`. https://github.com/KeepSafe/aiohttp/issues/977 * Update documentation for the `limit` parameter. In the docs, document the new behaviour of the `limit` parameter, which now defaults to 20, instead of `None`. * Fit doc text in 80 cols. --- .gitignore | 1 + aiohttp/connector.py | 14 ++++++++++++-- docs/client.rst | 11 +++++++++-- docs/client_reference.rst | 9 ++++++++- tests/test_connector.py | 7 ++++++- tests/test_proxy_connector.py | 32 +++++++++++++++++++++----------- 6 files changed, 57 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 105ea29b040..d90b55d4cc5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.swp *.bak *.egg *.egg-info diff --git a/aiohttp/connector.py b/aiohttp/connector.py index 9260599ef2a..36b9e0db1a2 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -33,6 +33,8 @@ 32: sha256, } +DEFAULT_CONN_LIMIT = 20 + class Connection(object): @@ -109,6 +111,7 @@ class BaseConnector(object): :param keepalive_timeout: (optional) Keep-alive timeout. :param bool force_close: Set to True to force close and do reconnect after each request (and between redirects). + :param limit: The limit of simultaneous connections to the same endpoint. :param loop: Optional event loop. """ @@ -116,7 +119,7 @@ class BaseConnector(object): _source_traceback = None def __init__(self, *, conn_timeout=None, keepalive_timeout=_default, - force_close=False, limit=None, + force_close=False, limit=DEFAULT_CONN_LIMIT, loop=None): if force_close: @@ -170,6 +173,12 @@ def __del__(self, _warnings=warnings): context['source_traceback'] = self._source_traceback self._loop.call_exception_handler(context) + def __enter__(self): + return self + + def __exit__(self, *exc): + self.close() + @property def force_close(self): """Ultimately close connection on releasing if True.""" @@ -182,7 +191,8 @@ def limit(self): Endpoints are the same if they are have equal (host, port, is_ssl) triple. - If limit is None the connector has no limit (default). + If limit is None the connector has no limit. + The default limit size is 20. """ return self._limit diff --git a/docs/client.rst b/docs/client.rst index 7361d2f80d8..653404a8c80 100644 --- a/docs/client.rst +++ b/docs/client.rst @@ -413,6 +413,13 @@ parameter to *connector*:: The example limits amount of parallel connections to `30`. +The default is `20`. + +If you explicitly want not to have limits to the same endpoint, +pass `None`. For example:: + + conn = aiohttp.TCPConnector(limit=None) + Resolving using custom nameservers ---------------------------------- @@ -421,8 +428,8 @@ In order to specify the nameservers to when resolving the hostnames, aiodns is required. from aiohttp.resolver import AsyncResolver - - + + resolver = AsyncResolver(nameservers=["8.8.8.8", "8.8.4.4"]) conn = aiohttp.TCPConnector(resolver=resolver) diff --git a/docs/client_reference.rst b/docs/client_reference.rst index 3c0e59da6ba..8aebdf67301 100644 --- a/docs/client_reference.rst +++ b/docs/client_reference.rst @@ -690,7 +690,7 @@ BaseConnector ^^^^^^^^^^^^^ .. class:: BaseConnector(*, conn_timeout=None, keepalive_timeout=30, \ - limit=None, \ + limit=20, \ force_close=False, loop=None) Base class for all connectors. @@ -720,6 +720,13 @@ BaseConnector recommend to use explicit loops everywhere. (optional) + .. versionchanged:: 0.23 + + ``limit`` changed from unlimited (``None``) to 20. + Expect a max of up to 20 connections to the same endpoint, + if it is not especified. + For limitless connections, pass `None` explicitly. + .. attribute:: closed Read-only property, ``True`` if connector is closed. diff --git a/tests/test_connector.py b/tests/test_connector.py index c8cffea960e..a81e8583c55 100644 --- a/tests/test_connector.py +++ b/tests/test_connector.py @@ -16,6 +16,7 @@ from aiohttp import helpers from aiohttp.client import ClientResponse from aiohttp.connector import Connection +from aiohttp.connector import DEFAULT_CONN_LIMIT class TestBaseConnector(unittest.TestCase): @@ -691,9 +692,13 @@ def test_limit_property(self): def test_limit_property_default(self): conn = aiohttp.BaseConnector(loop=self.loop) - self.assertIsNone(conn.limit) + self.assertEquals(conn.limit, DEFAULT_CONN_LIMIT) conn.close() + def test_limitless(self): + with aiohttp.BaseConnector(loop=self.loop, limit=None) as conn: + self.assertIsNone(conn.limit) + def test_force_close_and_explicit_keep_alive(self): with self.assertRaises(ValueError): aiohttp.BaseConnector(loop=self.loop, keepalive_timeout=30, diff --git a/tests/test_proxy_connector.py b/tests/test_proxy_connector.py index dfebbebbbc8..98f91700a63 100644 --- a/tests/test_proxy_connector.py +++ b/tests/test_proxy_connector.py @@ -6,6 +6,16 @@ from unittest import mock +def _create_mocked_loop(): + """ + Single place where to define the loop to be used as mock for the tests + Delete the `create_future()` from the mock, so it defaults to a new Future + """ + loop_mock = unittest.mock.Mock() + del loop_mock.create_future + return loop_mock + + class TestProxyConnector(unittest.TestCase): def setUp(self): @@ -44,7 +54,7 @@ def test_connect(self, ClientRequestMock): req = ClientRequest('GET', 'http://www.python.org', loop=self.loop) self.assertEqual(req.path, '/') - loop_mock = unittest.mock.Mock() + loop_mock = _create_mocked_loop() connector = aiohttp.ProxyConnector('http://proxy.example.com', loop=loop_mock) self.assertIs(loop_mock, connector._loop) @@ -102,7 +112,7 @@ def test_auth(self, ClientRequestMock): self.assertIn('AUTHORIZATION', proxy_req.headers) self.assertNotIn('PROXY-AUTHORIZATION', proxy_req.headers) - loop_mock = unittest.mock.Mock() + loop_mock = _create_mocked_loop() connector = aiohttp.ProxyConnector( 'http://proxy.example.com', loop=loop_mock, proxy_auth=aiohttp.helpers.BasicAuth('user', 'pass')) @@ -144,7 +154,7 @@ def test_auth_from_url(self, ClientRequestMock): self.assertIn('AUTHORIZATION', proxy_req.headers) self.assertNotIn('PROXY-AUTHORIZATION', proxy_req.headers) - loop_mock = unittest.mock.Mock() + loop_mock = _create_mocked_loop() connector = aiohttp.ProxyConnector( 'http://user:pass@proxy.example.com', loop=loop_mock) connector._resolve_host = resolve_mock = unittest.mock.Mock() @@ -176,7 +186,7 @@ def test_auth__not_modifying_request(self, ClientRequestMock): ClientRequestMock.return_value = proxy_req proxy_req_headers = dict(proxy_req.headers) - loop_mock = unittest.mock.Mock() + loop_mock = _create_mocked_loop() connector = aiohttp.ProxyConnector( 'http://user:pass@proxy.example.com', loop=loop_mock) connector._resolve_host = resolve_mock = unittest.mock.Mock() @@ -192,7 +202,7 @@ def test_auth__not_modifying_request(self, ClientRequestMock): @unittest.mock.patch('aiohttp.connector.ClientRequest') def test_https_connect(self, ClientRequestMock): - loop_mock = unittest.mock.Mock() + loop_mock = _create_mocked_loop() proxy_req = ClientRequest('GET', 'http://proxy.example.com', loop=loop_mock) ClientRequestMock.return_value = proxy_req @@ -225,7 +235,7 @@ def test_https_connect(self, ClientRequestMock): @unittest.mock.patch('aiohttp.connector.ClientRequest') def test_https_connect_runtime_error(self, ClientRequestMock): - loop_mock = unittest.mock.Mock() + loop_mock = _create_mocked_loop() proxy_req = ClientRequest('GET', 'http://proxy.example.com', loop=loop_mock) ClientRequestMock.return_value = proxy_req @@ -255,7 +265,7 @@ def test_https_connect_runtime_error(self, ClientRequestMock): @unittest.mock.patch('aiohttp.connector.ClientRequest') def test_https_connect_http_proxy_error(self, ClientRequestMock): - loop_mock = unittest.mock.Mock() + loop_mock = _create_mocked_loop() proxy_req = ClientRequest('GET', 'http://proxy.example.com', loop=loop_mock) ClientRequestMock.return_value = proxy_req @@ -286,7 +296,7 @@ def test_https_connect_http_proxy_error(self, ClientRequestMock): @unittest.mock.patch('aiohttp.connector.ClientRequest') def test_https_connect_resp_start_error(self, ClientRequestMock): - loop_mock = unittest.mock.Mock() + loop_mock = _create_mocked_loop() proxy_req = ClientRequest('GET', 'http://proxy.example.com', loop=loop_mock) ClientRequestMock.return_value = proxy_req @@ -315,7 +325,7 @@ def test_request_port(self, ClientRequestMock): loop=self.loop) ClientRequestMock.return_value = proxy_req - loop_mock = unittest.mock.Mock() + loop_mock = _create_mocked_loop() connector = aiohttp.ProxyConnector('http://proxy.example.com', loop=loop_mock) @@ -344,7 +354,7 @@ def test_proxy_auth_property_default(self): @unittest.mock.patch('aiohttp.connector.ClientRequest') def test_https_connect_pass_ssl_context(self, ClientRequestMock): - loop_mock = unittest.mock.Mock() + loop_mock = _create_mocked_loop() proxy_req = ClientRequest('GET', 'http://proxy.example.com', loop=loop_mock) ClientRequestMock.return_value = proxy_req @@ -383,7 +393,7 @@ def test_https_connect_pass_ssl_context(self, ClientRequestMock): @unittest.mock.patch('aiohttp.connector.ClientRequest') def test_https_auth(self, ClientRequestMock): - loop_mock = unittest.mock.Mock() + loop_mock = _create_mocked_loop() proxy_req = ClientRequest('GET', 'http://proxy.example.com', auth=aiohttp.helpers.BasicAuth('user', 'pass'), From 2bcdd5ce84902cf8f29553f6af677272cafd6b76 Mon Sep 17 00:00:00 2001 From: Aleksey Kutepov Date: Sat, 23 Jul 2016 16:54:56 +0200 Subject: [PATCH 05/13] Add IE support for cookie deletion. (#994) * Add IE support for cookie deletion. IE<=11 ( !!! ) doesn't support the MaxAge property for cookies so del_cookie() also needs to set the Expires cookie header to support IE. Uses the UTC epoch for a standard expire time. * Add IE support for cookie deletion. Style and test fixes. * Add IE support for cookie deletion. Documentation updates. * Add IE support for cookie deletion. Python 3.4 related test fixes. * Add IE support for cookie deletion. set_cookie correction. --- aiohttp/web_reqrep.py | 8 +++++++- docs/web_reference.rst | 5 +++++ tests/test_web_response.py | 7 +++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/aiohttp/web_reqrep.py b/aiohttp/web_reqrep.py index f3b195f779f..6e044abfc3f 100644 --- a/aiohttp/web_reqrep.py +++ b/aiohttp/web_reqrep.py @@ -511,8 +511,12 @@ def set_cookie(self, name, value, *, expires=None, self._cookies[name] = value c = self._cookies[name] + if expires is not None: c['expires'] = expires + elif c.get('expires') == 'Thu, 01 Jan 1970 00:00:00 GMT': + del c['expires'] + if domain is not None: c['domain'] = domain @@ -537,7 +541,9 @@ def del_cookie(self, name, *, domain=None, path='/'): """ # TODO: do we need domain/path here? self._cookies.pop(name, None) - self.set_cookie(name, '', max_age=0, domain=domain, path=path) + self.set_cookie(name, '', max_age=0, + expires="Thu, 01 Jan 1970 00:00:00 GMT", + domain=domain, path=path) @property def content_length(self): diff --git a/docs/web_reference.rst b/docs/web_reference.rst index 79368a98f73..5d781fab6e1 100644 --- a/docs/web_reference.rst +++ b/docs/web_reference.rst @@ -504,6 +504,11 @@ StreamResponse :param str path: optional cookie path, ``'/'`` by default + .. versionchanged:: 0.23 + + Fixed cookie expiration support for + Internet Explorer (version less than 11). + .. attribute:: content_length *Content-Length* for outgoing response. diff --git a/tests/test_web_response.py b/tests/test_web_response.py index f434caf8981..b554fbf6890 100644 --- a/tests/test_web_response.py +++ b/tests/test_web_response.py @@ -453,7 +453,8 @@ def test_response_cookies(): 'Set-Cookie: name=another_other_value; Max-Age=10; Path=/') resp.del_cookie('name') - expected = 'Set-Cookie: name=("")?; Max-Age=0; Path=/' + expected = ('Set-Cookie: name=("")?; ' + 'expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/') assert re.match(expected, str(resp.cookies)) resp.set_cookie('name', 'value', domain='local.host') @@ -491,7 +492,8 @@ def test_response_cookie__issue_del_cookie(): assert str(resp.cookies) == '' resp.del_cookie('name') - expected = 'Set-Cookie: name=("")?; Max-Age=0; Path=/' + expected = ('Set-Cookie: name=("")?; ' + 'expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/') assert re.match(expected, str(resp.cookies)) @@ -502,6 +504,7 @@ def test_cookie_set_after_del(): resp.set_cookie('name', 'val') # check for Max-Age dropped expected = 'Set-Cookie: name=val; Path=/' + print(expected) assert str(resp.cookies) == expected From 87444c54c483807e3d8bc3100ffc9a353194c99e Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 23 Jul 2016 16:57:35 +0200 Subject: [PATCH 06/13] Update CHANGES/CONTRIBUTORS --- CHANGES.txt | 4 +++- CONTRIBUTORS.txt | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 8d2f691b867..c3c3ab3bf92 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,7 +5,9 @@ CHANGES ------------------- - Change default size for client session's connection pool from - unlimited to 20 #997 + unlimited to 20 #977 + +- Add IE support for cookie deletion. #994 0.22.2 (07-23-2016) ------------------- diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 202168346cf..d76b5dcebe7 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -3,6 +3,7 @@ Contributors A. Jesse Jiryu Davis Alejandro Gómez +Aleksey Kutepov Alex Khomchenko Alex Lisovoy Alex Key @@ -65,6 +66,7 @@ Lubomir Gelo Ludovic Gasc Lukasz Marcin Dobrzanski Marco Paolini +Mariano Anaya Martin Richard Mathias Fröjdman Matthieu Hauglustaine From 934b67594a3cad0cee523a139c61a8007b85490a Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 23 Jul 2016 17:25:16 +0200 Subject: [PATCH 07/13] Drop printout call --- tests/test_web_response.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_web_response.py b/tests/test_web_response.py index b554fbf6890..34cc8e2472a 100644 --- a/tests/test_web_response.py +++ b/tests/test_web_response.py @@ -504,7 +504,6 @@ def test_cookie_set_after_del(): resp.set_cookie('name', 'val') # check for Max-Age dropped expected = 'Set-Cookie: name=val; Path=/' - print(expected) assert str(resp.cookies) == expected From e10b5551cdf84b9706de1f1d875c5057ab63f997 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 23 Jul 2016 23:14:53 +0200 Subject: [PATCH 08/13] Remove deprecated WebSocketResponse.wait_closed method --- CHANGES.txt | 2 ++ aiohttp/web_ws.py | 8 -------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index c3c3ab3bf92..d20d691bab1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -9,6 +9,8 @@ CHANGES - Add IE support for cookie deletion. #994 +- Remove deprecated `WebSocketResponse.wait_closed` method + 0.22.2 (07-23-2016) ------------------- diff --git a/aiohttp/web_ws.py b/aiohttp/web_ws.py index 3d46ed544f6..0a5812c5f56 100644 --- a/aiohttp/web_ws.py +++ b/aiohttp/web_ws.py @@ -154,14 +154,6 @@ def send_bytes(self, data): type(data)) self._writer.send(data, binary=True) - @asyncio.coroutine - def wait_closed(self): # pragma: no cover - warnings.warn( - 'wait_closed() coroutine is deprecated. use close() instead', - DeprecationWarning) - - return (yield from self.close()) - @asyncio.coroutine def write_eof(self): if self._eof_sent: From 498cf0c0c21c9c16cf1c3dff377ad6bbe58dafab Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 23 Jul 2016 23:57:33 +0200 Subject: [PATCH 09/13] Drop deprecated force paramerter --- CHANGES.txt | 6 +++++- aiohttp/client_reqrep.py | 5 +---- tests/test_client_response.py | 6 ------ tests/test_web_functional.py | 4 ++-- tests/test_web_websocket_functional_oldstyle.py | 2 +- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d20d691bab1..1a6552199a5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -9,7 +9,11 @@ CHANGES - Add IE support for cookie deletion. #994 -- Remove deprecated `WebSocketResponse.wait_closed` method +- Remove deprecated `WebSocketResponse.wait_closed` method (BACKWARD + INCOMPATIBLE) + +- Remove deprecated `force` parameter for `ClientResponse.close` + method (BACKWARD INCOMPATIBLE) 0.22.2 (07-23-2016) ------------------- diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index 1cf091684be..c565a82cb20 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -651,10 +651,7 @@ def start(self, connection, read_until_eof=False): 'Can not load response cookies: %s', exc) return self - def close(self, force=True): - if not force: - warnings.warn("force parameter should be True", DeprecationWarning, - stacklevel=2) + def close(self): if self._closed: return diff --git a/tests/test_client_response.py b/tests/test_client_response.py index cc25f07ebaa..5dcd0817c9e 100644 --- a/tests/test_client_response.py +++ b/tests/test_client_response.py @@ -269,12 +269,6 @@ def test_get_encoding_unknown(self, m_chardet): self.response.headers = {'CONTENT-TYPE': 'application/json'} self.assertEqual(self.response._get_encoding(), 'utf-8') - def test_close_deprecated(self): - self.response._connection = self.connection - with self.assertWarns(DeprecationWarning): - self.response.close(force=False) - self.assertIsNone(self.response._connection) - def test_raise_for_status_2xx(self): self.response.status = 200 self.response.reason = 'OK' diff --git a/tests/test_web_functional.py b/tests/test_web_functional.py index 7291f6b08bd..910fb3fa7fa 100644 --- a/tests/test_web_functional.py +++ b/tests/test_web_functional.py @@ -521,7 +521,7 @@ def go(): loop=self.loop) self.assertEqual(200, resp.status) - resp.close(force=True) + resp.close() auth_err = True resp = yield from request( @@ -529,7 +529,7 @@ def go(): expect100=True, # wait until server returns 100 continue loop=self.loop) self.assertEqual(403, resp.status) - resp.close(force=True) + resp.close() self.loop.run_until_complete(go()) diff --git a/tests/test_web_websocket_functional_oldstyle.py b/tests/test_web_websocket_functional_oldstyle.py index cf3dd2bcf8d..63b02a2efbc 100644 --- a/tests/test_web_websocket_functional_oldstyle.py +++ b/tests/test_web_websocket_functional_oldstyle.py @@ -63,7 +63,7 @@ def connect_ws(self, url, protocol=None): headers=headers, connector=conn, loop=self.loop) - self.addCleanup(response.close, True) + self.addCleanup(response.close) self.assertEqual(101, response.status) self.assertEqual(response.headers.get('upgrade', '').lower(), From 56bf8bb2ab92384c45b771432aa8e06270005da3 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 24 Jul 2016 09:08:34 +0200 Subject: [PATCH 10/13] Update statements about conditions for make_mocked_request usage --- docs/testing.rst | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index 2e2cbce0fd0..c2ee75611cd 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -185,10 +185,11 @@ functionality, the AioHTTPTestCase is provided:: Faking request object --------------------- -aiohttp provides test utility for creating fake `web.Request` objects: -:data:`aiohttp.test_utils.make_mocked_request`, it could be useful in case of -simple unit tests, like handler tests, or simulate error conditions that -hard to reproduce on real server. :: +aiohttp provides test utility for creating fake +:class:`aiohttp.web.Request` objects: +:func:`aiohttp.test_utils.make_mocked_request`, it could be useful in +case of simple unit tests, like handler tests, or simulate error +conditions that hard to reproduce on real server:: from aiohttp import web @@ -201,6 +202,18 @@ hard to reproduce on real server. :: resp = header(req) assert resp.body == b'data' +.. warning:: + + We don't recommed to apply + :func:`~aiohttp.test_utils.make_mocked_request` everywhere for + testing web-handler's business object -- please use test client and + real networking via 'localhost' as shown in examples before. + + :func:`~aiohttp.test_utils.make_mocked_request` exists only for + testing complex cases (e.g. emulating network errors) which + are extremely hard or even impossible to test by conventional + way. + aiohttp.test_utils ------------------ From 9b06e818c50945f6bd3121c39b3091e84c0e369f Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 24 Jul 2016 09:35:54 +0200 Subject: [PATCH 11/13] Fix an example --- examples/basic_srv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/basic_srv.py b/examples/basic_srv.py index bb6a99d9e55..bec362e626c 100755 --- a/examples/basic_srv.py +++ b/examples/basic_srv.py @@ -6,7 +6,7 @@ import asyncio from urllib.parse import urlparse, parse_qsl -from aiohttp.multidict import MultiDict +from aiohttp import MultiDict class HttpRequestHandler(aiohttp.server.ServerHttpProtocol): From ff3d52e30cbeefad50a0466499e22b325125c7e1 Mon Sep 17 00:00:00 2001 From: Pau Freixes Date: Sun, 24 Jul 2016 13:46:21 +0200 Subject: [PATCH 12/13] Avoid use mutable CIMultiDict kw param in make_mocked_request (#997) Even though is hard to get an issue, there is still chances to get unwished behaviours as a side effect because of that. --- CONTRIBUTORS.txt | 1 + aiohttp/test_utils.py | 17 ++++++++++++----- tests/test_web_request.py | 10 ++++++++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index d76b5dcebe7..c046b86db35 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -113,3 +113,4 @@ Yury Selivanov Yusuke Tsutsumi Марк Коренберг Семён Марьясин +Pau Freixes diff --git a/aiohttp/test_utils.py b/aiohttp/test_utils.py index 2c6aa17a684..825661ada83 100644 --- a/aiohttp/test_utils.py +++ b/aiohttp/test_utils.py @@ -561,7 +561,7 @@ def get_extra_info(key): _not_set = object() -def make_mocked_request(method, path, headers=CIMultiDict(), *, +def make_mocked_request(method, path, headers=None, *, version=HttpVersion(1, 1), closing=False, app=None, reader=_not_set, @@ -616,10 +616,17 @@ def make_mocked_request(method, path, headers=CIMultiDict(), *, if version < HttpVersion(1, 1): closing = True - message = RawRequestMessage(method, path, version, headers, - [(k.encode('utf-8'), v.encode('utf-8')) - for k, v in headers.items()], - closing, False) + + if headers: + hdrs = headers + raw_hdrs = [ + (k.encode('utf-8'), v.encode('utf-8')) for k, v in headers.items()] + else: + hdrs = CIMultiDict() + raw_hdrs = [] + + message = RawRequestMessage(method, path, version, hdrs, + raw_hdrs, closing, False) if app is None: app = _create_app_mock() diff --git a/tests/test_web_request.py b/tests/test_web_request.py index ff9692db54e..e15bd1b5c01 100644 --- a/tests/test_web_request.py +++ b/tests/test_web_request.py @@ -20,6 +20,8 @@ def test_ctor(make_request, warning): assert '/path/to?a=1&b=2' == req.path_qs assert '/path/to' == req.path assert 'a=1&b=2' == req.query_string + assert CIMultiDict() == req.headers + assert () == req.raw_headers get = req.GET assert MultiDict([('a', '1'), ('b', '2')]) == get @@ -29,18 +31,22 @@ def test_ctor(make_request, warning): assert req.keep_alive # just make sure that all lines of make_mocked_request covered + headers = CIMultiDict(FOO='bar') reader = mock.Mock() writer = mock.Mock() payload = mock.Mock() transport = mock.Mock() app = mock.Mock() - req = make_request('GET', '/path/to?a=1&b=2', writer=writer, reader=reader, - payload=payload, transport=transport, app=app) + req = make_request('GET', '/path/to?a=1&b=2', headers=headers, + writer=writer, reader=reader, payload=payload, + transport=transport, app=app) assert req.app is app assert req.content is payload assert req.transport is transport assert req._reader is reader assert req._writer is writer + assert req.headers == headers + assert req.raw_headers == ((b'FOO', b'bar'),) def test_doubleslashes(make_request): From 4bf83a81f4601dd97c039063230341ef848b0996 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 24 Jul 2016 13:47:00 +0200 Subject: [PATCH 13/13] Update CHANGES --- CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 1a6552199a5..8caac582e2d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -15,6 +15,9 @@ CHANGES - Remove deprecated `force` parameter for `ClientResponse.close` method (BACKWARD INCOMPATIBLE) +- Avoid using of mutable CIMultiDict kw param in make_mocked_request + #997 + 0.22.2 (07-23-2016) -------------------