Skip to content

Commit ca3a44f

Browse files
committed
Farewell, sweet Concorde!
Formally removing any remaining vestiges of Python 2 from Requests. We'll also leave behind Python 3.6 while we're at it.
1 parent 79f6027 commit ca3a44f

19 files changed

+98
-156
lines changed

.github/workflows/run-tests.yml

+2-5
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,11 @@ jobs:
99
strategy:
1010
fail-fast: false
1111
matrix:
12-
python-version: ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10"]
12+
python-version: ["3.7", "3.8", "3.9", "3.10"]
1313
os: [ubuntu-18.04, macOS-latest, windows-latest]
1414
include:
15-
# pypy3 on Mac OS currently fails trying to compile
15+
# pypy-3.7 on Mac OS currently fails trying to compile
1616
# brotlipy. Moving pypy3 to only test linux.
17-
- python-version: pypy3
18-
os: ubuntu-latest
19-
experimental: false
2017
- python-version: pypy-3.7
2118
os: ubuntu-latest
2219
experimental: false

HISTORY.md

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ dev
1111
- Fixed urllib3 exception leak, wrapping `urllib3.exceptions.SSLError` with
1212
`requests.exceptions.SSLError` for `content` and `iter_content`.
1313

14+
**Deprecations**
15+
16+
- ⚠️ Requests has officially dropped support for Python 2.7. ⚠️
17+
- Requests has officially dropped support for Python 3.6 (including pypy3).
18+
1419
2.27.1 (2022-01-05)
1520
-------------------
1621

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Requests is available on PyPI:
3333
$ python -m pip install requests
3434
```
3535

36-
Requests officially supports Python 2.7 & 3.6+.
36+
Requests officially supports Python 3.7+.
3737

3838
## Supported Features & Best–Practices
3939

docs/community/faq.rst

+6-9
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,16 @@ Chris Adams gave an excellent summary on
5555
Python 3 Support?
5656
-----------------
5757

58-
Yes! Requests officially supports Python 2.7 & 3.6+ and PyPy.
58+
Yes! Requests officially supports Python 3.7+ and PyPy.
5959

6060
Python 2 Support?
6161
-----------------
6262

63-
Yes! We understand that we have a large user base with varying needs. Through
64-
**at least** Requests 2.27.x, we will be providing continued support for Python
65-
2.7. However, this support is likely to end some time in 2022.
63+
No! As of Requests 2.28.0, Requests no longer supports Python 2.7. Users who
64+
have been unable to migrate should pin to `requests<2.28`. Full information
65+
can be found in `psf/requests#6023 <https://github.com/psf/requests/issues/6023>`_.
6666

67-
It is *highly* recommended users migrate to Python 3.7+ now since Python
67+
It is *highly* recommended users migrate to Python 3.8+ now since Python
6868
2.7 is no longer receiving bug fixes or security updates as of January 1, 2020.
6969

7070
What are "hostname doesn't match" errors?
@@ -83,10 +83,7 @@ when servers are using `Virtual Hosting`_. When such servers are hosting
8383
more than one SSL site they need to be able to return the appropriate
8484
certificate based on the hostname the client is connecting to.
8585

86-
Python3 and Python 2.7.9+ include native support for SNI in their SSL modules.
87-
For information on using SNI with Requests on Python < 2.7.9 refer to this
88-
`Stack Overflow answer`_.
86+
Python 3 already includes native support for SNI in their SSL modules.
8987

9088
.. _`Server-Name-Indication`: https://en.wikipedia.org/wiki/Server_Name_Indication
9189
.. _`virtual hosting`: https://en.wikipedia.org/wiki/Virtual_hosting
92-
.. _`Stack Overflow answer`: https://stackoverflow.com/questions/18578439/using-requests-with-tls-doesnt-give-sni-support/18579484#18579484

docs/index.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Requests is ready for today's web.
7272
- Chunked Requests
7373
- ``.netrc`` Support
7474

75-
Requests officially supports Python 2.7 & 3.6+, and runs great on PyPy.
75+
Requests officially supports Python 3.7+, and runs great on PyPy.
7676

7777

7878
The User Guide

