From bf5e0b4327768887ed91ee5dd731af7ae1fcf319 Mon Sep 17 00:00:00 2001 From: Paul Suh Date: Sun, 8 Sep 2024 17:41:06 -0400 Subject: [PATCH 1/8] Add provision for using method_or_response in add --- responses/__init__.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/responses/__init__.py b/responses/__init__.py index 097e61ee..01b22bb9 100644 --- a/responses/__init__.py +++ b/responses/__init__.py @@ -778,6 +778,7 @@ def add( url: "Optional[_URLPatternType]" = None, body: "_Body" = "", adding_headers: "_HeaderSet" = None, + method_or_response: "_HTTPMethodOrResponse" = None, *args: Any, **kwargs: Any, ) -> BaseResponse: @@ -808,9 +809,24 @@ def add( >>> headers={'X-Header': 'foo'}, >>> ) + Use the keyword argument method_or_response in place of method: + + >>> responses.add( + >>> method_or_response='GET', + >>> url='http://example.com', + >>> ) + """ - if isinstance(method, BaseResponse): - return self._registry.add(method) + # must have only one of method or method_or_response + assert (method is not None and method_or_response is None) or ( + method is None and method_or_response is not None + ) + + # for backwards compatibility, method takes priority over method_or_response + actual_method = method if method is not None else method_or_response + + if isinstance(actual_method, BaseResponse): + return self._registry.add(actual_method) if adding_headers is not None: kwargs.setdefault("headers", adding_headers) @@ -827,8 +843,8 @@ def add( ) assert url is not None - assert isinstance(method, str) - response = Response(method=method, url=url, body=body, **kwargs) + assert isinstance(actual_method, str) + response = Response(method=actual_method, url=url, body=body, **kwargs) return self._registry.add(response) delete = partialmethod(add, DELETE) From 761ab23b1aab0111da4477bbc7bf6c1f119ff462 Mon Sep 17 00:00:00 2001 From: Paul Suh Date: Sun, 8 Sep 2024 20:08:43 -0400 Subject: [PATCH 2/8] Add unit test and assertion error message --- responses/__init__.py | 2 +- responses/tests/test_responses.py | 35 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/responses/__init__.py b/responses/__init__.py index 01b22bb9..1f4ed0ec 100644 --- a/responses/__init__.py +++ b/responses/__init__.py @@ -820,7 +820,7 @@ def add( # must have only one of method or method_or_response assert (method is not None and method_or_response is None) or ( method is None and method_or_response is not None - ) + ), "Only one of `method` or `method_or_response` should be used." # for backwards compatibility, method takes priority over method_or_response actual_method = method if method is not None else method_or_response diff --git a/responses/tests/test_responses.py b/responses/tests/test_responses.py index 9088ff3f..4407a350 100644 --- a/responses/tests/test_responses.py +++ b/responses/tests/test_responses.py @@ -74,6 +74,41 @@ def run(): assert_reset() +def test_response_using_method_or_response(): + @responses.activate + def run(): + responses.add( + responses.GET, "http://example.net/rest/path/varname", body="return value" + ) + resp = requests.get("http://example.net/rest/path/varname") + assert_response(resp, "return value") + + responses.add( + method_or_response=responses.GET, + url="http://example.net/rest/path/varname", + body="return value", + ) + resp = requests.get("http://example.net/rest/path/varname") + assert_response(resp, "return value") + + with pytest.raises(AssertionError) as exc: + responses.add( + method=responses.GET, + method_or_response=responses.GET, + url="http://example.net/rest/path/varname", + body="return value", + ) + resp = requests.get("http://example.net/rest/path/varname") + assert_response(resp, "return value") + assert ( + exc.value.args[0] + == "Only one of `method` or `method_or_response` should be used." + ) + + run() + assert_reset() + + def test_response_encoded(): @responses.activate def run(): From 367ece557278bc8d51c5691e759a25f20a384810 Mon Sep 17 00:00:00 2001 From: Paul Suh Date: Sun, 8 Sep 2024 20:26:58 -0400 Subject: [PATCH 3/8] Add documentation to the README --- README.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9b255076..72df8994 100644 --- a/README.rst +++ b/README.rst @@ -296,7 +296,10 @@ Response Parameters The following attributes can be passed to a Response mock: method (``str``) - The HTTP method (GET, POST, etc). + The HTTP method (GET, POST, etc). You may also use `method_or_response=` + as a keyword parameter in place of `method`, in which case all attributes + must be passed in as keyword parameters. This is to match the parameter + name on the `remove()`, `replace()`, and `upsert ()` methods. url (``str`` or ``compiled regular expression``) The full resource URL. From 9ba45f02292add68b747cdf80f32073e7bced297 Mon Sep 17 00:00:00 2001 From: Paul Suh Date: Sat, 12 Oct 2024 13:40:29 -0400 Subject: [PATCH 4/8] Switch to adding "method" keyword in other functions --- responses/__init__.py | 71 +++++++++++++++++++++---------- responses/tests/test_responses.py | 68 ++++++++++++++--------------- 2 files changed, 82 insertions(+), 57 deletions(-) diff --git a/responses/__init__.py b/responses/__init__.py index 1f4ed0ec..b487a7be 100644 --- a/responses/__init__.py +++ b/responses/__init__.py @@ -778,7 +778,6 @@ def add( url: "Optional[_URLPatternType]" = None, body: "_Body" = "", adding_headers: "_HeaderSet" = None, - method_or_response: "_HTTPMethodOrResponse" = None, *args: Any, **kwargs: Any, ) -> BaseResponse: @@ -817,16 +816,9 @@ def add( >>> ) """ - # must have only one of method or method_or_response - assert (method is not None and method_or_response is None) or ( - method is None and method_or_response is not None - ), "Only one of `method` or `method_or_response` should be used." - - # for backwards compatibility, method takes priority over method_or_response - actual_method = method if method is not None else method_or_response - if isinstance(actual_method, BaseResponse): - return self._registry.add(actual_method) + if isinstance(method, BaseResponse): + return self._registry.add(method) if adding_headers is not None: kwargs.setdefault("headers", adding_headers) @@ -843,8 +835,8 @@ def add( ) assert url is not None - assert isinstance(actual_method, str) - response = Response(method=actual_method, url=url, body=body, **kwargs) + assert isinstance(method, str) + response = Response(method=method, url=url, body=body, **kwargs) return self._registry.add(response) delete = partialmethod(add, DELETE) @@ -900,6 +892,7 @@ def remove( self, method_or_response: "_HTTPMethodOrResponse" = None, url: "Optional[_URLPatternType]" = None, + method: "_HTTPMethodOrResponse" = None, ) -> List[BaseResponse]: """ Removes a response previously added using ``add()``, identified @@ -910,12 +903,16 @@ def remove( >>> responses.add(responses.GET, 'http://example.org') >>> responses.remove(responses.GET, 'http://example.org') """ - if isinstance(method_or_response, BaseResponse): - response = method_or_response + actual_method_or_response = self._check_method_or_response( + method, method_or_response + ) + + if isinstance(actual_method_or_response, BaseResponse): + response = actual_method_or_response else: assert url is not None - assert isinstance(method_or_response, str) - response = BaseResponse(method=method_or_response, url=url) + assert isinstance(actual_method_or_response, str) + response = BaseResponse(method=actual_method_or_response, url=url) return self._registry.remove(response) @@ -924,6 +921,7 @@ def replace( method_or_response: "_HTTPMethodOrResponse" = None, url: "Optional[_URLPatternType]" = None, body: "_Body" = "", + method: "_HTTPMethodOrResponse" = None, *args: Any, **kwargs: Any, ) -> BaseResponse: @@ -936,12 +934,18 @@ def replace( >>> responses.add(responses.GET, 'http://example.org', json={'data': 1}) >>> responses.replace(responses.GET, 'http://example.org', json={'data': 2}) """ - if isinstance(method_or_response, BaseResponse): - response = method_or_response + actual_method_or_response = self._check_method_or_response( + method, method_or_response + ) + + if isinstance(actual_method_or_response, BaseResponse): + response = actual_method_or_response else: assert url is not None - assert isinstance(method_or_response, str) - response = Response(method=method_or_response, url=url, body=body, **kwargs) + assert isinstance(actual_method_or_response, str) + response = Response( + method=actual_method_or_response, url=url, body=body, **kwargs + ) return self._registry.replace(response) @@ -950,6 +954,7 @@ def upsert( method_or_response: "_HTTPMethodOrResponse" = None, url: "Optional[_URLPatternType]" = None, body: "_Body" = "", + method: "_HTTPMethodOrResponse" = None, *args: Any, **kwargs: Any, ) -> BaseResponse: @@ -962,10 +967,32 @@ def upsert( >>> responses.add(responses.GET, 'http://example.org', json={'data': 1}) >>> responses.upsert(responses.GET, 'http://example.org', json={'data': 2}) """ + actual_method_or_response = self._check_method_or_response( + method, method_or_response + ) + try: - return self.replace(method_or_response, url, body, *args, **kwargs) + return self.replace(actual_method_or_response, url, body, *args, **kwargs) except ValueError: - return self.add(method_or_response, url, body, *args, **kwargs) + return self.add(actual_method_or_response, url, body, *args, **kwargs) + + def _check_method_or_response( + self, + method: "_HTTPMethodOrResponse", + method_or_response: "_HTTPMethodOrResponse", + ) -> "_HTTPMethodOrResponse": + """ + Checks that only one of method or method_or_response is not None. + + Returns whichever one should be used if no exception is raised. + """ + assert (method is not None and method_or_response is None) or ( + method is None and method_or_response is not None + ), "Only one of `method` or `method_or_response` should be used." + + # For backwards compatibility method_or_response takes priority over + # method, but by the time we hit here only one or the other should valid + return method_or_response if method_or_response is not None else method def add_callback( self, diff --git a/responses/tests/test_responses.py b/responses/tests/test_responses.py index 4407a350..8ca730c4 100644 --- a/responses/tests/test_responses.py +++ b/responses/tests/test_responses.py @@ -74,41 +74,6 @@ def run(): assert_reset() -def test_response_using_method_or_response(): - @responses.activate - def run(): - responses.add( - responses.GET, "http://example.net/rest/path/varname", body="return value" - ) - resp = requests.get("http://example.net/rest/path/varname") - assert_response(resp, "return value") - - responses.add( - method_or_response=responses.GET, - url="http://example.net/rest/path/varname", - body="return value", - ) - resp = requests.get("http://example.net/rest/path/varname") - assert_response(resp, "return value") - - with pytest.raises(AssertionError) as exc: - responses.add( - method=responses.GET, - method_or_response=responses.GET, - url="http://example.net/rest/path/varname", - body="return value", - ) - resp = requests.get("http://example.net/rest/path/varname") - assert_response(resp, "return value") - assert ( - exc.value.args[0] - == "Only one of `method` or `method_or_response` should be used." - ) - - run() - assert_reset() - - def test_response_encoded(): @responses.activate def run(): @@ -185,6 +150,39 @@ def run(): assert_reset() +def test_replace_keyword_arg_method(): + @responses.activate + def run(): + responses.add(responses.GET, "http://example.com/one", body="one") + responses.replace( + method=responses.GET, url="http://example.com/one", body="testone" + ) + resp = requests.get("http://example.com/one") + assert_response(resp, "testone") + + responses.add(responses.GET, "http://example.com/two", body="two") + replacement_dict = { + "method": "GET", + "url": "http://example.com/two", + "body": "testtwo", + } + responses.replace(**replacement_dict) + resp = requests.get("http://example.com/two") + assert_response(resp, "testtwo") + + responses.add( + Response(method=responses.GET, url="http://example.com/three", body="three") + ) + responses.replace( + method=Response(responses.GET, "http://example.com/three", body="testthree") + ) + resp = requests.get("http://example.com/three") + assert_response(resp, "testthree") + + run() + assert_reset() + + @pytest.mark.parametrize( "original,replacement", [ From 019d3ca3d4ee64ad052eca90c03c4fdd21d5ce9d Mon Sep 17 00:00:00 2001 From: Paul Suh Date: Sat, 12 Oct 2024 17:23:51 -0400 Subject: [PATCH 5/8] Add tests for keyword arg method --- responses/tests/test_responses.py | 61 +++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/responses/tests/test_responses.py b/responses/tests/test_responses.py index 8ca730c4..447ebe87 100644 --- a/responses/tests/test_responses.py +++ b/responses/tests/test_responses.py @@ -291,6 +291,39 @@ def run(): assert_reset() +def test_upsert_keyword_arg_method(): + @responses.activate + def run(): + responses.add(responses.GET, "http://example.com/one", body="one") + responses.upsert( + method=responses.GET, url="http://example.com/one", body="testone" + ) + resp = requests.get("http://example.com/one") + assert_response(resp, "testone") + + responses.add(responses.GET, "http://example.com/two", body="two") + replacement_dict = { + "method": "GET", + "url": "http://example.com/two", + "body": "testtwo", + } + responses.upsert(**replacement_dict) + resp = requests.get("http://example.com/two") + assert_response(resp, "testtwo") + + responses.add( + Response(method=responses.GET, url="http://example.com/three", body="three") + ) + responses.upsert( + method=Response(responses.GET, "http://example.com/three", body="testthree") + ) + resp = requests.get("http://example.com/three") + assert_response(resp, "testthree") + + run() + assert_reset() + + def test_remove(): @responses.activate def run(): @@ -317,6 +350,34 @@ def run(): assert_reset() +def test_remove_keyword_arg_method(): + @responses.activate + def run(): + responses.add(responses.GET, "http://example.com/one", body="one") + responses.remove(method=responses.GET, url="http://example.com/one") + with pytest.raises(ConnectionError): + _ = requests.get("http://example.com/one") + + responses.add(responses.GET, "http://example.com/two", body="two") + remove_dict = { + "method": "GET", + "url": "http://example.com/two", + } + responses.remove(**remove_dict) + with pytest.raises(ConnectionError): + _ = requests.get("http://example.com/two") + + responses.add( + Response(method=responses.GET, url="http://example.com/three", body="three") + ) + responses.remove(method=Response(responses.GET, "http://example.com/three")) + with pytest.raises(ConnectionError): + _ = requests.get("http://example.com/three") + + run() + assert_reset() + + @pytest.mark.parametrize( "args1,kwargs1,args2,kwargs2,expected", [ From d3f75bf2f83becbf15cc57e20b4df75a4e3cd9d2 Mon Sep 17 00:00:00 2001 From: Paul Suh Date: Mon, 14 Oct 2024 23:52:58 -0400 Subject: [PATCH 6/8] Add info to change log --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 46600398..b8bfa844 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +0.25.4 +------ + +* Added `method=` as an optional keyword argument to the replace, remove, and upsert methods on RequestsMock. See #737 + 0.25.3 ------ From a94313788e08426e79bef12916f631195757672e Mon Sep 17 00:00:00 2001 From: Paul Suh Date: Mon, 14 Oct 2024 23:58:37 -0400 Subject: [PATCH 7/8] Revert "Add info to change log" This reverts commit d3f75bf2f83becbf15cc57e20b4df75a4e3cd9d2. --- CHANGES | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CHANGES b/CHANGES index b8bfa844..46600398 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,3 @@ -0.25.4 ------- - -* Added `method=` as an optional keyword argument to the replace, remove, and upsert methods on RequestsMock. See #737 - 0.25.3 ------ From d0f66d09d130112cc2b611b7f81fa80c7dd31176 Mon Sep 17 00:00:00 2001 From: Paul Suh Date: Tue, 15 Oct 2024 00:00:29 -0400 Subject: [PATCH 8/8] Re-add info to change log --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 05f9fbc6..4201778a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +0.25.5 +------ + +* Added `method=` as an optional keyword argument to the replace, remove, and upsert methods on RequestsMock. See #737 + 0.25.4 ------