Repository: aio-libs/aiohttp

Issue:
Discussion: drop Python 3.5 support
Now aiohttp is Python 3.5 compatible.

The latest Debian Buster (released about 2 weeks ago) has Python 3.7 by default.
RHEL 8 (released on May 7th, 2019) has Python 3.6 by default.

I believe there is no sense in Python 3.5 support for aiohttp 4.0

In #3928 @AMDmi3 expressed an opinion that he is not interested in a contribution for legacy Python version.
In #3946 @ayushkovskiy  used Python 3.6 syntax.
IIRC @samuelcolvin asked for dropping 3.5 a half year ago.

Python 3.5 is in security-fixing mode and going to EOL soon.
 
Do somebody object for Python 3.5 support dropping?
Opinions?



Discussion:
+1 to drop 3.5. Quite soon there will be 3.8, so having 4 Python releases on the support board will be quite expensive (and demotivating).  
Let's drop it w/ aiohttp==4.0. Fedora also has Python 3.7 almost since the day of its release. Ubuntu/Debian will always be PITA. Let the users figure out their problems on their own.
So the next release would be aiohttp==4.0?
@yjqiang next *major* release. We'll probably still backport some bugfixes to 3.5, just not features.
Hmm, there is a blocker called `PyPy`.
PyPy3 3.6 is in beta phase still. aiohttp supports PyPy now, so we cannot drop 3.5 until PyPy3 3.6 will be released :(
I don't think it's a big deal. PyPy users can keep using `aiohttp<=3.5` until it's ready.
Do you propose dropping PyPy from travis build matrix for now?
It should work under pypy3 3.6 beta, which is actually available on Travis

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -6,8 +6,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
@@ -77,9 +77,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',
@@ -106,7 +106,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': [


Test patch:
diff --git a/tests/test_client_functional.py b/tests/test_client_functional.py
--- a/tests/test_client_functional.py
+++ b/tests/test_client_functional.py
@@ -10,7 +10,6 @@
 from unittest import mock
 
 import pytest
-from async_generator import async_generator, yield_
 from multidict import MultiDict
 
 import aiohttp
@@ -1518,12 +1517,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(
@@ -2813,10 +2811,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
--- a/tests/test_client_request.py
+++ b/tests/test_client_request.py
@@ -4,13 +4,11 @@
 import hashlib
 import io
 import pathlib
-import urllib.parse
 import zlib
 from http.cookies import SimpleCookie
 from unittest import mock
 
 import pytest
-from async_generator import async_generator, yield_
 from multidict import CIMultiDict, CIMultiDictProxy, istr
 from yarl import URL
 
@@ -451,32 +449,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(
@@ -866,10 +838,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 +877,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 +902,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 +930,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 +973,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
--- 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
--- 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
--- 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
 
@@ -785,12 +784,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):
@@ -816,12 +814,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):