From 7b4cca0efc331ae4b79a9a24138cd6c4b6a0609a Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 17 Sep 2019 22:57:50 +0300 Subject: [PATCH 1/5] Drop Python 3.5 --- .appveyor.yml | 2 -- .travis.yml | 25 +------------------------ docs/client_quickstart.rst | 6 ------ docs/faq.rst | 25 ------------------------- docs/index.rst | 2 +- docs/web_advanced.rst | 4 ---- requirements/ci-wheel.txt | 1 - setup.cfg | 4 ---- setup.py | 8 ++++---- tests/test_client_functional.py | 7 ++----- tests/test_client_request.py | 20 ++++++++------------ tests/test_payload.py | 9 ++++----- tests/test_web_app.py | 21 +++++++-------------- tests/test_web_functional.py | 7 ++----- 14 files changed, 29 insertions(+), 112 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index bdb67250fd6..c5480a27d97 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,8 +6,6 @@ environment: PYTHONIOENCODING: "utf8:backslashreplace" PYTHONLEGACYWINDOWSSTDIO: "1" matrix: - - PYTHON: "C:\\Python35" - - PYTHON: "C:\\Python35-x64" - PYTHON: "C:\\Python36" - PYTHON: "C:\\Python36-x64" - PYTHON: "C:\\Python37" diff --git a/.travis.yml b/.travis.yml index e0347fa677d..eae60d4fdfb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,11 +9,10 @@ sudo: required language: python python: -- &py35 3.5 - &py36 3.6 - &py37 3.7 - nightly -- &pypy3 pypy3.5-6.0.0 +- &pypy3 pypy3 install: - &upgrade_python_toolset pip install --upgrade pip wheel setuptools @@ -312,14 +311,6 @@ jobs: deploy: <<: *deploy_step - # Build and deploy MacOS binary wheels for each OSX+Python combo possible - # OS X 10.10, Python 3.5 - - <<: *osx_pypi_deploy_base_1010 - name: *env_os1010_msg - python: *py35 - env: - <<: *env_osx_base - PYTHON_VERSION: *py35 # OS X 10.10, Python 3.6 - <<: *osx_pypi_deploy_base_1010 name: *env_os1010_msg @@ -334,13 +325,6 @@ jobs: env: <<: *env_osx_base PYTHON_VERSION: *py37 - # OS X 10.11, Python 3.5 - - <<: *osx_pypi_deploy_base_1011 - name: *env_os1011_msg - python: *py35 - env: - <<: *env_osx_base - PYTHON_VERSION: *py35 # OS X 10.11, Python 3.6 - <<: *osx_pypi_deploy_base_1011 name: *env_os1011_msg @@ -355,13 +339,6 @@ jobs: env: <<: *env_osx_base PYTHON_VERSION: *py37 - # OS X 10.12, Python 3.5 - - <<: *osx_pypi_deploy_base_1012 - name: *env_os1012_msg - python: *py35 - env: - <<: *env_osx_base - PYTHON_VERSION: *py35 # OS X 10.12, Python 3.6 - <<: *osx_pypi_deploy_base_1012 name: *env_os1012_msg diff --git a/docs/client_quickstart.rst b/docs/client_quickstart.rst index 1d5d2fb4489..8bdb14e9778 100644 --- a/docs/client_quickstart.rst +++ b/docs/client_quickstart.rst @@ -354,12 +354,6 @@ can chain get and post requests together:: await session.post('http://httpbin.org/post', data=resp.content) -.. note:: - - Python 3.5 has no native support for asynchronous generators, use - ``async_generator`` library as workaround. - - .. _aiohttp-client-websockets: diff --git a/docs/faq.rst b/docs/faq.rst index a667184a4b8..da9ba37245c 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -77,31 +77,6 @@ other resource you want to share between handlers. return app -Why is Python 3.5.3 the lowest supported version? -------------------------------------------------- - -Python 3.5.2 fixes the protocol for async iterators: ``__aiter__()`` is -not a coroutine but a regular function. - -Python 3.5.3 has a more important change: :func:`asyncio.get_event_loop` -returns the running loop instance if called from a coroutine. -Previously it returned a *default* loop, set by -:func:`asyncio.set_event_loop`. - -Previous to Python 3.5.3, -:func:`asyncio.get_event_loop` was not reliable, so users were -forced to explicitly pass the event loop instance everywhere. -If a future object were created for one event loop -(e.g. the default loop) but a coroutine was run by another loop, the coroutine -was never awaited. As a result, the task would hang. - -Keep in mind that every internal ``await`` expression either passed -instantly or paused, waiting for a future. - -It's extremely important that all tasks (coroutine runners) and -futures use the same event loop. - - How can middleware store data for web handlers to use? ------------------------------------------------------ diff --git a/docs/index.rst b/docs/index.rst index 56aa7389f67..c61c14f6d0d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -130,7 +130,7 @@ Continuous Integration. Dependencies ============ -- Python 3.5.3+ +- Python 3.6+ - *async_timeout* - *attrs* - *chardet* diff --git a/docs/web_advanced.rst b/docs/web_advanced.rst index 355d4d1de8c..b9c9a299b2b 100644 --- a/docs/web_advanced.rst +++ b/docs/web_advanced.rst @@ -699,10 +699,6 @@ one ``yield``. *aiohttp* guarantees that *cleanup code* is called if and only if *startup code* was successfully finished. -Asynchronous generators are supported by Python 3.6+, on Python 3.5 -please use `async_generator `_ -library. - .. versionadded:: 3.1 .. _aiohttp-web-nested-applications: diff --git a/requirements/ci-wheel.txt b/requirements/ci-wheel.txt index 20461155d1e..ac5f3c51451 100644 --- a/requirements/ci-wheel.txt +++ b/requirements/ci-wheel.txt @@ -1,6 +1,5 @@ -r flake.txt attrs==19.1.0 -async-generator==1.10 async-timeout==3.0.1 Brotli==1.0.7 cchardet==2.1.4 diff --git a/setup.cfg b/setup.cfg index f7b55b1c23a..7b63e6fa23a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -59,10 +59,6 @@ ignore_missing_imports = true ignore_missing_imports = true -[mypy-async_generator] -ignore_missing_imports = true - - [mypy-aiodns] ignore_missing_imports = true diff --git a/setup.py b/setup.py index be8dfac9dd4..716976f9a2e 100644 --- a/setup.py +++ b/setup.py @@ -10,8 +10,8 @@ from setuptools import Extension, setup -if sys.version_info < (3, 5, 3): - raise RuntimeError("aiohttp 3.x requires Python 3.5.3+") +if sys.version_info < (3, 6): + raise RuntimeError("aiohttp 4.x requires Python 3.6+") NO_EXTENSIONS = bool(os.environ.get('AIOHTTP_NO_EXTENSIONS')) # type: bool @@ -101,9 +101,9 @@ def read(f): 'Intended Audience :: Developers', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Development Status :: 5 - Production/Stable', 'Operating System :: POSIX', 'Operating System :: MacOS :: MacOS X', @@ -130,7 +130,7 @@ def read(f): }, license='Apache 2', packages=['aiohttp'], - python_requires='>=3.5.3', + python_requires='>=3.6', install_requires=install_requires, extras_require={ 'speedups': [ diff --git a/tests/test_client_functional.py b/tests/test_client_functional.py index 765d2e7debb..80e911ec7f2 100644 --- a/tests/test_client_functional.py +++ b/tests/test_client_functional.py @@ -9,7 +9,6 @@ from unittest import mock import pytest -from async_generator import async_generator, yield_ from multidict import MultiDict import aiohttp @@ -1517,12 +1516,11 @@ async def handler(request): with fname.open('rb') as f: data_size = len(f.read()) - @async_generator async def gen(fname): with fname.open('rb') as f: data = f.read(100) while data: - await yield_(data) + yield data data = f.read(100) resp = await client.post( @@ -2734,10 +2732,9 @@ async def handler(request): client = await aiohttp_client(app) - @async_generator async def gen(): for i in range(100): - await yield_(b'1234567890') + yield b'1234567890' resp = await client.post('/', data=gen()) assert resp.status == 200 diff --git a/tests/test_client_request.py b/tests/test_client_request.py index 7b5882de29f..721e49fef21 100644 --- a/tests/test_client_request.py +++ b/tests/test_client_request.py @@ -10,7 +10,6 @@ from unittest import mock import pytest -from async_generator import async_generator, yield_ from multidict import CIMultiDict, CIMultiDictProxy, istr from yarl import URL @@ -866,10 +865,9 @@ async def test_expect_100_continue_header(loop, conn) -> None: async def test_data_stream(loop, buf, conn) -> None: - @async_generator async def gen(): - await yield_(b'binary data') - await yield_(b' result') + yield b'binary data' + yield b' result' req = ClientRequest( 'POST', URL('http://python.org/'), data=gen(), loop=loop) @@ -906,9 +904,8 @@ async def test_data_file(loop, buf, conn) -> None: async def test_data_stream_exc(loop, conn) -> None: fut = loop.create_future() - @async_generator async def gen(): - await yield_(b'binary data') + yield b'binary data' await fut req = ClientRequest( @@ -932,9 +929,10 @@ async def throw_exc(): async def test_data_stream_exc_chain(loop, conn) -> None: fut = loop.create_future() - @async_generator async def gen(): await fut + return + yield req = ClientRequest('POST', URL('http://python.org/'), data=gen(), loop=loop) @@ -959,10 +957,9 @@ async def throw_exc(): async def test_data_stream_continue(loop, buf, conn) -> None: - @async_generator async def gen(): - await yield_(b'binary data') - await yield_(b' result') + yield b'binary data' + yield b' result' req = ClientRequest( 'POST', URL('http://python.org/'), data=gen(), @@ -1003,10 +1000,9 @@ async def coro(): async def test_close(loop, buf, conn) -> None: - @async_generator async def gen(): await asyncio.sleep(0.00001) - await yield_(b'result') + yield b'result' req = ClientRequest( 'POST', URL('http://python.org/'), data=gen(), loop=loop) diff --git a/tests/test_payload.py b/tests/test_payload.py index 2348bdb0a4b..66acd1bc299 100644 --- a/tests/test_payload.py +++ b/tests/test_payload.py @@ -1,7 +1,6 @@ from io import StringIO import pytest -from async_generator import async_generator from aiohttp import payload @@ -90,18 +89,18 @@ def test_string_io_payload() -> None: def test_async_iterable_payload_default_content_type() -> None: - @async_generator async def gen(): - pass + return + yield p = payload.AsyncIterablePayload(gen()) assert p.content_type == 'application/octet-stream' def test_async_iterable_payload_explicit_content_type() -> None: - @async_generator async def gen(): - pass + return + yield p = payload.AsyncIterablePayload(gen(), content_type='application/custom') assert p.content_type == 'application/custom' diff --git a/tests/test_web_app.py b/tests/test_web_app.py index 74c8c8da6f3..20709dfbd9e 100644 --- a/tests/test_web_app.py +++ b/tests/test_web_app.py @@ -2,7 +2,6 @@ from unittest import mock import pytest -from async_generator import async_generator, yield_ from aiohttp import log, web from aiohttp.helpers import PY_36 @@ -182,10 +181,9 @@ async def test_cleanup_ctx() -> None: out = [] def f(num): - @async_generator async def inner(app): out.append('pre_' + str(num)) - await yield_(None) + yield None out.append('post_' + str(num)) return inner @@ -205,12 +203,11 @@ async def test_cleanup_ctx_exception_on_startup() -> None: exc = Exception('fail') def f(num, fail=False): - @async_generator async def inner(app): out.append('pre_' + str(num)) if fail: raise exc - await yield_(None) + yield None out.append('post_' + str(num)) return inner @@ -233,10 +230,9 @@ async def test_cleanup_ctx_exception_on_cleanup() -> None: exc = Exception('fail') def f(num, fail=False): - @async_generator async def inner(app): out.append('pre_' + str(num)) - await yield_(None) + yield None out.append('post_' + str(num)) if fail: raise exc @@ -259,10 +255,9 @@ async def test_cleanup_ctx_exception_on_cleanup_multiple() -> None: out = [] def f(num, fail=False): - @async_generator async def inner(app): out.append('pre_' + str(num)) - await yield_(None) + yield None out.append('post_' + str(num)) if fail: raise Exception('fail_' + str(num)) @@ -288,12 +283,11 @@ async def test_cleanup_ctx_multiple_yields() -> None: out = [] def f(num): - @async_generator async def inner(app): out.append('pre_' + str(num)) - await yield_(None) + yield None out.append('post_' + str(num)) - await yield_(None) + yield None return inner app.cleanup_ctx.append(f(1)) @@ -378,12 +372,11 @@ async def on_startup(app): ctx_pre_called = False ctx_post_called = False - @async_generator async def cleanup_ctx(app): nonlocal ctx_pre_called, ctx_post_called ctx_pre_called = True app['cleanup'] = True - await yield_(None) + yield None ctx_post_called = True subapp.cleanup_ctx.append(cleanup_ctx) diff --git a/tests/test_web_functional.py b/tests/test_web_functional.py index 81b60642ce0..0c0f0f41648 100644 --- a/tests/test_web_functional.py +++ b/tests/test_web_functional.py @@ -7,7 +7,6 @@ from unittest import mock import pytest -from async_generator import async_generator, yield_ from multidict import CIMultiDictProxy, MultiDict from yarl import URL @@ -784,12 +783,11 @@ async def test_response_with_async_gen(aiohttp_client, fname) -> None: data_size = len(data) - @async_generator async def stream(f_name): with f_name.open('rb') as f: data = f.read(100) while data: - await yield_(data) + yield data data = f.read(100) async def handler(request): @@ -815,12 +813,11 @@ async def test_response_with_async_gen_no_params(aiohttp_client, data_size = len(data) - @async_generator async def stream(): with fname.open('rb') as f: data = f.read(100) while data: - await yield_(data) + yield data data = f.read(100) async def handler(request): From 1db87780c67d85d62592bc6852d0a262dcf82892 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 18 Sep 2019 13:01:23 +0300 Subject: [PATCH 2/5] Restore a comment --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index eae60d4fdfb..17b3d0f6b6c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -311,6 +311,7 @@ jobs: deploy: <<: *deploy_step + # Build and deploy MacOS binary wheels for each OSX+Python combo possible # OS X 10.10, Python 3.6 - <<: *osx_pypi_deploy_base_1010 name: *env_os1010_msg From c6d984ae9e185970838fb806f020768f096a8594 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 2 Oct 2019 14:07:36 +0300 Subject: [PATCH 3/5] Drop unrelated tests --- tests/test_client_request.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/tests/test_client_request.py b/tests/test_client_request.py index 721e49fef21..4d4a9d03313 100644 --- a/tests/test_client_request.py +++ b/tests/test_client_request.py @@ -450,32 +450,6 @@ def test_cookies_merge_with_headers(make_request) -> None: assert 'cookie1=val1; cookie2=val2' == req.headers['COOKIE'] -def test_unicode_get1(make_request) -> None: - req = make_request('get', 'http://python.org', - params={'foo': 'f\xf8\xf8'}) - assert 'http://python.org/?foo=f%C3%B8%C3%B8' == str(req.url) - - -def test_unicode_get2(make_request) -> None: - req = make_request('', 'http://python.org', - params={'f\xf8\xf8': 'f\xf8\xf8'}) - - assert 'http://python.org/?f%C3%B8%C3%B8=f%C3%B8%C3%B8' == str(req.url) - - -def test_unicode_get3(make_request) -> None: - req = make_request('', 'http://python.org', params={'foo': 'foo'}) - assert 'http://python.org/?foo=foo' == str(req.url) - - -def test_unicode_get4(make_request) -> None: - def join(*suffix): - return urllib.parse.urljoin('http://python.org/', '/'.join(suffix)) - - req = make_request('', join('\xf8'), params={'foo': 'foo'}) - assert 'http://python.org/%C3%B8?foo=foo' == str(req.url) - - def test_query_multivalued_param(make_request) -> None: for meth in ClientRequest.ALL_METHODS: req = make_request( From c1f1c59e100a4fb417a1befb2cc1674323c1e052 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 2 Oct 2019 14:09:04 +0300 Subject: [PATCH 4/5] Add changelog --- CHANGES/4046.removal | 1 + 1 file changed, 1 insertion(+) create mode 100644 CHANGES/4046.removal diff --git a/CHANGES/4046.removal b/CHANGES/4046.removal new file mode 100644 index 00000000000..169774857bd --- /dev/null +++ b/CHANGES/4046.removal @@ -0,0 +1 @@ +Drop Python 3.5 support, aiohttp works on 3.6+ now. From 3d331c798005fd434b2b4b5ccdacbd52c51e4fe8 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 2 Oct 2019 14:10:32 +0300 Subject: [PATCH 5/5] Drop unused import --- tests/test_client_request.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_client_request.py b/tests/test_client_request.py index 4d4a9d03313..bb5ea5f3eea 100644 --- a/tests/test_client_request.py +++ b/tests/test_client_request.py @@ -4,7 +4,6 @@ import hashlib import io import pathlib -import urllib.parse import zlib from http.cookies import SimpleCookie from unittest import mock