Skip to content

Commit

Permalink
Drop support for Python 3.6
Browse files Browse the repository at this point in the history
Co-authored-by: Quentin Pradet <quentin.pradet@gmail.com>
Co-authored-by: Seth Michael Larson <sethmichaellarson@gmail.com>
  • Loading branch information
3 people committed Jul 27, 2021
1 parent c976e55 commit ad1712a
Show file tree
Hide file tree
Showing 14 changed files with 49 additions and 81 deletions.
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,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 @@ -50,7 +50,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 @@ -109,7 +108,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 @@ -269,7 +246,7 @@ class HTTPHeaderDict(MutableMapping[str, str]):

def __init__(self, headers: Optional[ValidHTTPHeaderSource] = None, **kwargs: str):
super().__init__()
self._container = _ordered_dict()
self._container = {} # 'dict' is insert-ordered in Python 3.7+
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.

22 changes: 8 additions & 14 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 @@ -146,18 +145,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 Expand Up @@ -483,7 +477,7 @@ def connect(self) -> None:
context.verify_mode = resolve_cert_reqs(self.cert_reqs)

# Try to load OS default certs if none are given.
# Works well on Windows (requires Python3.4+)
# Works well on Windows.
if (
not self.ca_certs
and not self.ca_cert_dir
Expand Down
6 changes: 4 additions & 2 deletions 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 Expand Up @@ -380,7 +382,7 @@ def ssl_wrap_socket(
raise SSLError(e)

elif ssl_context is None and hasattr(context, "load_default_certs"):
# try to load OS default certs; works well on Windows (require Python3.4+)
# try to load OS default certs; works well on Windows.
context.load_default_certs()

# Attempt to detect if we get the goofy behavior of the
Expand Down
3 changes: 2 additions & 1 deletion src/urllib3/util/ssl_match_hostname.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""The match_hostname() function from Python 3.3.3, essential when using SSL."""
"""The match_hostname() function from Python 3.5, essential when using SSL."""

# Note: This file is under the PSF license as the code comes from the python
# stdlib. http://docs.python.org/3/license.html
# It is modified to remove commonName support.

import ipaddress
import re
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.SSLCertVerificationError):
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

0 comments on commit ad1712a

Please sign in to comment.