Skip to content

Commit

Permalink
Drop python2 support
Browse files Browse the repository at this point in the history
Farewell, it's been a good run. Realistically this should have been
removed years ago, but it's causing pain now.

Remove six which handles the majority of the compatibility.
  • Loading branch information
jamielennox committed Jan 22, 2024
1 parent 55603cc commit c74eb15
Show file tree
Hide file tree
Showing 12 changed files with 41 additions and 60 deletions.
20 changes: 10 additions & 10 deletions requests_mock/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
# License for the specific language governing permissions and limitations
# under the License.

import urllib.parse
import weakref

from requests.adapters import BaseAdapter
from requests.utils import requote_uri
import six
from six.moves.urllib import parse as urlparse

from requests_mock import exceptions
from requests_mock.request import _RequestObjectProxy
Expand Down Expand Up @@ -99,8 +98,8 @@ def __init__(self, method, url, responses, complete_qs, request_headers,
self._additional_matcher = additional_matcher

# url can be a regex object or ANY so don't always run urlparse
if isinstance(url, six.string_types):
url_parts = urlparse.urlparse(url)
if isinstance(url, str):
url_parts = urllib.parse.urlparse(url)
self._scheme = url_parts.scheme.lower()
self._netloc = url_parts.netloc.lower()
self._path = requote_uri(url_parts.path or '/')
Expand Down Expand Up @@ -155,25 +154,26 @@ def _match_url(self, request):
return False

# construct our own qs structure as we remove items from it below
request_qs = urlparse.parse_qs(request.query, keep_blank_values=True)
matcher_qs = urlparse.parse_qs(self._query, keep_blank_values=True)
request_qs = urllib.parse.parse_qs(request.query,
keep_blank_values=True)
matcher_qs = urllib.parse.parse_qs(self._query, keep_blank_values=True)

for k, vals in six.iteritems(matcher_qs):
for k, vals in matcher_qs.items():
for v in vals:
try:
request_qs.get(k, []).remove(v)
except ValueError:
return False

if self._complete_qs:
for v in six.itervalues(request_qs):
for v in request_qs.values():
if v:
return False

return True

def _match_headers(self, request):
for k, vals in six.iteritems(self._request_headers):
for k, vals in self._request_headers.items():

try:
header = request.headers[k]
Expand All @@ -182,7 +182,7 @@ def _match_headers(self, request):
# difference, in 2 they are just whatever the user inputted in
# 1 they are bytes. Let's optionally handle both and look at
# removing this when we depend on requests 2.
if not isinstance(k, six.text_type):
if not isinstance(k, str):
return False

try:
Expand Down
6 changes: 3 additions & 3 deletions requests_mock/mocker.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import types

import requests
import six

from requests_mock import adapter
from requests_mock import exceptions
Expand Down Expand Up @@ -60,8 +59,9 @@ def _is_bound_method(method):
bound_method 's self is a obj
unbound_method 's self is None
"""
if isinstance(method, types.MethodType) and six.get_method_self(method):
if isinstance(method, types.MethodType) and hasattr(method, '__self__'):
return True

return False


Expand All @@ -74,7 +74,7 @@ def _set_method(target, name, method):
If method is a bound_method, can direct setattr
"""
if not isinstance(target, type) and not _is_bound_method(method):
method = six.create_bound_method(method, target)
method = types.MethodType(method, target)

setattr(target, name, method)

Expand Down
7 changes: 4 additions & 3 deletions requests_mock/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@

import copy
import json
import urllib.parse

import requests
from six.moves.urllib import parse as urlparse


class _RequestObjectProxy(object):
Expand Down Expand Up @@ -60,7 +60,7 @@ def _url_parts(self):
if not self._case_sensitive:
url = url.lower()

self._url_parts_ = urlparse.urlparse(url)
self._url_parts_ = urllib.parse.urlparse(url)

return self._url_parts_

Expand Down Expand Up @@ -109,7 +109,8 @@ def query(self):
@property
def qs(self):
if self._qs is None:
self._qs = urlparse.parse_qs(self.query, keep_blank_values=True)
self._qs = urllib.parse.parse_qs(self.query,
keep_blank_values=True)

return self._qs

Expand Down
11 changes: 5 additions & 6 deletions requests_mock/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# under the License.

import io
import http.client
import json as jsonutils

from requests.adapters import HTTPAdapter
Expand All @@ -19,7 +20,6 @@
from requests.cookies import merge_cookies, cookiejar_from_dict
from requests.packages.urllib3.response import HTTPResponse
from requests.utils import get_encoding_from_headers
import six

from requests_mock import compat
from requests_mock import exceptions
Expand Down Expand Up @@ -126,7 +126,7 @@ def read(self, *args, **kwargs):
# if the file is open, but you asked for zero bytes read you should get
# back zero without closing the stream.
if len(args) > 0 and args[0] == 0:
return six.b('')
return b''

result = io.BytesIO.read(self, *args, **kwargs)

Expand Down Expand Up @@ -174,7 +174,7 @@ def create_response(request, **kwargs):

if content is not None and not isinstance(content, bytes):
raise TypeError('Content should be binary data')
if text is not None and not isinstance(text, six.string_types):
if text is not None and not isinstance(text, str):
raise TypeError('Text should be string data')

if json is not None:
Expand All @@ -187,8 +187,7 @@ def create_response(request, **kwargs):
body = _IOReader(content)
if not raw:
status = kwargs.get('status_code', _DEFAULT_STATUS)
reason = kwargs.get('reason',
six.moves.http_client.responses.get(status))
reason = kwargs.get('reason', http.client.responses.get(status))

raw = HTTPResponse(status=status,
reason=reason,
Expand Down Expand Up @@ -245,7 +244,7 @@ def __init__(self, **kwargs):
raise TypeError('Content should be a callback or binary data')

if text is not None and not (callable(text) or
isinstance(text, six.string_types)):
isinstance(text, str)):
raise TypeError('Text should be a callback or string data')

def get_response(self, request):
Expand Down
6 changes: 3 additions & 3 deletions requests_mock/response.pyi
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# Stubs for requests_mock.response

import io
from typing import Any, Dict

import six

from requests import Request, Response
from requests.cookies import RequestsCookieJar

Expand All @@ -14,7 +13,8 @@ class _FakeConnection:
def send(self, request: Any, **kwargs: Any) -> None: ...
def close(self) -> None: ...

class _IOReader(six.BytesIO):
class _IOReader(io.BytesIO):

def read(self, *args: Any, **kwargs: Any) -> Any: ...

def create_response(request: Any, **kwargs: Any) -> Response: ...
Expand Down
3 changes: 1 addition & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
requests>=2.3,<3
six
requests>=2.3,<3
8 changes: 2 additions & 6 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,12 @@ classifier =
License :: OSI Approved :: Apache Software License
Operating System :: OS Independent
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
Topic :: Software Development :: Testing
Expand Down
1 change: 0 additions & 1 deletion test-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
fixtures
mock; python_version < '3.3'
purl
pytest
sphinx
Expand Down
23 changes: 8 additions & 15 deletions tests/test_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@

import json
import re
import urllib.parse

import purl
import requests
import six
from six.moves.urllib import parse as urlparse

import requests_mock
from . import base
Expand All @@ -41,16 +40,16 @@ def setUp(self):
self.headers = {'header_a': 'A', 'header_b': 'B'}

def assertHeaders(self, resp):
for k, v in six.iteritems(self.headers):
for k, v in self.headers.items():
self.assertEqual(v, resp.headers[k])

def assertLastRequest(self, method='GET', body=None):
self.assertEqual(self.url, self.adapter.last_request.url)
self.assertEqual(method, self.adapter.last_request.method)
self.assertEqual(body, self.adapter.last_request.body)

url_parts = urlparse.urlparse(self.url)
qs = urlparse.parse_qs(url_parts.query)
url_parts = urllib.parse.urlparse(self.url)
qs = urllib.parse.parse_qs(url_parts.query)
self.assertEqual(url_parts.scheme, self.adapter.last_request.scheme)
self.assertEqual(url_parts.netloc, self.adapter.last_request.netloc)
self.assertEqual(url_parts.path, self.adapter.last_request.path)
Expand Down Expand Up @@ -177,11 +176,11 @@ def test_multiple_responses(self):
out = [self.session.get(self.url) for i in range(0, len(inp))]

for i, o in zip(inp, out):
for k, v in six.iteritems(i):
for k, v in i.items():
self.assertEqual(v, getattr(o, k))

last = self.session.get(self.url)
for k, v in six.iteritems(inp[-1]):
for k, v in inp[-1].items():
self.assertEqual(v, getattr(last, k))

def test_callback_optional_status(self):
Expand All @@ -198,7 +197,7 @@ def _test_cb(request, context):
resp = self.session.get(self.url)
self.assertEqual(300, resp.status_code)

for k, v in six.iteritems(headers):
for k, v in headers.items():
self.assertEqual(v, resp.headers[k])

def test_callback_optional_headers(self):
Expand All @@ -216,7 +215,7 @@ def _test_cb(request, context):
resp = self.session.get(self.url)
self.assertEqual(300, resp.status_code)

for k, v in six.iteritems(headers):
for k, v in headers.items():
self.assertEqual(v, resp.headers[k])

def test_latest_register_overrides(self):
Expand Down Expand Up @@ -274,19 +273,13 @@ def test_dont_pass_empty_string_as_content(self):
content=u'')

