Skip to content

Commit

Permalink
Merge branch 'master' into fix-environment-proxies
Browse files Browse the repository at this point in the history
  • Loading branch information
tomchristie authored Sep 21, 2023
2 parents 2aca020 + 7c9db49 commit 1722464
Show file tree
Hide file tree
Showing 32 changed files with 225 additions and 91 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:
name: deploy

steps:
- uses: "actions/checkout@v3"
- uses: "actions/checkout@v4"
- uses: "actions/setup-python@v4"
with:
python-version: 3.7
python-version: 3.8
- name: "Install dependencies"
run: "scripts/install"
- name: "Build package & docs"
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/test-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ jobs:

strategy:
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

steps:
- uses: "actions/checkout@v3"
- uses: "actions/checkout@v4"
- uses: "actions/setup-python@v4"
with:
python-version: "${{ matrix.python-version }}"
allow-prereleases: true
- name: "Install dependencies"
run: "scripts/install"
- name: "Run linting checks"
Expand Down
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### Added

* Add support for Python 3.12. (#2854)

### Fixed

* Raise `ValueError` on `Response.encoding` being set after `Response.text` has been accessed. (#2852)

## 0.25.0 (11th Sep, 2023)

### Removed

* 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)

### 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)
* 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)

## 0.24.1 (17th May, 2023)

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Or, to include the optional HTTP/2 support, use:
$ pip install httpx[http2]
```

HTTPX requires Python 3.7+.
HTTPX requires Python 3.8+.

## Documentation

Expand Down
2 changes: 1 addition & 1 deletion README_chinese.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ $ pip install httpx
$ pip install httpx[http2]
```

HTTPX 要求 Python 3.7+ 版本。
HTTPX 要求 Python 3.8+ 版本。

## 文档

Expand Down
32 changes: 32 additions & 0 deletions docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,38 @@ with tempfile.NamedTemporaryFile() as download_file:

![rich progress bar](img/rich-progress.gif)

## Monitoring upload progress

If you need to monitor upload progress of large responses, you can use request content generator streaming.

