hishel 1.0.0.dev0__py3-none-any.whl → 1.0.0.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hishel/_async_cache.py +9 -2
- hishel/_core/_async/_storages/_sqlite.py +402 -356
- hishel/_core/_spec.py +120 -67
- hishel/_core/_sync/_storages/_sqlite.py +402 -356
- hishel/_core/models.py +7 -4
- hishel/_sync_cache.py +9 -2
- hishel/_utils.py +1 -377
- hishel/httpx.py +9 -2
- {hishel-1.0.0.dev0.dist-info → hishel-1.0.0.dev2.dist-info}/METADATA +48 -53
- hishel-1.0.0.dev2.dist-info/RECORD +19 -0
- hishel-1.0.0.dev0.dist-info/RECORD +0 -19
- {hishel-1.0.0.dev0.dist-info → hishel-1.0.0.dev2.dist-info}/WHEEL +0 -0
- {hishel-1.0.0.dev0.dist-info → hishel-1.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
hishel/_core/_spec.py
CHANGED
|
@@ -16,6 +16,7 @@ from typing import (
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
from hishel._core._headers import Headers, Range, Vary, parse_cache_control
|
|
19
|
+
from hishel._core.models import ResponseMetadata
|
|
19
20
|
from hishel._utils import parse_date, partition
|
|
20
21
|
|
|
21
22
|
if TYPE_CHECKING:
|
|
@@ -1412,16 +1413,11 @@ class IdleClient(State):
|
|
|
1412
1413
|
#
|
|
1413
1414
|
# The Age header informs the client how old the cached response is.
|
|
1414
1415
|
|
|
1415
|
-
# Mark all ready-to-use responses with metadata (for observability)
|
|
1416
|
-
for pair in ready_to_use:
|
|
1417
|
-
pair.response.metadata["hishel_from_cache"] = True # type: ignore
|
|
1418
|
-
|
|
1419
1416
|
# Use the most recent response (first in sorted list)
|
|
1420
1417
|
selected_pair = ready_to_use[0]
|
|
1421
1418
|
|
|
1422
1419
|
# Calculate current age and update the Age header
|
|
1423
1420
|
current_age = get_age(selected_pair.response)
|
|
1424
|
-
|
|
1425
1421
|
return FromCache(
|
|
1426
1422
|
pair=replace(
|
|
1427
1423
|
selected_pair,
|
|
@@ -1578,20 +1574,6 @@ class CacheMiss(State):
|
|
|
1578
1574
|
* an s-maxage response directive (if cache is shared)
|
|
1579
1575
|
* a status code that is defined as heuristically cacheable"
|
|
1580
1576
|
|
|
1581
|
-
Side Effects:
|
|
1582
|
-
------------
|
|
1583
|
-
Sets metadata flags on the response object:
|
|
1584
|
-
- hishel_spec_ignored: False (caching spec is being followed)
|
|
1585
|
-
- hishel_from_cache: False (response is from origin, not cache)
|
|
1586
|
-
- hishel_revalidated: True (if after_revalidation is True)
|
|
1587
|
-
- hishel_stored: True/False (whether response was stored)
|
|
1588
|
-
|
|
1589
|
-
Logging:
|
|
1590
|
-
-------
|
|
1591
|
-
When a response cannot be stored, detailed debug logs are emitted explaining
|
|
1592
|
-
which specific RFC requirement failed, with direct links to the relevant
|
|
1593
|
-
RFC sections.
|
|
1594
|
-
|
|
1595
1577
|
Examples:
|
|
1596
1578
|
--------
|
|
1597
1579
|
>>> # Cacheable response
|
|
@@ -1614,21 +1596,6 @@ class CacheMiss(State):
|
|
|
1614
1596
|
True
|
|
1615
1597
|
"""
|
|
1616
1598
|
|
|
1617
|
-
# ============================================================================
|
|
1618
|
-
# STEP 1: Set Response Metadata
|
|
1619
|
-
# ============================================================================
|
|
1620
|
-
# Initialize metadata flags to track the response lifecycle
|
|
1621
|
-
|
|
1622
|
-
response.metadata["hishel_spec_ignored"] = False # type: ignore
|
|
1623
|
-
# We are following the caching specification
|
|
1624
|
-
|
|
1625
|
-
response.metadata["hishel_from_cache"] = False # type: ignore
|
|
1626
|
-
# This response came from origin server, not cache
|
|
1627
|
-
|
|
1628
|
-
if self.after_revalidation:
|
|
1629
|
-
response.metadata["hishel_revalidated"] = True # type: ignore
|
|
1630
|
-
# Mark that this response is the result of a revalidation
|
|
1631
|
-
|
|
1632
1599
|
# ============================================================================
|
|
1633
1600
|
# STEP 2: Parse Cache-Control Directive
|
|
1634
1601
|
# ============================================================================
|
|
@@ -1723,11 +1690,14 @@ class CacheMiss(State):
|
|
|
1723
1690
|
#
|
|
1724
1691
|
# Requests with Authorization headers often contain user-specific data.
|
|
1725
1692
|
# Shared caches must be careful not to serve one user's data to another.
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1693
|
+
has_explicit_directive = (
|
|
1694
|
+
response_cache_control.public
|
|
1695
|
+
or response_cache_control.s_maxage is not None
|
|
1696
|
+
or response_cache_control.must_revalidate
|
|
1697
|
+
)
|
|
1698
|
+
can_cache_auth_request = (
|
|
1699
|
+
not self.options.shared or "authorization" not in request.headers or has_explicit_directive
|
|
1700
|
+
)
|
|
1731
1701
|
|
|
1732
1702
|
# CONDITION 7: Response Contains Required Caching Information
|
|
1733
1703
|
# RFC 9111 Section 3, paragraph 2.7:
|
|
@@ -1797,7 +1767,7 @@ class CacheMiss(State):
|
|
|
1797
1767
|
or not understands_how_to_cache
|
|
1798
1768
|
or not no_store_is_not_present
|
|
1799
1769
|
or not private_directive_allows_storing
|
|
1800
|
-
or not
|
|
1770
|
+
or not can_cache_auth_request
|
|
1801
1771
|
or not contains_required_component
|
|
1802
1772
|
):
|
|
1803
1773
|
# --------------------------------------------------------------------
|
|
@@ -1833,10 +1803,11 @@ class CacheMiss(State):
|
|
|
1833
1803
|
"Cannot store the response because the `private` response directive does not "
|
|
1834
1804
|
"allow shared caches to store it. See: https://www.rfc-editor.org/rfc/rfc9111.html#section-3-2.5.1"
|
|
1835
1805
|
)
|
|
1836
|
-
elif not
|
|
1806
|
+
elif not can_cache_auth_request:
|
|
1837
1807
|
logger.debug(
|
|
1838
|
-
"Cannot store the response because the
|
|
1839
|
-
"
|
|
1808
|
+
"Cannot store the response because the request contained an Authorization header "
|
|
1809
|
+
"and there was no explicit directive allowing shared caching. "
|
|
1810
|
+
"See: https://www.rfc-editor.org/rfc/rfc9111.html#section-3-5"
|
|
1840
1811
|
)
|
|
1841
1812
|
elif not contains_required_component:
|
|
1842
1813
|
logger.debug(
|
|
@@ -1844,10 +1815,9 @@ class CacheMiss(State):
|
|
|
1844
1815
|
"See: https://www.rfc-editor.org/rfc/rfc9111.html#section-3-2.7.1"
|
|
1845
1816
|
)
|
|
1846
1817
|
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
return CouldNotBeStored(response=response, pair_id=pair_id, options=self.options)
|
|
1818
|
+
return CouldNotBeStored(
|
|
1819
|
+
response=response, pair_id=pair_id, options=self.options, after_revalidation=self.after_revalidation
|
|
1820
|
+
)
|
|
1851
1821
|
|
|
1852
1822
|
# --------------------------------------------------------------------
|
|
1853
1823
|
# Transition to: StoreAndUse
|
|
@@ -1856,9 +1826,6 @@ class CacheMiss(State):
|
|
|
1856
1826
|
|
|
1857
1827
|
logger.debug("Storing response in cache")
|
|
1858
1828
|
|
|
1859
|
-
# Mark response as stored
|
|
1860
|
-
response.metadata["hishel_stored"] = True # type: ignore
|
|
1861
|
-
|
|
1862
1829
|
# Remove headers that should not be stored
|
|
1863
1830
|
# RFC 9111 Section 3.1: Storing Header and Trailer Fields
|
|
1864
1831
|
# https://www.rfc-editor.org/rfc/rfc9111.html#section-3.1
|
|
@@ -1869,6 +1836,7 @@ class CacheMiss(State):
|
|
|
1869
1836
|
pair_id=pair_id,
|
|
1870
1837
|
response=cleaned_response,
|
|
1871
1838
|
options=self.options,
|
|
1839
|
+
after_revalidation=self.after_revalidation,
|
|
1872
1840
|
)
|
|
1873
1841
|
|
|
1874
1842
|
|
|
@@ -1929,7 +1897,9 @@ class NeedRevalidation(State):
|
|
|
1929
1897
|
The stored pairs that the request was sent for revalidation.
|
|
1930
1898
|
"""
|
|
1931
1899
|
|
|
1932
|
-
def next(
|
|
1900
|
+
def next(
|
|
1901
|
+
self, revalidation_response: Response
|
|
1902
|
+
) -> Union["NeedToBeUpdated", "InvalidatePairs", "CacheMiss", "FromCache"]:
|
|
1933
1903
|
"""
|
|
1934
1904
|
Handles the response to a conditional request and determines the next state.
|
|
1935
1905
|
|
|
@@ -2084,6 +2054,15 @@ class NeedRevalidation(State):
|
|
|
2084
2054
|
after_revalidation=True,
|
|
2085
2055
|
).next(revalidation_response, pair_id=self.revalidating_pairs[-1].id),
|
|
2086
2056
|
)
|
|
2057
|
+
elif revalidation_response.status_code // 100 == 3:
|
|
2058
|
+
# 3xx Redirects should have been followed by the HTTP client
|
|
2059
|
+
return FromCache(
|
|
2060
|
+
pair=replace(
|
|
2061
|
+
self.revalidating_pairs[-1],
|
|
2062
|
+
response=revalidation_response,
|
|
2063
|
+
),
|
|
2064
|
+
options=self.options,
|
|
2065
|
+
)
|
|
2087
2066
|
|
|
2088
2067
|
# ============================================================================
|
|
2089
2068
|
# STEP 4: Handle Unexpected Status Codes
|
|
@@ -2316,32 +2295,99 @@ class NeedRevalidation(State):
|
|
|
2316
2295
|
return next_state
|
|
2317
2296
|
|
|
2318
2297
|
|
|
2319
|
-
@dataclass
|
|
2298
|
+
# @dataclass
|
|
2299
|
+
# class StoreAndUse(State):
|
|
2300
|
+
# """
|
|
2301
|
+
# The state that indicates that the response can be stored in the cache and used.
|
|
2302
|
+
# """
|
|
2303
|
+
|
|
2304
|
+
# pair_id: uuid.UUID
|
|
2305
|
+
|
|
2306
|
+
# response: Response
|
|
2307
|
+
|
|
2308
|
+
# def next(self) -> None:
|
|
2309
|
+
# return None # pragma: nocover
|
|
2310
|
+
|
|
2311
|
+
|
|
2320
2312
|
class StoreAndUse(State):
|
|
2321
2313
|
"""
|
|
2322
2314
|
The state that indicates that the response can be stored in the cache and used.
|
|
2323
|
-
"""
|
|
2324
2315
|
|
|
2325
|
-
|
|
2316
|
+
Attributes:
|
|
2317
|
+
----------
|
|
2318
|
+
pair_id : uuid.UUID
|
|
2319
|
+
The unique identifier for the cache pair.
|
|
2320
|
+
response : Response
|
|
2321
|
+
The HTTP response to be stored in the cache.
|
|
2322
|
+
after_revalidation : bool
|
|
2323
|
+
Indicates if the storage is occurring after a revalidation process.
|
|
2324
|
+
"""
|
|
2326
2325
|
|
|
2327
|
-
|
|
2326
|
+
def __init__(
|
|
2327
|
+
self, pair_id: uuid.UUID, response: Response, options: CacheOptions, after_revalidation: bool = False
|
|
2328
|
+
) -> None:
|
|
2329
|
+
super().__init__(options)
|
|
2330
|
+
self.pair_id = pair_id
|
|
2331
|
+
self.response = response
|
|
2332
|
+
self.after_revalidation = after_revalidation
|
|
2333
|
+
response_meta = ResponseMetadata(
|
|
2334
|
+
hishel_created_at=time.time(),
|
|
2335
|
+
hishel_from_cache=False,
|
|
2336
|
+
hishel_spec_ignored=False,
|
|
2337
|
+
hishel_revalidated=after_revalidation,
|
|
2338
|
+
hishel_stored=True,
|
|
2339
|
+
)
|
|
2340
|
+
self.response.metadata.update(response_meta) # type: ignore
|
|
2328
2341
|
|
|
2329
2342
|
def next(self) -> None:
|
|
2330
|
-
return None
|
|
2343
|
+
return None
|
|
2344
|
+
|
|
2345
|
+
|
|
2346
|
+
# @dataclass
|
|
2347
|
+
# class CouldNotBeStored(State):
|
|
2348
|
+
# """
|
|
2349
|
+
# The state that indicates that the response could not be stored in the cache.
|
|
2350
|
+
# """
|
|
2351
|
+
|
|
2352
|
+
# response: Response
|
|
2353
|
+
|
|
2354
|
+
# pair_id: uuid.UUID
|
|
2355
|
+
|
|
2356
|
+
# def next(self) -> None:
|
|
2357
|
+
# return None # pragma: nocover
|
|
2331
2358
|
|
|
2332
2359
|
|
|
2333
|
-
@dataclass
|
|
2334
2360
|
class CouldNotBeStored(State):
|
|
2335
2361
|
"""
|
|
2336
2362
|
The state that indicates that the response could not be stored in the cache.
|
|
2337
|
-
"""
|
|
2338
2363
|
|
|
2339
|
-
|
|
2364
|
+
Attributes:
|
|
2365
|
+
----------
|
|
2366
|
+
response : Response
|
|
2367
|
+
The HTTP response that could not be stored.
|
|
2368
|
+
pair_id : uuid.UUID
|
|
2369
|
+
The unique identifier for the cache pair.
|
|
2370
|
+
after_revalidation : bool
|
|
2371
|
+
Indicates if the storage attempt occurred after a revalidation process.
|
|
2372
|
+
"""
|
|
2340
2373
|
|
|
2341
|
-
|
|
2374
|
+
def __init__(
|
|
2375
|
+
self, response: Response, pair_id: uuid.UUID, options: CacheOptions, after_revalidation: bool = False
|
|
2376
|
+
) -> None:
|
|
2377
|
+
super().__init__(options)
|
|
2378
|
+
self.response = response
|
|
2379
|
+
self.pair_id = pair_id
|
|
2380
|
+
response_meta = ResponseMetadata(
|
|
2381
|
+
hishel_created_at=time.time(),
|
|
2382
|
+
hishel_from_cache=False,
|
|
2383
|
+
hishel_spec_ignored=False,
|
|
2384
|
+
hishel_revalidated=after_revalidation,
|
|
2385
|
+
hishel_stored=False,
|
|
2386
|
+
)
|
|
2387
|
+
self.response.metadata.update(response_meta) # type: ignore
|
|
2342
2388
|
|
|
2343
2389
|
def next(self) -> None:
|
|
2344
|
-
return None
|
|
2390
|
+
return None
|
|
2345
2391
|
|
|
2346
2392
|
|
|
2347
2393
|
@dataclass
|
|
@@ -2358,15 +2404,22 @@ class InvalidatePairs(State):
|
|
|
2358
2404
|
return self.next_state
|
|
2359
2405
|
|
|
2360
2406
|
|
|
2361
|
-
@dataclass
|
|
2362
2407
|
class FromCache(State):
|
|
2363
|
-
pair: CompletePair
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2408
|
+
def __init__(self, pair: CompletePair, options: CacheOptions, after_revalidation: bool = False) -> None:
|
|
2409
|
+
super().__init__(options)
|
|
2410
|
+
self.pair = pair
|
|
2411
|
+
self.after_revalidation = after_revalidation
|
|
2412
|
+
response_meta = ResponseMetadata(
|
|
2413
|
+
hishel_created_at=pair.meta.created_at,
|
|
2414
|
+
hishel_from_cache=True,
|
|
2415
|
+
hishel_spec_ignored=False,
|
|
2416
|
+
hishel_revalidated=after_revalidation,
|
|
2417
|
+
hishel_stored=False,
|
|
2418
|
+
)
|
|
2419
|
+
self.pair.response.metadata.update(response_meta) # type: ignore
|
|
2367
2420
|
|
|
2368
2421
|
def next(self) -> None:
|
|
2369
|
-
return None
|
|
2422
|
+
return None
|
|
2370
2423
|
|
|
2371
2424
|
|
|
2372
2425
|
@dataclass
|