Skip to content

Commit b398a85

Browse files
authored
Merge pull request #231 from getsentry/issue-230
fix(py3) Fix syntax error when generating wrappers
2 parents e60abff + b66891d commit b398a85

File tree

2 files changed

+76
-14
lines changed

2 files changed

+76
-14
lines changed

responses.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -128,25 +128,39 @@ def wrapper%(wrapper_args)s:
128128

129129

130130
def get_wrapped(func, responses):
131-
# Preserve the argspec for the wrapped function so that testing
132-
# tools such as pytest can continue to use their fixture injection.
133-
is_bound_method = hasattr(func, "__self__")
134-
135131
if six.PY2:
136132
args, a, kw, defaults = inspect.getargspec(func)
137133
wrapper_args = inspect.formatargspec(args, a, kw, defaults)
138-
if is_bound_method:
134+
135+
# Preserve the argspec for the wrapped function so that testing
136+
# tools such as pytest can continue to use their fixture injection.
137+
if hasattr(func, "__self__"):
139138
args = args[1:] # Omit 'self'
140139
func_args = inspect.formatargspec(args, a, kw, None)
141140
else:
142141
signature = inspect.signature(func)
143-
wrapper_args = str(signature)
144-
if is_bound_method:
145-
new_params = list(signature.parameters.values())[1:] # omit 'self'
146-
signature = signature.replace(parameters=new_params)
142+
signature = signature.replace(return_annotation=inspect.Signature.empty)
143+
# If the function is wrapped, switch to *args, **kwargs for the parameters
144+
# as we can't rely on the signature to give us the arguments the function will
145+
# be called with. For example unittest.mock.patch uses required args that are
146+
# not actually passed to the function when invoked.
147+
if hasattr(func, "__wrapped__"):
148+
wrapper_params = [
149+
inspect.Parameter("args", inspect.Parameter.VAR_POSITIONAL),
150+
inspect.Parameter("kwargs", inspect.Parameter.VAR_KEYWORD),
151+
]
152+
else:
153+
wrapper_params = [
154+
param.replace(annotation=inspect.Parameter.empty)
155+
for param in signature.parameters.values()
156+
]
157+
signature = signature.replace(parameters=wrapper_params)
147158

159+
wrapper_args = str(signature)
148160
params_without_defaults = [
149-
param.replace(default=inspect.Parameter.empty)
161+
param.replace(
162+
annotation=inspect.Parameter.empty, default=inspect.Parameter.empty
163+
)
150164
for param in signature.parameters.values()
151165
]
152166
signature = signature.replace(parameters=params_without_defaults)
@@ -158,10 +172,7 @@ def get_wrapped(func, responses):
158172
evaldict,
159173
)
160174
wrapper = evaldict["wrapper"]
161-
162175
update_wrapper(wrapper, func)
163-
if is_bound_method:
164-
wrapper = wrapper.__get__(func.__self__, type(func.__self__))
165176
return wrapper
166177

167178

test_responses.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
44

55
import inspect
66
import re
7+
import six
78

89
import pytest
910
import requests
10-
from requests.exceptions import ConnectionError, HTTPError
1111
import responses
12+
from requests.exceptions import ConnectionError, HTTPError
1213
from responses import BaseResponse, Response
1314

15+
try:
16+
from mock import patch, Mock
17+
except ImportError:
18+
from unittest.mock import patch, Mock
19+
1420

1521
def assert_reset():
1622
assert len(responses._default_mock._matches) == 0
@@ -524,6 +530,51 @@ def test_function(a, b=None):
524530
assert decorated_test_function(3) == test_function(3)
525531

526532

533+
def test_activate_mock_interaction():
534+
@patch("sys.stdout")
535+
def test_function(mock_stdout):
536+
return mock_stdout
537+
538+
decorated_test_function = responses.activate(test_function)
539+
if hasattr(inspect, "signature"):
540+
assert inspect.signature(test_function) == inspect.signature(
541+
decorated_test_function
542+
)
543+
else:
544+
assert inspect.getargspec(test_function) == inspect.getargspec(
545+
decorated_test_function
546+
)
547+
548+
value = test_function()
549+
assert isinstance(value, Mock)
550+
551+
value = decorated_test_function()
552+
assert isinstance(value, Mock)
553+
554+
555+
@pytest.mark.skipif(six.PY2, reason="Cannot run in python2")
556+
def test_activate_doesnt_change_signature_with_return_type():
557+
def test_function(a, b=None):
558+
return (a, b)
559+
560+
# Add type annotations as they are syntax errors in py2.
561+
# Use a class to test for import errors in evaled code.
562+
test_function.__annotations__["return"] = Mock
563+
test_function.__annotations__["a"] = Mock
564+
565+
decorated_test_function = responses.activate(test_function)
566+
if hasattr(inspect, "signature"):
567+
assert inspect.signature(test_function) == inspect.signature(
568+
decorated_test_function
569+
)
570+
else:
571+
assert inspect.getargspec(test_function) == inspect.getargspec(
572+
decorated_test_function
573+
)
574+
assert decorated_test_function(1, 2) == test_function(1, 2)
575+
assert decorated_test_function(3) == test_function(3)
576+
577+
527578
def test_activate_doesnt_change_signature_for_method():
528579
class TestCase(object):
529580
def test_function(self, a, b=None):

0 commit comments

Comments
 (0)