Skip to content

Commit ee9250d

Browse files
authored
Add cli support (#1855)
* Add cli support * Add setup.py * Import main to 'httpx.main' * Add 'cli' to requirements * Add tests for command-line client * Drop most CLI tests * Add test_json * Add test_redirects * Coverage exclusion over _main.py in order to test more clearly * Black formatting * Add test_follow_redirects * Add test_post, test_verbose, test_auth * Add test_errors * Remove test_errors * Add test_download * Change test_errors - perhaps the empty host header was causing the socket error? * Update test_errors to not break socket * Update docs * Update version to 1.0.0.beta0 * Tweak CHANGELOG * Fix up images in README * Tweak images in README * Update README
1 parent a761e17 commit ee9250d

14 files changed

+796
-67
lines changed

CHANGELOG.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,73 @@ 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+
## 1.0.0.beta0
8+
9+
The 1.0 pre-release adds an integrated command-line client, and also includes some
10+
design changes. The most notable of these is that redirect responses are no longer
11+
automatically followed, unless specifically requested.
12+
13+
This design decision prioritises a more explicit approach to redirects, in order
14+
to avoid code that unintentionally issues multiple requests as a result of
15+
misconfigured URLs.
16+
17+
For example, previously a client configured to send requests to `http://api.github.com/`
18+
would end up sending every API request twice, as each request would be redirected to `https://github.com/api/`.
19+
20+
If you do want auto-redirect behaviour, you can enable this either by configuring
21+
the client instance with `Client(follow_redirects=True)`, or on a per-request
22+
basis, with `.get(..., follow_redirects=True)`.
23+
24+
This change is a classic trade-off between convenience and precision, with no "right"
25+
answer. See [discussion #1785](https://github.com/encode/httpx/discussions/1785) for more
26+
context.
27+
28+
The other major design change is an update to the Transport API, which is the low-level
29+
interface against which requests are sent. Previously this interface used only primitive
30+
datastructures, like so...
31+
32+
```python
33+
(status_code, headers, stream, extensions) = transport.handle_request(method, url, headers, stream, extensions)
34+
try
35+
...
36+
finally:
37+
stream.close()
38+
```
39+
40+
Now the interface is much simpler...
41+
42+
```python
43+
response = transport.handle_request(request)
44+
try
45+
...
46+
finally:
47+
response.close()
48+
```
49+
50+
### Changed
51+
52+
* The `allow_redirects` flag is now `follow_redirects` and defaults to `False`.
53+
* The `raise_for_status()` method will now raise an exception for any responses
54+
except those with 2xx status codes. Previously only 4xx and 5xx status codes
55+
would result in an exception.
56+
* The low-level transport API changes to the much simpler `response = transport.handle_request(request)`.
57+
* The `client.send()` method no longer accepts a `timeout=...` argument, but the
58+
`client.build_request()` does. This required by the signature change of the
59+
Transport API. The request timeout configuration is now stored on the request
60+
instance, as `request.extensions['timeout']`.
61+
62+
### Added
63+
64+
* Added the `httpx` command-line client.
65+
* Response instances now include `.is_informational`, `.is_success`, `.is_redirect`, `.is_client_error`, and `.is_server_error`
66+
properties for checking 1xx, 2xx, 3xx, 4xx, and 5xx response types. Note that the behaviour of `.is_redirect` is slightly different in that it now returns True for all 3xx responses, in order to allow for a consistent set of properties onto the different HTTP status code types. The `response.has_redirect_location` location may be used to determine responses with properly formed URL redirects.
67+
68+
### Fixed
69+
70+
* `response.iter_bytes()` no longer raises a ValueError when called on a response with no content. (Pull #1827)
71+
* The `'wsgi.error'` configuration now defaults to `sys.stderr`, and is corrected to be a `TextIO` interface, not a `BytesIO` interface. Additionally, the WSGITransport now accepts a `wsgi_error` confguration. (Pull #1828)
72+
* Follow the WSGI spec by properly closing the iterable returned by the application. (Pull #1830)
73+
774
## 0.19.0 (19th August, 2021)
875

976
### Added

README.md

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,21 @@
1313
</a>
1414
</p>
1515

16-
HTTPX is a fully featured HTTP client for Python 3, which provides sync and async APIs, and support for both HTTP/1.1 and HTTP/2.
16+
HTTPX is a fully featured HTTP client library for Python 3. It includes **an integrated
17+
command line client**, has support for both **HTTP/1.1 and HTTP/2**, and provides both **sync
18+
and async APIs**.
1719

18-
**Note**: _HTTPX should be considered in beta. We believe we've got the public API to
19-
a stable point now, but would strongly recommend pinning your dependencies to the `0.19.*`
20-
release, so that you're able to properly review [API changes between package updates](https://github.com/encode/httpx/blob/master/CHANGELOG.md). A 1.0 release is expected to be issued sometime in 2021._
20+
**Note**: *This is the README for the 1.0 pre-release. This release adds support for an integrated command-line client, and also includes a couple of design changes from 0.19. Redirects are no longer followed by default, and the low-level Transport API has been updated. Upgrades from 0.19 will need to see [the CHANGELOG](https://github.com/encode/httpx/blob/version-1.0/CHANGELOG.md) for more details.*
2121

2222
---
2323

24-
Let's get started...
24+
Installing HTTPX.
25+
26+
```shell
27+
$ pip install httpx --pre
28+
```
29+
30+
Now, let's get started...
2531

2632
```pycon
2733
>>> import httpx
@@ -36,26 +42,32 @@ Let's get started...
3642
'<!doctype html>\n<html>\n<head>\n<title>Example Domain</title>...'
3743
```
3844

39-
Or, using the async API...
40-
41-
_Use [IPython](https://ipython.readthedocs.io/en/stable/) or Python 3.8+ with `python -m asyncio` to try this code interactively._
45+
Or, using the command-line client.
4246

43-
```pycon
44-
>>> import httpx
45-
>>> async with httpx.AsyncClient() as client:
46-
... r = await client.get('https://www.example.org/')
47-
...
48-
>>> r
49-
<Response [200 OK]>
47+
```shell
48+
$ pip install --pre 'httpx[cli]' # The command line client is an optional dependency.
5049
```
5150

51+
Which now allows us to use HTTPX directly from the command-line...
52+
53+
<p align="center">
54+
<img width="700" src="docs/img/httpx-help.png" alt='httpx --help'>
55+
</p>
56+
57+
Sending a request...
58+
59+
<p align="center">
60+
<img width="700" src="docs/img/httpx-request.png" alt='httpx http://httpbin.org/json'>
61+
</p>
62+
5263
## Features
5364

5465
HTTPX builds on the well-established usability of `requests`, and gives you:
5566

5667
* A broadly [requests-compatible API](https://www.python-httpx.org/compatibility/).
57-
* Standard synchronous interface, but with [async support if you need it](https://www.python-httpx.org/async/).
68+
* An integrated command-line client.
5869
* HTTP/1.1 [and HTTP/2 support](https://www.python-httpx.org/http2/).
70+
* Standard synchronous interface, but with [async support if you need it](https://www.python-httpx.org/async/).
5971
* Ability to make requests directly to [WSGI applications](https://www.python-httpx.org/advanced/#calling-into-python-web-apps) or [ASGI applications](https://www.python-httpx.org/async/#calling-into-python-web-apps).
6072
* Strict timeouts everywhere.
6173
* Fully type annotated.

docs/compatibility.md

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,10 @@
11
# Requests Compatibility Guide
22

3-
HTTPX aims to be broadly compatible with the `requests` API.
3+
HTTPX aims to be broadly compatible with the `requests` API, although there are a
4+
few design differences in places.
45

56
This documentation outlines places where the API differs...
67

7-
## Client instances
8-
9-
The HTTPX equivalent of `requests.Session` is `httpx.Client`.
10-
11-
```python
12-
session = requests.Session(**kwargs)
13-
```
14-
15-
is generally equivalent to
16-
17-
```python
18-
client = httpx.Client(**kwargs)
19-
```
20-
21-
## Request URLs
22-
23-
Accessing `response.url` will return a `URL` instance, rather than a string.
24-
25-
Use `str(response.url)` if you need a string instance.
26-
278
## Redirects
289

2910
Unlike `requests`, HTTPX does **not follow redirects by default**.
@@ -44,6 +25,26 @@ Or else instantiate a client, with redirect following enabled by default...
4425
client = httpx.Client(follow_redirects=True)
4526
```
4627

28+
## Client instances
29+
30+
The HTTPX equivalent of `requests.Session` is `httpx.Client`.
31+
32+
```python
33+
session = requests.Session(**kwargs)
34+
```
35+
36+
is generally equivalent to
37+
38+
```python
39+
client = httpx.Client(**kwargs)
40+
```
41+
42+
## Request URLs
43+
44+
Accessing `response.url` will return a `URL` instance, rather than a string.
45+
46+
Use `str(response.url)` if you need a string instance.
47+
4748
## Determining the next redirect request
4849

4950
The `requests` library exposes an attribute `response.next`, which can be used to obtain the next redirect request.
@@ -97,8 +98,7 @@ opened in text mode.
9798
## Content encoding
9899

99100
HTTPX uses `utf-8` for encoding `str` request bodies. For example, when using `content=<str>` the request body will be encoded to `utf-8` before being sent over the wire. This differs from Requests which uses `latin1`. If you need an explicit encoding, pass encoded bytes explictly, e.g. `content=<str>.encode("latin1")`.
100-
101-
For response bodies, assuming the server didn't send an explicit encoding then HTTPX will do its best to figure out an appropriate encoding. HTTPX makes a guess at the encoding to use for decoding the response using `charset_normalizer`. Fallback to that or any content with less than 32 octets will be decoded using `utf-8` with the `error="replace"` decoder strategy.
101+
For response bodies, assuming the server didn't send an explicit encoding then HTTPX will do its best to figure out an appropriate encoding. HTTPX makes a guess at the encoding to use for decoding the response using `charset_normalizer`. Fallback to that or any content with less than 32 octets will be decoded using `utf-8` with the `error="replace"` decoder strategy.
102102

103103
## Cookies
104104

@@ -133,7 +133,7 @@ HTTPX provides a `.stream()` interface rather than using `stream=True`. This ens
133133
For example:
134134

135135
```python
136-
with request.stream("GET", "https://www.example.com") as response:
136+
with httpx.stream("GET", "https://www.example.com") as response:
137137
...
138138
```
139139

@@ -165,13 +165,21 @@ Requests supports `REQUESTS_CA_BUNDLE` which points to either a file or a direct
165165

166166
## Request body on HTTP methods
167167

168-
The HTTP `GET`, `DELETE`, `HEAD`, and `OPTIONS` methods are specified as not supporting a request body. To stay in line with this, the `.get`, `.delete`, `.head` and `.options` functions do not support `files`, `data`, or `json` arguments.
168+
The HTTP `GET`, `DELETE`, `HEAD`, and `OPTIONS` methods are specified as not supporting a request body. To stay in line with this, the `.get`, `.delete`, `.head` and `.options` functions do not support `content`, `files`, `data`, or `json` arguments.
169169

170170
If you really do need to send request data using these http methods you should use the generic `.request` function instead.
171171

172-
## Checking for 4xx/5xx responses
172+
```python
173+
httpx.request(
174+
method="DELETE",
175+
url="https://www.example.com/",
176+
content=b'A request body on a DELETE request.'
177+
)
178+
```
179+
180+
## Checking for success and failure responses
173181

174-
We don't support `response.is_ok` since the naming is ambiguous there, and might incorrectly imply an equivalence to `response.status_code == codes.OK`. Instead we provide the `response.is_error` property. Use `if not response.is_error:` instead of `if response.is_ok:`.
182+
We don't support `response.is_ok` since the naming is ambiguous there, and might incorrectly imply an equivalence to `response.status_code == codes.OK`. Instead we provide the `response.is_success` property, which can be used to check for a 2xx response.
175183

176184
## Request instantiation
177185

docs/img/httpx-help.png

356 KB
Loading

docs/img/httpx-request.png

201 KB
Loading

docs/index.md

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,19 @@ HTTPX is a fully featured HTTP client for Python 3, which provides sync and asyn
2525

2626

2727
!!! note
28-
HTTPX should currently be considered in beta.
28+
This is the documentation for the 1.0 pre-release.
2929

30-
We believe we've got the public API to a stable point now, but would strongly recommend pinning your dependencies to the `0.19.*` release, so that you're able to properly review [API changes between package updates](https://github.com/encode/httpx/blob/master/CHANGELOG.md).
31-
32-
A 1.0 release is expected to be issued sometime in 2021.
30+
This release adds support for an integrated command-line client, and also includes a couple of design changes from 0.19. Redirects are no longer followed by default, and the low-level Transport API has been updated. See [the CHANGELOG](https://github.com/encode/httpx/blob/version-1.0/CHANGELOG.md) for more details.
3331

3432
---
3533

36-
Let's get started...
34+
Installing the HTTPX 1.0 pre-release.
35+
36+
```shell
37+
$ pip install httpx --pre
38+
```
39+
40+
Now, let's get started...
3741

3842
```pycon
3943
>>> import httpx
@@ -48,23 +52,24 @@ Let's get started...
4852
'<!doctype html>\n<html>\n<head>\n<title>Example Domain</title>...'
4953
```
5054

51-
Or, using the async API...
52-
53-
_Use [IPython](https://ipython.readthedocs.io/en/stable/) or Python 3.8+ with `python -m asyncio` to try this code interactively._
55+
Or, using the command-line client.
5456

55-
```pycon
56-
>>> import httpx
57-
>>> async with httpx.AsyncClient() as client:
58-
... r = await client.get('https://www.example.org/')
59-
...
60-
>>> r
61-
<Response [200 OK]>
57+
```shell
58+
# The command line client is an optional dependency.
59+
$ pip install --pre 'httpx[cli]'
6260
```
6361

62+
Which now allows us to use HTTPX directly from the command-line...
63+
64+
![httpx --help](img/httpx-help.png)
65+
66+
Sending a request...
67+
68+
![httpx http://httpbin.org/json](img/httpx-request.png)
69+
6470
## Features
6571

66-
HTTPX is a high performance asynchronous HTTP client, that builds on the
67-
well-established usability of `requests`, and gives you:
72+
HTTPX builds on the well-established usability of `requests`, and gives you:
6873

6974
* A broadly [requests-compatible API](compatibility.md).
7075
* Standard synchronous interface, but with [async support if you need it](async.md).

docs/quickstart.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,7 @@ You can inspect what encoding will be used to decode the response.
7373
```
7474

7575
In some cases the response may not contain an explicit encoding, in which case HTTPX
76-
will attempt to automatically determine an encoding to use. This defaults to
77-
UTF-8, but also includes robust fallback behaviour for handling ascii,
78-
iso-8859-1 and windows 1252 encodings.
76+
will attempt to automatically determine an encoding to use.
7977

8078
```pycon
8179
>>> r.encoding
@@ -84,7 +82,6 @@ None
8482
'<!doctype html>\n<html>\n<head>\n<title>Example Domain</title>...'
8583
```
8684

87-
8885
If you need to override the standard behaviour and explicitly set the encoding to
8986
use, then you can do that too.
9087

@@ -277,7 +274,7 @@ HTTPX also includes an easy shortcut for accessing status codes by their text ph
277274
True
278275
```
279276

280-
We can raise an exception for any Client or Server error responses (4xx or 5xx status codes):
277+
We can raise an exception for any responses which are not a 2xx success code:
281278

282279
```pycon
283280
>>> not_found = httpx.get('https://httpbin.org/status/404')

httpx/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@
4343
from ._transports.wsgi import WSGITransport
4444
from ._types import AsyncByteStream, SyncByteStream
4545

46+
try:
47+
from ._main import main
48+
except ImportError: # pragma: nocover
49+
50+
def main() -> None: # type: ignore
51+
import sys
52+
53+
print(
54+
"The httpx command line client could not run because the required "
55+
"dependencies were not installed.\nMake sure you've installed "
56+
"everything with: pip install 'httpx[cli]'"
57+
)
58+
sys.exit(1)
59+
60+
4661
__all__ = [
4762
"__description__",
4863
"__title__",
@@ -76,6 +91,7 @@
7691
"InvalidURL",
7792
"Limits",
7893
"LocalProtocolError",
94+
"main",
7995
"MockTransport",
8096
"NetworkError",
8197
"options",

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.19.0"
3+
__version__ = "1.0.0.beta0"

0 commit comments

Comments
 (0)