Backends: httpx2 and httpx¶
httpx development continues under pydantic’s stewardship as
httpx2, which is API-compatible with
httpx. Since 0.8, httpx2 is httpx-pki’s required dependency and the session
classes subclass httpx2.Client / httpx2.AsyncClient.
The original httpx remains fully supported as a fallback. Nothing in
httpx-pki’s own API changes between the two: the same constructors, the same
keyword arguments, the same behavior. What changes is which library
PKIClient inherits from — which matters in exactly one place, covered under
The isinstance caveat below.
How the backend is chosen¶
Resolution happens once, at first import of httpx_pki, and applies to the
whole process. Every internal module binds to the same resolved backend.
If
HTTPX_PKI_BACKENDis set tohttpxorhttpx2, that backend is used.Otherwise httpx2 is used when it can be imported.
Otherwise httpx-pki falls back to httpx.
So the ordinary install resolves to httpx2, and an environment with httpx and no httpx2 resolves to httpx — with no configuration in either case. See Using httpx instead of httpx2 for how to build the latter.
Note
httpx-pki never touches sys.modules. Your own import httpx is never
redirected, whichever backend httpx-pki resolved.
Checking which backend you have¶
import httpx_pki
print(httpx_pki.HTTP_BACKEND) # 'httpx2' or 'httpx'
Useful in a startup log line or a test assertion when you care which one you are on.
Forcing the choice¶
Set HTTPX_PKI_BACKEND before the process starts:
$ HTTPX_PKI_BACKEND=httpx python -m myapp
The main reason to reach for this is httpx2 arriving in your environment as
some other package’s transitive dependency, while your own code still expects
PKIClient to subclass the original httpx.Client.
Because the variable is read at import time, setting it from inside your
program only works before the first import httpx_pki — set it in the
environment, not in main().
Two ways it fails loudly rather than silently:
# HTTPX_PKI_BACKEND=foo
ImportError: HTTPX_PKI_BACKEND='foo' is not a supported backend; set it to
"httpx" or "httpx2", or unset it to prefer httpx2 when installed
# HTTPX_PKI_BACKEND=httpx2, but httpx2 is not installed
ModuleNotFoundError: No module named 'httpx2'
Forcing a backend never falls back — if you asked for one, you get it or an error.
The isinstance caveat¶
This is the one behavior difference worth knowing about. With both packages
installed, httpx-pki resolves to httpx2, so a client is not an instance of
the original httpx.Client:
import httpx, httpx2
from httpx_pki import PKIClient
client = PKIClient("client.p12", password="secret")
isinstance(client, httpx.Client) # False
isinstance(client, httpx2.Client) # True
Runtime type checks, @singledispatch registrations, and Pydantic models
annotated against httpx.Client will all be affected. Duck-typed code is not —
the two classes have the same interface.
There are two fixes.
Force the backend, if you want to keep using the original httpx:
$ HTTPX_PKI_BACKEND=httpx python -m myapp
Or alias httpx to httpx2 application-wide, which makes import httpx
resolve to httpx2 everywhere and keeps such checks consistent:
import httpx2
httpx2.alias_httpx() # before anything imports httpx
import httpx
from httpx_pki import PKIClient
httpx is httpx2 # True
isinstance(client, httpx.Client) # True
Calling alias_httpx() is your application’s decision — a library should not
make it for you, so httpx-pki does not.
Type annotations¶
For type checkers, httpx-pki always annotates against httpx2: it is the required dependency, so it resolves in every environment that has httpx-pki installed. httpx is typed API-compatibly, so the annotations stay correct on the fallback too — mypy will not complain either way.