diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..d6b56c2b --- /dev/null +++ b/404.html @@ -0,0 +1,632 @@ + + + +
+ + + + + + + + + + + + + + +Hishel
provides the Controllers
, which allow you to fully customize how the cache works at the specification level.
You can choose which parts of RFC 9111 to ignore. For example, this is useful when you want to ensure that your client does not use stale responses even if they are acceptable from the server.
+You can specify which HTTP methods Hishel
should cache.
Example:
+import hishel
+
+controller = hishel.Controller(cacheable_methods=["GET", "POST"])
+client = hishel.CacheClient(controller=controller)
+
Note
+Hishel
will only cache GET
methods if the cachable methods are not explicitly specified.
If you only want to cache specific status codes, do so.
+Example:
+import hishel
+
+controller = hishel.Controller(cacheable_status_codes=[301, 308])
+client = hishel.CacheClient(controller=controller)
+
Note
+If the cachable status codes are not explicitly specified, Hishel
will only cache status codes 200, 301, and 308.
You can enable heuristics calculations, which are disabled by default.
+Example:
+import hishel
+
+controller = hishel.Controller(allow_heuristics=True)
+client = hishel.CacheClient(controller=controller)
+
Hishel
is very conservative about what status codes are permitted
+to be heuristically cacheable. When allow_heuristics
is enabled,
+Hishel
will only cache responses having status codes 200, 301, and 308. In contrast, RFC 9111 specifies that many more responses can be heuristically cacheable, specifically 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, and 501.
If you would prefer heuristic caching to the fullest extent permitted by
+RFC 9111, then pass HEURISTICALLY_CACHEABLE_STATUS_CODES
to cacheable_status_codes
:
import hishel
+
+controller = hishel.Controller(
+ allow_heuristics=True,
+ cacheable_status_codes=hishel.HEURISTICALLY_CACHEABLE_STATUS_CODES
+ )
+client = hishel.CacheClient(controller=controller)
+
Tip
+If you're not familiar with Heuristics Caching
, you can read about it in the specification.
Some servers allow the use of stale responses if they cannot be re-validated or the client is disconnected from the server. Clients MAY use stale responses in such cases, but this behavior is disabled by default in Hishel
.
Example:
+import hishel
+
+controller = hishel.Controller(allow_stale=True)
+client = hishel.CacheClient(controller=controller)
+
Tip
+Hishel
will attempt to use stale response only if the client is unable to connect to the server to make a request. You can enable stale responses to receive responses even if your internet connection is lost.
Responses are revalidated by default when they become stale; however, you can always revalidate the responses if you wish.
+Example:
+import hishel
+
+controller = hishel.Controller(always_revalidate=True)
+client = hishel.CacheClient(controller=controller)
+
Note
+Because we already have the response body in our cache, revalidation is very quick.
+By default, Hishel
generates cache keys as a hash of the request method and url.
+However, you can customize cache key creation by writing a function with the signature Callable[[httpcore.Request], str]
and passing it to the controller.
Example:
+import hishel
+import httpcore
+from hishel._utils import generate_key
+
+def custom_key_generator(request: httpcore.Request, body: bytes):
+ key = generate_key(request, body)
+ method = request.method.decode()
+ host = request.url.host.decode()
+ return f"{method}|{host}|{key}"
+
+controller = hishel.Controller(key_generator=custom_key_generator)
+client = hishel.CacheClient(controller=controller)
+
+client.get("https://hishel.com")
+
Instead of just the hashed_value
, the key now has the format method|host|hashed_value
.
Note
+Cache keys are used to store responses in storages, such as filesystem storage, which will use the cache key to create a file with that value. +You can write your own cache key implementation to have more meaningful file names and simplify cache monitoring.
+HTTPX
provides an extension mechanism to allow additional information
+to be added to requests and to be returned in responses. hishel
makes use
+of these extensions to expose some additional cache-related options and metadata.
+These extensions are available from either the hishel.CacheClient
/
+hishel.AsyncCacheClient
or a httpx.Client
/ httpx.AsyncCacheClient
+using a hishel
transport.
If this extension is set to true, Hishel
will cache the response even if response headers
+would otherwise prevent caching the response.
For example, if the response has a Cache-Control
header that contains a no-store
directive, it will not cache the response unless the force_cache
extension is set to true.
>>> import hishel
+>>> client = hishel.CacheClient()
+>>> response = client.get("https://www.example.com/uncachable-endpoint", extensions={"force_cache": True})
+
This extension temporarily disables the cache by passing appropriate RFC9111 headers to +ignore cached responses and to not store incoming responses. For example:
+>>> import hishel
+>>> client = hishel.CacheClient()
+>>> response = client.get("https://www.example.com/cacheable-endpoint", extensions={"cache_disabled": True})
+
Every response from will have a from_cache
extension value that will be True
when the response was retrieved
+from the cache, and False
when the response was received over the network.
>>> import hishel
+>>> client = hishel.CacheClient()
+>>> response = client.get("https://www.example.com")
+>>> response.extensions["from_cache"]
+False
+>>> response = client.get("https://www.example.com")
+>>> response.extensions["from_cache"]
+True
+
If from_cache
is True
, the response will also include a cache_metadata
extension with additional information about
+the response retrieved from the cache. If from_cache
is False
, then cache_metadata
will not
+be present in the response extensions.
Example:
+>>> import hishel
+>>> client = hishel.CacheClient()
+>>> response = client.get("https://www.example.com/cacheable-endpoint")
+>>> response.extensions
+{
+ ... # other extensions
+ "from_cache": False
+}
+>>> response = client.get("https://www.example.com/cacheable-endpoint")
+>>> response.extensions
+{
+ ... # other extensions
+ "from_cache": True
+ "cache_metadata" : {
+ "cache_key': '1a4c648c9a61adf939eef934a73e0cbe',
+ 'created_at': datetime.datetime(2020, 1, 1, 0, 0, 0),
+ 'number_of_uses': 1,
+ }
+}
+
You can use the request Cache-Control
directives defined in RFC 9111 to make the cache behavior more explicit in some situations.
If this directive is present in the request headers, the cache should either use the cached response or return the 504 status code.
+Note
+It is guaranteed that the client will not make any requests; instead, it will try to find a response from the cache that can be used for this request.
+>>> import hishel
+>>>
+>>> client = hishel.CacheClient()
+>>> response = client.get("https://example.com", headers=[("Cache-Control", "only-if-cached")])
+>>> response
+<Response [504 Gateway Timeout]>
+
or
+>>> import hishel
+>>>
+>>> client = hishel.CacheClient()
+>>> client.get("https://google.com") # will cache
+>>> response = client.get("https://google.com", headers=[("Cache-Control", "only-if-cached")])
+>>> response
+<Response [301 Moved Permanently]>
+
If this directive is present in the request headers, the cache should ignore responses that are older than the specified number.
+Example:
+import hishel
+
+client = hishel.CacheClient()
+client.get("https://example.com", headers=[("Cache-Control", "max-age=3600")])
+
If this directive is present in the request headers, the cache should ignore responses that have exceeded their freshness lifetime by more than the specified number of seconds.
+import hishel
+
+client = hishel.CacheClient()
+client.get("https://example.com", headers=[("Cache-Control", "max-stale=3600")])
+
If this directive is present in the request headers, the cache should ignore responses that will not be fresh for at least the number of seconds specified.
+import hishel
+
+client = hishel.CacheClient()
+client.get("https://example.com", headers=[("Cache-Control", "min-fresh=3600")])
+
If this directive is present in the request headers, the cache should not use the response to this request unless it has been validated.
+import hishel
+
+client = hishel.CacheClient()
+client.get("https://example.com", headers=[("Cache-Control", "no-cache")])
+
If this directive is present in the request headers, the cache should not save the response to this request.
+ + + + + + + + + + + + + + + + + +Serializers are a component of storages that simply serialize and de-serialize responses. +Hishel will use JSONSerializer by default, but you can explicitly specify a serializer or even write your own.
+Example of the serialized responses:
+{
+ "response": {
+ "status": 301,
+ "headers": [
+ [
+ "Content-Length",
+ "0"
+ ],
+ [
+ "Location",
+ "https://github.com/"
+ ]
+ ],
+ "content": "",
+ "extensions": {
+ "http_version": "HTTP/1.1",
+ "reason_phrase": "Moved Permanently"
+ }
+ },
+ "request": {
+ "method": "GET",
+ "url": "https://www.github.com/",
+ "headers": [
+ [
+ "Host",
+ "www.github.com"
+ ],
+ [
+ "Accept",
+ "*/*"
+ ],
+ [
+ "Accept-Encoding",
+ "gzip, deflate"
+ ],
+ [
+ "Connection",
+ "keep-alive"
+ ],
+ [
+ "User-Agent",
+ "python-httpx/0.24.1"
+ ]
+ ],
+ "extensions": {
+ "timeout": {
+ "connect": 5.0,
+ "read": 5.0,
+ "write": 5.0,
+ "pool": 5.0
+ }
+ }
+ },
+ "metadata": {
+ "cache_key": "71b46af84732856e5c16d503b655fcd0",
+ "number_of_uses": 0,
+ "created_at": "Mon, 21 Aug 2023 05:22:20 GMT"
+ }
+}
+
response:
+ status: 301
+ headers:
+ - - Content-Length
+ - '0'
+ - - Location
+ - https://github.com/
+ content: ''
+ extensions:
+ http_version: HTTP/1.1
+ reason_phrase: Moved Permanently
+request:
+ method: GET
+ url: https://www.github.com/
+ headers:
+ - - Host
+ - www.github.com
+ - - Accept
+ - '*/*'
+ - - Accept-Encoding
+ - gzip, deflate
+ - - Connection
+ - keep-alive
+ - - User-Agent
+ - python-httpx/0.24.1
+ extensions:
+ timeout:
+ connect: 5.0
+ read: 5.0
+ write: 5.0
+ pool: 5.0
+metadata:
+ cache_key: 71b46af84732856e5c16d503b655fcd0
+ number_of_uses: 0
+ created_at: Mon, 21 Aug 2023 05:22:20 GMT
+
Example: +
import hishel
+
+serializer = hishel.JSONSerializer()
+storage = hishel.FileStorage(serializer=serializer)
+
Because serializers are supported by all of the built-in hishel
storages, you can pass serializers to any of them.
Example: +
import hishel
+
+serializer = hishel.JSONSerializer()
+storage = hishel.RedisStorage(serializer=serializer)
+
Example: +
import hishel
+
+serializer = hishel.YAMLSerializer()
+storage = hishel.FileStorage(serializer=serializer)
+
Note
+Make sure Hishel
has the yaml extension installed if you want to use the YAMLSerializer
.
+
Example: +
+ + + + + + + + + + + + + + + + +When using Hishel
, you have complete control over the configuration of how the responses should be stored. You can select the serializer and storage on your own.
This section contains examples of how to use the storages.
+To explicitly specify the storage, we should create it first and pass it to the HTTP caching class.
+Example: +
+Or if you are using Transports:
+import hishel
+import httpx
+
+storage = hishel.FileStorage()
+transport = hishel.CacheTransport(transport=httpx.HTTPTransport(), storage=storage)
+
Here's how the filesystem storage looks:
+📁 root
+└─╴📁 .cache
+ └─╴📁 hishel
+ ├─╴📄 GET|github.com|a9022e44881123781045f6fadf37a8b1
+ ├─╴📄 GET|www.google.com|8bfc7fffcfd5f2b8e3485d0cc7450c98
+ ├─╴📄 GET|www.python-httpx.org|5f004f4f08bd774c4bc4b270a0ca542e
+ └─╴📄 GET|hishel.com|41ebb4dd16761e94e2ee36b71e0d916e
+
Note
+Note that by default, file names are just the hashed value, without the http method or hostname; to have meaningful names, see custom cache keys.
+If the responses are saved in the filesystem, there should be a directory that contains our responses.
+By default it's .cache/hishel
.
If you want to change the directory, do so as follows.
+ +You can explicitly specify the ttl for stored responses in this manner.
+ +If you do this, Hishel
will delete any stored responses whose ttl has expired.
+In this example, the stored responses were limited to 1 hour.
In order to avoid excessive memory utilization, Hishel
must periodically clean the old responses, or responses that are not being used and should be deleted from the cache.
+It clears the cache by default every minute, but you may change the interval directly with the check_ttl_every
argument.
Example:
+ +Hishel
has an in-memory cache that can be used when you don't need the cache to be persistent.
You should understand that in memory cache means that all cached responses are stored in RAM, so you should be cautious and possibly configure the cache's maximum size to avoid wasting RAM.
+Example:
+ +Or if you are using Transports:
+import hishel
+import httpx
+
+storage = hishel.InMemoryStorage()
+client = hishel.CacheTransport(transport=httpx.HTTPTransport(), storage=storage)
+
You can also specify the maximum number of requests that the storage can cache.
+Example:
+import hishel
+
+storage = hishel.InMemoryStorage(capacity=64)
+client = hishel.CacheClient(storage=storage)
+
Note
+When the number of responses exceeds the cache's capacity, Hishel employs the LFU algorithm to remove some of the responses.
+Hishel
includes built-in redis support, allowing you to store your responses in redis.
Example:
+ +Or if you are using Transports:
+import hishel
+import httpx
+
+storage = hishel.RedisStorage()
+client = hishel.CacheTransport(transport=httpx.HTTPTransport(), storage=storage)
+
If you need to connect somewhere other than localhost, this is how you can do it.
+import hishel
+import redis
+
+storage = hishel.RedisStorage(
+ client=redis.Redis(
+ host="192.168.0.85",
+ port=8081,
+ )
+)
+
You can explicitly specify the ttl for stored responses in this manner.
+ +If you do this, Hishel
will delete any stored responses whose ttl has expired.
+In this example, the stored responses were limited to 1 hour.
Hishel
includes built-in sqlite support, allowing you to store your responses in sqlite database.
Example:
+ +Or if you are using Transports:
+import hishel
+import httpx
+
+storage = hishel.SQLiteStorage()
+client = hishel.CacheTransport(transport=httpx.HTTPTransport())
+
Note
+Make sure Hishel
has the sqlite extension installed if you want to use the AsyncSQLiteStorage
.
+
If you want more control over the underlying sqlite connection, you can explicitly pass it.
+import hishel
+import sqlite3
+
+client = hishel.CacheClient(
+ storage=hishel.SQLiteStorage(connection=sqlite3.connect("my_db_path", timeout=5))
+)
+
You can explicitly specify the ttl for stored responses in this manner.
+ +If you do this, Hishel
will delete any stored responses whose ttl has expired.
+In this example, the stored responses were limited to 1 hour.
Hishel
has built-in AWS S3 support, allowing users to store responses in the cloud.
Example:
+import hishel
+
+storage = hishel.S3Storage(bucket_name="cached_responses")
+client = hishel.CacheClient(storage=storage)
+
Or if you are using Transports +
import httpx
+import hishel
+
+storage = hishel.S3Storage(bucket_name="cached_responses")
+transport = hishel.CacheTransport(httpx.HTTPTransport(), storage=storage)
+
If you want to manually configure the client instance, pass it to Hishel.
+import hishel
+import boto3
+
+s3_client = boto3.client('s3')
+
+storage = hishel.S3Storage(bucket_name="cached_responses", client=s3_client)
+client = hishel.CacheClient(storage=storage)
+
You can explicitly specify the ttl for stored responses in this manner.
+ +If you do this, Hishel
will delete any stored responses whose ttl has expired.
+In this example, the stored responses were limited to 1 hour.
In order to avoid excessive memory utilization, Hishel
must periodically clean the old responses, or responses that are not being used and should be deleted from the cache.
+It clears the cache by default every minute, but you may change the interval directly with the check_ttl_every
argument.
Example:
+ + + + + + + + + + + + + + + + + +{"use strict";/*!
+ * escape-html
+ * Copyright(c) 2012-2013 TJ Holowaychuk
+ * Copyright(c) 2015 Andreas Lubbe
+ * Copyright(c) 2015 Tiancheng "Timothy" Gu
+ * MIT Licensed
+ */var Wa=/["'&<>]/;Vn.exports=Ua;function Ua(e){var t=""+e,r=Wa.exec(t);if(!r)return t;var o,n="",i=0,s=0;for(i=r.index;i