summaryrefslogtreecommitdiffstats
path: root/contrib/python/httpx
diff options
context:
space:
mode:
authorAlexSm <[email protected]>2024-01-09 18:56:40 +0100
committerGitHub <[email protected]>2024-01-09 18:56:40 +0100
commite95f266d2a3e48e62015220588a4fd73d5d5a5cb (patch)
treea8a784b6931fe52ad5f511cfef85af14e5f63991 /contrib/python/httpx
parent50a65e3b48a82d5b51f272664da389f2e0b0c99a (diff)
Library import 6 (#888)
Diffstat (limited to 'contrib/python/httpx')
-rw-r--r--contrib/python/httpx/.dist-info/METADATA20
-rw-r--r--contrib/python/httpx/httpx/__version__.py2
-rw-r--r--contrib/python/httpx/httpx/_api.py20
-rw-r--r--contrib/python/httpx/httpx/_client.py43
-rw-r--r--contrib/python/httpx/httpx/_content.py2
-rw-r--r--contrib/python/httpx/httpx/_decoders.py9
-rw-r--r--contrib/python/httpx/httpx/_exceptions.py10
-rw-r--r--contrib/python/httpx/httpx/_main.py21
-rw-r--r--contrib/python/httpx/httpx/_models.py7
-rw-r--r--contrib/python/httpx/httpx/_multipart.py17
-rw-r--r--contrib/python/httpx/httpx/_transports/asgi.py2
-rw-r--r--contrib/python/httpx/httpx/_transports/default.py15
-rw-r--r--contrib/python/httpx/httpx/_types.py3
-rw-r--r--contrib/python/httpx/httpx/_urlparse.py96
-rw-r--r--contrib/python/httpx/httpx/_urls.py22
-rw-r--r--contrib/python/httpx/httpx/_utils.py16
-rw-r--r--contrib/python/httpx/ya.make2
17 files changed, 216 insertions, 91 deletions
diff --git a/contrib/python/httpx/.dist-info/METADATA b/contrib/python/httpx/.dist-info/METADATA
index 247ac2e1189..ab2b707fcdc 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.2
+Version: 0.26.0
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
@@ -31,8 +31,8 @@ Requires-Dist: httpcore==1.*
Requires-Dist: idna
Requires-Dist: sniffio
Provides-Extra: brotli
-Requires-Dist: brotli; platform_python_implementation == 'CPython' and extra == 'brotli'
-Requires-Dist: brotlicffi; platform_python_implementation != 'CPython' and extra == 'brotli'
+Requires-Dist: brotli; (platform_python_implementation == 'CPython') and extra == 'brotli'
+Requires-Dist: brotlicffi; (platform_python_implementation != 'CPython') and extra == 'brotli'
Provides-Extra: cli
Requires-Dist: click==8.*; extra == 'cli'
Requires-Dist: pygments==2.*; extra == 'cli'
@@ -196,7 +196,19 @@ inspiration around the lower-level networking details.
### Added
-* Add missing type hints to few `__init__()` methods. (#2938)
+* The `proxy` argument was added. You should use the `proxy` argument instead of the deprecated `proxies`, or use `mounts=` for more complex configurations. (#2879)
+
+### Deprecated
+
+* The `proxies` argument is now deprecated. It will still continue to work, but it will be removed in the future. (#2879)
+
+### Fixed
+
+* Fix cases of double escaping of URL path components. Allow / as a safe character in the query portion. (#2990)
+* Handle `NO_PROXY` envvar cases when a fully qualified URL is supplied as the value. (#2741)
+* Allow URLs where username or password contains unescaped '@'. (#2986)
+* Ensure ASGI `raw_path` does not include URL query component. (#2999)
+* Ensure `Response.iter_text()` cannot yield empty strings. (#2998)
---
diff --git a/contrib/python/httpx/httpx/__version__.py b/contrib/python/httpx/httpx/__version__.py
index c6bc0ac0230..3edc842c69e 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.2"
+__version__ = "0.26.0"
diff --git a/contrib/python/httpx/httpx/_api.py b/contrib/python/httpx/httpx/_api.py
index 571289cf2b3..c7af947218a 100644
--- a/contrib/python/httpx/httpx/_api.py
+++ b/contrib/python/httpx/httpx/_api.py
@@ -10,6 +10,7 @@ from ._types import (
CookieTypes,
HeaderTypes,
ProxiesTypes,
+ ProxyTypes,
QueryParamTypes,
RequestContent,
RequestData,
@@ -32,6 +33,7 @@ def request(
headers: typing.Optional[HeaderTypes] = None,
cookies: typing.Optional[CookieTypes] = None,
auth: typing.Optional[AuthTypes] = None,
+ proxy: typing.Optional[ProxyTypes] = None,
proxies: typing.Optional[ProxiesTypes] = None,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
follow_redirects: bool = False,
@@ -63,6 +65,7 @@ def request(
request.
* **auth** - *(optional)* An authentication class to use when sending the
request.
+ * **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
* **proxies** - *(optional)* A dictionary mapping proxy keys to proxy URLs.
* **timeout** - *(optional)* The timeout configuration to use when sending
the request.
@@ -91,6 +94,7 @@ def request(
"""
with Client(
cookies=cookies,
+ proxy=proxy,
proxies=proxies,
cert=cert,
verify=verify,
@@ -124,6 +128,7 @@ def stream(
headers: typing.Optional[HeaderTypes] = None,
cookies: typing.Optional[CookieTypes] = None,
auth: typing.Optional[AuthTypes] = None,
+ proxy: typing.Optional[ProxyTypes] = None,
proxies: typing.Optional[ProxiesTypes] = None,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
follow_redirects: bool = False,
@@ -143,6 +148,7 @@ def stream(
"""
with Client(
cookies=cookies,
+ proxy=proxy,
proxies=proxies,
cert=cert,
verify=verify,
@@ -171,6 +177,7 @@ def get(
headers: typing.Optional[HeaderTypes] = None,
cookies: typing.Optional[CookieTypes] = None,
auth: typing.Optional[AuthTypes] = None,
+ proxy: typing.Optional[ProxyTypes] = None,
proxies: typing.Optional[ProxiesTypes] = None,
follow_redirects: bool = False,
cert: typing.Optional[CertTypes] = None,
@@ -193,6 +200,7 @@ def get(
headers=headers,
cookies=cookies,
auth=auth,
+ proxy=proxy,
proxies=proxies,
follow_redirects=follow_redirects,
cert=cert,
@@ -209,6 +217,7 @@ def options(
headers: typing.Optional[HeaderTypes] = None,
cookies: typing.Optional[CookieTypes] = None,
auth: typing.Optional[AuthTypes] = None,
+ proxy: typing.Optional[ProxyTypes] = None,
proxies: typing.Optional[ProxiesTypes] = None,
follow_redirects: bool = False,
cert: typing.Optional[CertTypes] = None,
@@ -231,6 +240,7 @@ def options(
headers=headers,
cookies=cookies,
auth=auth,
+ proxy=proxy,
proxies=proxies,
follow_redirects=follow_redirects,
cert=cert,
@@ -247,6 +257,7 @@ def head(
headers: typing.Optional[HeaderTypes] = None,
cookies: typing.Optional[CookieTypes] = None,
auth: typing.Optional[AuthTypes] = None,
+ proxy: typing.Optional[ProxyTypes] = None,
proxies: typing.Optional[ProxiesTypes] = None,
follow_redirects: bool = False,
cert: typing.Optional[CertTypes] = None,
@@ -269,6 +280,7 @@ def head(
headers=headers,
cookies=cookies,
auth=auth,
+ proxy=proxy,
proxies=proxies,
follow_redirects=follow_redirects,
cert=cert,
@@ -289,6 +301,7 @@ def post(
headers: typing.Optional[HeaderTypes] = None,
cookies: typing.Optional[CookieTypes] = None,
auth: typing.Optional[AuthTypes] = None,
+ proxy: typing.Optional[ProxyTypes] = None,
proxies: typing.Optional[ProxiesTypes] = None,
follow_redirects: bool = False,
cert: typing.Optional[CertTypes] = None,
@@ -312,6 +325,7 @@ def post(
headers=headers,
cookies=cookies,
auth=auth,
+ proxy=proxy,
proxies=proxies,
follow_redirects=follow_redirects,
cert=cert,
@@ -332,6 +346,7 @@ def put(
headers: typing.Optional[HeaderTypes] = None,
cookies: typing.Optional[CookieTypes] = None,
auth: typing.Optional[AuthTypes] = None,
+ proxy: typing.Optional[ProxyTypes] = None,
proxies: typing.Optional[ProxiesTypes] = None,
follow_redirects: bool = False,
cert: typing.Optional[CertTypes] = None,
@@ -355,6 +370,7 @@ def put(
headers=headers,
cookies=cookies,
auth=auth,
+ proxy=proxy,
proxies=proxies,
follow_redirects=follow_redirects,
cert=cert,
@@ -375,6 +391,7 @@ def patch(
headers: typing.Optional[HeaderTypes] = None,
cookies: typing.Optional[CookieTypes] = None,
auth: typing.Optional[AuthTypes] = None,
+ proxy: typing.Optional[ProxyTypes] = None,
proxies: typing.Optional[ProxiesTypes] = None,
follow_redirects: bool = False,
cert: typing.Optional[CertTypes] = None,
@@ -398,6 +415,7 @@ def patch(
headers=headers,
cookies=cookies,
auth=auth,
+ proxy=proxy,
proxies=proxies,
follow_redirects=follow_redirects,
cert=cert,
@@ -414,6 +432,7 @@ def delete(
headers: typing.Optional[HeaderTypes] = None,
cookies: typing.Optional[CookieTypes] = None,
auth: typing.Optional[AuthTypes] = None,
+ proxy: typing.Optional[ProxyTypes] = None,
proxies: typing.Optional[ProxiesTypes] = None,
follow_redirects: bool = False,
cert: typing.Optional[CertTypes] = None,
@@ -436,6 +455,7 @@ def delete(
headers=headers,
cookies=cookies,
auth=auth,
+ proxy=proxy,
proxies=proxies,
follow_redirects=follow_redirects,
cert=cert,
diff --git a/contrib/python/httpx/httpx/_client.py b/contrib/python/httpx/httpx/_client.py
index 2c7ae030f50..2813a84f010 100644
--- a/contrib/python/httpx/httpx/_client.py
+++ b/contrib/python/httpx/httpx/_client.py
@@ -36,6 +36,7 @@ from ._types import (
CookieTypes,
HeaderTypes,
ProxiesTypes,
+ ProxyTypes,
QueryParamTypes,
RequestContent,
RequestData,
@@ -597,6 +598,7 @@ class Client(BaseClient):
to authenticate the client. Either a path to an SSL certificate file, or
two-tuple of (certificate file, key file), or a three-tuple of (certificate
file, key file, password).
+ * **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
* **proxies** - *(optional)* A dictionary mapping proxy keys to proxy
URLs.
* **timeout** - *(optional)* The timeout configuration to use when sending
@@ -628,8 +630,11 @@ class Client(BaseClient):
cert: typing.Optional[CertTypes] = None,
http1: bool = True,
http2: bool = False,
+ proxy: typing.Optional[ProxyTypes] = None,
proxies: typing.Optional[ProxiesTypes] = None,
- mounts: typing.Optional[typing.Mapping[str, BaseTransport]] = None,
+ mounts: typing.Optional[
+ typing.Mapping[str, typing.Optional[BaseTransport]]
+ ] = None,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
follow_redirects: bool = False,
limits: Limits = DEFAULT_LIMITS,
@@ -666,8 +671,17 @@ class Client(BaseClient):
"Make sure to install httpx using `pip install httpx[http2]`."
) from None
+ if proxies:
+ message = (
+ "The 'proxies' argument is now deprecated."
+ " Use 'proxy' or 'mounts' instead."
+ )
+ warnings.warn(message, DeprecationWarning)
+ if proxy:
+ raise RuntimeError("Use either `proxy` or 'proxies', not both.")
+
allow_env_proxies = trust_env and app is None and transport is None
- proxy_map = self._get_proxy_map(proxies, allow_env_proxies)
+ proxy_map = self._get_proxy_map(proxies or proxy, allow_env_proxies)
self._transport = self._init_transport(
verify=verify,
@@ -1264,7 +1278,9 @@ class Client(BaseClient):
if self._state != ClientState.UNOPENED:
msg = {
ClientState.OPENED: "Cannot open a client instance more than once.",
- ClientState.CLOSED: "Cannot reopen a client instance, once it has been closed.",
+ ClientState.CLOSED: (
+ "Cannot reopen a client instance, once it has been closed."
+ ),
}[self._state]
raise RuntimeError(msg)
@@ -1322,6 +1338,7 @@ class AsyncClient(BaseClient):
file, key file, password).
* **http2** - *(optional)* A boolean indicating if HTTP/2 support should be
enabled. Defaults to `False`.
+ * **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
* **proxies** - *(optional)* A dictionary mapping HTTP protocols to proxy
URLs.
* **timeout** - *(optional)* The timeout configuration to use when sending
@@ -1353,8 +1370,11 @@ class AsyncClient(BaseClient):
cert: typing.Optional[CertTypes] = None,
http1: bool = True,
http2: bool = False,
+ proxy: typing.Optional[ProxyTypes] = None,
proxies: typing.Optional[ProxiesTypes] = None,
- mounts: typing.Optional[typing.Mapping[str, AsyncBaseTransport]] = None,
+ mounts: typing.Optional[
+ typing.Mapping[str, typing.Optional[AsyncBaseTransport]]
+ ] = None,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
follow_redirects: bool = False,
limits: Limits = DEFAULT_LIMITS,
@@ -1391,8 +1411,17 @@ class AsyncClient(BaseClient):
"Make sure to install httpx using `pip install httpx[http2]`."
) from None
+ if proxies:
+ message = (
+ "The 'proxies' argument is now deprecated."
+ " Use 'proxy' or 'mounts' instead."
+ )
+ warnings.warn(message, DeprecationWarning)
+ if proxy:
+ raise RuntimeError("Use either `proxy` or 'proxies', not both.")
+
allow_env_proxies = trust_env and app is None and transport is None
- proxy_map = self._get_proxy_map(proxies, allow_env_proxies)
+ proxy_map = self._get_proxy_map(proxies or proxy, allow_env_proxies)
self._transport = self._init_transport(
verify=verify,
@@ -1980,7 +2009,9 @@ class AsyncClient(BaseClient):
if self._state != ClientState.UNOPENED:
msg = {
ClientState.OPENED: "Cannot open a client instance more than once.",
- ClientState.CLOSED: "Cannot reopen a client instance, once it has been closed.",
+ ClientState.CLOSED: (
+ "Cannot reopen a client instance, once it has been closed."
+ ),
}[self._state]
raise RuntimeError(msg)
diff --git a/contrib/python/httpx/httpx/_content.py b/contrib/python/httpx/httpx/_content.py
index 0aaea33749a..cd0d17f1711 100644
--- a/contrib/python/httpx/httpx/_content.py
+++ b/contrib/python/httpx/httpx/_content.py
@@ -105,7 +105,7 @@ class UnattachedStream(AsyncByteStream, SyncByteStream):
def encode_content(
- content: Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]
+ content: Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]],
) -> Tuple[Dict[str, str], Union[SyncByteStream, AsyncByteStream]]:
if isinstance(content, (bytes, str)):
body = content.encode("utf-8") if isinstance(content, str) else content
diff --git a/contrib/python/httpx/httpx/_decoders.py b/contrib/python/httpx/httpx/_decoders.py
index b4ac9a44af6..3f507c8e040 100644
--- a/contrib/python/httpx/httpx/_decoders.py
+++ b/contrib/python/httpx/httpx/_decoders.py
@@ -212,7 +212,7 @@ class TextChunker:
def decode(self, content: str) -> typing.List[str]:
if self._chunk_size is None:
- return [content]
+ return [content] if content else []
self._buffer.write(content)
if self._buffer.tell() >= self._chunk_size:
@@ -259,7 +259,8 @@ class LineDecoder:
"""
Handles incrementally reading lines from text.
- Has the same behaviour as the stdllib splitlines, but handling the input iteratively.
+ Has the same behaviour as the stdllib splitlines,
+ but handling the input iteratively.
"""
def __init__(self) -> None:
@@ -279,7 +280,9 @@ class LineDecoder:
text = text[:-1]
if not text:
- return []
+ # NOTE: the edge case input of empty text doesn't occur in practice,
+ # because other httpx internals filter out this value
+ return [] # pragma: no cover
trailing_newline = text[-1] in NEWLINE_CHARS
lines = text.splitlines()
diff --git a/contrib/python/httpx/httpx/_exceptions.py b/contrib/python/httpx/httpx/_exceptions.py
index 24a4f8aba33..123692955b1 100644
--- a/contrib/python/httpx/httpx/_exceptions.py
+++ b/contrib/python/httpx/httpx/_exceptions.py
@@ -313,7 +313,10 @@ class ResponseNotRead(StreamError):
"""
def __init__(self) -> None:
- message = "Attempted to access streaming response content, without having called `read()`."
+ message = (
+ "Attempted to access streaming response content,"
+ " without having called `read()`."
+ )
super().__init__(message)
@@ -323,7 +326,10 @@ class RequestNotRead(StreamError):
"""
def __init__(self) -> None:
- message = "Attempted to access streaming request content, without having called `read()`."
+ message = (
+ "Attempted to access streaming request content,"
+ " without having called `read()`."
+ )
super().__init__(message)
diff --git a/contrib/python/httpx/httpx/_main.py b/contrib/python/httpx/httpx/_main.py
index 7c12ce841d3..adb57d5fc0a 100644
--- a/contrib/python/httpx/httpx/_main.py
+++ b/contrib/python/httpx/httpx/_main.py
@@ -63,20 +63,21 @@ def print_help() -> None:
)
table.add_row(
"--auth [cyan]<USER PASS>",
- "Username and password to include in the request. Specify '-' for the password to use "
- "a password prompt. Note that using --verbose/-v will expose the Authorization "
- "header, including the password encoding in a trivially reversible format.",
+ "Username and password to include in the request. Specify '-' for the password"
+ " to use a password prompt. Note that using --verbose/-v will expose"
+ " the Authorization header, including the password encoding"
+ " in a trivially reversible format.",
)
table.add_row(
- "--proxies [cyan]URL",
+ "--proxy [cyan]URL",
"Send the request via a proxy. Should be the URL giving the proxy address.",
)
table.add_row(
"--timeout [cyan]FLOAT",
- "Timeout value to use for network operations, such as establishing the connection, "
- "reading some data, etc... [Default: 5.0]",
+ "Timeout value to use for network operations, such as establishing the"
+ " connection, reading some data, etc... [Default: 5.0]",
)
table.add_row("--follow-redirects", "Automatically follow redirects.")
@@ -385,8 +386,8 @@ def handle_help(
),
)
@click.option(
- "--proxies",
- "proxies",
+ "--proxy",
+ "proxy",
type=str,
default=None,
help="Send the request via a proxy. Should be the URL giving the proxy address.",
@@ -455,7 +456,7 @@ def main(
headers: typing.List[typing.Tuple[str, str]],
cookies: typing.List[typing.Tuple[str, str]],
auth: typing.Optional[typing.Tuple[str, str]],
- proxies: str,
+ proxy: str,
timeout: float,
follow_redirects: bool,
verify: bool,
@@ -472,7 +473,7 @@ def main(
try:
with Client(
- proxies=proxies,
+ proxy=proxy,
timeout=timeout,
verify=verify,
http2=http2,
diff --git a/contrib/python/httpx/httpx/_models.py b/contrib/python/httpx/httpx/_models.py
index 8a5e6280f3f..b8617cdab56 100644
--- a/contrib/python/httpx/httpx/_models.py
+++ b/contrib/python/httpx/httpx/_models.py
@@ -358,7 +358,8 @@ class Request:
# Using `content=...` implies automatically populated `Host` and content
# headers, of either `Content-Length: ...` or `Transfer-Encoding: chunked`.
#
- # Using `stream=...` will not automatically include *any* auto-populated headers.
+ # Using `stream=...` will not automatically include *any*
+ # auto-populated headers.
#
# As an end-user you don't really need `stream=...`. It's only
# useful when:
@@ -852,7 +853,7 @@ class Response:
yield chunk
text_content = decoder.flush()
for chunk in chunker.decode(text_content):
- yield chunk
+ yield chunk # pragma: no cover
for chunk in chunker.flush():
yield chunk
@@ -956,7 +957,7 @@ class Response:
yield chunk
text_content = decoder.flush()
for chunk in chunker.decode(text_content):
- yield chunk
+ yield chunk # pragma: no cover
for chunk in chunker.flush():
yield chunk
diff --git a/contrib/python/httpx/httpx/_multipart.py b/contrib/python/httpx/httpx/_multipart.py
index 6d5baa86398..5122d5114fb 100644
--- a/contrib/python/httpx/httpx/_multipart.py
+++ b/contrib/python/httpx/httpx/_multipart.py
@@ -48,7 +48,8 @@ class DataField:
)
if value is not None and not isinstance(value, (str, bytes, int, float)):
raise TypeError(
- f"Invalid type for value. Expected primitive type, got {type(value)}: {value!r}"
+ "Invalid type for value. Expected primitive type,"
+ f" got {type(value)}: {value!r}"
)
self.name = name
self.value: typing.Union[str, bytes] = (
@@ -96,11 +97,13 @@ class FileField:
content_type: typing.Optional[str] = None
# This large tuple based API largely mirror's requests' API
- # It would be good to think of better APIs for this that we could include in httpx 2.0
- # since variable length tuples (especially of 4 elements) are quite unwieldly
+ # It would be good to think of better APIs for this that we could
+ # include in httpx 2.0 since variable length tuples(especially of 4 elements)
+ # are quite unwieldly
if isinstance(value, tuple):
if len(value) == 2:
- # neither the 3rd parameter (content_type) nor the 4th (headers) was included
+ # neither the 3rd parameter (content_type) nor the 4th (headers)
+ # was included
filename, fileobj = value # type: ignore
elif len(value) == 3:
filename, fileobj, content_type = value # type: ignore
@@ -116,9 +119,9 @@ class FileField:
has_content_type_header = any("content-type" in key.lower() for key in headers)
if content_type is not None and not has_content_type_header:
- # note that unlike requests, we ignore the content_type
- # provided in the 3rd tuple element if it is also included in the headers
- # requests does the opposite (it overwrites the header with the 3rd tuple element)
+ # note that unlike requests, we ignore the content_type provided in the 3rd
+ # tuple element if it is also included in the headers requests does
+ # the opposite (it overwrites the headerwith the 3rd tuple element)
headers["Content-Type"] = content_type
if isinstance(fileobj, io.StringIO):
diff --git a/contrib/python/httpx/httpx/_transports/asgi.py b/contrib/python/httpx/httpx/_transports/asgi.py
index f67f0fbd5b5..08cd392f75c 100644
--- a/contrib/python/httpx/httpx/_transports/asgi.py
+++ b/contrib/python/httpx/httpx/_transports/asgi.py
@@ -103,7 +103,7 @@ class ASGITransport(AsyncBaseTransport):
"headers": [(k.lower(), v) for (k, v) in request.headers.raw],
"scheme": request.url.scheme,
"path": request.url.path,
- "raw_path": request.url.raw_path,
+ "raw_path": request.url.raw_path.split(b"?")[0],
"query_string": request.url.query,
"server": (request.url.host, request.url.port),
"client": self.client,
diff --git a/contrib/python/httpx/httpx/_transports/default.py b/contrib/python/httpx/httpx/_transports/default.py
index 343c588f9ff..14a087389a8 100644
--- a/contrib/python/httpx/httpx/_transports/default.py
+++ b/contrib/python/httpx/httpx/_transports/default.py
@@ -47,7 +47,8 @@ from .._exceptions import (
WriteTimeout,
)
from .._models import Request, Response
-from .._types import AsyncByteStream, CertTypes, SyncByteStream, VerifyTypes
+from .._types import AsyncByteStream, CertTypes, ProxyTypes, SyncByteStream, VerifyTypes
+from .._urls import URL
from .base import AsyncBaseTransport, BaseTransport
T = typing.TypeVar("T", bound="HTTPTransport")
@@ -124,13 +125,14 @@ class HTTPTransport(BaseTransport):
http2: bool = False,
limits: Limits = DEFAULT_LIMITS,
trust_env: bool = True,
- proxy: typing.Optional[Proxy] = None,
+ proxy: typing.Optional[ProxyTypes] = None,
uds: typing.Optional[str] = None,
local_address: typing.Optional[str] = None,
retries: int = 0,
socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None,
) -> None:
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
+ proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
if proxy is None:
self._pool = httpcore.ConnectionPool(
@@ -190,7 +192,8 @@ class HTTPTransport(BaseTransport):
)
else: # pragma: no cover
raise ValueError(
- f"Proxy protocol must be either 'http', 'https', or 'socks5', but got {proxy.url.scheme!r}."
+ "Proxy protocol must be either 'http', 'https', or 'socks5',"
+ f" but got {proxy.url.scheme!r}."
)
def __enter__(self: T) -> T: # Use generics for subclass support.
@@ -263,13 +266,14 @@ class AsyncHTTPTransport(AsyncBaseTransport):
http2: bool = False,
limits: Limits = DEFAULT_LIMITS,
trust_env: bool = True,
- proxy: typing.Optional[Proxy] = None,
+ proxy: typing.Optional[ProxyTypes] = None,
uds: typing.Optional[str] = None,
local_address: typing.Optional[str] = None,
retries: int = 0,
socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None,
) -> None:
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
+ proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
if proxy is None:
self._pool = httpcore.AsyncConnectionPool(
@@ -328,7 +332,8 @@ class AsyncHTTPTransport(AsyncBaseTransport):
)
else: # pragma: no cover
raise ValueError(
- f"Proxy protocol must be either 'http', 'https', or 'socks5', but got {proxy.url.scheme!r}."
+ "Proxy protocol must be either 'http', 'https', or 'socks5',"
+ " but got {proxy.url.scheme!r}."
)
async def __aenter__(self: A) -> A: # Use generics for subclass support.
diff --git a/contrib/python/httpx/httpx/_types.py b/contrib/python/httpx/httpx/_types.py
index 83cf35a32a4..649d101d54a 100644
--- a/contrib/python/httpx/httpx/_types.py
+++ b/contrib/python/httpx/httpx/_types.py
@@ -78,7 +78,8 @@ TimeoutTypes = Union[
Tuple[Optional[float], Optional[float], Optional[float], Optional[float]],
"Timeout",
]
-ProxiesTypes = Union[URLTypes, "Proxy", Dict[URLTypes, Union[None, URLTypes, "Proxy"]]]
+ProxyTypes = Union[URLTypes, "Proxy"]
+ProxiesTypes = Union[ProxyTypes, Dict[URLTypes, Union[None, ProxyTypes]]]
AuthTypes = Union[
Tuple[Union[str, bytes], Union[str, bytes]],
diff --git a/contrib/python/httpx/httpx/_urlparse.py b/contrib/python/httpx/httpx/_urlparse.py
index 8e060424e89..07bbea9070c 100644
--- a/contrib/python/httpx/httpx/_urlparse.py
+++ b/contrib/python/httpx/httpx/_urlparse.py
@@ -62,8 +62,8 @@ AUTHORITY_REGEX = re.compile(
(
r"(?:(?P<userinfo>{userinfo})@)?" r"(?P<host>{host})" r":?(?P<port>{port})?"
).format(
- userinfo="[^@]*", # Any character sequence not including '@'.
- host="(\\[.*\\]|[^:]*)", # Either any character sequence not including ':',
+ userinfo=".*", # Any character sequence.
+ host="(\\[.*\\]|[^:@]*)", # Either any character sequence excluding ':' or '@',
# or an IPv6 address enclosed within square brackets.
port=".*", # Any character sequence.
)
@@ -260,10 +260,8 @@ def urlparse(url: str = "", **kwargs: typing.Optional[str]) -> ParseResult:
# For 'path' we need to drop ? and # from the GEN_DELIMS set.
parsed_path: str = quote(path, safe=SUB_DELIMS + ":/[]@")
# For 'query' we need to drop '#' from the GEN_DELIMS set.
- # We also exclude '/' because it is more robust to replace it with a percent
- # encoding despite it not being a requirement of the spec.
parsed_query: typing.Optional[str] = (
- None if query is None else quote(query, safe=SUB_DELIMS + ":?[]@")
+ None if query is None else quote(query, safe=SUB_DELIMS + ":/?[]@")
)
# For 'fragment' we can include all of the GEN_DELIMS set.
parsed_fragment: typing.Optional[str] = (
@@ -360,24 +358,25 @@ def normalize_port(
def validate_path(path: str, has_scheme: bool, has_authority: bool) -> None:
"""
- Path validation rules that depend on if the URL contains a scheme or authority component.
+ Path validation rules that depend on if the URL contains
+ a scheme or authority component.
See https://datatracker.ietf.org/doc/html/rfc3986.html#section-3.3
"""
if has_authority:
- # > If a URI contains an authority component, then the path component
- # > must either be empty or begin with a slash ("/") character."
+ # If a URI contains an authority component, then the path component
+ # must either be empty or begin with a slash ("/") character."
if path and not path.startswith("/"):
raise InvalidURL("For absolute URLs, path must be empty or begin with '/'")
else:
- # > If a URI does not contain an authority component, then the path cannot begin
- # > with two slash characters ("//").
+ # If a URI does not contain an authority component, then the path cannot begin
+ # with two slash characters ("//").
if path.startswith("//"):
raise InvalidURL(
"URLs with no authority component cannot have a path starting with '//'"
)
- # > In addition, a URI reference (Section 4.1) may be a relative-path reference, in which
- # > case the first path segment cannot contain a colon (":") character.
+ # In addition, a URI reference (Section 4.1) may be a relative-path reference,
+ # in which case the first path segment cannot contain a colon (":") character.
if path.startswith(":") and not has_scheme:
raise InvalidURL(
"URLs with no scheme component cannot have a path starting with ':'"
@@ -431,13 +430,12 @@ def is_safe(string: str, safe: str = "/") -> bool:
if char not in NON_ESCAPED_CHARS:
return False
- # Any '%' characters must be valid '%xx' escape sequences.
- return string.count("%") == len(PERCENT_ENCODED_REGEX.findall(string))
+ return True
-def quote(string: str, safe: str = "/") -> str:
+def percent_encoded(string: str, safe: str = "/") -> str:
"""
- Use percent-encoding to quote a string if required.
+ Use percent-encoding to quote a string.
"""
if is_safe(string, safe=safe):
return string
@@ -448,17 +446,57 @@ def quote(string: str, safe: str = "/") -> str:
)
+def quote(string: str, safe: str = "/") -> str:
+ """
+ Use percent-encoding to quote a string, omitting existing '%xx' escape sequences.
+
+ See: https://www.rfc-editor.org/rfc/rfc3986#section-2.1
+
+ * `string`: The string to be percent-escaped.
+ * `safe`: A string containing characters that may be treated as safe, and do not
+ need to be escaped. Unreserved characters are always treated as safe.
+ See: https://www.rfc-editor.org/rfc/rfc3986#section-2.3
+ """
+ parts = []
+ current_position = 0
+ for match in re.finditer(PERCENT_ENCODED_REGEX, string):
+ start_position, end_position = match.start(), match.end()
+ matched_text = match.group(0)
+ # Add any text up to the '%xx' escape sequence.
+ if start_position != current_position:
+ leading_text = string[current_position:start_position]
+ parts.append(percent_encoded(leading_text, safe=safe))
+
+ # Add the '%xx' escape sequence.
+ parts.append(matched_text)
+ current_position = end_position
+
+ # Add any text after the final '%xx' escape sequence.
+ if current_position != len(string):
+ trailing_text = string[current_position:]
+ parts.append(percent_encoded(trailing_text, safe=safe))
+
+ return "".join(parts)
+
+
def urlencode(items: typing.List[typing.Tuple[str, str]]) -> str:
- # We can use a much simpler version of the stdlib urlencode here because
- # we don't need to handle a bunch of different typing cases, such as bytes vs str.
- #
- # https://github.com/python/cpython/blob/b2f7b2ef0b5421e01efb8c7bee2ef95d3bab77eb/Lib/urllib/parse.py#L926
- #
- # Note that we use '%20' encoding for spaces. and '%2F for '/'.
- # This is slightly different than `requests`, but is the behaviour that browsers use.
- #
- # See
- # - https://github.com/encode/httpx/issues/2536
- # - https://github.com/encode/httpx/issues/2721
- # - https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlencode
- return "&".join([quote(k, safe="") + "=" + quote(v, safe="") for k, v in items])
+ """
+ We can use a much simpler version of the stdlib urlencode here because
+ we don't need to handle a bunch of different typing cases, such as bytes vs str.
+
+ https://github.com/python/cpython/blob/b2f7b2ef0b5421e01efb8c7bee2ef95d3bab77eb/Lib/urllib/parse.py#L926
+
+ Note that we use '%20' encoding for spaces. and '%2F for '/'.
+ This is slightly different than `requests`, but is the behaviour that browsers use.
+
+ See
+ - https://github.com/encode/httpx/issues/2536
+ - https://github.com/encode/httpx/issues/2721
+ - https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlencode
+ """
+ return "&".join(
+ [
+ percent_encoded(k, safe="") + "=" + percent_encoded(v, safe="")
+ for k, v in items
+ ]
+ )
diff --git a/contrib/python/httpx/httpx/_urls.py b/contrib/python/httpx/httpx/_urls.py
index b023941b623..26202e95db2 100644
--- a/contrib/python/httpx/httpx/_urls.py
+++ b/contrib/python/httpx/httpx/_urls.py
@@ -51,21 +51,23 @@ class URL:
assert url.raw_host == b"xn--fiqs8s.icom.museum"
* `url.port` is either None or an integer. URLs that include the default port for
- "http", "https", "ws", "wss", and "ftp" schemes have their port normalized to `None`.
+ "http", "https", "ws", "wss", and "ftp" schemes have their port
+ normalized to `None`.
assert httpx.URL("http://example.com") == httpx.URL("http://example.com:80")
assert httpx.URL("http://example.com").port is None
assert httpx.URL("http://example.com:80").port is None
- * `url.userinfo` is raw bytes, without URL escaping. Usually you'll want to work with
- `url.username` and `url.password` instead, which handle the URL escaping.
+ * `url.userinfo` is raw bytes, without URL escaping. Usually you'll want to work
+ with `url.username` and `url.password` instead, which handle the URL escaping.
* `url.raw_path` is raw bytes of both the path and query, without URL escaping.
This portion is used as the target when constructing HTTP requests. Usually you'll
want to work with `url.path` instead.
- * `url.query` is raw bytes, without URL escaping. A URL query string portion can only
- be properly URL escaped when decoding the parameter names and values themselves.
+ * `url.query` is raw bytes, without URL escaping. A URL query string portion can
+ only be properly URL escaped when decoding the parameter names and values
+ themselves.
"""
def __init__(
@@ -115,7 +117,8 @@ class URL:
self._uri_reference = url._uri_reference.copy_with(**kwargs)
else:
raise TypeError(
- f"Invalid type for url. Expected str or httpx.URL, got {type(url)}: {url!r}"
+ "Invalid type for url. Expected str or httpx.URL,"
+ f" got {type(url)}: {url!r}"
)
@property
@@ -305,7 +308,8 @@ class URL:
Provides the (scheme, host, port, target) for the outgoing request.
In older versions of `httpx` this was used in the low-level transport API.
- We no longer use `RawURL`, and this property will be deprecated in a future release.
+ We no longer use `RawURL`, and this property will be deprecated
+ in a future release.
"""
return RawURL(
self.raw_scheme,
@@ -342,7 +346,9 @@ class URL:
For example:
- url = httpx.URL("https://www.example.com").copy_with(username="[email protected]", password="a secret")
+ url = httpx.URL("https://www.example.com").copy_with(
+ username="[email protected]", password="a secret"
+ )
assert url == "https://jo%40email.com:a%[email protected]"
"""
return URL(self, **kwargs)
diff --git a/contrib/python/httpx/httpx/_utils.py b/contrib/python/httpx/httpx/_utils.py
index ba5807c0487..bc3cb001dd7 100644
--- a/contrib/python/httpx/httpx/_utils.py
+++ b/contrib/python/httpx/httpx/_utils.py
@@ -152,7 +152,7 @@ SENSITIVE_HEADERS = {"authorization", "proxy-authorization"}
def obfuscate_sensitive_headers(
- items: typing.Iterable[typing.Tuple[typing.AnyStr, typing.AnyStr]]
+ items: typing.Iterable[typing.Tuple[typing.AnyStr, typing.AnyStr]],
) -> typing.Iterator[typing.Tuple[typing.AnyStr, typing.AnyStr]]:
for k, v in items:
if to_str(k.lower()) in SENSITIVE_HEADERS:
@@ -227,7 +227,9 @@ def get_environment_proxies() -> typing.Dict[str, typing.Optional[str]]:
# (But not "wwwgoogle.com")
# NO_PROXY can include domains, IPv6, IPv4 addresses and "localhost"
# NO_PROXY=example.com,::1,localhost,192.168.0.0/16
- if is_ipv4_hostname(hostname):
+ if "://" in hostname:
+ mounts[hostname] = None
+ elif is_ipv4_hostname(hostname):
mounts[f"all://{hostname}"] = None
elif is_ipv6_hostname(hostname):
mounts[f"all://[{hostname}]"] = None
@@ -293,14 +295,10 @@ class Timer:
import trio
return trio.current_time()
- elif library == "curio": # pragma: no cover
- import curio
-
- return typing.cast(float, await curio.clock())
-
- import asyncio
+ else:
+ import asyncio
- return asyncio.get_event_loop().time()
+ return asyncio.get_event_loop().time()
def sync_start(self) -> None:
self.started = time.perf_counter()
diff --git a/contrib/python/httpx/ya.make b/contrib/python/httpx/ya.make
index f07d3a08ce1..2f8eaa9087f 100644
--- a/contrib/python/httpx/ya.make
+++ b/contrib/python/httpx/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(0.25.2)
+VERSION(0.26.0)
LICENSE(BSD-3-Clause)