requests and httpx, Now That You Know · practice
httpx, the Same Ideas
httpx is a second httpx.Client is its session: it keeps with. Its calls look familiar too, client.get(url), client.post(url, auth=...), client.request("GET", url), and the 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:
| requests | httpx | |
|---|---|---|
| redirects | followed unless allow_redirects=False | not followed unless follow_redirects=True |
| timeout | none unless you pass one | 5 seconds for each phase |
raise_for_status() | raises for 4xx and 5xx | raises for every status outside 2xx, 3xx included |
auth= with text | encoded as Latin-1 | encoded as |
| its | subclasses of OSError | not 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
The two libraries even disagree on the 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
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:
who_am_i_httpx(base_url, user, password, *, client, timeout=HTTPX_TIMEOUT)does whatwho_am_idoes, on the httpx client it is given:POST base_url + "/login"withauth=(user, password),raise_for_status(),GET base_url + "/me", and return theuser, passingtimeout=timeoutto both calls. Plain text credentials are fine here, because httpx encodes them as UTF-8.trail_httpx(url, *, client=None, timeout=HTTPX_TIMEOUT)sends one GET withfollow_redirects=Trueandtimeout=timeout, and returnsredirect_trail(response). Useclient = httpx if client is None else client. Yourredirect_trailworks unchanged on an httpx response, because it hasstatus_code,url, andhistorytoo.
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 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 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:
who_am_i_httpx(base_url, user, password, *, client, timeout=HTTPX_TIMEOUT)sendsPOST base_url + "/login"withauth=(user, password)on the client it is given, callsraise_for_status()on that reply, sendsGET base_url + "/me", and returns theuserfrom its. Both calls pass timeout=timeout.trail_httpx(url, *, client=None, timeout=HTTPX_TIMEOUT)usesclient = httpx if client is None else client, sends one GET ofurlwithfollow_redirects=Trueandtimeout=timeout, and returnsredirect_trail(response). Do not callraise_for_status()here.
httpx’s follow_redirects; requests’ allow_redirects is a 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 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.