Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop support for Python 3.6 #2336

Merged
merged 8 commits into from
Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,26 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Set up Python 3.7
- name: Set up Python 3
uses: actions/setup-python@v2
with:
python-version: 3.7
python-version: 3
- name: Check packages
run: |
python3.7 -m pip install wheel twine rstcheck;
python3.7 setup.py sdist bdist_wheel;
python3 -m pip install wheel twine rstcheck;
python3 setup.py sdist bdist_wheel;
rstcheck README.rst CHANGES.rst
python3.7 -m twine check dist/*
python3 -m twine check dist/*
test:
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: [3.7, 3.8, 3.9]
os: [macos-latest, windows-latest, ubuntu-latest]
experimental: [false]
nox-session: ['']
include:
- python-version: pypy3
- python-version: pypy-3.7
os: ubuntu-latest
experimental: false
nox-session: test-pypy
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ repos:
rev: v2.7.4
hooks:
- id: pyupgrade
args: ["--py36-plus"]
args: ["--py37-plus"]

- repo: https://github.com/psf/black
rev: 20.8b1
hooks:
- id: black
args: ["--target-version", "py36"]
args: ["--target-version", "py37"]

- repo: https://github.com/PyCQA/isort
rev: 5.6.4
Expand Down
2 changes: 1 addition & 1 deletion docs/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ suite::
[ Nox will create virtualenv if needed, install the specified dependencies, and run the commands in order.]
.......
.......
nox > Session test-3.6 was successful.
nox > Session test-3.7 was successful.
nox > Session test-3.8 was successful.
nox > Session test-3.9 was successful.
nox > Session test-3.10 was successful.
nox > Session test-pypy was successful.

Our test suite `runs continuously on Travis CI
Expand Down
8 changes: 4 additions & 4 deletions docs/v2-roadmap.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,15 @@ verification in Python or OpenSSL will be immediately
available.


**✨ Optimized for Python 3.6+**
**✨ Optimized for Python 3.7+**
--------------------------------

In v2.0 we'll be specifically be targeting
CPython 3.6+ and PyPy 7.0+ (compatible with CPython 3.6)
and dropping support Python versions 2.7 and 3.5.
CPython 3.7+ and PyPy 7.0+ (compatible with CPython 3.7)
and dropping support Python versions 2.7, 3.5, and 3.6.

By dropping end-of-life Python versions we're able to optimize
the codebase for Python 3.6+ by using new features to improve
the codebase for Python 3.7+ by using new features to improve
performance and reduce the amount of code that needs to be executed
in order to support legacy versions.

Expand Down
2 changes: 1 addition & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def tests_impl(session, extras="socks,secure,brotli"):
session.run("coverage", "xml")


@nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10", "pypy"])
@nox.session(python=["3.7", "3.8", "3.9", "3.10", "pypy"])
def test(session):
tests_impl(session)

Expand Down
9 changes: 4 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from setuptools import setup

CURRENT_PYTHON = sys.version_info[:2]
REQUIRED_PYTHON = (3, 6)
REQUIRED_PYTHON = (3, 7)

# This check and everything above must remain compatible with Python 2.7.
if CURRENT_PYTHON < REQUIRED_PYTHON:
Expand Down Expand Up @@ -77,7 +77,6 @@
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
Expand All @@ -91,9 +90,9 @@
keywords="urllib httplib threadsafe filepost http https ssl pooling",
author="Andrey Petrov",
author_email="andrey.petrov@shazow.net",
url="https://urllib3.readthedocs.io/",
url="https://urllib3.readthedocs.io",
project_urls={
"Documentation": "https://urllib3.readthedocs.io/",
"Documentation": "https://urllib3.readthedocs.io",
"Code": "https://github.com/urllib3/urllib3",
"Issue tracker": "https://github.com/urllib3/urllib3/issues",
},
Expand All @@ -106,7 +105,7 @@
],
package_dir={"": "src"},
requires=[],
python_requires=">=3.6, <4",
python_requires=">=3.7, <4",
extras_require={
"brotli": [
"brotli>=1.0.9; platform_python_implementation == 'CPython'",
Expand Down
33 changes: 5 additions & 28 deletions src/urllib3/_collections.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import sys
from collections import OrderedDict
from enum import Enum, auto
from threading import RLock
from typing import (
TYPE_CHECKING,
Callable,
Expand All @@ -12,16 +12,11 @@
MutableMapping,
NoReturn,
Optional,
Set,
Tuple,
TypeVar,
Union,
cast,
overload,
)
from typing import OrderedDict as OrderedDictType
from typing import Set, Tuple, TypeVar, Union, cast, overload

if TYPE_CHECKING:
from threading import RLock

# We can only import Protocol if TYPE_CHECKING because it's a development
# dependency, and is not available at runtime.
Expand All @@ -35,24 +30,6 @@ def __getitem__(self, key: str) -> str:
...


else:
try:
from threading import RLock
except ImportError: # Python 3.6
from ._compat import RLock


# Starting in Python 3.7 the 'dict' class is guaranteed to be
# ordered by insertion. This behavior was an implementation
# detail in Python 3.6. OrderedDict is implemented in C in
# later Python versions but still requires a lot more memory
# due to being implemented as a linked list.
if sys.version_info >= (3, 7):
_ordered_dict = dict
else:
_ordered_dict = OrderedDict


__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"]


Expand Down Expand Up @@ -109,7 +86,7 @@ class RecentlyUsedContainer(Generic[_KT, _VT], MutableMapping[_KT, _VT]):
``dispose_func(value)`` is called. Callback which will get called
"""

_container: "OrderedDict[_KT, _VT]"
_container: OrderedDictType[_KT, _VT]
_maxsize: int
dispose_func: Optional[Callable[[_VT], None]]
lock: RLock
Expand Down Expand Up @@ -268,7 +245,7 @@ class HTTPHeaderDict(MutableMapping[str, str]):

def __init__(self, headers: Optional[ValidHTTPHeaderSource] = None, **kwargs: str):
super().__init__()
self._container = _ordered_dict()
self._container = dict() # 'dict' is insert-ordered in Python 3.7+
hramezani marked this conversation as resolved.
Show resolved Hide resolved
if headers is not None:
if isinstance(headers, HTTPHeaderDict):
self._copy_from(headers)
Expand Down
8 changes: 0 additions & 8 deletions src/urllib3/_compat.py

This file was deleted.

20 changes: 7 additions & 13 deletions src/urllib3/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import os
import re
import socket
import sys
import warnings
from copy import copy
from http.client import HTTPConnection as _HTTPConnection
Expand Down Expand Up @@ -145,18 +144,13 @@ def __init__(
self.proxy = proxy
self.proxy_config = proxy_config

if sys.version_info >= (3, 7):
super().__init__(
host=host,
port=port,
timeout=timeout,
source_address=source_address,
blocksize=blocksize,
)
else:
super().__init__(
host=host, port=port, timeout=timeout, source_address=source_address
)
super().__init__(
host=host,
port=port,
timeout=timeout,
source_address=source_address,
blocksize=blocksize,
)

# https://github.com/python/mypy/issues/4125
# Mypy treats this as LSP violation, which is considered a bug.
Expand Down
4 changes: 3 additions & 1 deletion src/urllib3/util/ssl_.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
SSLContext = None
SSLTransport = None
HAS_SNI = False
HAS_NEVER_CHECK_COMMON_NAME = False
IS_PYOPENSSL = False
IS_SECURETRANSPORT = False
ALPN_PROTOCOLS = ["http/1.1"]
Expand Down Expand Up @@ -44,6 +45,7 @@ def _is_ge_openssl_v1_1_1(
import ssl
from ssl import ( # type: ignore
CERT_REQUIRED,
HAS_NEVER_CHECK_COMMON_NAME,
HAS_SNI,
OP_NO_COMPRESSION,
OP_NO_TICKET,
Expand Down Expand Up @@ -278,7 +280,7 @@ def create_urllib3_context(
context.check_hostname = False
context.verify_mode = cert_reqs

if hasattr(context, "hostname_checks_common_name"):
if HAS_NEVER_CHECK_COMMON_NAME:
context.hostname_checks_common_name = False

# Enable logging of TLS session keys via defacto standard environment variable
Expand Down
5 changes: 1 addition & 4 deletions test/test_ssltransport.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,13 +367,10 @@ def test_wrong_sni_hint(self):
with self.client_context.wrap_socket(
sock, server_hostname="localhost"
) as proxy_sock:
with pytest.raises(Exception) as e:
with pytest.raises(ssl.SSLError):
sethmlarson marked this conversation as resolved.
Show resolved Hide resolved
SSLTransport(
proxy_sock, self.client_context, server_hostname="veryverywrong"
)
# ssl.CertificateError is a child of ValueError in python3.6 or
# before. After python3.7 it's a child of SSLError
assert e.type in [ssl.SSLError, ssl.CertificateError]

@pytest.mark.timeout(PER_TEST_TIMEOUT)
@pytest.mark.parametrize("buffering", [None, 0])
Expand Down
8 changes: 6 additions & 2 deletions test/with_dummyserver/test_https.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,10 @@ def test_verified_with_bad_ca_certs(self):
with pytest.raises(MaxRetryError) as e:
https_pool.request("GET", "/")
assert isinstance(e.value.reason, SSLError)
assert "certificate verify failed" in str(
e.value.reason
assert (
"certificate verify failed" in str(e.value.reason)
# PyPy is more specific
or "self signed certificate in certificate chain" in str(e.value.reason)
), f"Expected 'certificate verify failed', instead got: {e.value.reason!r}"

def test_verified_without_ca_certs(self):
Expand All @@ -295,6 +297,8 @@ def test_verified_without_ca_certs(self):
# not pyopenssl is injected
assert (
"No root certificates specified" in str(e.value.reason)
# PyPy is more specific
or "self signed certificate in certificate chain" in str(e.value.reason)
# PyPy sometimes uses all-caps here
or "certificate verify failed" in str(e.value.reason).lower()
or "invalid certificate chain" in str(e.value.reason)
Expand Down
6 changes: 4 additions & 2 deletions test/with_dummyserver/test_proxy_poolmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,10 @@ def test_proxy_verified(self):
with pytest.raises(MaxRetryError) as e:
https_pool.request("GET", "/", retries=0)
assert isinstance(e.value.reason, SSLError)
assert "certificate verify failed" in str(
e.value.reason
assert (
"certificate verify failed" in str(e.value.reason)
# PyPy is more specific
or "self signed certificate in certificate chain" in str(e.value.reason)
), f"Expected 'certificate verify failed', instead got: {e.value.reason!r}"

http = proxy_from_url(
Expand Down