def test_dont_pass_bytes_as_text(self):
if six.PY2:
self.skipTest('Cannot enforce byte behaviour in PY2')

self.assertRaises(TypeError,
self.adapter.register_uri,
'GET',
self.url,
text=b'bytes')

def test_dont_pass_empty_string_as_text(self):
if six.PY2:
self.skipTest('Cannot enforce byte behaviour in PY2')

self.assertRaises(TypeError,
self.adapter.register_uri,
'GET',
Expand Down
12 changes: 4 additions & 8 deletions tests/test_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

import io
import pickle
import six

from requests_mock import exceptions
from requests_mock import request
Expand Down Expand Up @@ -47,10 +46,7 @@ def test_create_response_body_args(self):
def test_content_type(self):
self.assertRaises(TypeError, self.create_response, text=55)
self.assertRaises(TypeError, self.create_response, text={'a': 1})

# this only works on python 2 because bytes is a string
if six.PY3:
self.assertRaises(TypeError, self.create_response, text=b'')
self.assertRaises(TypeError, self.create_response, text=b'')

def test_text_type(self):
self.assertRaises(TypeError, self.create_response, content=u't')
Expand All @@ -62,7 +58,7 @@ def test_json_body(self):
resp = self.create_response(json=data)

self.assertEqual('{"a": 1}', resp.text)
self.assertIsInstance(resp.text, six.string_types)
self.assertIsInstance(resp.text, str)
self.assertIsInstance(resp.content, bytes)
self.assertEqual(data, resp.json())

Expand All @@ -72,7 +68,7 @@ def test_body_body(self):
resp = self.create_response(body=body)

self.assertEqual(value.decode(), resp.text)
self.assertIsInstance(resp.text, six.string_types)
self.assertIsInstance(resp.text, str)
self.assertIsInstance(resp.content, bytes)

def test_setting_connection(self):
Expand Down Expand Up @@ -150,6 +146,6 @@ def test_some_other_response_reasons(self):
503: 'Service Unavailable',
}

for code, reason in six.iteritems(reasons):
for code, reason in reasons.items():
self.assertEqual(reason,
self.create_response(status_code=code).reason)
1 change: 0 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ deps =

[testenv:requests-tip]
deps =
six
-e "git+https://github.com/kennethreitz/requests.git\#egg=requests"
-r{toxinidir}/test-requirements.txt

Expand Down
3 changes: 1 addition & 2 deletions typing-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
mypy
types-requests
types-six
types-requests

2 comments on commit c74eb15

@wuch-g2v
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to release new version because of this patch? 🤔
I'm trying package set of python modules without py2 and six dependencies and your module is one of those which blocks such goal 😋

@jamielennox
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Please sign in to comment.