hishel 1.1.5__py3-none-any.whl → 1.1.7__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 +5 -5
- hishel/_core/_spec.py +38 -1
- hishel/_core/_storages/_async_sqlite.py +9 -9
- hishel/_core/_storages/_sync_sqlite.py +9 -9
- hishel/_sync_cache.py +5 -5
- {hishel-1.1.5.dist-info → hishel-1.1.7.dist-info}/METADATA +40 -1
- {hishel-1.1.5.dist-info → hishel-1.1.7.dist-info}/RECORD +9 -9
- {hishel-1.1.5.dist-info → hishel-1.1.7.dist-info}/WHEEL +0 -0
- {hishel-1.1.5.dist-info → hishel-1.1.7.dist-info}/licenses/LICENSE +0 -0
hishel/_async_cache.py
CHANGED
|
@@ -197,12 +197,12 @@ class AsyncCacheProxy:
|
|
|
197
197
|
return state.next(revalidation_response)
|
|
198
198
|
|
|
199
199
|
async def _handle_update(self, state: NeedToBeUpdated) -> AnyState:
|
|
200
|
-
for
|
|
200
|
+
for updating_entry in state.updating_entries:
|
|
201
201
|
await self.storage.update_entry(
|
|
202
|
-
|
|
203
|
-
lambda
|
|
204
|
-
|
|
205
|
-
response=replace(
|
|
202
|
+
updating_entry.id,
|
|
203
|
+
lambda existing_entry: replace(
|
|
204
|
+
existing_entry,
|
|
205
|
+
response=replace(existing_entry.response, headers=updating_entry.response.headers),
|
|
206
206
|
),
|
|
207
207
|
)
|
|
208
208
|
return state.next()
|
hishel/_core/_spec.py
CHANGED
|
@@ -1207,6 +1207,7 @@ class IdleClient(State):
|
|
|
1207
1207
|
- Unsafe methods (POST, PUT, DELETE, etc.) are written through to origin
|
|
1208
1208
|
- Multiple matching responses are sorted by Date header (most recent first)
|
|
1209
1209
|
- Age header is updated when serving from cache
|
|
1210
|
+
- Request no-cache directive forces revalidation of cached responses
|
|
1210
1211
|
|
|
1211
1212
|
Examples:
|
|
1212
1213
|
--------
|
|
@@ -1229,6 +1230,18 @@ class IdleClient(State):
|
|
|
1229
1230
|
>>> next_state = idle.next(get_request, [cached_pair])
|
|
1230
1231
|
>>> isinstance(next_state, NeedRevalidation)
|
|
1231
1232
|
True
|
|
1233
|
+
|
|
1234
|
+
>>> # Need revalidation - request no-cache forces validation of fresh response
|
|
1235
|
+
>>> idle = IdleClient(options=default_options)
|
|
1236
|
+
>>> no_cache_request = Request(
|
|
1237
|
+
... method="GET",
|
|
1238
|
+
... url="https://example.com",
|
|
1239
|
+
... headers=Headers({"cache-control": "no-cache"})
|
|
1240
|
+
... )
|
|
1241
|
+
>>> cached_pair = CompletePair(no_cache_request, fresh_response)
|
|
1242
|
+
>>> next_state = idle.next(no_cache_request, [cached_pair])
|
|
1243
|
+
>>> isinstance(next_state, NeedRevalidation)
|
|
1244
|
+
True
|
|
1232
1245
|
"""
|
|
1233
1246
|
|
|
1234
1247
|
# ============================================================================
|
|
@@ -1388,7 +1401,31 @@ class IdleClient(State):
|
|
|
1388
1401
|
ready_to_use, need_revalidation = partition(filtered_pairs, fresh_or_allowed_stale)
|
|
1389
1402
|
|
|
1390
1403
|
# ============================================================================
|
|
1391
|
-
# STEP 7:
|
|
1404
|
+
# STEP 7: Handle Request no-cache Directive
|
|
1405
|
+
# ============================================================================
|
|
1406
|
+
# RFC 9111 Section 5.2.1.4: no-cache Request Directive
|
|
1407
|
+
# https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.4
|
|
1408
|
+
#
|
|
1409
|
+
# "The no-cache request directive indicates that a cache MUST NOT use a
|
|
1410
|
+
# stored response to satisfy the request without successful validation on
|
|
1411
|
+
# the origin server."
|
|
1412
|
+
#
|
|
1413
|
+
# When a client sends Cache-Control: no-cache in the request, it's explicitly
|
|
1414
|
+
# requesting that the cache not use any stored response without first validating
|
|
1415
|
+
# it with the origin server. This is different from the response no-cache directive,
|
|
1416
|
+
# which applies to how responses should be cached.
|
|
1417
|
+
request_cache_control = parse_cache_control(request.headers.get("cache-control"))
|
|
1418
|
+
|
|
1419
|
+
if request_cache_control.no_cache is True:
|
|
1420
|
+
# Move all fresh responses to the revalidation queue
|
|
1421
|
+
# This ensures that even fresh cached responses will be validated
|
|
1422
|
+
# with the origin server via conditional requests (If-None-Match,
|
|
1423
|
+
# If-Modified-Since) before being served to the client.
|
|
1424
|
+
need_revalidation.extend(ready_to_use)
|
|
1425
|
+
ready_to_use = []
|
|
1426
|
+
|
|
1427
|
+
# ============================================================================
|
|
1428
|
+
# STEP 8: Determine Next State Based on Available Responses
|
|
1392
1429
|
# ============================================================================
|
|
1393
1430
|
|
|
1394
1431
|
if ready_to_use:
|
|
@@ -43,16 +43,12 @@ try:
|
|
|
43
43
|
self,
|
|
44
44
|
*,
|
|
45
45
|
connection: Optional[anysqlite.Connection] = None,
|
|
46
|
-
database_path: str = "hishel_cache.db",
|
|
46
|
+
database_path: Union[str, Path] = "hishel_cache.db",
|
|
47
47
|
default_ttl: Optional[float] = None,
|
|
48
48
|
refresh_ttl_on_access: bool = True,
|
|
49
49
|
) -> None:
|
|
50
|
-
db_path = Path(database_path)
|
|
51
|
-
|
|
52
50
|
self.connection = connection
|
|
53
|
-
self.database_path = (
|
|
54
|
-
ensure_cache_dict(db_path.parent if db_path.parent != Path(".") else None) / db_path.name
|
|
55
|
-
)
|
|
51
|
+
self.database_path: Path = database_path if isinstance(database_path, Path) else Path(database_path)
|
|
56
52
|
self.default_ttl = default_ttl
|
|
57
53
|
self.refresh_ttl_on_access = refresh_ttl_on_access
|
|
58
54
|
self.last_cleanup = time.time() - BATCH_CLEANUP_INTERVAL + BATCH_CLEANUP_START_DELAY
|
|
@@ -63,7 +59,10 @@ try:
|
|
|
63
59
|
async def _ensure_connection(self) -> anysqlite.Connection:
|
|
64
60
|
"""Ensure connection is established and database is initialized."""
|
|
65
61
|
if self.connection is None:
|
|
66
|
-
|
|
62
|
+
# Create cache directory and resolve full path on first connection
|
|
63
|
+
parent = self.database_path.parent if self.database_path.parent != Path(".") else None
|
|
64
|
+
full_path = ensure_cache_dict(parent) / self.database_path.name
|
|
65
|
+
self.connection = await anysqlite.connect(str(full_path))
|
|
67
66
|
if not self._initialized:
|
|
68
67
|
await self._initialize_database()
|
|
69
68
|
self._initialized = True
|
|
@@ -340,8 +339,8 @@ try:
|
|
|
340
339
|
await connection.commit()
|
|
341
340
|
|
|
342
341
|
async def _is_corrupted(self, pair: Entry, cursor: anysqlite.Cursor) -> bool:
|
|
343
|
-
# if entry was created more than 1 hour ago and still has no response
|
|
344
|
-
if pair.meta.created_at + 3600 < time.time() and not self._is_stream_complete(pair.id, cursor):
|
|
342
|
+
# if entry was created more than 1 hour ago and still has no full response data
|
|
343
|
+
if pair.meta.created_at + 3600 < time.time() and not (await self._is_stream_complete(pair.id, cursor)):
|
|
345
344
|
return True
|
|
346
345
|
return False
|
|
347
346
|
|
|
@@ -421,6 +420,7 @@ try:
|
|
|
421
420
|
break
|
|
422
421
|
yield chunk
|
|
423
422
|
chunk_number += 1
|
|
423
|
+
|
|
424
424
|
except ImportError:
|
|
425
425
|
|
|
426
426
|
class AsyncSqliteStorage: # type: ignore[no-redef]
|
|
@@ -43,16 +43,12 @@ try:
|
|
|
43
43
|
self,
|
|
44
44
|
*,
|
|
45
45
|
connection: Optional[sqlite3.Connection] = None,
|
|
46
|
-
database_path: str = "hishel_cache.db",
|
|
46
|
+
database_path: Union[str, Path] = "hishel_cache.db",
|
|
47
47
|
default_ttl: Optional[float] = None,
|
|
48
48
|
refresh_ttl_on_access: bool = True,
|
|
49
49
|
) -> None:
|
|
50
|
-
db_path = Path(database_path)
|
|
51
|
-
|
|
52
50
|
self.connection = connection
|
|
53
|
-
self.database_path = (
|
|
54
|
-
ensure_cache_dict(db_path.parent if db_path.parent != Path(".") else None) / db_path.name
|
|
55
|
-
)
|
|
51
|
+
self.database_path: Path = database_path if isinstance(database_path, Path) else Path(database_path)
|
|
56
52
|
self.default_ttl = default_ttl
|
|
57
53
|
self.refresh_ttl_on_access = refresh_ttl_on_access
|
|
58
54
|
self.last_cleanup = time.time() - BATCH_CLEANUP_INTERVAL + BATCH_CLEANUP_START_DELAY
|
|
@@ -63,7 +59,10 @@ try:
|
|
|
63
59
|
def _ensure_connection(self) -> sqlite3.Connection:
|
|
64
60
|
"""Ensure connection is established and database is initialized."""
|
|
65
61
|
if self.connection is None:
|
|
66
|
-
|
|
62
|
+
# Create cache directory and resolve full path on first connection
|
|
63
|
+
parent = self.database_path.parent if self.database_path.parent != Path(".") else None
|
|
64
|
+
full_path = ensure_cache_dict(parent) / self.database_path.name
|
|
65
|
+
self.connection = sqlite3.connect(str(full_path))
|
|
67
66
|
if not self._initialized:
|
|
68
67
|
self._initialize_database()
|
|
69
68
|
self._initialized = True
|
|
@@ -340,8 +339,8 @@ try:
|
|
|
340
339
|
connection.commit()
|
|
341
340
|
|
|
342
341
|
def _is_corrupted(self, pair: Entry, cursor: sqlite3.Cursor) -> bool:
|
|
343
|
-
# if entry was created more than 1 hour ago and still has no response
|
|
344
|
-
if pair.meta.created_at + 3600 < time.time() and not self._is_stream_complete(pair.id, cursor):
|
|
342
|
+
# if entry was created more than 1 hour ago and still has no full response data
|
|
343
|
+
if pair.meta.created_at + 3600 < time.time() and not (self._is_stream_complete(pair.id, cursor)):
|
|
345
344
|
return True
|
|
346
345
|
return False
|
|
347
346
|
|
|
@@ -421,6 +420,7 @@ try:
|
|
|
421
420
|
break
|
|
422
421
|
yield chunk
|
|
423
422
|
chunk_number += 1
|
|
423
|
+
|
|
424
424
|
except ImportError:
|
|
425
425
|
|
|
426
426
|
class SyncSqliteStorage: # type: ignore[no-redef]
|
hishel/_sync_cache.py
CHANGED
|
@@ -197,12 +197,12 @@ class SyncCacheProxy:
|
|
|
197
197
|
return state.next(revalidation_response)
|
|
198
198
|
|
|
199
199
|
def _handle_update(self, state: NeedToBeUpdated) -> AnyState:
|
|
200
|
-
for
|
|
200
|
+
for updating_entry in state.updating_entries:
|
|
201
201
|
self.storage.update_entry(
|
|
202
|
-
|
|
203
|
-
lambda
|
|
204
|
-
|
|
205
|
-
response=replace(
|
|
202
|
+
updating_entry.id,
|
|
203
|
+
lambda existing_entry: replace(
|
|
204
|
+
existing_entry,
|
|
205
|
+
response=replace(existing_entry.response, headers=updating_entry.response.headers),
|
|
206
206
|
),
|
|
207
207
|
)
|
|
208
208
|
return state.next()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hishel
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.7
|
|
4
4
|
Summary: Elegant HTTP Caching for Python
|
|
5
5
|
Project-URL: Homepage, https://hishel.com
|
|
6
6
|
Project-URL: Source, https://github.com/karpetrosyan/hishel
|
|
@@ -406,6 +406,45 @@ Hishel is inspired by and builds upon the excellent work in the Python HTTP ecos
|
|
|
406
406
|
<strong>Made with ❤️ by <a href="https://github.com/karpetrosyan">Kar Petrosyan</a></strong>
|
|
407
407
|
</p>
|
|
408
408
|
|
|
409
|
+
## What's Changed in 1.1.7
|
|
410
|
+
### ♻️ Refactoring
|
|
411
|
+
|
|
412
|
+
* refactor(storage): create sqlite database path only when creating connections by @jeefberkey in [#426](https://github.com/karpetrosyan/hishel/pull/426)
|
|
413
|
+
### ⚙️ Miscellaneous Tasks
|
|
414
|
+
|
|
415
|
+
* chore(deps-dev): bump the python-packages group with 5 updates by @dependabot[bot] in [#424](https://github.com/karpetrosyan/hishel/pull/424)
|
|
416
|
+
### 🐛 Bug Fixes
|
|
417
|
+
|
|
418
|
+
* fix(cache): Lambda parameter name clashes the loop variable being closed over by @dump247 in [#427](https://github.com/karpetrosyan/hishel/pull/427)
|
|
419
|
+
### 📚 Documentation
|
|
420
|
+
|
|
421
|
+
* add release process guidelines for maintainers by @karpetrosyan
|
|
422
|
+
### 🚀 Features
|
|
423
|
+
|
|
424
|
+
* Feature/accept pathlib path in SqliteStorage by @daudef in [#419](https://github.com/karpetrosyan/hishel/pull/419)
|
|
425
|
+
|
|
426
|
+
### Contributors
|
|
427
|
+
* @daudef
|
|
428
|
+
* @dependabot[bot]
|
|
429
|
+
* @jeefberkey
|
|
430
|
+
* @dump247
|
|
431
|
+
* @karpetrosyan
|
|
432
|
+
|
|
433
|
+
**Full Changelog**: https://github.com/karpetrosyan/hishel/compare/1.1.6...1.1.7
|
|
434
|
+
|
|
435
|
+
## What's Changed in 1.1.6
|
|
436
|
+
### 📚 Documentation
|
|
437
|
+
|
|
438
|
+
* remove some stale httpx configs by @karpetrosyan
|
|
439
|
+
### 🚀 Features
|
|
440
|
+
|
|
441
|
+
* Add support for request no-cache directive by @karpetrosyan in [#416](https://github.com/karpetrosyan/hishel/pull/416)
|
|
442
|
+
|
|
443
|
+
### Contributors
|
|
444
|
+
* @karpetrosyan
|
|
445
|
+
|
|
446
|
+
**Full Changelog**: https://github.com/karpetrosyan/hishel/compare/1.1.5...1.1.6
|
|
447
|
+
|
|
409
448
|
## What's Changed in 1.1.5
|
|
410
449
|
### 🐛 Bug Fixes
|
|
411
450
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
hishel/__init__.py,sha256=1EdAEXWx41gmxUzG1Fchd_B4gQDtqlxlqQw0WkCBaUE,1826
|
|
2
|
-
hishel/_async_cache.py,sha256=
|
|
2
|
+
hishel/_async_cache.py,sha256=QprSuucR6OXYWNVS9lzM1jHjUEC33AGq8zpUT27Cngs,8839
|
|
3
3
|
hishel/_async_httpx.py,sha256=89i92f2SlvgWrav_TDNU1iUzMxdR607apauxXA3pE3U,8127
|
|
4
4
|
hishel/_policies.py,sha256=1ae_rmDF7oaG91-lQyOGVaTrRX8uI2GImmu5gN6WJa4,1135
|
|
5
|
-
hishel/_sync_cache.py,sha256=
|
|
5
|
+
hishel/_sync_cache.py,sha256=afM1MfBlT3kitBJX-YmDEJ4i6Kc1d0A4PcrvCL6LlPI,8564
|
|
6
6
|
hishel/_sync_httpx.py,sha256=z1pwVUQfRf72Q48PXXZ4FKwXGevll0X5iHcVRANiP38,7952
|
|
7
7
|
hishel/_utils.py,sha256=kR7RnhFqLzFRmB-YNnZteQVP0iDPUouCscA0_FHHFls,3837
|
|
8
8
|
hishel/asgi.py,sha256=ocXzqrrYGazeJxlKFcz1waoKvKGOqJ7YBEAmly4Towk,14998
|
|
@@ -11,14 +11,14 @@ hishel/httpx.py,sha256=99a8X9COPiPHSgGW61O2uMWMZB7dY93Ty9DTCJ9C18Q,467
|
|
|
11
11
|
hishel/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
hishel/requests.py,sha256=F3BntFGeesm19NmWFgGoBg-rfkcIs9OTpXDass73t6w,6819
|
|
13
13
|
hishel/_core/_headers.py,sha256=hGaT6o1F-gs1pm5RpdGb0IMQL3uJYDH1xpwJLy28Cys,17514
|
|
14
|
-
hishel/_core/_spec.py,sha256=
|
|
14
|
+
hishel/_core/_spec.py,sha256=26mrK0MFSN_03ZecKem0asHYCXqzJ0tmcVmJXG7VHeI,105016
|
|
15
15
|
hishel/_core/models.py,sha256=EabP2qnjYVzhPWhQer3QFmdDE6TDbqEBEqPHzv25VnA,7978
|
|
16
16
|
hishel/_core/_storages/_async_base.py,sha256=iZ6Mb30P0ho5h4UU5bgOrcsSMZ1427j9tht-tupZs68,2106
|
|
17
|
-
hishel/_core/_storages/_async_sqlite.py,sha256=
|
|
17
|
+
hishel/_core/_storages/_async_sqlite.py,sha256=3h1VEYGaiWdr-q3XT30MYPLBEN2R7g96BfUG5-hsRqM,16102
|
|
18
18
|
hishel/_core/_storages/_packing.py,sha256=mC8LMFQ5uPfFOgingKm2WKFO_DwcZ1OjTgI6xc0hfJI,3708
|
|
19
19
|
hishel/_core/_storages/_sync_base.py,sha256=qfOvcFY5qvrzSh4ztV2Trlxft-BF7An5SFsLlEb8EeE,2075
|
|
20
|
-
hishel/_core/_storages/_sync_sqlite.py,sha256=
|
|
21
|
-
hishel-1.1.
|
|
22
|
-
hishel-1.1.
|
|
23
|
-
hishel-1.1.
|
|
24
|
-
hishel-1.1.
|
|
20
|
+
hishel/_core/_storages/_sync_sqlite.py,sha256=t8OAVhydr56SDFditvhjpUwt_Nco4dAKEv7-bRPMPgM,15571
|
|
21
|
+
hishel-1.1.7.dist-info/METADATA,sha256=1crLOlKwSXnos2wCQ0fb0BWU42_hBR_unEcAGHLAX3c,21752
|
|
22
|
+
hishel-1.1.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
23
|
+
hishel-1.1.7.dist-info/licenses/LICENSE,sha256=1qQj7pE0V2O9OIedvyOgLGLvZLaPd3nFEup3IBEOZjQ,1493
|
|
24
|
+
hishel-1.1.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|