Migrating from 1.x
Hishel 2.0 is a redesign with a smaller API. Policies, proxies and metadata are gone, and the storages have changed. This page covers what to change in your code, and what happens to the data you have already cached.
The Default Mode Changed
This is the change most likely to catch you out, because your code keeps running without it.
Hishel 1.x followed RFC 9111 by default. HTTPX and Requests now default to a simpler mode, which ignores HTTP caching headers and keeps every successful GET and HEAD response until the storage expires it. That includes no-store and private responses, and the default SQLite storage never expires them on its own.
To keep the behaviour of 1.x, turn on RFC mode:
from hishel.httpx import SyncCacheClient
client = SyncCacheClient()from hishel.httpx import CacheClient
client = CacheClient(rfc=True)rfc is accepted in the same way by AsyncCacheClient, by the cache transports and by CacheAdapter. ASGICacheMiddleware still follows RFC 9111 by default. See Caching Modes for how the two modes differ.
Renamed Classes
The sync classes have lost their Sync prefix, so every sync name is its async name without Async:
| 1.x | 2.0 |
|---|---|
SyncSqliteStorage | SqliteStorage |
SyncBaseStorage | BaseStorage |
hishel.httpx.SyncCacheClient | hishel.httpx.CacheClient |
hishel.httpx.SyncCacheTransport | hishel.httpx.CacheTransport |
The same goes for hishel.httpx2. RedisStorage keeps its name. The old names are removed, so importing them raises an ImportError.
Policies
The policy argument is replaced by rfc.
SpecificationPolicy
Pass its CacheOptions directly. The options themselves haven't changed.
from hishel import CacheOptions, SpecificationPolicy
from hishel.httpx import SyncCacheClient
policy = SpecificationPolicy(cache_options=CacheOptions(shared=False))
client = SyncCacheClient(policy=policy)from hishel import CacheOptions
from hishel.httpx import CacheClient
client = CacheClient(rfc=CacheOptions(shared=False))FilterPolicy
A FilterPolicy without filters is closest to the new default mode, but that mode caches less. FilterPolicy also stored other methods and statuses, such as POST responses and 404s. The default mode only stores successful GET and HEAD responses, other than 206.
from hishel import FilterPolicy
from hishel.httpx import SyncCacheClient
client = SyncCacheClient(policy=FilterPolicy())from hishel.httpx import CacheClient
client = CacheClient()There are no filters in 2.0. Choosing which requests are cached is left to the HTTP library you use. With HTTPX, for example, you can send them through different transports:
import httpx
from hishel.httpx import CacheTransport
class CacheReportsOnly(httpx.BaseTransport):
def __init__(self):
self.plain = httpx.HTTPTransport()
self.cached = CacheTransport(next_transport=self.plain)
def handle_request(self, request):
transport = self.cached if request.url.path.startswith("/reports/") else self.plain
return transport.handle_request(request)
def close(self):
self.cached.close()
client = httpx.Client(transport=CacheReportsOnly())Response filters have no direct replacement. In RFC mode, the server's headers decide what is stored.
Metadata
The hishel_* extensions and X-Hishel-* request headers are gone, other than the two expiry headers on Requests. Response metadata has moved, as described below.
Request Metadata
hishel_ttl: With HTTPX, useextensions={"caching": {"ttl": 3600}}. With Requests,X-Hishel-Ttlstill works. To set it for everything, usedefault_ttlon the storage.hishel_spec_ignore: Useextensions={"caching": {"rfc": False}}with HTTPX, orCacheAdapter(rfc=False)with Requests.hishel_refresh_ttl_on_access: Use a TTI, as an entry then expires when it hasn't been used for that long, so using it keeps it alive. With HTTPX, that isextensions={"caching": {"tti": 3600}}. With Requests, sendX-Hishel-Tti. To set it for everything, usedefault_ttion the storage.hishel_body_key: Removed. You can set your own cache key, with thekeyin thecachingextension or theX-Hishel-Keyheader, but responses toPOSTand other unsafe methods are still never served from the cache. So GraphQL and search requests sent withPOSTcan't be cached, and you should stay on 1.x if you rely on this.
Response Metadata
With HTTPX, hishel_from_cache, hishel_revalidated and hishel_stored are now in the caching extension of the response:
response.extensions["hishel_from_cache"]response.extensions["caching"]["from_cache"]With Requests, the X-Hishel-From-Cache, X-Hishel-Revalidated and X-Hishel-Stored response headers are unchanged, other than their values, which are now true or false.
See Hits and Misses for HTTPX and Requests.
hishel_created_at has no replacement.
Storages
The storages are SqliteStorage, AsyncSqliteStorage, RedisStorage and AsyncRedisStorage. SyncSqliteStorage is now SqliteStorage, as described in Renamed Classes. Some options have changed too:
refresh_ttl_on_accessis removed from the SQLite storages. It already had no effect in 1.x, so you can delete it.ttlon the Redis storages is nowdefault_ttl.- Everything after
clienton the Redis storages is now keyword-only, soRedisStorage(client, 3600)becomesRedisStorage(client, default_ttl=3600).
Some behaviour is new too:
- SQLite has a size limit:
max_sizedefaults to 1 GiB, and the least recently used entries are removed beyond that. Passmax_size=Nonefor no limit, as in 1.x. - SQLite can limit body size:
max_stream_sizeis now available, as it already was on Redis. - Every storage can expire idle entries:
default_ttiis new. See Expiring Entries. - Redis needs version 7 or newer.
Your Existing Cache
Nothing already cached carries over, so expect more requests to reach the server just after you upgrade. No manual steps are needed:
- SQLite: 2.0 uses a new default file,
hishel_cache_v2.db, and doesn't touchhishel_cache.db. So both versions can run side by side during a rolling deployment, and rolling back needs no cleanup. Once you're done with 1.x, you can delete the old file. - Redis: 1.x entries can no longer be found, because cache keys have changed. They stay in Redis until their own TTL runs out.
WARNING
This only covers the default path. If you pass a database_path or connection= that holds a 1.x cache, the storage raises a RuntimeError on first use and leaves it as it is, so give 2.0 a new one.
Integrations
The module paths are unchanged: hishel.httpx, hishel.httpx2, hishel.requests, hishel.asgi and hishel.fastapi.
- HTTPX and HTTPX2: use
rfcinstead ofpolicy, as above. - Requests:
CacheAdapteralso takesrfcinstead ofpolicy. The only per-request settings are the expiry and cache key headers. For the rest, mount another adapter on a more specific prefix. - ASGI:
ASGICacheMiddlewaretakesrfcinstead ofpolicy. It still follows RFC 9111 by default. - FastAPI: the
cache()dependency hasn't changed.
Custom Integrations and Storages
AsyncCacheProxy and SyncCacheProxy are removed. Drive the state machine yourself, as shown in Writing an Integration.
Request and Response no longer have metadata, and Request no longer has stream. The models RequestMetadata, ResponseMetadata and EntryMeta are removed, and Entry now only has id, request and response.
If you wrote your own storage, the interface is smaller:
create_entrynow takesttlandttiinstead ofid_.update_entryis replaced byupdate_headers(id, headers).refresh_entry_ttl,mark_pair_as_deleted,is_soft_deletedandis_safe_to_hard_deleteare removed from the base class. Expiry and cleanup are now private to each storage.
See Custom Storages for the full interface.