Repository: getsentry/responses
Pull request #431 (2021-11-04 14:57:34)

added badge for coverage
we recent efforts we were able to push coverage to 100%
I think now we can proudly add badge with coverage :D

codecov[bot]: # [Codecov](https://app.codecov.io/gh/getsentry/responses/compare/431?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry) Report
> Merging [#431](https://app.codecov.io/gh/getsentry/responses/compare/431?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry) (d48ee95) into [master](https://codecov.io/gh/getsentry/responses/commit/391cf203a9eb9106f097cda6dba0eba5a6505703?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry) (391cf20) will **increase** coverage by `1.05%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/getsentry/responses/pull/431/graphs/tree.svg?width=650&height=150&src=pr&token=x2tAGR7rxE&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry)](https://app.codecov.io/gh/getsentry/responses/compare/431?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry)

```diff
@@             Coverage Diff             @@
##           master      #431      +/-   ##
===========================================
+ Coverage   98.94%   100.00%   +1.05%     
===========================================
  Files           4         3       -1     
  Lines        1895      1875      -20     
===========================================
  Hits         1875      1875              
+ Misses         20         0      -20     
```


| [Impacted Files](https://app.codecov.io/gh/getsentry/responses/compare/431?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry) | Coverage Δ | |
|---|---|---|
| [setup.py](https://app.codecov.io/gh/getsentry/responses/compare/431/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry#diff-c2V0dXAucHk=) | | |

------

[Continue to review full report at Codecov](https://app.codecov.io/gh/getsentry/responses/compare/431?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://app.codecov.io/gh/getsentry/responses/compare/431?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry). Last update [391cf20...d48ee95](https://app.codecov.io/gh/getsentry/responses/compare/431?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry).


Patch:
diff --git a/CHANGES b/CHANGES
index 60a0617..3505ba9 100644
--- a/CHANGES
+++ b/CHANGES
@@ -3,11 +3,11 @@
 
 * Fixed regression with `stream` parameter deprecation, requests.session() and cookie handling.
 * Replaced adhoc URL parsing with `urllib.parse`.
+* Added ``match`` parameter to ``add_callback`` method
 * Added `responses.matchers.fragment_identifier_matcher`. This matcher allows you
   to match request URL fragment identifier.
 * Improved test coverage.
 
-
 0.15.0
 ------
 
diff --git a/README.rst b/README.rst
index f04dfb3..7f7857f 100644
--- a/README.rst
+++ b/README.rst
@@ -7,6 +7,9 @@ Responses
 .. image:: https://img.shields.io/pypi/pyversions/responses.svg
     :target: https://pypi.org/project/responses/
 
+.. image:: https://codecov.io/gh/getsentry/responses/branch/master/graph/badge.svg
+    :target: https://codecov.io/gh/getsentry/responses/
+
 A utility library for mocking out the ``requests`` Python library.
 
 ..  note::
diff --git a/responses/__init__.py b/responses/__init__.py
index 7de3b64..1b1ce57 100644
--- a/responses/__init__.py
+++ b/responses/__init__.py
@@ -259,7 +259,7 @@ class BaseResponse(object):
 
     stream = False
 
-    def __init__(self, method, url, match_querystring=_unspecified, match=[]):
+    def __init__(self, method, url, match_querystring=_unspecified, match=()):
         self.method = method
         # ensure the url has a default path set if the url is a string
         self.url = _ensure_url_default_path(url)
@@ -667,7 +667,13 @@ class RequestsMock(object):
             self.add(method_or_response, url, body, *args, **kwargs)
 
     def add_callback(
-        self, method, url, callback, match_querystring=False, content_type="text/plain"
+        self,
+        method,
+        url,
+        callback,
+        match_querystring=False,
+        content_type="text/plain",
+        match=(),
     ):
         # ensure the url has a default path set if the url is a string
         # url = _ensure_url_default_path(url, match_querystring)
@@ -679,6 +685,7 @@ class RequestsMock(object):
                 callback=callback,
                 content_type=content_type,
                 match_querystring=match_querystring,
+                match=match,
             )
         )
 
diff --git a/responses/__init__.pyi b/responses/__init__.pyi
index 8e0bf35..898c68a 100644
--- a/responses/__init__.pyi
+++ b/responses/__init__.pyi
@@ -11,7 +11,8 @@ from typing import (
     Dict,
     List,
     Tuple,
-    Union
+    Union,
+    Iterable
 )
 from io import BufferedReader, BytesIO
 from re import Pattern
@@ -47,6 +48,8 @@ class Call(NamedTuple):
 
 _Body = Union[str, BaseException, "Response", BufferedReader, bytes]
 
+MatcherIterable = Iterable[Callable[[Any], Callable[..., Any]]]
+
 class CallList(Sequence[Call], Sized):
     def __init__(self) -> None: ...
     def __iter__(self) -> Iterator[Call]: ...
@@ -63,19 +66,19 @@ class BaseResponse:
     method: Any = ...
     url: Any = ...
     match_querystring: Any = ...
-    match: List[Any] = ...
+    match: MatcherIterable = ...
     call_count: int = ...
     def __init__(
         self,
         method: str,
         url: Union[Pattern[str], str],
         match_querystring: Union[bool, object] = ...,
-        match: List[Callable[..., Any]] = ...,
+        match: MatcherIterable = ...,
     ) -> None: ...
     def __eq__(self, other: Any) -> bool: ...
     def __ne__(self, other: Any) -> bool: ...
     def _req_attr_matches(
-        self, match: List[Callable[..., Any]], request: Optional[Union[bytes, str]]
+        self, match: MatcherIterable, request: Optional[Union[bytes, str]]
     ) -> Tuple[bool, str]: ...
     def _should_match_querystring(
         self, match_querystring_argument: Union[bool, object]
@@ -107,7 +110,7 @@ class Response(BaseResponse):
         content_type: Optional[str] = ...,
         auto_calculate_content_length: bool = ...,
         match_querystring: bool = ...,
-        match: List[Any] = ...,
+        match: MatcherIterable = ...,
     ) -> None: ...
     def get_response(  # type: ignore [override]
         self, request: PreparedRequest
@@ -125,7 +128,7 @@ class CallbackResponse(BaseResponse):
         stream: bool = ...,
         content_type: Optional[str] = ...,
         match_querystring: bool = ...,
-        match: List[Any] = ...,
+        match: MatcherIterable = ...,
     ) -> None: ...
     def get_response(  # type: ignore [override]
         self, request: PreparedRequest
@@ -202,7 +205,7 @@ class _Add(Protocol):
         auto_calculate_content_length: bool = ...,
         adding_headers: HeaderSet = ...,
         match_querystring: bool = ...,
-        match: List[Any] = ...,
+        match: MatcherIterable = ...,
     ) -> None: ...
 
 class _AddCallback(Protocol):
@@ -213,6 +216,7 @@ class _AddCallback(Protocol):
         callback: Callable[[PreparedRequest], Union[Exception, Tuple[int, Mapping[str, str], _Body]]],
         match_querystring: bool = ...,
         content_type: Optional[str] = ...,
+        match: MatcherIterable = ...,
     ) -> None: ...
 
 class _AddPassthru(Protocol):
@@ -240,7 +244,7 @@ class _Replace(Protocol):
         content_type: Optional[str] = ...,
         adding_headers: HeaderSet = ...,
         match_querystring: bool = ...,
-        match: List[Any] = ...,
+        match: MatcherIterable = ...,
     ) -> None: ...
 
 class _Upsert(Protocol):