requests/_internal_utils.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
which depend on extremely few external helpers (such as compat)
99
"""
1010

11-
from .compat import is_py2, builtin_str, str
11+
from .compat import builtin_str
1212

1313

1414
def to_native_string(string, encoding='ascii'):
@@ -19,10 +19,7 @@ def to_native_string(string, encoding='ascii'):
1919
if isinstance(string, builtin_str):
2020
out = string
2121
else:
22-
if is_py2:
23-
out = string.encode(encoding)
24-
else:
25-
out = string.decode(encoding)
22+
out = string.decode(encoding)
2623

2724
return out
2825

requests/adapters.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -477,12 +477,7 @@ def send(self, request, stream=False, timeout=None, verify=True, cert=None, prox
477477
low_conn.send(b'0\r\n\r\n')
478478

479479
# Receive the response from the server
480-
try:
481-
# For Python 2.7, use buffering of HTTP responses
482-
r = low_conn.getresponse(buffering=True)
483-
except TypeError:
484-
# For compatibility with Python 3.3+
485-
r = low_conn.getresponse()
480+
r = low_conn.getresponse()
486481

487482
resp = HTTPResponse.from_httplib(
488483
r,

requests/compat.py

+26-44
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
requests.compat
55
~~~~~~~~~~~~~~~
66
7-
This module handles import compatibility issues between Python 2 and
8-
Python 3.
7+
This module previously handled import compatibility issues
8+
between Python 2 and Python 3. It remains for backwards
9+
compatibility until the next major version.
910
"""
1011

1112
try:
@@ -28,54 +29,35 @@
2829
#: Python 3.x?
2930
is_py3 = (_ver[0] == 3)
3031

32+
# json/simplejson module import resolution
3133
has_simplejson = False
3234
try:
3335
import simplejson as json
3436
has_simplejson = True
3537
except ImportError:
3638
import json
3739

40+
if has_simplejson:
41+
from simplejson import JSONDecodeError
42+
else:
43+
from json import JSONDecodeError
44+
3845
# ---------
39-
# Specifics
46+
# Legacy Imports
4047
# ---------
41-
42-
if is_py2:
43-
from urllib import (
44-
quote, unquote, quote_plus, unquote_plus, urlencode, getproxies,
45-
proxy_bypass, proxy_bypass_environment, getproxies_environment)
46-
from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag
47-
from urllib2 import parse_http_list
48-
import cookielib
49-
from Cookie import Morsel
50-
from StringIO import StringIO
51-
# Keep OrderedDict for backwards compatibility.
52-
from collections import Callable, Mapping, MutableMapping, OrderedDict
53-
54-
builtin_str = str
55-
bytes = str
56-
str = unicode
57-
basestring = basestring
58-
numeric_types = (int, long, float)
59-
integer_types = (int, long)
60-
JSONDecodeError = ValueError
61-
62-
elif is_py3:
63-
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
64-
from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment
65-
from http import cookiejar as cookielib
66-
from http.cookies import Morsel
67-
from io import StringIO
68-
# Keep OrderedDict for backwards compatibility.
69-
from collections import OrderedDict
70-
from collections.abc import Callable, Mapping, MutableMapping
71-
if has_simplejson:
72-
from simplejson import JSONDecodeError
73-
else:
74-
from json import JSONDecodeError
75-
76-
builtin_str = str
77-
str = str
78-
bytes = bytes
79-
basestring = (str, bytes)
80-
numeric_types = (int, float)
81-
integer_types = (int,)
48+
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
49+
from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment
50+
from http import cookiejar as cookielib
51+
from http.cookies import Morsel
52+
from io import StringIO
53+
54+
# Keep OrderedDict for backwards compatibility.
55+
from collections import OrderedDict
56+
from collections.abc import Callable, Mapping, MutableMapping
57+
58+
builtin_str = str
59+
str = str
60+
bytes = bytes
61+
basestring = (str, bytes)
62+
numeric_types = (int, float)
63+
integer_types = (int,)

requests/help.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ def _implementation():
3636
"""Return a dict with the Python implementation and version.
3737
3838
Provide both the name and the version of the Python implementation
39-
currently running. For example, on CPython 2.7.5 it will return
40-
{'name': 'CPython', 'version': '2.7.5'}.
39+
currently running. For example, on CPython 3.10.3 it will return
40+
{'name': 'CPython', 'version': '3.10.3'}.
4141
4242
This function works best on CPython and PyPy: in particular, it probably
4343
doesn't work for Jython or IronPython. Future investigation should be done

requests/models.py

+4-20
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
"""
99

1010
import datetime
11-
import sys
1211

1312
# Import encoding now, to avoid implicit import later.
1413
# Implicit import within threads may cause LookupError when standard library is in a ZIP,
@@ -45,8 +44,8 @@
4544
iter_slices, guess_json_utf, super_len, check_header_validity)
4645
from .compat import (
4746
Callable, Mapping,
48-
cookielib, urlunparse, urlsplit, urlencode, str, bytes,
49-
is_py2, chardet, builtin_str, basestring, JSONDecodeError)
47+
cookielib, urlunparse, urlsplit, urlencode,
48+
chardet, builtin_str, basestring, JSONDecodeError)
5049
from .compat import json as complexjson
5150
from .status_codes import codes
5251

