User Guide
Hishel
provides powerful tools for improving your transports.
It analyzes your responses and saves them so they can be reused in the future.
It is very simple to integrate with your existing application; simply change the Client
or Transport
class that you are using.
Clients and Transports
There are three ways to make the httpx library cacheable when working with it.
- Use the class provided by
Hishel
to completely replaceHTTPX's Client
. - Simply use your existing httpx client along with
Hishel's transports
. - Mock the httpx classes using the
hishel.install_cache
function.
It is always advised to use the second option because it is more reliable and adaptable.
Warning
Use the hishel.install_cache
function only for experiments, and do not rely on the functionality provided by hishel.install_cache
.
Using the Clients
Hishel
offers two classes for the first choice.
hishel.AsyncCacheClient
forhttpx.AsyncClient
hishel.CacheClient
forhttpx.Client
This implies that you can enable HTTP caching in your existing application by simply switching to the proper Client.
Examples:
>>> import hishel
>>>
>>> with hishel.CacheClient() as client:
>>> client.get("https://example.com/cachable-endpoint")
>>> response = client.get("https://example.com/cachable-endpoint") # from the cache!
Asynchronous Example:
>>> with hishel.AsyncCacheClient() as client:
>>> await client.get("https://example.com/cachable-endpoint")
>>> response = await client.get("https://example.com/cachable-endpoint") # from the cache!
Warning
The client classes that Hishel
offers hide the constructor signature in order to support all possible httpx versions. This means that all httpx client fields are fully valid for those clients, but because they are hidden, your IDE cannot suggest which arguments you can pass. In other words, these classes merely use *args and **kwargs and add a few arguments for cache configuration.
This example also functions as long as the cache clients are fully compatible with the standard clients.
Example:
client = hishel.CacheClient(
proxies={
"all://": "https://myproxy.com"
},
auth=("login", "password"),
follow_redirects=True,
http1=False,
http2=True
)
client.get("https://example.com")
Specifying the Client storage
Sometimes you may need to select storage rather than filesystem, and this is how you do it.
import hishel
storage = hishel.RedisStorage()
with hishel.CacheClient(storage=storage) as client:
client.get("https://example.com")
The responses are now saved in the redis database.
By default it will use...
- host: localhost
- port: 6379.
Of course, you can explicitly set each configuration.
Example:
import hishel
import redis
storage = hishel.RedisStorage(
client=redis.Redis(
host="192.168.0.85",
port=8081,
)
)
with hishel.CacheClient(storage=storage) as client:
client.get("https://example.com")
Note
Make sure Hishel
has the redis extension installed if you want to use the Redis database.
Using the Transports
It is always preferable to use transports that Hishel
offers for more dependable and predictable behavior.
We advise you to read the transports documentation if you have never used HTTPX's transports
before continuing.
We can divide the httpx library into two parts: the transports and the rest of the httpx library. Transports are the objects that are actually making the request.
For synchronous and asynchronous requests, Hishel
offers two different transports.
- CacheTransport
- AsyncCacheTransport
Hishel
always needs a transport to work on top of it, as long as he respects the custom or third-party transports that are offered.
Example:
import hishel
import httpx
with httpx.HTTPTransport() as transport:
with hishel.CacheTransport(transport=transport) as cache_transport:
request = httpx.Request("GET", "https://example.com/cachable-endpoint")
cache_transport.handle_request(request)
response = cache_transport.handle_request(request) # from the cache!
Using the Transports with the Clients
If you have a transport, you can provide it to clients who will use it for underlying requests.
import hishel
import httpx
cache_transport = hishel.CacheTransport(transport=httpx.HTTPTransport())
with httpx.Client(transport=cache_transport) as client:
client.get("https://example.com/cachable-endpoint")
response = client.get("https://example.com/cachable-endpoint") # from the cache
Specifying the Transport storage
In the same way that we can choose the storage for our clients, we can do the same for our transport.
import hishel
storage = hishel.RedisStorage()
with httpx.HTTPTransport() as transport:
with hishel.CacheTransport(transport=transport, storage=storage) as cache_transport:
request = httpx.Request("GET", "https://example.com/cachable-endpoint")
cache_transport.handle_request(request)
Combining with the existing Transports
Assume you already have a custom transport adapted to your business logic that you use for all requests; this is how you can add the caching layer on top of it.
import hishel
import httpx
from my_custom_transports import MyLovelyTransport
cache_transport = hishel.CacheTransport(transport=MyLovelyTransport())
with httpx.Client(transport=cache_transport) as client:
client.get("https://example.com/cachable-endpoint")
response = client.get("https://example.com/cachable-endpoint") # from the cache
Using the Connection Pool
Hishel
also provides caching support for the httpcore library, which handles all of the low-level network staff for httpx.
You may skip this section if you do not use HTTP Core.
Example:
import hishel
import httpcore
with httpcore.ConnectionPool() as pool:
with hishel.CacheConnectionPool(pool=pool) as cache_pool:
cache_pool.get("https://example.com/cachable-endpoint")
response = cache_pool.get("https://example.com/cachable-endpoint") # from the cache
Specifying the Connection Pool storage
In the same way that we can choose the storage for our clients and transports, we can do the same for our connection pools.
import hishel
import httpcore
storage = hishel.RedisStorage()
with httpcore.ConnectionPool() as pool:
with hishel.CacheConnectionPool(pool=pool, storage=storage) as cache_pool:
cache_pool.get("https://example.com/cachable-endpoint")
response = cache_pool.get("https://example.com/cachable-endpoint") # from the cache
Temporarily Disabling the Cache
Hishel
allows you to temporarily disable the cache for specific requests using the cache_disabled
extension.
Per RFC9111, the cache can effectively be disabled using the Cache-Control
headers no-store
(which requests that the response not be added to the cache),
and max-age=0
(which demands that any response in the cache must have 0 age - i.e. be a new request). Hishel
respects this behavior, which can be
used in two ways. First, you can specify the headers directly:
import hishel
import httpx
# With the clients
client = hishel.CacheClient()
client.get(
"https://example.com/cacheable-endpoint",
headers=[("Cache-Control", "no-store"), ("Cache-Control", "max-age=0")]
) # Ignores the cache
# With the transport
cache_transport = hishel.CacheTransport(transport=httpx.HTTPTransport())
client = httpx.Client(transport=cache_transport)
client.get(
"https://example.com/cacheable-endpoint",
headers=[("Cache-Control", "no-store"), ("Cache-Control", "max-age=0")]
) # Ignores the cache
Since this can be cumbersome, Hishel
also provides some "syntactic sugar" to accomplish the same result using HTTPX
extensions:
import hishel
import httpx
# With the clients
client = hishel.CacheClient()
client.get("https://example.com/cacheable-endpoint", extensions={"cache_disabled": True}) # Ignores the cache
# With the transport
cache_transport = hishel.CacheTransport(transport=httpx.HTTPTransport())
client = httpx.Client(transport=cache_transport)
client.get("https://example.com/cacheable-endpoint", extensions={"cache_disabled": True}) # Ignores the cache