import inspect import json as json_module import logging from functools import partialmethod from functools import wraps from http import client from itertools import groupby from re import Pattern from threading import Lock as _ThreadingLock from typing import TYPE_CHECKING from typing import Any from typing import Callable from typing import Dict from typing import Iterable from typing import Iterator from typing import List from typing import Mapping from typing import NamedTuple from typing import Optional from typing import Sequence from typing import Sized from typing import Tuple from typing import Type from typing import Union from typing import overload from warnings import warn
import yaml from requests.adapters import HTTPAdapter from requests.adapters import MaxRetryError from requests.exceptions import ConnectionError from requests.exceptions import RetryError
from responses.matchers import json_params_matcher as _json_params_matcher from responses.matchers import query_string_matcher as _query_string_matcher from responses.matchers import urlencoded_params_matcher as _urlencoded_params_matcher from responses.registries import FirstMatchRegistry
try: from typing_extensions import Literal except ImportError: # pragma: no cover from typing import Literal # type: ignore # pragma: no cover
from io import BufferedReader from io import BytesIO from unittest import mock as std_mock from urllib.parse import parse_qsl from urllib.parse import quote from urllib.parse import urlsplit from urllib.parse import urlunparse from urllib.parse import urlunsplit
from urllib3.response import HTTPHeaderDict from urllib3.response import HTTPResponse from urllib3.util.url import parse_url
if TYPE_CHECKING: # pragma: no cover # import only for linter run import os from typing import Protocol from unittest.mock import _patch as _mock_patcher
from requests import PreparedRequest from requests import models from urllib3 import Retry as _Retry
def _clean_unicode(url: str) -> str: """Clean up URLs, which use punycode to handle unicode chars.
Applies percent encoding to URL path and query if required.
Parameters
----------
url : str
URL that should be cleaned from unicode
Returns
-------
str
Cleaned URL
"""
urllist = list(urlsplit(url))
netloc = urllist[1] if _has_unicode(netloc):
domains = netloc.split(".") for i, d in enumerate(domains): if _has_unicode(d):
d = "xn--" + d.encode("punycode").decode("ascii")
domains[i] = d
urllist[1] = ".".join(domains)
url = urlunsplit(urllist)
# Clean up path/query/params, which use url-encoding to handle unicode chars
chars = list(url) for i, x in enumerate(chars): if ord(x) > 128:
chars[i] = quote(x)
Provides a synchronous or asynchronous wrapper for the function.
Parameters
----------
func : Callable
Function to wrap.
responses : RequestsMock
Mock object that is used as context manager.
registry : FirstMatchRegistry, optional
Custom registry that should be applied. See ``responses.registries``
assert_all_requests_are_fired : bool Raise an error ifnot all registered responses were executed.
if inspect.iscoroutinefunction(func): # set asynchronous wrapper if requestor function is asynchronous
@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any: # type: ignore[misc] if registry isnotNone:
responses._set_registry(registry)
with assert_mock, responses: return await func(*args, **kwargs)
with assert_mock, responses: # set 'assert_all_requests_are_fired' temporarily for a single run. # Mock automatically unsets to avoid leakage to another decorated # function since we still apply the value on 'responses.mock' object return func(*args, **kwargs)
Parameters
----------
body : str or bytes or BufferedReader
Input data to generate `Response` body.
Returns
-------
body : BufferedReader or BytesIO
`Response` body
""" if isinstance(body, str):
body = body.encode("utf-8") if isinstance(body, BufferedReader): return body
data = BytesIO(body) # type: ignore[arg-type]
def is_closed() -> bool: """
Real Response uses HTTPResponse as body object.
Thus, when method is_closed is called first to check if there is any more
content to consume and the file-like object is still opened
where file should be intentionally be left opened to continue consumption """ ifnot data.closed and data.read(1): # if there is more bytes to read then keep open, but return pointer
data.seek(-1, 1) returnFalse else: ifnot data.closed: # close but return False to mock like is still opened
data.close() returnFalse
# only if file really closed (by us) return True returnTrue
data.isclosed = is_closed # type: ignore[attr-defined] return data
# Can't simply do an equality check on the objects directly here since __eq__ isn't # implemented for regex. It might seem to work as regex is using a cache to return # the same regex instances, but it doesn't in all cases.
self_url = self.url.pattern if isinstance(self.url, Pattern) else self.url
other_url = other.url.pattern if isinstance(other.url, Pattern) else other.url
# Add Content-Type if it exists and is not already in headers if self.content_type and ( not self.headers or"Content-Type"notin self.headers
):
headers["Content-Type"] = self.content_type
# Extend headers if they exist if self.headers:
headers.extend(self.headers)
The cookie handling functionality of the `requests` library relies on the response object
having an original response object with the headers stored in the `msg` attribute.
Instead of supplying a file-like object of type `HTTPMessage` for the headers, we provide
the headers directly. This approach eliminates the need to parse the headers into a file-like
object and then rely on the library to unparse it back. These additional conversions can
introduce potential errors. """
data = BytesIO()
data.close()
"""
The type `urllib3.response.HTTPResponse` is incorrect; we should
use `http.client.HTTPResponse` instead. However, changing this requires opening
a real socket to imitate the object. This may not be desired, as some users may
want to completely restrict network access in their tests.
See https://github.com/getsentry/responses/issues/691 """
orig_response = HTTPResponse(
body=data, # required to avoid "ValueError: Unable to determine whether fp is closed."
msg=headers, # type: ignore[arg-type]
preload_content=False,
) return HTTPResponse(
status=status,
reason=client.responses.get(status, None),
body=body,
headers=headers,
original_response=orig_response, # type: ignore[arg-type] # See comment above
preload_content=False,
request_method=request_method,
)
# if we were passed a `json` argument, # override the body and content_type if json isnotNone: assertnot body
body = json_module.dumps(json) if content_type is _UNSET:
content_type = "application/json"
if content_type is _UNSET: if isinstance(body, str) and _has_unicode(body):
content_type = "text/plain; charset=utf-8" else:
content_type = "text/plain"
self.body: "_Body" = body
self.status: int = status
self.headers: Optional[Mapping[str, str]] = headers
if stream isnotNone:
warn( "stream argument is deprecated. Use stream parameter in request directly",
DeprecationWarning,
)
result = self.callback(request) if isinstance(result, Exception): raise result
status, r_headers, body = result if isinstance(body, Exception): raise body
# If the callback set a content-type remove the one # set in add_callback() so that we don't have multiple # content type values.
has_content_type = False if isinstance(r_headers, dict) and"Content-Type"in r_headers:
has_content_type = True elif isinstance(r_headers, list):
has_content_type = any(
[h for h in r_headers if h and h[0].lower() == "content-type"]
) if has_content_type:
headers.pop("Content-Type", None)
body = _handle_body(body)
headers.extend(r_headers)
""" if isinstance(method, BaseResponse): return self._registry.add(method)
if adding_headers isnotNone:
kwargs.setdefault("headers", adding_headers) if ( "content_type"in kwargs and"headers"in kwargs and kwargs["headers"] isnotNone
):
header_keys = [header.lower() for header in kwargs["headers"]] if"content-type"in header_keys: raise RuntimeError( "You cannot define both `content_type` and `headers[Content-Type]`." " Using the `content_type` kwarg is recommended."
)
delete = partialmethod(add, DELETE)
get = partialmethod(add, GET)
head = partialmethod(add, HEAD)
options = partialmethod(add, OPTIONS)
patch = partialmethod(add, PATCH)
post = partialmethod(add, POST)
put = partialmethod(add, PUT)
def _parse_response_file(
self, file_path: "Union[str, bytes, os.PathLike[Any]]"
) -> "Dict[str, Any]": with open(file_path) as file:
data = yaml.safe_load(file) return data
>>> import re
>>> responses.add_passthru(re.compile('https://example.com/\\w+')) """ ifnot isinstance(prefix, Pattern) and _has_unicode(prefix):
prefix = _clean_unicode(prefix)
self.passthru_prefixes += (prefix,)
def remove(
self,
method_or_response: "_HTTPMethodOrResponse" = None,
url: "Optional[_URLPatternType]" = None,
) -> List[BaseResponse]: """
Removes a response previously added using ``add()``, identified
either by a response object inheriting ``BaseResponse`` or
``method`` and ``url``. Removes all matching responses.
def replace(
self,
method_or_response: "_HTTPMethodOrResponse" = None,
url: "Optional[_URLPatternType]" = None,
body: "_Body" = "",
*args: Any,
**kwargs: Any,
) -> BaseResponse: """
Replaces a response previously added using ``add()``. The signature is identical to ``add()``. The response is identified using ``method`` and ``url``, and the first matching response is replaced.
def upsert(
self,
method_or_response: "_HTTPMethodOrResponse" = None,
url: "Optional[_URLPatternType]" = None,
body: "_Body" = "",
*args: Any,
**kwargs: Any,
) -> BaseResponse: """
Replaces a response previously added using ``add()``, or adds the response if no response exists. Responses are matched using ``method``and ``url``.
The first matching response is replaced.
def _find_match(
self, request: "PreparedRequest"
) -> Tuple[Optional["BaseResponse"], List[str]]: """
Iterates through all available matches and validates if any of them matches the request
:param request: (PreparedRequest), request object
:return:
(Response) found match. If multiple found, then remove & return the first match.
(list) list with reasons why other matches don't match """ with self._thread_lock: return self._registry.find(request)
def _read_filelike_body(
self, body: Union[str, bytes, BufferedReader, None]
) -> Union[str, bytes, None]: # Requests/urllib support multiple types of body, including file-like objects. # Read from the file if it's a file-like object to avoid storing a closed file # in the call list and allow the user to compare against the data that was in the # request. # See GH #719 if isinstance(body, str) or isinstance(body, bytes) or body isNone: return body # Based on # https://github.com/urllib3/urllib3/blob/abbfbcb1dd274fc54b4f0a7785fd04d59b634195/src/urllib3/util/request.py#L220 if hasattr(body, "read") or isinstance(body, BufferedReader): return body.read() return body
def _on_request(
self,
adapter: "HTTPAdapter",
request: "PreparedRequest",
*,
retries: Optional["_Retry"] = None,
**kwargs: Any,
) -> "models.Response": # add attributes params and req_kwargs to 'request' object for further match comparison # original request object does not have these attributes
request.params = self._parse_request_params(request.path_url) # type: ignore[attr-defined]
request.req_kwargs = kwargs # type: ignore[attr-defined]
request_url = str(request.url)
request.body = self._read_filelike_body(request.body)
if match isNone: if any(
[
p.match(request_url) if isinstance(p, Pattern) else request_url.startswith(p) for p in self.passthru_prefixes
]
):
logger.info("request.allowed-passthru", extra={"url": request_url}) return self._real_send(adapter, request, **kwargs) # type: ignore
error_msg = ( "Connection refused by Responses - the call doesn't " "match any registered mock.\n\n" "Request: \n"
f"- {request.method} {request_url}\n\n" "Available matches:\n"
) for i, m in enumerate(self.registered()):
error_msg += "- {} {} {}\n".format(
m.method, m.url, match_failed_reasons[i]
)
if self.passthru_prefixes:
error_msg += "Passthru prefixes:\n" for p in self.passthru_prefixes:
error_msg += f"- {p}\n"
retries = retries or adapter.max_retries # first validate that current request is eligible to be retried. # See ``urllib3.util.retry.Retry`` documentation. if retries.is_retry(
method=response.request.method, status_code=response.status_code # type: ignore[misc]
): try:
retries = retries.increment(
method=response.request.method, # type: ignore[misc]
url=response.url, # type: ignore[misc]
response=response.raw, # type: ignore[misc]
) return self._on_request(adapter, request, retries=retries, **kwargs) except MaxRetryError as e: if retries.raise_on_status: """Since we call 'retries.increment()' by ourselves, we always set "error"
argument equal to None, thus, MaxRetryError exception will be raised with
ResponseError as a 'reason'.
Here we're emulating the `if isinstance(e.reason, ResponseError):`
branch found at: https://github.com/psf/requests/blob/ 177dd90f18a8f4dc79a7d2049f0a3f4fcc5932a0/requests/adapters.py#L549 """ raise RetryError(e, request=request)
return response return response
def unbound_on_send(self) -> "UnboundSend": def send(
adapter: "HTTPAdapter",
request: "PreparedRequest",
*args: Any,
**kwargs: Any,
) -> "models.Response": if args: # that probably means that the request was sent from the custom adapter # It is fully legit to send positional args from adapter, although, # `requests` implementation does it always with kwargs # See for more info: https://github.com/getsentry/responses/issues/642 try:
kwargs["stream"] = args[0]
kwargs["timeout"] = args[1]
kwargs["verify"] = args[2]
kwargs["cert"] = args[3]
kwargs["proxies"] = args[4] except IndexError: # not all kwargs are required pass
def start(self) -> None: if self._patcher: # we must not override value of the _patcher if already applied # this prevents issues when one decorated function is called from # another decorated function return
# once patcher is stopped, clean it. This is required to create a new # fresh patcher on self.start()
self._patcher = None
ifnot self.assert_all_requests_are_fired: return
ifnot allow_assert: return
not_called = [m for m in self.registered() if m.call_count == 0] if not_called: raise AssertionError( "Not all requests have been executed {!r}".format(
[(match.method, match.url) for match in not_called]
)
)
def assert_call_count(self, url: str, count: int) -> bool:
call_count = len(
[ 1 for call in self.calls if call.request.url == _ensure_url_default_path(url)
]
) if call_count == count: returnTrue else: raise AssertionError(
f"Expected URL '{url}' to be called {count} times. Called {call_count} times."
)
def __getattr__(name: str) -> Any: if name in deprecated_names:
warn(
f"{name} is deprecated. Please use 'responses.mock.{name}",
DeprecationWarning,
) return globals()[f"_deprecated_{name}"] raise AttributeError(f"module {__name__} has no attribute {name}")
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.