@@ -373,7 +372,7 @@ def prepare_url(self, url, params):
373372
if isinstance(url, bytes):
374373
url = url.decode('utf8')
375374
else:
376-
url = unicode(url) if is_py2 else str(url)
375+
url = str(url)
377376

378377
# Remove leading whitespaces from url
379378
url = url.lstrip()
@@ -424,18 +423,6 @@ def prepare_url(self, url, params):
424423
if not path:
425424
path = '/'
426425

427-
if is_py2:
428-
if isinstance(scheme, str):
429-
scheme = scheme.encode('utf-8')
430-
if isinstance(netloc, str):
431-
netloc = netloc.encode('utf-8')
432-
if isinstance(path, str):
433-
path = path.encode('utf-8')
434-
if isinstance(query, str):
435-
query = query.encode('utf-8')
436-
if isinstance(fragment, str):
437-
fragment = fragment.encode('utf-8')
438-
439426
if isinstance(params, (str, bytes)):
440427
params = to_native_string(params)
441428

@@ -919,10 +906,7 @@ def json(self, **kwargs):
919906
except JSONDecodeError as e:
920907
# Catch JSON-related errors and raise as requests.JSONDecodeError
921908
# This aliases json.JSONDecodeError and simplejson.JSONDecodeError
922-
if is_py2: # e is a ValueError
923-
raise RequestsJSONDecodeError(e.message)
924-
else:
925-
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
909+
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
926910

927911
@property
928912
def links(self):

requests/sessions.py

+3-7
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from collections import OrderedDict
1515

1616
from .auth import _basic_auth_str
17-
from .compat import cookielib, is_py3, urljoin, urlparse, Mapping
17+
from .compat import cookielib, urljoin, urlparse, Mapping
1818
from .cookies import (
1919
cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
2020
from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT
@@ -39,10 +39,7 @@
3939

4040
# Preferred clock, based on which one is more accurate on a given system.
4141
if sys.platform == 'win32':
42-
try: # Python 3.4+
43-
preferred_clock = time.perf_counter
44-
except AttributeError: # Earlier than Python 3.
45-
preferred_clock = time.clock
42+
preferred_clock = time.perf_counter
4643
else:
4744
preferred_clock = time.time
4845

@@ -111,8 +108,7 @@ def get_redirect_target(self, resp):
111108
# It is more likely to get UTF8 header rather than latin1.
112109
# This causes incorrect handling of UTF8 encoded location headers.
113110
# To solve this, we re-encode the location in latin1.
114-
if is_py3:
115-
location = location.encode('latin1')
111+
location = location.encode('latin1')
116112
return to_native_string(location, 'utf8')
117113
return None
118114

requests/utils.py

+3-7
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from .compat import parse_http_list as _parse_list_header
3131
from .compat import (
3232
quote, urlparse, bytes, str, unquote, getproxies,
33-
proxy_bypass, urlunparse, basestring, integer_types, is_py3,
33+
proxy_bypass, urlunparse, basestring, integer_types,
3434
proxy_bypass_environment, getproxies_environment, Mapping)
3535
from .cookies import cookiejar_from_dict
3636
from .structures import CaseInsensitiveDict
@@ -54,10 +54,7 @@
5454

5555
def proxy_bypass_registry(host):
5656
try:
57-
if is_py3:
58-
import winreg
59-
else:
60-
import _winreg as winreg
57+
import winreg
6158
except ImportError:
6259
return False
6360

@@ -281,12 +278,11 @@ def extract_zipped_paths(path):
281278
@contextlib.contextmanager
282279
def atomic_open(filename):
283280
"""Write a file to the disk in an atomic fashion"""
284-
replacer = os.rename if sys.version_info[0] == 2 else os.replace
285281
tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename))
286282
try:
287283
with os.fdopen(tmp_descriptor, 'wb') as tmp_handler:
288284
yield tmp_handler
289-
replacer(tmp_name, filename)
285+
os.replace(tmp_name, filename)
290286
except BaseException:
291287
os.remove(tmp_name)
292288
raise

setup.cfg

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
[bdist_wheel]
2-
universal = 1
3-
41
[metadata]
52
license_file = LICENSE
3+
provides-extra =
4+
socks
5+
use_chardet_on_py3
6+
requires-dist =
7+
certifi>=2017.4.17
8+
charset_normalizer~=2.0.0
9+
idna>=2.5,<4
10+
urllib3>=1.21.1,<1.27

0 commit comments

Comments
 (0)