hishel 1.1.4__py3-none-any.whl → 1.1.6__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/_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: Determine Next State Based on Available Responses
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:
@@ -161,8 +161,19 @@ try:
161
161
  for row in await cursor.fetchall():
162
162
  pair_data = unpack(row[1], kind="pair")
163
163
 
164
+ if pair_data is None:
165
+ continue
166
+
164
167
  # Skip entries without a response (incomplete)
165
- if not isinstance(pair_data, Entry) or pair_data.response is None:
168
+ if not await self._is_stream_complete(pair_data.id, cursor=cursor):
169
+ continue
170
+
171
+ # Skip expired entries
172
+ if await self._is_pair_expired(pair_data, cursor=cursor):
173
+ continue
174
+
175
+ # Skip soft-deleted entries
176
+ if self.is_soft_deleted(pair_data):
166
177
  continue
167
178
 
168
179
  final_pairs.append(pair_data)
@@ -329,16 +340,8 @@ try:
329
340
  await connection.commit()
330
341
 
331
342
  async def _is_corrupted(self, pair: Entry, cursor: anysqlite.Cursor) -> bool:
332
- # if entry was created more than 1 hour ago and still has no response (incomplete)
333
- if pair.meta.created_at + 3600 < time.time() and pair.response is None:
334
- return True
335
-
336
- # Check if response stream is complete for Entry with response
337
- if (
338
- isinstance(pair, Entry)
339
- and pair.response is not None
340
- and not await self._is_stream_complete(pair.id, cursor)
341
- ):
343
+ # if entry was created more than 1 hour ago and still has no full response data
344
+ if pair.meta.created_at + 3600 < time.time() and not (await self._is_stream_complete(pair.id, cursor)):
342
345
  return True
343
346
  return False
344
347
 
@@ -161,8 +161,19 @@ try:
161
161
  for row in cursor.fetchall():
162
162
  pair_data = unpack(row[1], kind="pair")
163
163
 
164
+ if pair_data is None:
165
+ continue
166
+
164
167
  # Skip entries without a response (incomplete)
165
- if not isinstance(pair_data, Entry) or pair_data.response is None:
168
+ if not self._is_stream_complete(pair_data.id, cursor=cursor):
169
+ continue
170
+
171
+ # Skip expired entries
172
+ if self._is_pair_expired(pair_data, cursor=cursor):
173
+ continue
174
+
175
+ # Skip soft-deleted entries
176
+ if self.is_soft_deleted(pair_data):
166
177
  continue
167
178
 
168
179
  final_pairs.append(pair_data)
@@ -329,16 +340,8 @@ try:
329
340
  connection.commit()
330
341
 
331
342
  def _is_corrupted(self, pair: Entry, cursor: sqlite3.Cursor) -> bool:
332
- # if entry was created more than 1 hour ago and still has no response (incomplete)
333
- if pair.meta.created_at + 3600 < time.time() and pair.response is None:
334
- return True
335
-
336
- # Check if response stream is complete for Entry with response
337
- if (
338
- isinstance(pair, Entry)
339
- and pair.response is not None
340
- and not self._is_stream_complete(pair.id, cursor)
341
- ):
343
+ # if entry was created more than 1 hour ago and still has no full response data
344
+ if pair.meta.created_at + 3600 < time.time() and not (self._is_stream_complete(pair.id, cursor)):
342
345
  return True
343
346
  return False
344
347
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hishel
3
- Version: 1.1.4
3
+ Version: 1.1.6
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,29 @@ 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.6
410
+ ### 📚 Documentation
411
+
412
+ * remove some stale httpx configs by @karpetrosyan
413
+ ### 🚀 Features
414
+
415
+ * Add support for request no-cache directive by @karpetrosyan in [#416](https://github.com/karpetrosyan/hishel/pull/416)
416
+
417
+ ### Contributors
418
+ * @karpetrosyan
419
+
420
+ **Full Changelog**: https://github.com/karpetrosyan/hishel/compare/1.1.5...1.1.6
421
+
422
+ ## What's Changed in 1.1.5
423
+ ### 🐛 Bug Fixes
424
+
425
+ * filter out soft-deleted, expired and incomplete entries in `get_entries` by @karpetrosyan
426
+
427
+ ### Contributors
428
+ * @karpetrosyan
429
+
430
+ **Full Changelog**: https://github.com/karpetrosyan/hishel/compare/1.1.4...1.1.5
431
+
409
432
  ## What's Changed in 1.1.4
410
433
  ### 🐛 Bug Fixes
411
434
 
@@ -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=5VLwyT-eSdbez87A6xJG8T_Z3ySVcEEQp8WyTY_mhCo,102988
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=h22qGIQk8ceo9_rKtptEw7LszWCmb3aPwE1_DXOTDXc,15843
17
+ hishel/_core/_storages/_async_sqlite.py,sha256=IDMbtmbQ8e12HUlNJGyn6SIYColupuIbtvGfNl47_pM,15916
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=ZO_FnK4IH014plPHuhzM7PfZklNAmUhTeOfGFJh9U8c,15324
21
- hishel-1.1.4.dist-info/METADATA,sha256=Yivm81fEo6fXTN5uHNCMnVX-Mrnfks1hQk-7QTT9Tm4,20188
22
- hishel-1.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
- hishel-1.1.4.dist-info/licenses/LICENSE,sha256=1qQj7pE0V2O9OIedvyOgLGLvZLaPd3nFEup3IBEOZjQ,1493
24
- hishel-1.1.4.dist-info/RECORD,,
20
+ hishel/_core/_storages/_sync_sqlite.py,sha256=MidCVhhzlf2bZQ2_UzZzh0r_14HGZpFEomkGU5POjuk,15385
21
+ hishel-1.1.6.dist-info/METADATA,sha256=xQuRX_gSxX_sqXkwKnH7Mtsft7tUfym5L05CfbKeog4,20803
22
+ hishel-1.1.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ hishel-1.1.6.dist-info/licenses/LICENSE,sha256=1qQj7pE0V2O9OIedvyOgLGLvZLaPd3nFEup3IBEOZjQ,1493
24
+ hishel-1.1.6.dist-info/RECORD,,
File without changes