Skip to content

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:

python
from hishel.httpx import SyncCacheClient

client = SyncCacheClient()
python
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.x2.0
SyncSqliteStorageSqliteStorage
SyncBaseStorageBaseStorage
hishel.httpx.SyncCacheClienthishel.httpx.CacheClient
hishel.httpx.SyncCacheTransporthishel.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.

python
from hishel import CacheOptions, SpecificationPolicy
from hishel.httpx import SyncCacheClient

policy = SpecificationPolicy(cache_options=CacheOptions(shared=False))
client = SyncCacheClient(policy=policy)
python
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.

python
from hishel import FilterPolicy
from hishel.httpx import SyncCacheClient

client = SyncCacheClient(policy=FilterPolicy())
python
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:

python
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, use extensions={"caching": {"ttl": 3600}}. With Requests, X-Hishel-Ttl still works. To set it for everything, use default_ttl on the storage.
  • hishel_spec_ignore: Use extensions={"caching": {"rfc": False}} with HTTPX, or CacheAdapter(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 is extensions={"caching": {"tti": 3600}}. With Requests, send X-Hishel-Tti. To set it for everything, use default_tti on the storage.
  • hishel_body_key: Removed. You can set your own cache key, with the key in the caching extension or the X-Hishel-Key header, but responses to POST and other unsafe methods are still never served from the cache. So GraphQL and search requests sent with POST can'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:

python
response.extensions["hishel_from_cache"]
python
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_access is removed from the SQLite storages. It already had no effect in 1.x, so you can delete it.
  • ttl on the Redis storages is now default_ttl.
  • Everything after client on the Redis storages is now keyword-only, so RedisStorage(client, 3600) becomes RedisStorage(client, default_ttl=3600).

Some behaviour is new too:

  • SQLite has a size limit: max_size defaults to 1 GiB, and the least recently used entries are removed beyond that. Pass max_size=None for no limit, as in 1.x.
  • SQLite can limit body size: max_stream_size is now available, as it already was on Redis.
  • Every storage can expire idle entries: default_tti is 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 touch hishel_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 rfc instead of policy, as above.
  • Requests: CacheAdapter also takes rfc instead of policy. The only per-request settings are the expiry and cache key headers. For the rest, mount another adapter on a more specific prefix.
  • ASGI: ASGICacheMiddleware takes rfc instead of policy. 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_entry now takes ttl and tti instead of id_.
  • update_entry is replaced by update_headers(id, headers).
  • refresh_entry_ttl, mark_pair_as_deleted, is_soft_deleted and is_safe_to_hard_delete are removed from the base class. Expiry and cleanup are now private to each storage.

See Custom Storages for the full interface.