For example, showing a progress bar using the [`tqdm`](https://github.com/tqdm/tqdm) library.

```python
import io
import random

import httpx
from tqdm import tqdm


def gen():
"""
this is a complete example with generated random bytes.
you can replace `io.BytesIO` with real file object.
"""
total = 32 * 1024 * 1024 # 32m
with tqdm(ascii=True, unit_scale=True, unit='B', unit_divisor=1024, total=total) as bar:
with io.BytesIO(random.randbytes(total)) as f:
while data := f.read(1024):
yield data
bar.update(len(data))


httpx.post("https://httpbin.org/post", content=gen())
```

![tqdm progress bar](img/tqdm-progress.gif)

## .netrc Support

HTTPX can be configured to use [a `.netrc` config file](https://everything.curl.dev/usingcurl/netrc) for authentication.
Expand Down
2 changes: 1 addition & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
* The amount of time elapsed between sending the request and calling `close()` on the corresponding response received for that request.
[total_seconds()](https://docs.python.org/3/library/datetime.html#datetime.timedelta.total_seconds) to correctly get
the total elapsed seconds.
* `def .raise_for_status()` - **None**
* `def .raise_for_status()` - **Response**
* `def .json()` - **Any**
* `def .read()` - **bytes**
* `def .iter_raw([chunk_size])` - **bytes iterator**
Expand Down
3 changes: 3 additions & 0 deletions docs/async.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ async with httpx.AsyncClient() as client:
...
```

!!! warning
In order to get the most benefit from connection pooling, make sure you're not instantiating multiple client instances - for example by using `async with` inside a "hot loop". This can be achieved either by having a single scoped client that's passed throughout wherever it's needed, or by having a single global client instance.

Alternatively, use `await client.aclose()` if you want to close a client explicitly:

```python
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,6 @@ To include the optional brotli decoder support, use:
$ pip install httpx[brotli]
```

HTTPX requires Python 3.7+
HTTPX requires Python 3.8+

[sync-support]: https://github.com/encode/httpx/issues/572
2 changes: 1 addition & 1 deletion docs/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ DEBUG [2023-03-16 14:36:21] httpcore - connection.close.started
DEBUG [2023-03-16 14:36:21] httpcore - connection.close.complete
```

Logging output includes information from both the high-level `httpx` logger, and the network-level `httpcore` logger, which can be configured seperately.
Logging output includes information from both the high-level `httpx` logger, and the network-level `httpcore` logger, which can be configured separately.

For handling more complex logging configurations you might want to use the dictionary configuration style...

Expand Down
13 changes: 10 additions & 3 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,15 +285,22 @@ Traceback (most recent call last):
File "/Users/tomchristie/GitHub/encode/httpcore/httpx/models.py", line 837, in raise_for_status
raise HTTPStatusError(message, response=self)
httpx._exceptions.HTTPStatusError: 404 Client Error: Not Found for url: https://httpbin.org/status/404
For more information check: https://httpstatuses.com/404
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
```

Any successful response codes will simply return `None` rather than raising an exception.
Any successful response codes will return the `Response` instance rather than raising an exception.

```pycon
>>> r.raise_for_status()
```

The method returns the response instance, allowing you to use it inline. For example:

```pycon
>>> r = httpx.get('...').raise_for_status()
>>> data = httpx.get('...').raise_for_status().json()
```

## Response Headers

The response headers are available as a dictionary-like interface.
Expand Down Expand Up @@ -367,7 +374,7 @@ If you're using streaming responses in any of these ways then the `response.cont

```pycon
>>> with httpx.stream("GET", "https://www.example.com") as r:
... if r.headers['Content-Length'] < TOO_LONG:
... if int(r.headers['Content-Length']) < TOO_LONG:
... r.read()
... print(r.text)
```
Expand Down
6 changes: 6 additions & 0 deletions docs/third_party_packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ As HTTPX usage grows, there is an expanding community of developers building too

<!-- NOTE: this list is in alphabetical order. -->

### Hishel

[GitHub](https://github.com/karosis88/hishel) - [Documentation](https://karosis88.github.io/hishel/)

An elegant HTTP Cache implementation for HTTPX and HTTP Core.

### Authlib

[GitHub](https://github.com/lepture/authlib) - [Documentation](https://docs.authlib.org/en/latest/)
Expand Down
2 changes: 1 addition & 1 deletion httpx/__version__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__title__ = "httpx"
__description__ = "A next generation HTTP client, for Python 3."
__version__ = "0.24.1"
__version__ = "0.25.0"
4 changes: 3 additions & 1 deletion httpx/_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
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
Expand Down Expand Up @@ -217,6 +217,8 @@ def auth_flow(self, request: Request) -> typing.Generator[Request, Response, Non
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(
Expand Down
4 changes: 1 addition & 3 deletions httpx/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
except ImportError:
brotli = None

if sys.version_info >= (3, 10) or (
sys.version_info >= (3, 7) 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
Expand Down
19 changes: 9 additions & 10 deletions httpx/_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import logging
import os
import ssl
import sys
import typing
from pathlib import Path

Expand Down Expand Up @@ -128,11 +127,10 @@ def load_ssl_context_verify(self) -> ssl.SSLContext:

# 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.
Expand Down Expand Up @@ -168,10 +166,9 @@ def _create_default_ssl_context(self) -> ssl.SSLContext:
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

Expand Down Expand Up @@ -326,6 +323,7 @@ def __init__(
self,
url: URLTypes,
*,
ssl_context: typing.Optional[ssl.SSLContext] = None,
auth: typing.Optional[typing.Tuple[str, str]] = None,
headers: typing.Optional[HeaderTypes] = None,
):
Expand All @@ -343,6 +341,7 @@ def __init__(
self.url = url
self.auth = auth
self.headers = headers
self.ssl_context = ssl_context

@property
def raw_auth(self) -> typing.Optional[typing.Tuple[bytes, bytes]]:
Expand Down
20 changes: 15 additions & 5 deletions httpx/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ def __init__(
# the client will set `response.next_request`.
self.next_request: typing.Optional[Request] = None

self.extensions = {} if extensions is None else extensions
self.extensions: ResponseExtensions = {} if extensions is None else extensions
self.history = [] if history is None else list(history)

self.is_closed = False
Expand Down Expand Up @@ -603,6 +603,16 @@ def encoding(self) -> typing.Optional[str]:

@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
Expand Down Expand Up @@ -711,7 +721,7 @@ def has_redirect_location(self) -> bool:
and "Location" in self.headers
)

def raise_for_status(self) -> None:
def raise_for_status(self) -> "Response":
"""
Raise the `HTTPStatusError` if one occurred.
"""
Expand All @@ -723,18 +733,18 @@ def raise_for_status(self) -> None:
)

if self.is_success:
return
return self

if self.has_redirect_location:
message = (
"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
"Redirect location: '{0.headers[location]}'\n"
"For more information check: https://httpstatuses.com/{0.status_code}"
"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
)
else:
message = (
"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
"For more information check: https://httpstatuses.com/{0.status_code}"
"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
)

status_class = self.status_code // 100
Expand Down
1 change: 1 addition & 0 deletions httpx/_transports/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ def __init__(
proxy_auth=proxy.raw_auth,
proxy_headers=proxy.headers.raw,
ssl_context=ssl_context,
proxy_ssl_context=proxy.ssl_context,
max_connections=limits.max_connections,
max_keepalive_connections=limits.max_keepalive_connections,
keepalive_expiry=limits.keepalive_expiry,
Expand Down
5 changes: 3 additions & 2 deletions httpx/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
Iterator,
List,
Mapping,
MutableMapping,
NamedTuple,
Optional,
Sequence,
Expand Down Expand Up @@ -87,7 +88,7 @@

RequestContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]
ResponseContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]
ResponseExtensions = Mapping[str, Any]
ResponseExtensions = MutableMapping[str, Any]

RequestData = Mapping[str, Any]

Expand All @@ -104,7 +105,7 @@
]
RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]]

RequestExtensions = Mapping[str, Any]
RequestExtensions = MutableMapping[str, Any]


class SyncByteStream:
Expand Down
Loading

0 comments on commit 1722464

Please sign in to comment.