Version 0.15.0 (#1301)
* Version 0.15.0 * Update CHANGELOG.md Co-authored-by: Jamie Hewland <jamie.hewland@hpe.com> * Escalate deprecations into removals. * Deprecate overly verbose timeout parameter names * Fully deprecate max_keepalive in favour of explicit max_keepalive_connections * Fully deprecate PoolLimits in favour of Limits * Deprecate instantiating 'Timeout' without fully explicit values * Include deprecation notes in changelog * Use httpcore 0.11.x Co-authored-by: Jamie Hewland <jamie.hewland@hpe.com>
This commit is contained in:
parent
8e4a8a1c73
commit
f932af9172
42
CHANGELOG.md
42
CHANGELOG.md
@ -4,6 +4,48 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## 0.15.0
|
||||
|
||||
### Added
|
||||
|
||||
* Added support for event hooks. (Pull #1246)
|
||||
* Added support for authentication flows which require either sync or async I/O. (Pull #1217)
|
||||
* Added support for monitoring download progress with `response.num_bytes_downloaded`. (Pull #1268)
|
||||
* Added `Request(content=...)` for byte content, instead of overloading `Request(data=...)` (Pull #1266)
|
||||
* Added support for all URL components as parameter names when using `url.copy_with(...)`. (Pull #1285)
|
||||
* Neater split between automatically populated headers on `Request` instances, vs default `client.headers`. (Pull #1248)
|
||||
* Unclosed `AsyncClient` instances will now raise warnings if garbage collected. (Pull #1197)
|
||||
* Support `Response(content=..., text=..., html=..., json=...)` for creating usable response instances in code. (Pull #1265, #1297)
|
||||
* Support instantiating requests from the low-level transport API. (Pull #1293)
|
||||
* Raise errors on invalid URL types. (Pull #1259)
|
||||
|
||||
### Changed
|
||||
|
||||
* Cleaned up expected behaviour for URL escaping. `url.path` is now URL escaped. (Pull #1285)
|
||||
* Cleaned up expected behaviour for bytes vs str in URL components. `url.userinfo` and `url.query` are not URL escaped, and so return bytes. (Pull #1285)
|
||||
* Drop `url.authority` property in favour of `url.netloc`, since "authority" was semantically incorrect. (Pull #1285)
|
||||
* Drop `url.full_path` property in favour of `url.raw_path`, for better consistency with other parts of the API. (Pull #1285)
|
||||
* No longer use the `chardet` library for auto-detecting charsets, instead defaulting to a simpler approach when no charset is specified. (#1269)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Swapped ordering of redirects and authentication flow. (Pull #1267)
|
||||
* `.netrc` lookups should use host, not host+port. (Pull #1298)
|
||||
|
||||
### Removed
|
||||
|
||||
* The `URLLib3Transport` class no longer exists. We've published it instead as an example of [a custom transport class](https://gist.github.com/florimondmanca/d56764d78d748eb9f73165da388e546e). (Pull #1182)
|
||||
* Drop `request.timer` attribute, which was being used internally to set `response.elapsed`. (Pull #1249)
|
||||
* Drop `response.decoder` attribute, which was being used internally. (Pull #1276)
|
||||
* `Request.prepare()` is now a private method. (Pull #1284)
|
||||
* The `Headers.getlist()` method had previously been deprecated in favour of `Headers.get_list()`. It is now fully removed.
|
||||
* The `QueryParams.getlist()` method had previously been deprecated in favour of `QueryParams.get_list()`. It is now fully removed.
|
||||
* The `URL.is_ssl` property had previously been deprecated in favour of `URL.scheme == "https"`. It is now fully removed.
|
||||
* The `httpx.PoolLimits` class had previously been deprecated in favour of `httpx.Limits`. It is now fully removed.
|
||||
* The `max_keepalive` setting had previously been deprecated in favour of the more explicit `max_keepalive_connections`. It is now fully removed.
|
||||
* The verbose `httpx.Timeout(5.0, connect_timeout=60.0)` style had previously been deprecated in favour of `httpx.Timeout(5.0, connect=60.0)`. It is now fully removed.
|
||||
* Support for instantiating a timeout config missing some defaults, such as `httpx.Timeout(connect=60.0)`, had previously been deprecated in favour of enforcing a more explicit style, such as `httpx.Timeout(5.0, connect=60.0)`. This is now strictly enforced.
|
||||
|
||||
## 0.14.3 (September 2nd, 2020)
|
||||
|
||||
### Added
|
||||
|
||||
@ -2,7 +2,7 @@ from .__version__ import __description__, __title__, __version__
|
||||
from ._api import delete, get, head, options, patch, post, put, request, stream
|
||||
from ._auth import Auth, BasicAuth, DigestAuth
|
||||
from ._client import AsyncClient, Client
|
||||
from ._config import Limits, PoolLimits, Proxy, Timeout, create_ssl_context
|
||||
from ._config import Limits, Proxy, Timeout, create_ssl_context
|
||||
from ._exceptions import (
|
||||
CloseError,
|
||||
ConnectError,
|
||||
@ -70,7 +70,6 @@ __all__ = [
|
||||
"NotRedirectResponse",
|
||||
"options",
|
||||
"patch",
|
||||
"PoolLimits",
|
||||
"PoolTimeout",
|
||||
"post",
|
||||
"ProtocolError",
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
__title__ = "httpx"
|
||||
__description__ = "A next generation HTTP client, for Python 3."
|
||||
__version__ = "0.14.3"
|
||||
__version__ = "0.15.0"
|
||||
|
||||
@ -850,18 +850,12 @@ class Client(BaseClient):
|
||||
timer.sync_start()
|
||||
|
||||
with map_exceptions(HTTPCORE_EXC_MAP, request=request):
|
||||
(
|
||||
http_version,
|
||||
status_code,
|
||||
reason_phrase,
|
||||
headers,
|
||||
stream,
|
||||
) = transport.request(
|
||||
(status_code, headers, stream, ext) = transport.request(
|
||||
request.method.encode(),
|
||||
request.url.raw,
|
||||
headers=request.headers.raw,
|
||||
stream=request.stream, # type: ignore
|
||||
timeout=timeout.as_dict(),
|
||||
ext={"timeout": timeout.as_dict()},
|
||||
)
|
||||
|
||||
def on_close(response: Response) -> None:
|
||||
@ -871,9 +865,9 @@ class Client(BaseClient):
|
||||
|
||||
response = Response(
|
||||
status_code,
|
||||
http_version=http_version.decode("ascii"),
|
||||
headers=headers,
|
||||
stream=stream, # type: ignore
|
||||
ext=ext,
|
||||
request=request,
|
||||
on_close=on_close,
|
||||
)
|
||||
@ -1501,18 +1495,12 @@ class AsyncClient(BaseClient):
|
||||
await timer.async_start()
|
||||
|
||||
with map_exceptions(HTTPCORE_EXC_MAP, request=request):
|
||||
(
|
||||
http_version,
|
||||
status_code,
|
||||
reason_phrase,
|
||||
headers,
|
||||
stream,
|
||||
) = await transport.request(
|
||||
(status_code, headers, stream, ext,) = await transport.arequest(
|
||||
request.method.encode(),
|
||||
request.url.raw,
|
||||
headers=request.headers.raw,
|
||||
stream=request.stream, # type: ignore
|
||||
timeout=timeout.as_dict(),
|
||||
ext={"timeout": timeout.as_dict()},
|
||||
)
|
||||
|
||||
async def on_close(response: Response) -> None:
|
||||
@ -1522,9 +1510,9 @@ class AsyncClient(BaseClient):
|
||||
|
||||
response = Response(
|
||||
status_code,
|
||||
http_version=http_version.decode("ascii"),
|
||||
headers=headers,
|
||||
stream=stream, # type: ignore
|
||||
ext=ext,
|
||||
request=request,
|
||||
on_close=on_close,
|
||||
)
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import os
|
||||
import ssl
|
||||
import typing
|
||||
import warnings
|
||||
from base64 import b64encode
|
||||
from pathlib import Path
|
||||
|
||||
@ -9,7 +8,7 @@ import certifi
|
||||
|
||||
from ._models import URL, Headers
|
||||
from ._types import CertTypes, HeaderTypes, TimeoutTypes, URLTypes, VerifyTypes
|
||||
from ._utils import get_ca_bundle_from_env, get_logger, warn_deprecated
|
||||
from ._utils import get_ca_bundle_from_env, get_logger
|
||||
|
||||
DEFAULT_CIPHERS = ":".join(
|
||||
[
|
||||
@ -212,44 +211,7 @@ class Timeout:
|
||||
read: typing.Union[None, float, UnsetType] = UNSET,
|
||||
write: typing.Union[None, float, UnsetType] = UNSET,
|
||||
pool: typing.Union[None, float, UnsetType] = UNSET,
|
||||
# Deprecated aliases.
|
||||
connect_timeout: typing.Union[None, float, UnsetType] = UNSET,
|
||||
read_timeout: typing.Union[None, float, UnsetType] = UNSET,
|
||||
write_timeout: typing.Union[None, float, UnsetType] = UNSET,
|
||||
pool_timeout: typing.Union[None, float, UnsetType] = UNSET,
|
||||
):
|
||||
if not isinstance(connect_timeout, UnsetType):
|
||||
warn_deprecated(
|
||||
"httpx.Timeout(..., connect_timeout=...) is deprecated and will "
|
||||
"raise errors in a future version. "
|
||||
"Use httpx.Timeout(..., connect=...) instead."
|
||||
)
|
||||
connect = connect_timeout
|
||||
|
||||
if not isinstance(read_timeout, UnsetType):
|
||||
warn_deprecated(
|
||||
"httpx.Timeout(..., read_timeout=...) is deprecated and will "
|
||||
"raise errors in a future version. "
|
||||
"Use httpx.Timeout(..., write=...) instead."
|
||||
)
|
||||
read = read_timeout
|
||||
|
||||
if not isinstance(write_timeout, UnsetType):
|
||||
warn_deprecated(
|
||||
"httpx.Timeout(..., write_timeout=...) is deprecated and will "
|
||||
"raise errors in a future version. "
|
||||
"Use httpx.Timeout(..., write=...) instead."
|
||||
)
|
||||
write = write_timeout
|
||||
|
||||
if not isinstance(pool_timeout, UnsetType):
|
||||
warn_deprecated(
|
||||
"httpx.Timeout(..., pool_timeout=...) is deprecated and will "
|
||||
"raise errors in a future version. "
|
||||
"Use httpx.Timeout(..., pool=...) instead."
|
||||
)
|
||||
pool = pool_timeout
|
||||
|
||||
if isinstance(timeout, Timeout):
|
||||
# Passed as a single explicit Timeout.
|
||||
assert connect is UNSET
|
||||
@ -278,13 +240,10 @@ class Timeout:
|
||||
self.pool = pool
|
||||
else:
|
||||
if isinstance(timeout, UnsetType):
|
||||
warnings.warn(
|
||||
raise ValueError(
|
||||
"httpx.Timeout must either include a default, or set all "
|
||||
"four parameters explicitly. Omitting the default argument "
|
||||
"is deprecated and will raise errors in a future version.",
|
||||
DeprecationWarning,
|
||||
"four parameters explicitly."
|
||||
)
|
||||
timeout = None
|
||||
self.connect = timeout if isinstance(connect, UnsetType) else connect
|
||||
self.read = timeout if isinstance(read, UnsetType) else read
|
||||
self.write = timeout if isinstance(write, UnsetType) else write
|
||||
@ -335,16 +294,7 @@ class Limits:
|
||||
*,
|
||||
max_connections: int = None,
|
||||
max_keepalive_connections: int = None,
|
||||
# Deprecated parameter naming, in favour of more explicit version:
|
||||
max_keepalive: int = None,
|
||||
):
|
||||
if max_keepalive is not None:
|
||||
warnings.warn(
|
||||
"'max_keepalive' is deprecated. Use 'max_keepalive_connections'.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
max_keepalive_connections = max_keepalive
|
||||
|
||||
self.max_connections = max_connections
|
||||
self.max_keepalive_connections = max_keepalive_connections
|
||||
|
||||
@ -363,15 +313,6 @@ class Limits:
|
||||
)
|
||||
|
||||
|
||||
class PoolLimits(Limits):
|
||||
def __init__(self, **kwargs: typing.Any) -> None:
|
||||
warn_deprecated(
|
||||
"httpx.PoolLimits(...) is deprecated and will raise errors in the future. "
|
||||
"Use httpx.Limits(...) instead."
|
||||
)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class Proxy:
|
||||
def __init__(
|
||||
self, url: URLTypes, *, headers: HeaderTypes = None, mode: str = "DEFAULT"
|
||||
|
||||
@ -5,7 +5,6 @@ import email.message
|
||||
import json as jsonlib
|
||||
import typing
|
||||
import urllib.request
|
||||
import warnings
|
||||
from collections.abc import MutableMapping
|
||||
from http.cookiejar import Cookie, CookieJar
|
||||
from urllib.parse import parse_qsl, quote, unquote, urlencode
|
||||
@ -272,12 +271,6 @@ class URL:
|
||||
self.raw_path,
|
||||
)
|
||||
|
||||
@property
|
||||
def is_ssl(self) -> bool:
|
||||
message = 'URL.is_ssl() is pending deprecation. Use url.scheme == "https"'
|
||||
warnings.warn(message, DeprecationWarning)
|
||||
return self.scheme == "https"
|
||||
|
||||
@property
|
||||
def is_absolute_url(self) -> bool:
|
||||
"""
|
||||
@ -525,13 +518,6 @@ class QueryParams(typing.Mapping[str, str]):
|
||||
query_string = str(self)
|
||||
return f"{class_name}({query_string!r})"
|
||||
|
||||
def getlist(self, key: typing.Any) -> typing.List[str]:
|
||||
message = (
|
||||
"QueryParams.getlist() is pending deprecation. Use QueryParams.get_list()"
|
||||
)
|
||||
warnings.warn(message, DeprecationWarning)
|
||||
return self.get_list(key)
|
||||
|
||||
|
||||
class Headers(typing.MutableMapping[str, str]):
|
||||
"""
|
||||
@ -757,11 +743,6 @@ class Headers(typing.MutableMapping[str, str]):
|
||||
return f"{class_name}({as_dict!r}{encoding_str})"
|
||||
return f"{class_name}({as_list!r}{encoding_str})"
|
||||
|
||||
def getlist(self, key: str, split_commas: bool = False) -> typing.List[str]:
|
||||
message = "Headers.getlist() is pending deprecation. Use Headers.get_list()"
|
||||
warnings.warn(message, DeprecationWarning)
|
||||
return self.get_list(key, split_commas=split_commas)
|
||||
|
||||
|
||||
class Request:
|
||||
def __init__(
|
||||
@ -883,19 +864,19 @@ class Response:
|
||||
html: str = None,
|
||||
json: typing.Any = None,
|
||||
stream: ByteStream = None,
|
||||
http_version: str = None,
|
||||
request: Request = None,
|
||||
ext: dict = None,
|
||||
history: typing.List["Response"] = None,
|
||||
on_close: typing.Callable = None,
|
||||
):
|
||||
self.status_code = status_code
|
||||
self.http_version = http_version
|
||||
self.headers = Headers(headers)
|
||||
|
||||
self._request: typing.Optional[Request] = request
|
||||
|
||||
self.call_next: typing.Optional[typing.Callable] = None
|
||||
|
||||
self.ext = {} if ext is None else ext
|
||||
self.history = [] if history is None else list(history)
|
||||
self._on_close = on_close
|
||||
|
||||
@ -964,9 +945,13 @@ class Response:
|
||||
def request(self, value: Request) -> None:
|
||||
self._request = value
|
||||
|
||||
@property
|
||||
def http_version(self) -> str:
|
||||
return self.ext.get("http_version", "HTTP/1.1")
|
||||
|
||||
@property
|
||||
def reason_phrase(self) -> str:
|
||||
return codes.get_reason_phrase(self.status_code)
|
||||
return self.ext.get("reason", codes.get_reason_phrase(self.status_code))
|
||||
|
||||
@property
|
||||
def url(self) -> typing.Optional[URL]:
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, Callable, List, Mapping, Optional, Tuple, Union
|
||||
from typing import TYPE_CHECKING, Callable, List, Optional, Tuple, Union
|
||||
|
||||
import httpcore
|
||||
import sniffio
|
||||
@ -67,14 +67,14 @@ class ASGITransport(httpcore.AsyncHTTPTransport):
|
||||
self.root_path = root_path
|
||||
self.client = client
|
||||
|
||||
async def request(
|
||||
async def arequest(
|
||||
self,
|
||||
method: bytes,
|
||||
url: Tuple[bytes, bytes, Optional[int], bytes],
|
||||
headers: List[Tuple[bytes, bytes]] = None,
|
||||
stream: httpcore.AsyncByteStream = None,
|
||||
timeout: Mapping[str, Optional[float]] = None,
|
||||
) -> Tuple[bytes, int, bytes, List[Tuple[bytes, bytes]], httpcore.AsyncByteStream]:
|
||||
ext: dict = None,
|
||||
) -> Tuple[int, List[Tuple[bytes, bytes]], httpcore.AsyncByteStream, dict]:
|
||||
headers = [] if headers is None else headers
|
||||
stream = httpcore.PlainByteStream(content=b"") if stream is None else stream
|
||||
|
||||
@ -154,5 +154,6 @@ class ASGITransport(httpcore.AsyncHTTPTransport):
|
||||
assert response_headers is not None
|
||||
|
||||
stream = httpcore.PlainByteStream(content=b"".join(body_parts))
|
||||
ext = {}
|
||||
|
||||
return (b"HTTP/1.1", status_code, b"", response_headers, stream)
|
||||
return (status_code, response_headers, stream, ext)
|
||||
|
||||
@ -64,13 +64,9 @@ class WSGITransport(httpcore.SyncHTTPTransport):
|
||||
url: typing.Tuple[bytes, bytes, typing.Optional[int], bytes],
|
||||
headers: typing.List[typing.Tuple[bytes, bytes]] = None,
|
||||
stream: httpcore.SyncByteStream = None,
|
||||
timeout: typing.Mapping[str, typing.Optional[float]] = None,
|
||||
ext: dict = None,
|
||||
) -> typing.Tuple[
|
||||
bytes,
|
||||
int,
|
||||
bytes,
|
||||
typing.List[typing.Tuple[bytes, bytes]],
|
||||
httpcore.SyncByteStream,
|
||||
int, typing.List[typing.Tuple[bytes, bytes]], httpcore.SyncByteStream, dict
|
||||
]:
|
||||
headers = [] if headers is None else headers
|
||||
stream = httpcore.PlainByteStream(content=b"") if stream is None else stream
|
||||
@ -127,5 +123,6 @@ class WSGITransport(httpcore.SyncHTTPTransport):
|
||||
for key, value in seen_response_headers
|
||||
]
|
||||
stream = httpcore.IteratorByteStream(iterator=result)
|
||||
ext = {}
|
||||
|
||||
return (b"HTTP/1.1", status_code, b"", headers, stream)
|
||||
return (status_code, headers, stream, ext)
|
||||
|
||||
2
setup.py
2
setup.py
@ -58,7 +58,7 @@ setup(
|
||||
"certifi",
|
||||
"sniffio",
|
||||
"rfc3986[idna2008]>=1.3,<2",
|
||||
"httpcore==0.10.*",
|
||||
"httpcore==0.11.*",
|
||||
],
|
||||
extras_require={
|
||||
"http2": "h2==3.*",
|
||||
|
||||
@ -13,7 +13,7 @@ import pytest
|
||||
|
||||
import httpx
|
||||
from httpx import URL, Auth, BasicAuth, DigestAuth, ProtocolError, Request, Response
|
||||
from tests.utils import AsyncMockTransport, MockTransport
|
||||
from tests.utils import MockTransport
|
||||
|
||||
from ..common import FIXTURES_DIR
|
||||
|
||||
@ -155,7 +155,7 @@ async def test_basic_auth() -> None:
|
||||
auth = ("tomchristie", "password123")
|
||||
app = App()
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -167,7 +167,7 @@ async def test_basic_auth_in_url() -> None:
|
||||
url = "https://tomchristie:password123@example.org/"
|
||||
app = App()
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -180,9 +180,7 @@ async def test_basic_auth_on_session() -> None:
|
||||
auth = ("tomchristie", "password123")
|
||||
app = App()
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
transport=AsyncMockTransport(app), auth=auth
|
||||
) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app), auth=auth) as client:
|
||||
response = await client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -198,7 +196,7 @@ async def test_custom_auth() -> None:
|
||||
request.headers["Authorization"] = "Token 123"
|
||||
return request
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -211,7 +209,7 @@ async def test_netrc_auth() -> None:
|
||||
url = "http://netrcexample.org"
|
||||
app = App()
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -226,7 +224,7 @@ async def test_auth_header_has_priority_over_netrc() -> None:
|
||||
url = "http://netrcexample.org"
|
||||
app = App()
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url, headers={"Authorization": "Override"})
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -240,7 +238,7 @@ async def test_trust_env_auth() -> None:
|
||||
app = App()
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
transport=AsyncMockTransport(app), trust_env=False
|
||||
transport=MockTransport(app), trust_env=False
|
||||
) as client:
|
||||
response = await client.get(url)
|
||||
|
||||
@ -248,7 +246,7 @@ async def test_trust_env_auth() -> None:
|
||||
assert response.json() == {"auth": None}
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
transport=AsyncMockTransport(app), trust_env=True
|
||||
transport=MockTransport(app), trust_env=True
|
||||
) as client:
|
||||
response = await client.get(url)
|
||||
|
||||
@ -264,9 +262,7 @@ async def test_auth_disable_per_request() -> None:
|
||||
auth = ("tomchristie", "password123")
|
||||
app = App()
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
transport=AsyncMockTransport(app), auth=auth
|
||||
) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app), auth=auth) as client:
|
||||
response = await client.get(url, auth=None)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -286,7 +282,7 @@ async def test_auth_hidden_header() -> None:
|
||||
auth = ("example-username", "example-password")
|
||||
app = App()
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert "'authorization': '[secure]'" in str(response.request.headers)
|
||||
@ -296,7 +292,7 @@ async def test_auth_hidden_header() -> None:
|
||||
async def test_auth_property() -> None:
|
||||
app = App()
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
assert client.auth is None
|
||||
|
||||
client.auth = ("tomchristie", "password123") # type: ignore
|
||||
@ -314,11 +310,11 @@ async def test_auth_invalid_type() -> None:
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
client = httpx.AsyncClient(
|
||||
transport=AsyncMockTransport(app),
|
||||
transport=MockTransport(app),
|
||||
auth="not a tuple, not a callable", # type: ignore
|
||||
)
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
with pytest.raises(TypeError):
|
||||
await client.get(auth="not a tuple, not a callable") # type: ignore
|
||||
|
||||
@ -332,7 +328,7 @@ async def test_digest_auth_returns_no_auth_if_no_digest_header_in_response() ->
|
||||
auth = DigestAuth(username="tomchristie", password="password123")
|
||||
app = App()
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -361,7 +357,7 @@ async def test_digest_auth_200_response_including_digest_auth_header() -> None:
|
||||
auth_header = 'Digest realm="realm@host.com",qop="auth",nonce="abc",opaque="xyz"'
|
||||
app = App(auth_header=auth_header, status_code=200)
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -375,7 +371,7 @@ async def test_digest_auth_401_response_without_digest_auth_header() -> None:
|
||||
auth = DigestAuth(username="tomchristie", password="password123")
|
||||
app = App(auth_header="", status_code=401)
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 401
|
||||
@ -404,7 +400,7 @@ async def test_digest_auth(
|
||||
auth = DigestAuth(username="tomchristie", password="password123")
|
||||
app = DigestApp(algorithm=algorithm)
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -435,7 +431,7 @@ async def test_digest_auth_no_specified_qop() -> None:
|
||||
auth = DigestAuth(username="tomchristie", password="password123")
|
||||
app = DigestApp(qop="")
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -467,7 +463,7 @@ async def test_digest_auth_qop_including_spaces_and_auth_returns_auth(qop: str)
|
||||
auth = DigestAuth(username="tomchristie", password="password123")
|
||||
app = DigestApp(qop=qop)
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -480,7 +476,7 @@ async def test_digest_auth_qop_auth_int_not_implemented() -> None:
|
||||
auth = DigestAuth(username="tomchristie", password="password123")
|
||||
app = DigestApp(qop="auth-int")
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
with pytest.raises(NotImplementedError):
|
||||
await client.get(url, auth=auth)
|
||||
|
||||
@ -491,7 +487,7 @@ async def test_digest_auth_qop_must_be_auth_or_auth_int() -> None:
|
||||
auth = DigestAuth(username="tomchristie", password="password123")
|
||||
app = DigestApp(qop="not-auth")
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
with pytest.raises(ProtocolError):
|
||||
await client.get(url, auth=auth)
|
||||
|
||||
@ -502,7 +498,7 @@ async def test_digest_auth_incorrect_credentials() -> None:
|
||||
auth = DigestAuth(username="tomchristie", password="password123")
|
||||
app = DigestApp(send_response_after_attempt=2)
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 401
|
||||
@ -524,7 +520,7 @@ async def test_async_digest_auth_raises_protocol_error_on_malformed_header(
|
||||
auth = DigestAuth(username="tomchristie", password="password123")
|
||||
app = App(auth_header=auth_header, status_code=401)
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
with pytest.raises(ProtocolError):
|
||||
await client.get(url, auth=auth)
|
||||
|
||||
@ -558,7 +554,7 @@ async def test_async_auth_history() -> None:
|
||||
auth = RepeatAuth(repeat=2)
|
||||
app = App(auth_header="abc")
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -610,7 +606,7 @@ async def test_digest_auth_unavailable_streaming_body():
|
||||
async def streaming_body():
|
||||
yield b"Example request body" # pragma: nocover
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
with pytest.raises(httpx.StreamConsumed):
|
||||
await client.post(url, data=streaming_body(), auth=auth)
|
||||
|
||||
@ -625,7 +621,7 @@ async def test_async_auth_reads_response_body() -> None:
|
||||
auth = ResponseBodyAuth("xyz")
|
||||
app = App()
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -659,7 +655,7 @@ async def test_async_auth() -> None:
|
||||
auth = SyncOrAsyncAuth()
|
||||
app = App()
|
||||
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(app)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(app)) as client:
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
@ -179,8 +179,6 @@ def test_merge_absolute_url():
|
||||
client = httpx.Client(base_url="https://www.example.com/")
|
||||
request = client.build_request("GET", "http://www.example.com/")
|
||||
assert request.url == "http://www.example.com/"
|
||||
with pytest.warns(DeprecationWarning):
|
||||
assert not request.url.is_ssl
|
||||
|
||||
|
||||
def test_merge_relative_url():
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
import httpx
|
||||
from tests.utils import AsyncMockTransport, MockTransport
|
||||
from tests.utils import MockTransport
|
||||
|
||||
|
||||
def app(request: httpx.Request) -> httpx.Response:
|
||||
@ -73,7 +73,7 @@ async def test_async_event_hooks():
|
||||
event_hooks = {"request": [on_request], "response": [on_response]}
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
event_hooks=event_hooks, transport=AsyncMockTransport(app)
|
||||
event_hooks=event_hooks, transport=MockTransport(app)
|
||||
) as http:
|
||||
await http.get("http://127.0.0.1:8000/", auth=("username", "password"))
|
||||
|
||||
@ -104,7 +104,7 @@ async def test_async_event_hooks_raising_exception():
|
||||
event_hooks = {"response": [raise_on_4xx_5xx]}
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
event_hooks=event_hooks, transport=AsyncMockTransport(app)
|
||||
event_hooks=event_hooks, transport=MockTransport(app)
|
||||
) as http:
|
||||
try:
|
||||
await http.get("http://127.0.0.1:8000/status/400")
|
||||
@ -166,7 +166,7 @@ async def test_async_event_hooks_with_redirect():
|
||||
event_hooks = {"request": [on_request], "response": [on_response]}
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
event_hooks=event_hooks, transport=AsyncMockTransport(app)
|
||||
event_hooks=event_hooks, transport=MockTransport(app)
|
||||
) as http:
|
||||
await http.get("http://127.0.0.1:8000/redirect", auth=("username", "password"))
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import httpcore
|
||||
import pytest
|
||||
|
||||
import httpx
|
||||
from tests.utils import AsyncMockTransport, MockTransport
|
||||
from tests.utils import MockTransport
|
||||
|
||||
|
||||
def redirects(request: httpx.Request) -> httpx.Response:
|
||||
@ -232,14 +232,14 @@ def test_multiple_redirects():
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_async_too_many_redirects():
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(redirects)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(redirects)) as client:
|
||||
with pytest.raises(httpx.TooManyRedirects):
|
||||
await client.get("https://example.org/multiple_redirects?count=21")
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_async_too_many_redirects_calling_next():
|
||||
async with httpx.AsyncClient(transport=AsyncMockTransport(redirects)) as client:
|
||||
async with httpx.AsyncClient(transport=MockTransport(redirects)) as client:
|
||||
url = "https://example.org/multiple_redirects?count=21"
|
||||
response = await client.get(url, allow_redirects=False)
|
||||
with pytest.raises(httpx.TooManyRedirects):
|
||||
|
||||
@ -15,9 +15,6 @@ def test_headers():
|
||||
assert h.get("nope", default=None) is None
|
||||
assert h.get_list("a") == ["123", "456"]
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
assert h.getlist("a") == ["123", "456"]
|
||||
|
||||
assert list(h.keys()) == ["a", "b"]
|
||||
assert list(h.values()) == ["123, 456", "789"]
|
||||
assert list(h.items()) == [("a", "123, 456"), ("b", "789")]
|
||||
|
||||
@ -21,9 +21,6 @@ def test_queryparams(source):
|
||||
assert q.get("nope", default=None) is None
|
||||
assert q.get_list("a") == ["123", "456"]
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
assert q.getlist("a") == ["123", "456"]
|
||||
|
||||
assert list(q.keys()) == ["a", "b"]
|
||||
assert list(q.values()) == ["456", "789"]
|
||||
assert list(q.items()) == [("a", "456"), ("b", "789")]
|
||||
|
||||
@ -110,16 +110,6 @@ def test_limits_eq():
|
||||
assert limits == httpx.Limits(max_connections=100)
|
||||
|
||||
|
||||
def test_pool_limits_deprecated():
|
||||
with pytest.warns(DeprecationWarning):
|
||||
httpx.PoolLimits()
|
||||
|
||||
|
||||
def test_max_keepalive_deprecated():
|
||||
with pytest.warns(DeprecationWarning):
|
||||
httpx.Limits(max_keepalive=50)
|
||||
|
||||
|
||||
def test_timeout_eq():
|
||||
timeout = httpx.Timeout(timeout=5.0)
|
||||
assert timeout == httpx.Timeout(timeout=5.0)
|
||||
@ -159,9 +149,8 @@ def test_timeout_from_one_value_and_default():
|
||||
|
||||
|
||||
def test_timeout_missing_default():
|
||||
with pytest.warns(DeprecationWarning):
|
||||
timeout = httpx.Timeout(pool=60.0)
|
||||
assert timeout == httpx.Timeout(timeout=(None, None, None, 60.0))
|
||||
with pytest.raises(ValueError):
|
||||
httpx.Timeout(pool=60.0)
|
||||
|
||||
|
||||
def test_timeout_from_tuple():
|
||||
|
||||
@ -41,17 +41,3 @@ async def test_pool_timeout(server):
|
||||
async with client.stream("GET", server.url):
|
||||
with pytest.raises(httpx.PoolTimeout):
|
||||
await client.get("http://localhost:8000/")
|
||||
|
||||
|
||||
def test_deprecated_verbose_timeout_params():
|
||||
with pytest.warns(DeprecationWarning):
|
||||
httpx.Timeout(None, read_timeout=1.0)
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
httpx.Timeout(None, write_timeout=1.0)
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
httpx.Timeout(None, connect_timeout=1.0)
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
httpx.Timeout(None, pool_timeout=1.0)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
from typing import Callable, List, Mapping, Optional, Tuple
|
||||
from typing import Callable, List, Optional, Tuple
|
||||
|
||||
import httpcore
|
||||
|
||||
@ -24,7 +24,7 @@ def override_log_level(log_level: str):
|
||||
logging.getLogger("httpx").handlers = []
|
||||
|
||||
|
||||
class MockTransport(httpcore.SyncHTTPTransport):
|
||||
class MockTransport(httpcore.SyncHTTPTransport, httpcore.AsyncHTTPTransport):
|
||||
def __init__(self, handler: Callable) -> None:
|
||||
self.handler = handler
|
||||
|
||||
@ -34,8 +34,8 @@ class MockTransport(httpcore.SyncHTTPTransport):
|
||||
url: Tuple[bytes, bytes, Optional[int], bytes],
|
||||
headers: List[Tuple[bytes, bytes]] = None,
|
||||
stream: httpcore.SyncByteStream = None,
|
||||
timeout: Mapping[str, Optional[float]] = None,
|
||||
) -> Tuple[bytes, int, bytes, List[Tuple[bytes, bytes]], httpcore.SyncByteStream]:
|
||||
ext: dict = None,
|
||||
) -> Tuple[int, List[Tuple[bytes, bytes]], httpcore.SyncByteStream, dict]:
|
||||
request = httpx.Request(
|
||||
method=method,
|
||||
url=url,
|
||||
@ -45,26 +45,20 @@ class MockTransport(httpcore.SyncHTTPTransport):
|
||||
request.read()
|
||||
response = self.handler(request)
|
||||
return (
|
||||
(response.http_version or "HTTP/1.1").encode("ascii"),
|
||||
response.status_code,
|
||||
response.reason_phrase.encode("ascii"),
|
||||
response.headers.raw,
|
||||
response.stream,
|
||||
response.ext,
|
||||
)
|
||||
|
||||
|
||||
class AsyncMockTransport(httpcore.AsyncHTTPTransport):
|
||||
def __init__(self, handler: Callable) -> None:
|
||||
self.handler = handler
|
||||
|
||||
async def request(
|
||||
async def arequest(
|
||||
self,
|
||||
method: bytes,
|
||||
url: Tuple[bytes, bytes, Optional[int], bytes],
|
||||
headers: List[Tuple[bytes, bytes]] = None,
|
||||
stream: httpcore.AsyncByteStream = None,
|
||||
timeout: Mapping[str, Optional[float]] = None,
|
||||
) -> Tuple[bytes, int, bytes, List[Tuple[bytes, bytes]], httpcore.AsyncByteStream]:
|
||||
ext: dict = None,
|
||||
) -> Tuple[int, List[Tuple[bytes, bytes]], httpcore.AsyncByteStream, dict]:
|
||||
request = httpx.Request(
|
||||
method=method,
|
||||
url=url,
|
||||
@ -74,9 +68,8 @@ class AsyncMockTransport(httpcore.AsyncHTTPTransport):
|
||||
await request.aread()
|
||||
response = self.handler(request)
|
||||
return (
|
||||
(response.http_version or "HTTP/1.1").encode("ascii"),
|
||||
response.status_code,
|
||||
response.reason_phrase.encode("ascii"),
|
||||
response.headers.raw,
|
||||
response.stream,
|
||||
response.ext,
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user