Skip to content

Commit

Permalink
Add cache_disabled extension
Browse files Browse the repository at this point in the history
  • Loading branch information
karpetrosyan committed Nov 16, 2023
1 parent 893690f commit 6474d60
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 0 deletions.
46 changes: 46 additions & 0 deletions docs/userguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,49 @@ with httpcore.ConnectionPool() as 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:

```python
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:

```python
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

```
Both of these are entirely equivalent to specifying the headers directly.
4 changes: 4 additions & 0 deletions hishel/_async/_transports.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ async def handle_async_request(self, request: Request) -> Response:
:return: An HTTP response
:rtype: httpx.Response
"""

if "cache_disabled" in request.extensions and request.extensions["cache_disabled"]:
request.headers.update([("Cache-Control", "no-store"), ("Cache-Control", "max-age=0")])

httpcore_request = httpcore.Request(
method=request.method,
url=httpcore.URL(
Expand Down
4 changes: 4 additions & 0 deletions hishel/_sync/_transports.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ def handle_request(self, request: Request) -> Response:
:return: An HTTP response
:rtype: httpx.Response
"""

if "cache_disabled" in request.extensions and request.extensions["cache_disabled"]:
request.headers.update([("Cache-Control", "no-store"), ("Cache-Control", "max-age=0")])

httpcore_request = httpcore.Request(
method=request.method,
url=httpcore.URL(
Expand Down
35 changes: 35 additions & 0 deletions tests/_async/test_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pytest

import hishel
from hishel._utils import BaseClock


@pytest.mark.anyio
Expand Down Expand Up @@ -165,3 +166,37 @@ async def test_transport_with_only_if_cached_directive_with_stored_response(
)
)
assert response.status_code == 504


@pytest.mark.anyio
async def test_transport_with_cache_disabled_extension(use_temp_dir):
class MockedClock(BaseClock):
def now(self) -> int:
return 1440504001 # Mon, 25 Aug 2015 12:00:01 GMT

cachable_response = httpx.Response(
200,
headers=[
(b"Cache-Control", b"max-age=3600"),
(b"Date", b"Mon, 25 Aug 2015 12:00:00 GMT"), # 1 second before the clock
],
)

async with hishel.MockAsyncTransport() as transport:
transport.add_responses([cachable_response, httpx.Response(201)])
async with hishel.AsyncCacheTransport(
transport=transport, controller=hishel.Controller(clock=MockedClock())
) as cache_transport:
request = httpx.Request("GET", "https://www.example.com")
# This should create a cache entry
await cache_transport.handle_async_request(request)
# This should return from cache
response = await cache_transport.handle_async_request(request)
assert response.extensions["from_cache"]
# This should ignore the cache
caching_disabled_request = httpx.Request(
"GET", "https://www.example.com", extensions={"cache_disabled": True}
)
response = await cache_transport.handle_async_request(caching_disabled_request)
assert not response.extensions["from_cache"]
assert response.status_code == 201
35 changes: 35 additions & 0 deletions tests/_sync/test_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pytest

import hishel
from hishel._utils import BaseClock



Expand Down Expand Up @@ -165,3 +166,37 @@ def test_transport_with_only_if_cached_directive_with_stored_response(
)
)
assert response.status_code == 504



def test_transport_with_cache_disabled_extension(use_temp_dir):
class MockedClock(BaseClock):
def now(self) -> int:
return 1440504001 # Mon, 25 Aug 2015 12:00:01 GMT

cachable_response = httpx.Response(
200,
headers=[
(b"Cache-Control", b"max-age=3600"),
(b"Date", b"Mon, 25 Aug 2015 12:00:00 GMT"), # 1 second before the clock
],
)

with hishel.MockTransport() as transport:
transport.add_responses([cachable_response, httpx.Response(201)])
with hishel.CacheTransport(
transport=transport, controller=hishel.Controller(clock=MockedClock())
) as cache_transport:
request = httpx.Request("GET", "https://www.example.com")
# This should create a cache entry
cache_transport.handle_request(request)
# This should return from cache
response = cache_transport.handle_request(request)
assert response.extensions["from_cache"]
# This should ignore the cache
caching_disabled_request = httpx.Request(
"GET", "https://www.example.com", extensions={"cache_disabled": True}
)
response = cache_transport.handle_request(caching_disabled_request)
assert not response.extensions["from_cache"]
assert response.status_code == 201

0 comments on commit 6474d60

Please sign in to comment.