Skip to content

Commit f932af9

Browse files
tomchristieJamie Hewland
and
Jamie Hewland
authored
Version 0.15.0 (#1301)
* Version 0.15.0 * Update CHANGELOG.md Co-authored-by: Jamie Hewland <[email protected]> * 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 <[email protected]>
1 parent 8e4a8a1 commit f932af9

18 files changed

+117
-208
lines changed

CHANGELOG.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,48 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

7+
## 0.15.0
8+
9+
### Added
10+
11+
* Added support for event hooks. (Pull #1246)
12+
* Added support for authentication flows which require either sync or async I/O. (Pull #1217)
13+
* Added support for monitoring download progress with `response.num_bytes_downloaded`. (Pull #1268)
14+
* Added `Request(content=...)` for byte content, instead of overloading `Request(data=...)` (Pull #1266)
15+
* Added support for all URL components as parameter names when using `url.copy_with(...)`. (Pull #1285)
16+
* Neater split between automatically populated headers on `Request` instances, vs default `client.headers`. (Pull #1248)
17+
* Unclosed `AsyncClient` instances will now raise warnings if garbage collected. (Pull #1197)
18+
* Support `Response(content=..., text=..., html=..., json=...)` for creating usable response instances in code. (Pull #1265, #1297)
19+
* Support instantiating requests from the low-level transport API. (Pull #1293)
20+
* Raise errors on invalid URL types. (Pull #1259)
21+
22+
### Changed
23+
24+
* Cleaned up expected behaviour for URL escaping. `url.path` is now URL escaped. (Pull #1285)
25+
* 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)
26+
* Drop `url.authority` property in favour of `url.netloc`, since "authority" was semantically incorrect. (Pull #1285)
27+
* Drop `url.full_path` property in favour of `url.raw_path`, for better consistency with other parts of the API. (Pull #1285)
28+
* No longer use the `chardet` library for auto-detecting charsets, instead defaulting to a simpler approach when no charset is specified. (#1269)
29+
30+
### Fixed
31+
32+
* Swapped ordering of redirects and authentication flow. (Pull #1267)
33+
* `.netrc` lookups should use host, not host+port. (Pull #1298)
34+
35+
### Removed
36+
37+
* 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)
38+
* Drop `request.timer` attribute, which was being used internally to set `response.elapsed`. (Pull #1249)
39+
* Drop `response.decoder` attribute, which was being used internally. (Pull #1276)
40+
* `Request.prepare()` is now a private method. (Pull #1284)
41+
* The `Headers.getlist()` method had previously been deprecated in favour of `Headers.get_list()`. It is now fully removed.
42+
* The `QueryParams.getlist()` method had previously been deprecated in favour of `QueryParams.get_list()`. It is now fully removed.
43+
* The `URL.is_ssl` property had previously been deprecated in favour of `URL.scheme == "https"`. It is now fully removed.
44+
* The `httpx.PoolLimits` class had previously been deprecated in favour of `httpx.Limits`. It is now fully removed.
45+
* The `max_keepalive` setting had previously been deprecated in favour of the more explicit `max_keepalive_connections`. It is now fully removed.
46+
* 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.
47+
* 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.
48+
749
## 0.14.3 (September 2nd, 2020)
850

951
### Added

httpx/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from ._api import delete, get, head, options, patch, post, put, request, stream
33
from ._auth import Auth, BasicAuth, DigestAuth
44
from ._client import AsyncClient, Client
5-
from ._config import Limits, PoolLimits, Proxy, Timeout, create_ssl_context
5+
from ._config import Limits, Proxy, Timeout, create_ssl_context
66
from ._exceptions import (
77
CloseError,
88
ConnectError,
@@ -70,7 +70,6 @@
7070
"NotRedirectResponse",
7171
"options",
7272
"patch",
73-
"PoolLimits",
7473
"PoolTimeout",
7574
"post",
7675
"ProtocolError",

httpx/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__title__ = "httpx"
22
__description__ = "A next generation HTTP client, for Python 3."
3-
__version__ = "0.14.3"
3+
__version__ = "0.15.0"

httpx/_client.py

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -850,18 +850,12 @@ def _send_single_request(self, request: Request, timeout: Timeout) -> Response:
850850
timer.sync_start()
851851

852852
with map_exceptions(HTTPCORE_EXC_MAP, request=request):
853-
(
854-
http_version,
855-
status_code,
856-
reason_phrase,
857-
headers,
858-
stream,
859-
) = transport.request(
853+
(status_code, headers, stream, ext) = transport.request(
860854
request.method.encode(),
861855
request.url.raw,
862856
headers=request.headers.raw,
863857
stream=request.stream, # type: ignore
864-
timeout=timeout.as_dict(),
858+
ext={"timeout": timeout.as_dict()},
865859
)
866860

867861
def on_close(response: Response) -> None:
@@ -871,9 +865,9 @@ def on_close(response: Response) -> None:
871865

872866
response = Response(
873867
status_code,
874-
http_version=http_version.decode("ascii"),
875868
headers=headers,
876869
stream=stream, # type: ignore
870+
ext=ext,
877871
request=request,
878872
on_close=on_close,
879873
)
@@ -1501,18 +1495,12 @@ async def _send_single_request(
15011495
await timer.async_start()
15021496

15031497
with map_exceptions(HTTPCORE_EXC_MAP, request=request):
1504-
(
1505-
http_version,
1506-
status_code,
1507-
reason_phrase,
1508-
headers,
1509-
stream,
1510-
) = await transport.request(
1498+
(status_code, headers, stream, ext,) = await transport.arequest(
15111499
request.method.encode(),
15121500
request.url.raw,
15131501
headers=request.headers.raw,
15141502
stream=request.stream, # type: ignore
1515-
timeout=timeout.as_dict(),
1503+
ext={"timeout": timeout.as_dict()},
15161504
)
15171505

15181506
async def on_close(response: Response) -> None:
@@ -1522,9 +1510,9 @@ async def on_close(response: Response) -> None:
15221510

15231511
response = Response(
15241512
status_code,
1525-
http_version=http_version.decode("ascii"),
15261513
headers=headers,
15271514
stream=stream, # type: ignore
1515+
ext=ext,
15281516
request=request,
15291517
on_close=on_close,
15301518
)

httpx/_config.py

Lines changed: 3 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import os
22
import ssl
33
import typing
4-
import warnings
54
from base64 import b64encode
65
from pathlib import Path
76

87
import certifi
98

109
from ._models import URL, Headers
1110
from ._types import CertTypes, HeaderTypes, TimeoutTypes, URLTypes, VerifyTypes
12-
from ._utils import get_ca_bundle_from_env, get_logger, warn_deprecated
11+
from ._utils import get_ca_bundle_from_env, get_logger
1312

1413
DEFAULT_CIPHERS = ":".join(
1514
[
@@ -212,44 +211,7 @@ def __init__(
212211
read: typing.Union[None, float, UnsetType] = UNSET,
213212
write: typing.Union[None, float, UnsetType] = UNSET,
214213
pool: typing.Union[None, float, UnsetType] = UNSET,
215-
# Deprecated aliases.
216-
connect_timeout: typing.Union[None, float, UnsetType] = UNSET,
217-
read_timeout: typing.Union[None, float, UnsetType] = UNSET,
218-
write_timeout: typing.Union[None, float, UnsetType] = UNSET,
219-
pool_timeout: typing.Union[None, float, UnsetType] = UNSET,
220214
):
221-
if not isinstance(connect_timeout, UnsetType):
222-
warn_deprecated(
223-
"httpx.Timeout(..., connect_timeout=...) is deprecated and will "
224-
"raise errors in a future version. "
225-
"Use httpx.Timeout(..., connect=...) instead."
226-
)
227-
connect = connect_timeout
228-
229-
if not isinstance(read_timeout, UnsetType):
230-
warn_deprecated(
231-
"httpx.Timeout(..., read_timeout=...) is deprecated and will "
232-
"raise errors in a future version. "
233-
"Use httpx.Timeout(..., write=...) instead."
234-
)
235-
read = read_timeout
236-
237-
if not isinstance(write_timeout, UnsetType):
238-
warn_deprecated(
239-
"httpx.Timeout(..., write_timeout=...) is deprecated and will "
240-
"raise errors in a future version. "
241-
"Use httpx.Timeout(..., write=...) instead."
242-
)
243-
write = write_timeout
244-
245-
if not isinstance(pool_timeout, UnsetType):
246-
warn_deprecated(
247-
"httpx.Timeout(..., pool_timeout=...) is deprecated and will "
248-
"raise errors in a future version. "
249-
"Use httpx.Timeout(..., pool=...) instead."
250-
)
251-
pool = pool_timeout
252-
253215
if isinstance(timeout, Timeout):
254216
# Passed as a single explicit Timeout.
255217
assert connect is UNSET
@@ -278,13 +240,10 @@ def __init__(
278240
self.pool = pool
279241
else:
280242
if isinstance(timeout, UnsetType):
281-
warnings.warn(
243+
raise ValueError(
282244
"httpx.Timeout must either include a default, or set all "
283-
"four parameters explicitly. Omitting the default argument "
284-
"is deprecated and will raise errors in a future version.",
285-
DeprecationWarning,
245+
"four parameters explicitly."
286246
)
287-
timeout = None
288247
self.connect = timeout if isinstance(connect, UnsetType) else connect
289248
self.read = timeout if isinstance(read, UnsetType) else read
290249
self.write = timeout if isinstance(write, UnsetType) else write
@@ -335,16 +294,7 @@ def __init__(
335294
*,
336295
max_connections: int = None,
337296
max_keepalive_connections: int = None,
338-
# Deprecated parameter naming, in favour of more explicit version:
339-
max_keepalive: int = None,
340297
):
341-
if max_keepalive is not None:
342-
warnings.warn(
343-
"'max_keepalive' is deprecated. Use 'max_keepalive_connections'.",
344-
DeprecationWarning,
345-
)
346-
max_keepalive_connections = max_keepalive
347-
348298
self.max_connections = max_connections
349299
self.max_keepalive_connections = max_keepalive_connections
350300

@@ -363,15 +313,6 @@ def __repr__(self) -> str:
363313
)
364314

365315

366-
class PoolLimits(Limits):
367-
def __init__(self, **kwargs: typing.Any) -> None:
368-
warn_deprecated(
369-
"httpx.PoolLimits(...) is deprecated and will raise errors in the future. "
370-
"Use httpx.Limits(...) instead."
371-
)
372-
super().__init__(**kwargs)
373-
374-
375316
class Proxy:
376317
def __init__(
377318
self, url: URLTypes, *, headers: HeaderTypes = None, mode: str = "DEFAULT"

httpx/_models.py

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import json as jsonlib
66
import typing
77
import urllib.request
8-
import warnings
98
from collections.abc import MutableMapping
109
from http.cookiejar import Cookie, CookieJar
1110
from urllib.parse import parse_qsl, quote, unquote, urlencode
@@ -272,12 +271,6 @@ def raw(self) -> RawURL:
272271
self.raw_path,
273272
)
274273

275-
@property
276-
def is_ssl(self) -> bool:
277-
message = 'URL.is_ssl() is pending deprecation. Use url.scheme == "https"'
278-
warnings.warn(message, DeprecationWarning)
279-
return self.scheme == "https"
280-
281274
@property
282275
def is_absolute_url(self) -> bool:
283276
"""
@@ -525,13 +518,6 @@ def __repr__(self) -> str:
525518
query_string = str(self)
526519
return f"{class_name}({query_string!r})"
527520

528-
def getlist(self, key: typing.Any) -> typing.List[str]:
529-
message = (
530-
"QueryParams.getlist() is pending deprecation. Use QueryParams.get_list()"
531-
)
532-
warnings.warn(message, DeprecationWarning)
533-
return self.get_list(key)
534-
535521

536522
class Headers(typing.MutableMapping[str, str]):
537523
"""
@@ -757,11 +743,6 @@ def __repr__(self) -> str:
757743
return f"{class_name}({as_dict!r}{encoding_str})"
758744
return f"{class_name}({as_list!r}{encoding_str})"
759745

760-
def getlist(self, key: str, split_commas: bool = False) -> typing.List[str]:
761-
message = "Headers.getlist() is pending deprecation. Use Headers.get_list()"
762-
warnings.warn(message, DeprecationWarning)
763-
return self.get_list(key, split_commas=split_commas)
764-
765746

766747
class Request:
767748
def __init__(
@@ -883,19 +864,19 @@ def __init__(
883864
html: str = None,
884865
json: typing.Any = None,
885866
stream: ByteStream = None,
886-
http_version: str = None,
887867
request: Request = None,
868+
ext: dict = None,
888869
history: typing.List["Response"] = None,
889870
on_close: typing.Callable = None,
890871
):
891872
self.status_code = status_code
892-
self.http_version = http_version
893873
self.headers = Headers(headers)
894874

895875
self._request: typing.Optional[Request] = request
896876

897877
self.call_next: typing.Optional[typing.Callable] = None
898878

879+
self.ext = {} if ext is None else ext
899880
self.history = [] if history is None else list(history)
900881
self._on_close = on_close
901882

@@ -964,9 +945,13 @@ def request(self) -> Request:
964945
def request(self, value: Request) -> None:
965946
self._request = value
966947

948+
@property
949+
def http_version(self) -> str:
950+
return self.ext.get("http_version", "HTTP/1.1")
951+
967952
@property
968953
def reason_phrase(self) -> str:
969-
return codes.get_reason_phrase(self.status_code)
954+
return self.ext.get("reason", codes.get_reason_phrase(self.status_code))
970955

971956
@property
972957
def url(self) -> typing.Optional[URL]:

httpx/_transports/asgi.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import TYPE_CHECKING, Callable, List, Mapping, Optional, Tuple, Union
1+
from typing import TYPE_CHECKING, Callable, List, Optional, Tuple, Union
22

33
import httpcore
44
import sniffio
@@ -67,14 +67,14 @@ def __init__(
6767
self.root_path = root_path
6868
self.client = client
6969

70-
async def request(
70+
async def arequest(
7171
self,
7272
method: bytes,
7373
url: Tuple[bytes, bytes, Optional[int], bytes],
7474
headers: List[Tuple[bytes, bytes]] = None,
7575
stream: httpcore.AsyncByteStream = None,
76-
timeout: Mapping[str, Optional[float]] = None,
77-
) -> Tuple[bytes, int, bytes, List[Tuple[bytes, bytes]], httpcore.AsyncByteStream]:
76+
ext: dict = None,
77+
) -> Tuple[int, List[Tuple[bytes, bytes]], httpcore.AsyncByteStream, dict]:
7878
headers = [] if headers is None else headers
7979
stream = httpcore.PlainByteStream(content=b"") if stream is None else stream
8080

@@ -154,5 +154,6 @@ async def send(message: dict) -> None:
154154
assert response_headers is not None
155155

156156
stream = httpcore.PlainByteStream(content=b"".join(body_parts))
157+
ext = {}
157158

158-
return (b"HTTP/1.1", status_code, b"", response_headers, stream)
159+
return (status_code, response_headers, stream, ext)

httpx/_transports/wsgi.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,9 @@ def request(
6464
url: typing.Tuple[bytes, bytes, typing.Optional[int], bytes],
6565
headers: typing.List[typing.Tuple[bytes, bytes]] = None,
6666
stream: httpcore.SyncByteStream = None,
67-
timeout: typing.Mapping[str, typing.Optional[float]] = None,
67+
ext: dict = None,
6868
) -> typing.Tuple[
69-
bytes,
70-
int,
71-
bytes,
72-
typing.List[typing.Tuple[bytes, bytes]],
73-
httpcore.SyncByteStream,
69+
int, typing.List[typing.Tuple[bytes, bytes]], httpcore.SyncByteStream, dict
7470
]:
7571
headers = [] if headers is None else headers
7672
stream = httpcore.PlainByteStream(content=b"") if stream is None else stream
@@ -127,5 +123,6 @@ def start_response(
127123
for key, value in seen_response_headers
128124
]
129125
stream = httpcore.IteratorByteStream(iterator=result)
126+
ext = {}
130127

131-
return (b"HTTP/1.1", status_code, b"", headers, stream)
128+
return (status_code, headers, stream, ext)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def get_packages(package):
5858
"certifi",
5959
"sniffio",
6060
"rfc3986[idna2008]>=1.3,<2",
61-
"httpcore==0.10.*",
61+
"httpcore==0.11.*",
6262
],
6363
extras_require={
6464
"http2": "h2==3.*",

0 commit comments

Comments
 (0)