diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2023-11-18 09:45:45 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2023-11-18 10:04:04 +0300 |
commit | df03ef42eed0a5b601011204e47b72d37c6373a9 (patch) | |
tree | 530e7eed0215db0b677aa07c5ccc834a8cb60e5a | |
parent | 02af688a175993fc8169edf79bef767977b91764 (diff) | |
download | ydb-df03ef42eed0a5b601011204e47b72d37c6373a9.tar.gz |
Intermediate changes
21 files changed, 286 insertions, 273 deletions
diff --git a/contrib/python/httpx/.dist-info/METADATA b/contrib/python/httpx/.dist-info/METADATA index f3a6d509cd..e4abd6660e 100644 --- a/contrib/python/httpx/.dist-info/METADATA +++ b/contrib/python/httpx/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: httpx -Version: 0.25.0 +Version: 0.25.1 Summary: The next generation HTTP client. Project-URL: Changelog, https://github.com/encode/httpx/blob/master/CHANGELOG.md Project-URL: Documentation, https://www.python-httpx.org @@ -22,10 +22,12 @@ Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Internet :: WWW/HTTP Requires-Python: >=3.8 +Requires-Dist: anyio Requires-Dist: certifi -Requires-Dist: httpcore<0.19.0,>=0.18.0 +Requires-Dist: httpcore Requires-Dist: idna Requires-Dist: sniffio Provides-Extra: brotli @@ -192,23 +194,14 @@ inspiration around the lower-level networking details. ## Release Information -### Removed +### 0.25.1 (3rd November, 2023) -* Drop support for Python 3.7. (#2813) - -### Added - -* Support HTTPS proxies. (#2845) -* Change the type of `Extensions` from `Mapping[Str, Any]` to `MutableMapping[Str, Any]`. (#2803) -* Add `socket_options` argument to `httpx.HTTPTransport` and `httpx.AsyncHTTPTransport` classes. (#2716) -* The `Response.raise_for_status()` method now returns the response instance. For example: `data = httpx.get('...').raise_for_status().json()`. (#2776) +* Add support for Python 3.12. (#2854) +* Add support for httpcore 1.0 (#2885) ### Fixed -* Return `500` error response instead of exceptions when `raise_app_exceptions=False` is set on `ASGITransport`. (#2669) -* Ensure all `WSGITransport` environs have a `SERVER_PROTOCOL`. (#2708) -* Always encode forward slashes as `%2F` in query parameters (#2723) -* Use Mozilla documentation instead of `httpstatuses.com` for HTTP error reference (#2768) +* Raise `ValueError` on `Response.encoding` being set after `Response.text` has been accessed. (#2852) --- diff --git a/contrib/python/httpx/httpx/__version__.py b/contrib/python/httpx/httpx/__version__.py index bfa421ad60..9f92ef99b9 100644 --- a/contrib/python/httpx/httpx/__version__.py +++ b/contrib/python/httpx/httpx/__version__.py @@ -1,3 +1,3 @@ __title__ = "httpx" __description__ = "A next generation HTTP client, for Python 3." -__version__ = "0.25.0" +__version__ = "0.25.1" diff --git a/contrib/python/httpx/httpx/_auth.py b/contrib/python/httpx/httpx/_auth.py index 1d7385d573..c2c38f3945 100644 --- a/contrib/python/httpx/httpx/_auth.py +++ b/contrib/python/httpx/httpx/_auth.py @@ -1,5 +1,4 @@ import hashlib -import netrc import os import re import time @@ -8,7 +7,7 @@ from base64 import b64encode from urllib.request import parse_http_list from ._exceptions import ProtocolError -from ._models import Request, Response +from ._models import Cookies, Request, Response from ._utils import to_bytes, to_str, unquote if typing.TYPE_CHECKING: # pragma: no cover @@ -148,6 +147,10 @@ class NetRCAuth(Auth): """ def __init__(self, file: typing.Optional[str] = None): + # Lazily import 'netrc'. + # There's no need for us to load this module unless 'NetRCAuth' is being used. + import netrc + self._netrc_info = netrc.netrc(file) def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]: @@ -217,6 +220,8 @@ class DigestAuth(Auth): request.headers["Authorization"] = self._build_auth_header( request, self._last_challenge ) + if response.cookies: + Cookies(response.cookies).set_cookie_header(request=request) yield request def _parse_challenge( diff --git a/contrib/python/httpx/httpx/_compat.py b/contrib/python/httpx/httpx/_compat.py index a271c6b800..493e621087 100644 --- a/contrib/python/httpx/httpx/_compat.py +++ b/contrib/python/httpx/httpx/_compat.py @@ -16,9 +16,7 @@ except ImportError: # pragma: no cover except ImportError: brotli = None -if sys.version_info >= (3, 10) or ( - sys.version_info >= (3, 8) and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0, 7) -): +if sys.version_info >= (3, 10) or ssl.OPENSSL_VERSION_INFO >= (1, 1, 0, 7): def set_minimum_tls_version_1_2(context: ssl.SSLContext) -> None: # The OP_NO_SSL* and OP_NO_TLS* become deprecated in favor of diff --git a/contrib/python/httpx/httpx/_config.py b/contrib/python/httpx/httpx/_config.py index 8d4e03add5..50bcede7f7 100644 --- a/contrib/python/httpx/httpx/_config.py +++ b/contrib/python/httpx/httpx/_config.py @@ -1,7 +1,6 @@ import logging import os import ssl -import sys import typing from pathlib import Path @@ -132,11 +131,10 @@ class SSLConfig: # Signal to server support for PHA in TLS 1.3. Raises an # AttributeError if only read-only access is implemented. - if sys.version_info >= (3, 8): # pragma: no cover - try: - context.post_handshake_auth = True - except AttributeError: # pragma: no cover - pass + try: + context.post_handshake_auth = True + except AttributeError: # pragma: no cover + pass # Disable using 'commonName' for SSLContext.check_hostname # when the 'subjectAltName' extension isn't available. @@ -175,10 +173,9 @@ class SSLConfig: alpn_idents = ["http/1.1", "h2"] if self.http2 else ["http/1.1"] context.set_alpn_protocols(alpn_idents) - if sys.version_info >= (3, 8): # pragma: no cover - keylogfile = os.environ.get("SSLKEYLOGFILE") - if keylogfile and self.trust_env: - context.keylog_filename = keylogfile + keylogfile = os.environ.get("SSLKEYLOGFILE") + if keylogfile and self.trust_env: + context.keylog_filename = keylogfile return context diff --git a/contrib/python/httpx/httpx/_models.py b/contrib/python/httpx/httpx/_models.py index e1e45cf06b..4e4162db1a 100644 --- a/contrib/python/httpx/httpx/_models.py +++ b/contrib/python/httpx/httpx/_models.py @@ -43,7 +43,6 @@ from ._types import ( ) from ._urls import URL from ._utils import ( - guess_json_utf, is_known_encoding, normalize_header_key, normalize_header_value, @@ -603,6 +602,16 @@ class Response: @encoding.setter def encoding(self, value: str) -> None: + """ + Set the encoding to use for decoding the byte content into text. + + If the `text` attribute has been accessed, attempting to set the + encoding will throw a ValueError. + """ + if hasattr(self, "_text"): + raise ValueError( + "Setting encoding after `text` has been accessed is not allowed." + ) self._encoding = value @property @@ -749,11 +758,7 @@ class Response: raise HTTPStatusError(message, request=request, response=self) def json(self, **kwargs: typing.Any) -> typing.Any: - if self.charset_encoding is None and self.content and len(self.content) > 3: - encoding = guess_json_utf(self.content) - if encoding is not None: - return jsonlib.loads(self.content.decode(encoding), **kwargs) - return jsonlib.loads(self.text, **kwargs) + return jsonlib.loads(self.content, **kwargs) @property def cookies(self) -> "Cookies": diff --git a/contrib/python/httpx/httpx/_multipart.py b/contrib/python/httpx/httpx/_multipart.py index 446f4ad2df..6d5baa8639 100644 --- a/contrib/python/httpx/httpx/_multipart.py +++ b/contrib/python/httpx/httpx/_multipart.py @@ -1,4 +1,3 @@ -import binascii import io import os import typing @@ -200,7 +199,7 @@ class MultipartStream(SyncByteStream, AsyncByteStream): boundary: typing.Optional[bytes] = None, ) -> None: if boundary is None: - boundary = binascii.hexlify(os.urandom(16)) + boundary = os.urandom(16).hex().encode("ascii") self.boundary = boundary self.content_type = "multipart/form-data; boundary=%s" % boundary.decode( diff --git a/contrib/python/httpx/httpx/_transports/default.py b/contrib/python/httpx/httpx/_transports/default.py index 7dba5b8208..76c543ce4e 100644 --- a/contrib/python/httpx/httpx/_transports/default.py +++ b/contrib/python/httpx/httpx/_transports/default.py @@ -64,7 +64,7 @@ SOCKET_OPTION = typing.Union[ def map_httpcore_exceptions() -> typing.Iterator[None]: try: yield - except Exception as exc: # noqa: PIE-786 + except Exception as exc: mapped_exc = None for from_exc, to_exc in HTTPCORE_EXC_MAP.items(): diff --git a/contrib/python/httpx/httpx/_urlparse.py b/contrib/python/httpx/httpx/_urlparse.py index e1ba8dcdb7..8e060424e8 100644 --- a/contrib/python/httpx/httpx/_urlparse.py +++ b/contrib/python/httpx/httpx/_urlparse.py @@ -87,7 +87,7 @@ COMPONENT_REGEX = { # We use these simple regexs as a first pass before handing off to # the stdlib 'ipaddress' module for IP address validation. -IPv4_STYLE_HOSTNAME = re.compile(r"^[0-9]+.[0-9]+.[0-9]+.[0-9]+$") +IPv4_STYLE_HOSTNAME = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$") IPv6_STYLE_HOSTNAME = re.compile(r"^\[.*\]$") diff --git a/contrib/python/httpx/httpx/_utils.py b/contrib/python/httpx/httpx/_utils.py index 1775b1a1ef..ba5807c048 100644 --- a/contrib/python/httpx/httpx/_utils.py +++ b/contrib/python/httpx/httpx/_utils.py @@ -91,41 +91,6 @@ def format_form_param(name: str, value: str) -> bytes: return f'{name}="{value}"'.encode() -# Null bytes; no need to recreate these on each call to guess_json_utf -_null = b"\x00" -_null2 = _null * 2 -_null3 = _null * 3 - - -def guess_json_utf(data: bytes) -> typing.Optional[str]: - # JSON always starts with two ASCII characters, so detection is as - # easy as counting the nulls and from their location and count - # determine the encoding. Also detect a BOM, if present. - sample = data[:4] - if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE): - return "utf-32" # BOM included - if sample[:3] == codecs.BOM_UTF8: - return "utf-8-sig" # BOM included, MS style (discouraged) - if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): - return "utf-16" # BOM included - nullcount = sample.count(_null) - if nullcount == 0: - return "utf-8" - if nullcount == 2: - if sample[::2] == _null2: # 1st and 3rd are null - return "utf-16-be" - if sample[1::2] == _null2: # 2nd and 4th are null - return "utf-16-le" - # Did not detect 2 valid UTF-16 ascii-range characters - if nullcount == 3: - if sample[:3] == _null3: - return "utf-32-be" - if sample[1:] == _null3: - return "utf-32-le" - # Did not detect a valid UTF-32 ascii-range character - return None - - def get_ca_bundle_from_env() -> typing.Optional[str]: if "SSL_CERT_FILE" in os.environ: ssl_file = Path(os.environ["SSL_CERT_FILE"]) diff --git a/contrib/python/httpx/ya.make b/contrib/python/httpx/ya.make index 850e354ef0..112b04e998 100644 --- a/contrib/python/httpx/ya.make +++ b/contrib/python/httpx/ya.make @@ -2,11 +2,12 @@ PY3_LIBRARY() -VERSION(0.25.0) +VERSION(0.25.1) LICENSE(BSD-3-Clause) PEERDIR( + contrib/python/anyio contrib/python/certifi contrib/python/httpcore contrib/python/idna diff --git a/contrib/python/responses/py3/.dist-info/METADATA b/contrib/python/responses/py3/.dist-info/METADATA index 9d56ee6bec..1604e06711 100644 --- a/contrib/python/responses/py3/.dist-info/METADATA +++ b/contrib/python/responses/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: responses -Version: 0.23.3 +Version: 0.24.0 Summary: A utility library for mocking out the `requests` Python library. Home-page: https://github.com/getsentry/responses Author: David Cramer @@ -15,27 +15,25 @@ Classifier: Intended Audience :: System Administrators Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Topic :: Software Development -Requires-Python: >=3.7 +Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE -Requires-Dist: requests (<3.0,>=2.30.0) -Requires-Dist: urllib3 (<3.0,>=1.25.10) +Requires-Dist: requests <3.0,>=2.30.0 +Requires-Dist: urllib3 <3.0,>=1.25.10 Requires-Dist: pyyaml -Requires-Dist: types-PyYAML -Requires-Dist: typing-extensions ; python_version < "3.8" Provides-Extra: tests -Requires-Dist: pytest (>=7.0.0) ; extra == 'tests' -Requires-Dist: coverage (>=6.0.0) ; extra == 'tests' +Requires-Dist: pytest >=7.0.0 ; extra == 'tests' +Requires-Dist: coverage >=6.0.0 ; extra == 'tests' Requires-Dist: pytest-cov ; extra == 'tests' Requires-Dist: pytest-asyncio ; extra == 'tests' Requires-Dist: pytest-httpserver ; extra == 'tests' Requires-Dist: flake8 ; extra == 'tests' +Requires-Dist: types-PyYAML ; extra == 'tests' Requires-Dist: types-requests ; extra == 'tests' Requires-Dist: mypy ; extra == 'tests' Requires-Dist: tomli-w ; extra == 'tests' @@ -60,7 +58,7 @@ A utility library for mocking out the ``requests`` Python library. .. note:: - Responses requires Python 3.7 or newer, and requests >= 2.30.0 + Responses requires Python 3.8 or newer, and requests >= 2.30.0 Table of Contents @@ -1122,6 +1120,59 @@ Assert that the request was called exactly n times. responses.assert_call_count("http://www.example.com?hello=world", 1) is True +Assert Request Calls data +------------------------- + +``Request`` object has ``calls`` list which elements correspond to ``Call`` objects +in the global list of ``Registry``. This can be useful when the order of requests is not +guaranteed, but you need to check their correctness, for example in multithreaded +applications. + +.. code-block:: python + + import concurrent.futures + import responses + import requests + + + @responses.activate + def test_assert_calls_on_resp(): + rsp1 = responses.patch("http://www.foo.bar/1/", status=200) + rsp2 = responses.patch("http://www.foo.bar/2/", status=400) + rsp3 = responses.patch("http://www.foo.bar/3/", status=200) + + def update_user(uid, is_active): + url = f"http://www.foo.bar/{uid}/" + response = requests.patch(url, json={"is_active": is_active}) + return response + + with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: + future_to_uid = { + executor.submit(update_user, uid, is_active): uid + for (uid, is_active) in [("3", True), ("2", True), ("1", False)] + } + for future in concurrent.futures.as_completed(future_to_uid): + uid = future_to_uid[future] + response = future.result() + print(f"{uid} updated with {response.status_code} status code") + + assert len(responses.calls) == 3 # total calls count + + assert rsp1.call_count == 1 + assert rsp1.calls[0] in responses.calls + assert rsp1.calls[0].response.status_code == 200 + assert json.loads(rsp1.calls[0].request.body) == {"is_active": False} + + assert rsp2.call_count == 1 + assert rsp2.calls[0] in responses.calls + assert rsp2.calls[0].response.status_code == 400 + assert json.loads(rsp2.calls[0].request.body) == {"is_active": True} + + assert rsp3.call_count == 1 + assert rsp3.calls[0] in responses.calls + assert rsp3.calls[0].response.status_code == 200 + assert json.loads(rsp3.calls[0].request.body) == {"is_active": True} + Multiple Responses ------------------ diff --git a/contrib/python/responses/py3/README.rst b/contrib/python/responses/py3/README.rst index 7d790eb0e3..e2096c6cae 100644 --- a/contrib/python/responses/py3/README.rst +++ b/contrib/python/responses/py3/README.rst @@ -17,7 +17,7 @@ A utility library for mocking out the ``requests`` Python library. .. note:: - Responses requires Python 3.7 or newer, and requests >= 2.30.0 + Responses requires Python 3.8 or newer, and requests >= 2.30.0 Table of Contents @@ -1079,6 +1079,59 @@ Assert that the request was called exactly n times. responses.assert_call_count("http://www.example.com?hello=world", 1) is True +Assert Request Calls data +------------------------- + +``Request`` object has ``calls`` list which elements correspond to ``Call`` objects +in the global list of ``Registry``. This can be useful when the order of requests is not +guaranteed, but you need to check their correctness, for example in multithreaded +applications. + +.. code-block:: python + + import concurrent.futures + import responses + import requests + + + @responses.activate + def test_assert_calls_on_resp(): + rsp1 = responses.patch("http://www.foo.bar/1/", status=200) + rsp2 = responses.patch("http://www.foo.bar/2/", status=400) + rsp3 = responses.patch("http://www.foo.bar/3/", status=200) + + def update_user(uid, is_active): + url = f"http://www.foo.bar/{uid}/" + response = requests.patch(url, json={"is_active": is_active}) + return response + + with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: + future_to_uid = { + executor.submit(update_user, uid, is_active): uid + for (uid, is_active) in [("3", True), ("2", True), ("1", False)] + } + for future in concurrent.futures.as_completed(future_to_uid): + uid = future_to_uid[future] + response = future.result() + print(f"{uid} updated with {response.status_code} status code") + + assert len(responses.calls) == 3 # total calls count + + assert rsp1.call_count == 1 + assert rsp1.calls[0] in responses.calls + assert rsp1.calls[0].response.status_code == 200 + assert json.loads(rsp1.calls[0].request.body) == {"is_active": False} + + assert rsp2.call_count == 1 + assert rsp2.calls[0] in responses.calls + assert rsp2.calls[0].response.status_code == 400 + assert json.loads(rsp2.calls[0].request.body) == {"is_active": True} + + assert rsp3.call_count == 1 + assert rsp3.calls[0] in responses.calls + assert rsp3.calls[0].response.status_code == 200 + assert json.loads(rsp3.calls[0].request.body) == {"is_active": True} + Multiple Responses ------------------ diff --git a/contrib/python/responses/py3/responses/__init__.py b/contrib/python/responses/py3/responses/__init__.py index 2666b7c8bc..0e74f9b907 100644 --- a/contrib/python/responses/py3/responses/__init__.py +++ b/contrib/python/responses/py3/responses/__init__.py @@ -1,6 +1,8 @@ +import http import inspect import json as json_module import logging +import socket from collections import namedtuple from functools import partialmethod from functools import wraps @@ -22,7 +24,6 @@ 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 @@ -41,20 +42,6 @@ try: except ImportError: # pragma: no cover from typing import Literal # type: ignore # pragma: no cover -try: - from requests.packages.urllib3.response import HTTPResponse -except ImportError: # pragma: no cover - from urllib3.response import HTTPResponse # pragma: no cover - -try: - from requests.packages.urllib3.connection import HTTPHeaderDict -except ImportError: # pragma: no cover - from urllib3.response import HTTPHeaderDict # type: ignore[attr-defined] -try: - from requests.packages.urllib3.util.url import parse_url -except ImportError: # pragma: no cover - from urllib3.util.url import parse_url # pragma: no cover - from io import BufferedReader from io import BytesIO from unittest import mock as std_mock @@ -64,6 +51,10 @@ 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 @@ -84,14 +75,25 @@ if TYPE_CHECKING: # pragma: no cover ) -> models.Response: ... - -# Block of type annotations -_Body = Union[str, BaseException, "Response", BufferedReader, bytes, None] -_F = Callable[..., Any] -_HeaderSet = Optional[Union[Mapping[str, str], List[Tuple[str, str]]]] -_MatcherIterable = Iterable[Callable[..., Tuple[bool, str]]] -_HTTPMethodOrResponse = Optional[Union[str, "BaseResponse"]] -_URLPatternType = Union["Pattern[str]", str] + # Block of type annotations + _Body = Union[str, BaseException, "Response", BufferedReader, bytes, None] + _F = Callable[..., Any] + _HeaderSet = Optional[Union[Mapping[str, str], List[Tuple[str, str]]]] + _MatcherIterable = Iterable[Callable[..., Tuple[bool, str]]] + _HTTPMethodOrResponse = Optional[Union[str, "BaseResponse"]] + _URLPatternType = Union["Pattern[str]", str] + _HTTPAdapterSend = Callable[ + [ + HTTPAdapter, + PreparedRequest, + bool, + Union[float, Tuple[float, float], Tuple[float, None], None], + Union[bool, str], + Union[bytes, str, Tuple[Union[bytes, str], Union[bytes, str]], None], + Optional[Mapping[str, str]], + ], + models.Response, + ] Call = namedtuple("Call", ["request", "response"]) @@ -111,8 +113,6 @@ class FalseBool: def __bool__(self) -> bool: return False - __nonzero__ = __bool__ - def urlencoded_params_matcher(params: Optional[Dict[str, str]]) -> Callable[..., Any]: warn( @@ -241,27 +241,22 @@ class CallList(Sequence[Any], Sized): def __len__(self) -> int: return len(self._calls) - @overload - def __getitem__(self, idx: int) -> Call: - """Overload when get a single item.""" - - @overload - def __getitem__(self, idx: slice) -> List[Call]: - """Overload when a slice is requested.""" - def __getitem__(self, idx: Union[int, slice]) -> Union[Call, List[Call]]: return self._calls[idx] - def add(self, request: "PreparedRequest", response: _Body) -> None: + def add(self, request: "PreparedRequest", response: "_Body") -> None: self._calls.append(Call(request, response)) + def add_call(self, call: Call) -> None: + self._calls.append(call) + def reset(self) -> None: self._calls = [] def _ensure_url_default_path( - url: _URLPatternType, -) -> _URLPatternType: + url: "_URLPatternType", +) -> "_URLPatternType": """Add empty URL path '/' if doesn't exist. Examples @@ -369,7 +364,7 @@ def _handle_body( return data -class BaseResponse(object): +class BaseResponse: passthrough: bool = False content_type: Optional[str] = None headers: Optional[Mapping[str, str]] = None @@ -378,7 +373,7 @@ class BaseResponse(object): def __init__( self, method: str, - url: _URLPatternType, + url: "_URLPatternType", match_querystring: Union[bool, object] = None, match: "_MatcherIterable" = (), *, @@ -386,7 +381,7 @@ class BaseResponse(object): ) -> None: self.method: str = method # ensure the url has a default path set if the url is a string - self.url: _URLPatternType = _ensure_url_default_path(url) + self.url: "_URLPatternType" = _ensure_url_default_path(url) if self._should_match_querystring(match_querystring): match = tuple(match) + ( @@ -394,7 +389,7 @@ class BaseResponse(object): ) self.match: "_MatcherIterable" = match - self.call_count: int = 0 + self._calls: CallList = CallList() self.passthrough = passthrough def __eq__(self, other: Any) -> bool: @@ -436,7 +431,7 @@ class BaseResponse(object): return bool(urlsplit(self.url).query) - def _url_matches(self, url: _URLPatternType, other: str) -> bool: + def _url_matches(self, url: "_URLPatternType", other: str) -> bool: """Compares two URLs. Compares only scheme, netloc and path. If 'url' is a re.Pattern, then checks that @@ -481,10 +476,17 @@ class BaseResponse(object): def get_headers(self) -> HTTPHeaderDict: headers = HTTPHeaderDict() # Duplicate headers are legal - if self.content_type is not None: + + # Add Content-Type if it exists and is not already in headers + if self.content_type and ( + not self.headers or "Content-Type" not in self.headers + ): headers["Content-Type"] = self.content_type + + # Extend headers if they exist if self.headers: headers.extend(self.headers) + return headers def get_response(self, request: "PreparedRequest") -> HTTPResponse: @@ -503,23 +505,33 @@ class BaseResponse(object): return True, "" + @property + def call_count(self) -> int: + return len(self._calls) + + @property + def calls(self) -> CallList: + return self._calls + def _form_response( body: Union[BufferedReader, BytesIO], headers: Optional[Mapping[str, str]], status: int, ) -> HTTPResponse: - # The requests library's cookie handling depends on the response object - # having an original response object with the headers as the `msg`, so - # we give it what it needs. - data = BytesIO() - data.close() - - orig_response = HTTPResponse( - body=data, # required to avoid "ValueError: Unable to determine whether fp is closed." - msg=headers, - preload_content=False, - ) + dummy_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + orig_response = http.client.HTTPResponse(sock=dummy_socket) + """ + 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. + Therefore, we intentionally ignore type checking for this assignment. + """ + orig_response.msg = headers # type: ignore[assignment] + return HTTPResponse( status=status, reason=client.responses.get(status, None), @@ -534,8 +546,8 @@ class Response(BaseResponse): def __init__( self, method: str, - url: _URLPatternType, - body: _Body = "", + url: "_URLPatternType", + body: "_Body" = "", json: Optional[Any] = None, status: int = 200, headers: Optional[Mapping[str, str]] = None, @@ -558,7 +570,7 @@ class Response(BaseResponse): else: content_type = "text/plain" - self.body: _Body = body + self.body: "_Body" = body self.status: int = status self.headers: Optional[Mapping[str, str]] = headers @@ -610,7 +622,7 @@ class CallbackResponse(BaseResponse): def __init__( self, method: str, - url: _URLPatternType, + url: "_URLPatternType", callback: Callable[[Any], Any], stream: Optional[bool] = None, content_type: Optional[str] = "text/plain", @@ -662,7 +674,7 @@ class PassthroughResponse(BaseResponse): super().__init__(*args, passthrough=True, **kwargs) -class RequestsMock(object): +class RequestsMock: DELETE: Literal["DELETE"] = "DELETE" GET: Literal["GET"] = "GET" HEAD: Literal["HEAD"] = "HEAD" @@ -680,6 +692,8 @@ class RequestsMock(object): passthru_prefixes: Tuple[str, ...] = (), target: str = "requests.adapters.HTTPAdapter.send", registry: Type[FirstMatchRegistry] = FirstMatchRegistry, + *, + real_adapter_send: "_HTTPAdapterSend" = _real_send, ) -> None: self._calls: CallList = CallList() self.reset() @@ -690,6 +704,7 @@ class RequestsMock(object): self.target: str = target self._patcher: Optional["_mock_patcher[Any]"] = None self._thread_lock = _ThreadingLock() + self._real_send = real_adapter_send def get_registry(self) -> FirstMatchRegistry: """Returns current registry instance with responses. @@ -728,10 +743,10 @@ class RequestsMock(object): def add( self, - method: _HTTPMethodOrResponse = None, + method: "_HTTPMethodOrResponse" = None, url: "Optional[_URLPatternType]" = None, - body: _Body = "", - adding_headers: _HeaderSet = None, + body: "_Body" = "", + adding_headers: "_HeaderSet" = None, *args: Any, **kwargs: Any, ) -> BaseResponse: @@ -792,7 +807,7 @@ class RequestsMock(object): def _parse_response_file( self, file_path: "Union[str, bytes, os.PathLike[Any]]" ) -> "Dict[str, Any]": - with open(file_path, "r") as file: + with open(file_path) as file: data = yaml.safe_load(file) return data @@ -810,7 +825,7 @@ class RequestsMock(object): auto_calculate_content_length=rsp["auto_calculate_content_length"], ) - def add_passthru(self, prefix: _URLPatternType) -> None: + def add_passthru(self, prefix: "_URLPatternType") -> None: """ Register a URL prefix or regex to passthru any non-matching mock requests to. @@ -831,7 +846,7 @@ class RequestsMock(object): def remove( self, - method_or_response: _HTTPMethodOrResponse = None, + method_or_response: "_HTTPMethodOrResponse" = None, url: "Optional[_URLPatternType]" = None, ) -> List[BaseResponse]: """ @@ -854,9 +869,9 @@ class RequestsMock(object): def replace( self, - method_or_response: _HTTPMethodOrResponse = None, + method_or_response: "_HTTPMethodOrResponse" = None, url: "Optional[_URLPatternType]" = None, - body: _Body = "", + body: "_Body" = "", *args: Any, **kwargs: Any, ) -> BaseResponse: @@ -880,9 +895,9 @@ class RequestsMock(object): def upsert( self, - method_or_response: _HTTPMethodOrResponse = None, + method_or_response: "_HTTPMethodOrResponse" = None, url: "Optional[_URLPatternType]" = None, - body: _Body = "", + body: "_Body" = "", *args: Any, **kwargs: Any, ) -> BaseResponse: @@ -903,9 +918,10 @@ class RequestsMock(object): def add_callback( self, method: str, - url: _URLPatternType, + url: "_URLPatternType", callback: Callable[ - ["PreparedRequest"], Union[Exception, Tuple[int, Mapping[str, str], _Body]] + ["PreparedRequest"], + Union[Exception, Tuple[int, Mapping[str, str], "_Body"]], ], match_querystring: Union[bool, FalseBool] = FalseBool(), content_type: Optional[str] = "text/plain", @@ -941,34 +957,17 @@ class RequestsMock(object): self.reset() return success - @overload - def activate(self, func: _F = ...) -> _F: - """Overload for scenario when 'responses.activate' is used.""" - - @overload - def activate( - self, - *, - registry: Type[Any] = ..., - assert_all_requests_are_fired: bool = ..., - ) -> Callable[["_F"], "_F"]: - """Overload for scenario when - 'responses.activate(registry=, assert_all_requests_are_fired=True)' is used. - - See https://github.com/getsentry/responses/pull/469 for more details - """ - def activate( self, - func: Optional[_F] = None, + func: Optional["_F"] = None, *, registry: Optional[Type[Any]] = None, assert_all_requests_are_fired: bool = False, - ) -> Union[Callable[["_F"], "_F"], _F]: + ) -> Union[Callable[["_F"], "_F"], "_F"]: if func is not None: return get_wrapped(func, self) - def deco_activate(function: _F) -> Callable[..., Any]: + def deco_activate(function: "_F") -> Callable[..., Any]: return get_wrapped( function, self, @@ -1030,7 +1029,7 @@ class RequestsMock(object): ] ): logger.info("request.allowed-passthru", extra={"url": request_url}) - return _real_send(adapter, request, **kwargs) + return self._real_send(adapter, request, **kwargs) # type: ignore error_msg = ( "Connection refused by Responses - the call doesn't " @@ -1047,7 +1046,7 @@ class RequestsMock(object): if self.passthru_prefixes: error_msg += "Passthru prefixes:\n" for p in self.passthru_prefixes: - error_msg += "- {}\n".format(p) + error_msg += f"- {p}\n" response = ConnectionError(error_msg) response.request = request @@ -1057,25 +1056,27 @@ class RequestsMock(object): if match.passthrough: logger.info("request.passthrough-response", extra={"url": request_url}) - response = _real_send(adapter, request, **kwargs) # type: ignore[assignment] + response = self._real_send(adapter, request, **kwargs) # type: ignore else: try: response = adapter.build_response( # type: ignore[no-untyped-call] request, match.get_response(request) ) except BaseException as response: - match.call_count += 1 - self._calls.add(request, response) + call = Call(request, response) + self._calls.add_call(call) + match.calls.add_call(call) raise if resp_callback: response = resp_callback(response) # type: ignore[misc] - match.call_count += 1 - self._calls.add(request, response) # type: ignore[misc] + call = Call(request, response) # type: ignore[misc] + self._calls.add_call(call) + match.calls.add_call(call) retries = retries or adapter.max_retries # first validate that current request is eligible to be retried. - # See ``requests.packages.urllib3.util.retry.Retry`` documentation. + # See ``urllib3.util.retry.Retry`` documentation. if retries.is_retry( method=response.request.method, status_code=response.status_code # type: ignore[misc] ): @@ -1156,7 +1157,7 @@ class RequestsMock(object): not_called = [m for m in self.registered() if m.call_count == 0] if not_called: raise AssertionError( - "Not all requests have been executed {0!r}".format( + "Not all requests have been executed {!r}".format( [(match.method, match.url) for match in not_called] ) ) diff --git a/contrib/python/responses/py3/responses/matchers.py b/contrib/python/responses/py3/responses/matchers.py index 4880e5082e..175f967035 100644 --- a/contrib/python/responses/py3/responses/matchers.py +++ b/contrib/python/responses/py3/responses/matchers.py @@ -1,4 +1,6 @@ +import gzip import json as json_module +import re from json.decoder import JSONDecodeError from typing import Any from typing import Callable @@ -11,7 +13,7 @@ from urllib.parse import parse_qsl from urllib.parse import urlparse from requests import PreparedRequest -from requests.packages.urllib3.util.url import parse_url +from urllib3.util.url import parse_url def _create_key_val_str(input_dict: Union[Dict[Any, Any], Any]) -> str: @@ -47,7 +49,7 @@ def _create_key_val_str(input_dict: Union[Dict[Any, Any], Any]) -> str: elif isinstance(val, list): val = list_to_str(input_list=val) - items_list.append("{}: {}".format(key, val)) + items_list.append(f"{key}: {val}") key_val_str = "{{{}}}".format(", ".join(items_list)) return key_val_str @@ -124,8 +126,11 @@ def json_params_matcher( request_body = request.body json_params = (params or {}) if not isinstance(params, list) else params try: - if isinstance(request_body, bytes): - request_body = request_body.decode("utf-8") + if isinstance(request.body, bytes): + try: + request_body = request.body.decode("utf-8") + except UnicodeDecodeError: + request_body = gzip.decompress(request.body).decode("utf-8") json_body = json_module.loads(request_body) if request_body else {} if ( @@ -251,7 +256,7 @@ def query_string_matcher(query: Optional[str]) -> Callable[..., Any]: def match(request: PreparedRequest) -> Tuple[bool, str]: reason = "" - data = parse_url(request.url) + data = parse_url(request.url or "") request_query = data.query request_qsl = sorted(parse_qsl(request_query)) if request_query else {} @@ -402,6 +407,23 @@ def header_matcher( :return: (func) matcher """ + def _compare_with_regex(request_headers: Union[Dict[Any, Any], Any]) -> bool: + if strict_match and len(request_headers) != len(headers): + return False + + for k, v in headers.items(): + if request_headers.get(k) is not None: + if isinstance(v, re.Pattern): + if re.match(v, request_headers[k]) is None: + return False + else: + if not v == request_headers[k]: + return False + elif strict_match: + return False + + return True + def match(request: PreparedRequest) -> Tuple[bool, str]: request_headers: Union[Dict[Any, Any], Any] = request.headers or {} @@ -409,7 +431,7 @@ def header_matcher( # filter down to just the headers specified in the matcher request_headers = {k: v for k, v in request_headers.items() if k in headers} - valid = sorted(headers.items()) == sorted(request_headers.items()) + valid = _compare_with_regex(request_headers) if not valid: return False, "Headers do not match: {} doesn't match {}".format( diff --git a/contrib/python/responses/py3/responses/registries.py b/contrib/python/responses/py3/responses/registries.py index 4b7e2337e6..fcf481a880 100644 --- a/contrib/python/responses/py3/responses/registries.py +++ b/contrib/python/responses/py3/responses/registries.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: # pragma: no cover from responses import BaseResponse -class FirstMatchRegistry(object): +class FirstMatchRegistry: def __init__(self) -> None: self._responses: List["BaseResponse"] = [] @@ -67,9 +67,7 @@ class FirstMatchRegistry(object): try: index = self.registered.index(response) except ValueError: - raise ValueError( - "Response is not registered for URL {}".format(response.url) - ) + raise ValueError(f"Response is not registered for URL {response.url}") self.registered[index] = response return response diff --git a/contrib/python/responses/py3/ya.make b/contrib/python/responses/py3/ya.make index 20cb7a76c8..bd363a36d8 100644 --- a/contrib/python/responses/py3/ya.make +++ b/contrib/python/responses/py3/ya.make @@ -2,14 +2,13 @@ PY3_LIBRARY() -VERSION(0.23.3) +VERSION(0.24.0) LICENSE(Apache-2.0) PEERDIR( contrib/python/PyYAML contrib/python/requests - contrib/python/types-PyYAML contrib/python/urllib3 ) diff --git a/contrib/python/types-PyYAML/.dist-info/METADATA b/contrib/python/types-PyYAML/.dist-info/METADATA deleted file mode 100644 index 1805257c6b..0000000000 --- a/contrib/python/types-PyYAML/.dist-info/METADATA +++ /dev/null @@ -1,31 +0,0 @@ -Metadata-Version: 2.1 -Name: types-PyYAML -Version: 6.0.12.12 -Summary: Typing stubs for PyYAML -Home-page: https://github.com/python/typeshed -License: Apache-2.0 license -Project-URL: GitHub, https://github.com/python/typeshed -Project-URL: Changes, https://github.com/typeshed-internal/stub_uploader/blob/main/data/changelogs/PyYAML.md -Project-URL: Issue tracker, https://github.com/python/typeshed/issues -Project-URL: Chat, https://gitter.im/python/typing -Classifier: License :: OSI Approved :: Apache Software License -Classifier: Programming Language :: Python :: 3 -Classifier: Typing :: Stubs Only -Description-Content-Type: text/markdown - -## Typing stubs for PyYAML - -This is a PEP 561 type stub package for the `PyYAML` package. It -can be used by type-checking tools like -[mypy](https://github.com/python/mypy/), -[pyright](https://github.com/microsoft/pyright), -[pytype](https://github.com/google/pytype/), -PyCharm, etc. to check code that uses -`PyYAML`. The source for this package can be found at -https://github.com/python/typeshed/tree/main/stubs/PyYAML. All fixes for -types and metadata should be contributed there. - -See https://github.com/python/typeshed/blob/main/README.md for more details. -This package was generated from typeshed commit `e40b5be2ea48dc83ffc2f38bfd542334deb98b20` and was tested -with mypy 1.5.1, pyright 1.1.328, and -pytype 2023.8.31. diff --git a/contrib/python/types-PyYAML/.dist-info/top_level.txt b/contrib/python/types-PyYAML/.dist-info/top_level.txt deleted file mode 100644 index 06a155d8c8..0000000000 --- a/contrib/python/types-PyYAML/.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -yaml-stubs diff --git a/contrib/python/types-PyYAML/ya.make b/contrib/python/types-PyYAML/ya.make deleted file mode 100644 index 144b1c420f..0000000000 --- a/contrib/python/types-PyYAML/ya.make +++ /dev/null @@ -1,40 +0,0 @@ -# Generated by devtools/yamaker (pypi). - -PY3_LIBRARY() - -VERSION(6.0.12.12) - -LICENSE(Apache-2.0) - -NO_LINT() - -PY_SRCS( - TOP_LEVEL - yaml-stubs/__init__.pyi - yaml-stubs/_yaml.pyi - yaml-stubs/composer.pyi - yaml-stubs/constructor.pyi - yaml-stubs/cyaml.pyi - yaml-stubs/dumper.pyi - yaml-stubs/emitter.pyi - yaml-stubs/error.pyi - yaml-stubs/events.pyi - yaml-stubs/loader.pyi - yaml-stubs/nodes.pyi - yaml-stubs/parser.pyi - yaml-stubs/reader.pyi - yaml-stubs/representer.pyi - yaml-stubs/resolver.pyi - yaml-stubs/scanner.pyi - yaml-stubs/serializer.pyi - yaml-stubs/tokens.pyi -) - -RESOURCE_FILES( - PREFIX contrib/python/types-PyYAML/ - .dist-info/METADATA - .dist-info/top_level.txt - yaml-stubs/METADATA.toml -) - -END() diff --git a/contrib/python/types-PyYAML/yaml-stubs/METADATA.toml b/contrib/python/types-PyYAML/yaml-stubs/METADATA.toml deleted file mode 100644 index c31b588e64..0000000000 --- a/contrib/python/types-PyYAML/yaml-stubs/METADATA.toml +++ /dev/null @@ -1,2 +0,0 @@ -version = "6.0.*" -upstream_repository = "https://github.com/yaml/pyyaml" |