@@ -256,7 +260,7 @@ class _Upsert(Protocol):
         content_type: Optional[str] = ...,
         adding_headers: HeaderSet = ...,
         match_querystring: bool = ...,
-        match: List[Any] = ...,
+        match: MatcherIterable = ...,
     ) -> None: ...
 
 class _Registered(Protocol):


Test patch:
diff --git a/responses/test_responses.py b/responses/test_responses.py
index 9c02fe6..2b8f403 100644
--- a/responses/test_responses.py
+++ b/responses/test_responses.py
@@ -669,6 +669,62 @@ def test_callback_content_type_dict():
     assert_reset()
 
 
+def test_callback_matchers():
+    def request_callback(request):
+        return (
+            200,
+            {"Content-Type": "application/json"},
+            b"foo",
+        )
+
+    @responses.activate
+    def run():
+        req_data = {"some": "other", "data": "fields"}
+        req_files = {"file_name": b"Old World!"}
+
+        responses.add_callback(
+            responses.POST,
+            url="http://httpbin.org/post",
+            match=[matchers.multipart_matcher(req_files, data=req_data)],
+            callback=request_callback,
+        )
+        resp = requests.post("http://httpbin.org/post", data=req_data, files=req_files)
+        assert resp.text == "foo"
+        assert resp.headers["content-type"] == "application/json"
+
+    run()
+    assert_reset()
+
+
+def test_callback_matchers_fail():
+    @responses.activate
+    def run():
+        req_data = {"some": "other", "data": "fields"}
+        req_files = {"file_name": b"Old World!"}
+
+        responses.add_callback(
+            responses.POST,
+            url="http://httpbin.org/post",
+            match=[matchers.multipart_matcher(req_files, data=req_data)],
+            callback=lambda x: (
+                0,
+                {"a": ""},
+                "",
+            ),
+        )
+        with pytest.raises(ConnectionError) as exc:
+            requests.post(
+                "http://httpbin.org/post",
+                data={"some": "other", "data": "wrong"},
+                files=req_files,
+            )
+
+        assert "multipart/form-data doesn't match." in str(exc.value)
+
+    run()
+    assert_reset()
+
+
 def test_callback_content_type_tuple():
     def request_callback(request):
         return (