0%

requests and httpx, Now That You Know · practice

httpx, the Same Ideas

httpx is a second client library for Python, newer than requests and built on the same ideas. An httpx.Client is its session: it keeps in a jar, default headers, and a pool of open connections, and you close it with with. Its calls look familiar too, client.get(url), client.post(url, auth=...), client.request("GET", url), and the itself has httpx.get and httpx.post, just as requests has requests.get. So the line you wrote in Lesson 2 reads the same here:

client = httpx if client is None else client
response = client.get(url, timeout=timeout)

The differences are in the defaults, and each one is a decision you already made by hand in this course:

requestshttpx
redirectsfollowed unless allow_redirects=Falsenot followed unless follow_redirects=True
timeoutnone unless you pass one5 seconds for each phase
raise_for_status()raises for 4xx and 5xxraises for every status outside 2xx, 3xx included
auth= with textencoded as Latin-1encoded as
its subclasses of OSErrornot subclasses of OSError

Here is where you made each one. Your follow followed only because you called it; plain http.client never did. Your timeouts were always your own numbers, set on every connection. classify put a 3xx in its own class, redirection, apart from client and server errors. basic_header encoded UTF-8. And your Chapter 3 code caught TimeoutError, which is an OSError, as requests’ exceptions are. httpx simply decided differently on each one, so code moved from one library to the other has to be read row by row, not trusted to behave the same.

Follow only when asked

A default httpx.Client does not follow redirects. A 308 comes back as the response itself, with response.history empty and response.next_request set to the request httpx would send next. Pass follow_redirects=True and httpx follows, by the same rules as requests, including the browser rule for 302 from Lesson 2.

The two libraries even disagree on the ’s name. Passing requests’ allow_redirects= to httpx is an error, and the message points at the right word:

Client.get() got an unexpected keyword argument 'allow_redirects'. Did you mean 'follow_redirects'?

requests answers the opposite mistake, follow_redirects=, with the mirror image of that message.

client.get(url) with a default httpx.Client meets a 308. What do you get?

A default timeout is still a choice

httpx bounds every call: 5 seconds to connect, 5 for each read, 5 for each write, and 5 to wait for a free connection in the pool. That is safer than requests’ default of waiting forever, but it is still a number you did not choose. This course chooses its own, HTTPX_TIMEOUT at the top of side_by_side.py:

HTTPX_TIMEOUT = httpx.Timeout(2.0, connect=1.0)

That is 1 second to connect and 2 seconds for each of the other three phases. Why not requests’ pair, (1.0, 2.0)? httpx accepts a , but reads it as connect and read only, and leaves writing and waiting for the pool with no limit at all.

client.get(url, timeout=(1.0, 2.0)) in httpx. What is bounded?

Errors

httpx’s raise_for_status() is stricter than requests’. It raises httpx.HTTPStatusError for anything outside 200 to 299, so a 308 that was not followed raises too, with a message that starts Redirect response '308 Permanent Redirect'. requests lets a 3xx through.

Which call raises on a 308 response?

The exceptions themselves differ in their family. Every requests exception is a subclass of OSError, so except OSError: around a requests call catches its timeouts and connection failures. httpx’s are not: httpx.ReadTimeout is a TimeoutException, which is a TransportError, then a RequestError, then an httpx.HTTPError, then a plain Exception. Catch the narrowest class that fits what you want to handle:

try:
    response = client.get(url, timeout=HTTPX_TIMEOUT)
except httpx.TimeoutException:
    print("no answer in time")

except httpx.HTTPError: is wider: it catches a timeout, a refused connection, and the HTTPStatusError from raise_for_status() alike.

An httpx call sits inside try with except OSError:. The server never answers. What happens?

httpx also has an AsyncClient for programs written with async and await. This course does not use it.

Your task

In side_by_side.py:

  1. who_am_i_httpx(base_url, user, password, *, client, timeout=HTTPX_TIMEOUT) does what who_am_i does, on the httpx client it is given: POST base_url + "/login" with auth=(user, password), raise_for_status(), GET base_url + "/me", and return the user, passing timeout=timeout to both calls. Plain text credentials are fine here, because httpx encodes them as UTF-8.

  2. trail_httpx(url, *, client=None, timeout=HTTPX_TIMEOUT) sends one GET with follow_redirects=True and timeout=timeout, and returns redirect_trail(response). Use client = httpx if client is None else client. Your redirect_trail works unchanged on an httpx response, because it has status_code, url, and history too.

Then run:

python pl_http.py httpx

It first shows what a default client does with the old path, before your code runs:

--- pl_http's own httpx client.get() of /v0/books/2?x=1, without follow_redirects ---
status_code: 308
history: []
next_request: 'http://books.test/books/2?x=1'

Then your trail_httpx follows it, and its lines match Lesson 2’s requests trail exactly:

--- your trail_httpx() of /v0/books/2?x=1 ---
value: ['308 http://books.test/v0/books/2?x=1', '200 http://books.test/books/2?x=1']

The still say books.test because pl_http’s client hands each request to your server on this machine through a transport of its own, a part Lesson 4 replaces with a you write. The run ends with who_am_i_httpx as zoë, whose ✓ needs nothing extra this time.

Press Check my work. Lesson 4 tests both libraries without any server at all.

Task

Log in and follow a redirect with httpx.

In side_by_side.py:

  1. who_am_i_httpx(base_url, user, password, *, client, timeout=HTTPX_TIMEOUT) sends POST base_url + "/login" with auth=(user, password) on the client it is given, calls raise_for_status() on that reply, sends GET base_url + "/me", and returns the user from its . Both calls pass timeout=timeout.

  2. trail_httpx(url, *, client=None, timeout=HTTPX_TIMEOUT) uses client = httpx if client is None else client, sends one GET of url with follow_redirects=True and timeout=timeout, and returns redirect_trail(response). Do not call raise_for_status() here.

httpx’s is follow_redirects; requests’ allow_redirects is a in httpx. Use the client you are given: a call to httpx.get or httpx.post that ignores it fails the check.

In the , python pl_http.py httpx shows a default client stopping at an old path’s 308, then runs your trail_httpx on that path and your who_am_i_httpx as zoë.

Then press Check my work. It passes your an httpx client whose transport answers from a small server inside the check, in memory, and records the timeout attached to each request. With a user, a password, a base , and a timeout you have not seen, the login must succeed with both requests carrying your timeout, not httpx’s 5 seconds, and a wrong password must raise httpx.HTTPStatusError before /me is asked. trail_httpx must return both lines of a 308 and the book it leads to, with a client and without one. Nothing from your terminal is graded.