hishel 0.0.32__py3-none-any.whl → 0.0.33__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/__init__.py CHANGED
@@ -14,4 +14,4 @@ def install_cache() -> None: # pragma: no cover
14
14
  httpx.Client = CacheClient # type: ignore
15
15
 
16
16
 
17
- __version__ = "0.0.32"
17
+ __version__ = "0.0.33"
hishel/_controller.py CHANGED
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import typing as tp
2
3
 
3
4
  from httpcore import Request, Response
@@ -10,10 +11,13 @@ from ._utils import (
10
11
  extract_header_values,
11
12
  extract_header_values_decoded,
12
13
  generate_key,
14
+ get_safe_url,
13
15
  header_presents,
14
16
  parse_date,
15
17
  )
16
18
 
19
+ logger = logging.getLogger("hishel.controller")
20
+
17
21
  HEURISTICALLY_CACHEABLE_STATUS_CODES = (200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501)
18
22
  HTTP_METHODS = ["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"]
19
23
 
@@ -152,16 +156,41 @@ class Controller:
152
156
  force_cache = request.extensions.get("force_cache", None)
153
157
 
154
158
  if response.status not in self._cacheable_status_codes:
159
+ logger.debug(
160
+ (
161
+ f"Considering the resource located at {get_safe_url(request.url)} "
162
+ f"as not cachable since its status code ({response.status})"
163
+ " is not in the list of cacheable status codes."
164
+ )
165
+ )
155
166
  return False
156
167
 
157
168
  if response.status in (301, 308):
169
+ logger.debug(
170
+ (
171
+ f"Considering the resource located at {get_safe_url(request.url)} "
172
+ "as cachable since its status code is a permanent redirect."
173
+ )
174
+ )
158
175
  return True
159
176
 
160
177
  # the request method is understood by the cache
161
178
  if method not in self._cacheable_methods:
179
+ logger.debug(
180
+ (
181
+ f"Considering the resource located at {get_safe_url(request.url)} "
182
+ f"as not cachable since the request method ({method}) is not in the list of cacheable methods."
183
+ )
184
+ )
162
185
  return False
163
186
 
164
187
  if force_cache if force_cache is not None else self._force_cache:
188
+ logger.debug(
189
+ (
190
+ f"Considering the resource located at {get_safe_url(request.url)} "
191
+ "as cachable since the request is forced to use the cache."
192
+ )
193
+ )
165
194
  return True
166
195
 
167
196
  response_cache_control = parse_cache_control(extract_header_values_decoded(response.headers, b"cache-control"))
@@ -169,21 +198,53 @@ class Controller:
169
198
 
170
199
  # the response status code is final
171
200
  if response.status // 100 == 1:
201
+ logger.debug(
202
+ (
203
+ f"Considering the resource located at {get_safe_url(request.url)} "
204
+ "as not cachable since its status code is informational."
205
+ )
206
+ )
172
207
  return False
173
208
 
174
209
  # the no-store cache directive is not present (see Section 5.2.2.5)
175
210
  if request_cache_control.no_store:
211
+ logger.debug(
212
+ (
213
+ f"Considering the resource located at {get_safe_url(request.url)} "
214
+ "as not cachable since the request contains the no-store directive."
215
+ )
216
+ )
176
217
  return False
177
218
 
178
219
  # note that the must-understand cache directive overrides
179
220
  # no-store in certain circumstances; see Section 5.2.2.3.
180
- if response_cache_control.no_store and not response_cache_control.must_understand:
181
- return False
221
+ if response_cache_control.no_store:
222
+ if response_cache_control.must_understand:
223
+ logger.debug(
224
+ (
225
+ f"Skipping the no-store directive for the resource located at {get_safe_url(request.url)} "
226
+ "since the response contains the must-understand directive."
227
+ )
228
+ )
229
+ else:
230
+ logger.debug(
231
+ (
232
+ f"Considering the resource located at {get_safe_url(request.url)} "
233
+ "as not cachable since the response contains the no-store directive."
234
+ )
235
+ )
236
+ return False
182
237
 
183
238
  # a shared cache must not store a response with private directive
184
239
  # Note that we do not implement special handling for the qualified form,
185
240
  # which would only forbid storing specified headers.
186
241
  if not self._cache_private and response_cache_control.private:
242
+ logger.debug(
243
+ (
244
+ f"Considering the resource located at {get_safe_url(request.url)} "
245
+ "as not cachable since the response contains the private directive."
246
+ )
247
+ )
187
248
  return False
188
249
 
189
250
  expires_presents = header_presents(response.headers, b"expires")
@@ -196,6 +257,12 @@ class Controller:
196
257
  # - a cache extension that allows it to be cached (see Section 5.2.3); or
197
258
  # - a status code that is defined as heuristically cacheable (see Section 4.2.2).
198
259
  if self._allow_heuristics and response.status in HEURISTICALLY_CACHEABLE_STATUS_CODES:
260
+ logger.debug(
261
+ (
262
+ f"Considering the resource located at {get_safe_url(request.url)} "
263
+ "as cachable since its status code is heuristically cacheable."
264
+ )
265
+ )
199
266
  return True
200
267
 
201
268
  if not any(
@@ -206,8 +273,20 @@ class Controller:
206
273
  response_cache_control.max_age is not None,
207
274
  ]
208
275
  ):
276
+ logger.debug(
277
+ (
278
+ f"Considering the resource located at {get_safe_url(request.url)} "
279
+ "as not cachable since it does not contain any of the required cache directives."
280
+ )
281
+ )
209
282
  return False
210
283
 
284
+ logger.debug(
285
+ (
286
+ f"Considering the resource located at {get_safe_url(request.url)} "
287
+ "as cachable since it meets the criteria for being stored in the cache."
288
+ )
289
+ )
211
290
  # response is a cachable!
212
291
  return True
213
292
 
@@ -223,11 +302,23 @@ class Controller:
223
302
 
224
303
  if header_presents(response.headers, b"last-modified"):
225
304
  last_modified = extract_header_values(response.headers, b"last-modified", single=True)[0]
305
+ logger.debug(
306
+ (
307
+ f"Adding the 'If-Modified-Since' header with the value of '{last_modified.decode('ascii')}' "
308
+ f"to the request for the resource located at {get_safe_url(request.url)}."
309
+ )
310
+ )
226
311
  else:
227
312
  last_modified = None
228
313
 
229
314
  if header_presents(response.headers, b"etag"):
230
315
  etag = extract_header_values(response.headers, b"etag", single=True)[0]
316
+ logger.debug(
317
+ (
318
+ f"Adding the 'If-None-Match' header with the value of '{etag.decode('ascii')}' "
319
+ f"to the request for the resource located at {get_safe_url(request.url)}."
320
+ )
321
+ )
231
322
  else:
232
323
  etag = None
233
324
 
@@ -280,6 +371,12 @@ class Controller:
280
371
  # Use of responses with status codes 301 and 308 is always
281
372
  # legal as long as they don't adhere to any caching rules.
282
373
  if response.status in (301, 308):
374
+ logger.debug(
375
+ (
376
+ f"Considering the resource located at {get_safe_url(request.url)} "
377
+ "as valid for cache use since its status code is a permanent redirect."
378
+ )
379
+ )
283
380
  return response
284
381
 
285
382
  response_cache_control = parse_cache_control(extract_header_values_decoded(response.headers, b"Cache-Control"))
@@ -289,11 +386,23 @@ class Controller:
289
386
  # response (if any) match those presented (see Section 4.1)
290
387
  if not self._validate_vary(request=request, response=response, original_request=original_request):
291
388
  # If the vary headers does not match, then do not use the response
389
+ logger.debug(
390
+ (
391
+ f"Considering the resource located at {get_safe_url(request.url)} "
392
+ "as invalid for cache use since the vary headers do not match."
393
+ )
394
+ )
292
395
  return None # pragma: no cover
293
396
 
294
397
  # !!! this should be after the "vary" header validation.
295
398
  force_cache = request.extensions.get("force_cache", None)
296
399
  if force_cache if force_cache is not None else self._force_cache:
400
+ logger.debug(
401
+ (
402
+ f"Considering the resource located at {get_safe_url(request.url)} "
403
+ "as valid for cache use since the request is forced to use the cache."
404
+ )
405
+ )
297
406
  return response
298
407
 
299
408
  # the stored response does not contain the
@@ -305,15 +414,58 @@ class Controller:
305
414
  or response_cache_control.must_revalidate
306
415
  or request_cache_control.no_cache
307
416
  ):
417
+ if self._always_revalidate:
418
+ log_text = (
419
+ f"Considering the resource located at {get_safe_url(request.url)} "
420
+ "as needing revalidation since the cache is set to always revalidate."
421
+ )
422
+ elif response_cache_control.no_cache:
423
+ log_text = (
424
+ f"Considering the resource located at {get_safe_url(request.url)} "
425
+ "as needing revalidation since the response contains the no-cache directive."
426
+ )
427
+ elif response_cache_control.must_revalidate:
428
+ log_text = (
429
+ f"Considering the resource located at {get_safe_url(request.url)} "
430
+ "as needing revalidation since the response contains the must-revalidate directive."
431
+ )
432
+ elif request_cache_control.no_cache:
433
+ log_text = (
434
+ f"Considering the resource located at {get_safe_url(request.url)} "
435
+ "as needing revalidation since the request contains the no-cache directive."
436
+ )
437
+ else:
438
+ assert False, "Unreachable code " # pragma: no cover
439
+ logger.debug(log_text)
308
440
  self._make_request_conditional(request=request, response=response)
309
441
  return request
310
442
 
311
443
  freshness_lifetime = get_freshness_lifetime(response)
312
444
 
313
445
  if freshness_lifetime is None:
446
+ logger.debug(
447
+ (
448
+ "Could not determine the freshness lifetime of "
449
+ f"the resource located at {get_safe_url(request.url)}, "
450
+ "trying to use heuristics to calculate it."
451
+ )
452
+ )
314
453
  if self._allow_heuristics and response.status in HEURISTICALLY_CACHEABLE_STATUS_CODES:
315
454
  freshness_lifetime = get_heuristic_freshness(response=response, clock=self._clock)
455
+ logger.debug(
456
+ (
457
+ f"Successfully calculated the freshness lifetime of the resource located at "
458
+ f"{get_safe_url(request.url)} using heuristics."
459
+ )
460
+ )
316
461
  else:
462
+ logger.debug(
463
+ (
464
+ "Could not calculate the freshness lifetime of "
465
+ f"the resource located at {get_safe_url(request.url)}. "
466
+ "Making a conditional request to revalidate the response."
467
+ )
468
+ )
317
469
  # If Freshness cannot be calculated, then send the request
318
470
  self._make_request_conditional(request=request, response=response)
319
471
  return request
@@ -328,6 +480,13 @@ class Controller:
328
480
  # be fresh for at least the specified number of seconds.
329
481
  if request_cache_control.min_fresh is not None:
330
482
  if freshness_lifetime < (age + request_cache_control.min_fresh):
483
+ logger.debug(
484
+ (
485
+ f"Considering the resource located at {get_safe_url(request.url)} "
486
+ "as invalid for cache use since the time left for "
487
+ "freshness is less than the min-fresh directive."
488
+ )
489
+ )
331
490
  return None
332
491
 
333
492
  # The max-stale request directive indicates that the
@@ -340,7 +499,21 @@ class Controller:
340
499
  exceeded_freshness_lifetime = age - freshness_lifetime
341
500
 
342
501
  if request_cache_control.max_stale < exceeded_freshness_lifetime:
502
+ logger.debug(
503
+ (
504
+ f"Considering the resource located at {get_safe_url(request.url)} "
505
+ "as invalid for cache use since the freshness lifetime has been exceeded more than max-stale."
506
+ )
507
+ )
343
508
  return None
509
+ else:
510
+ logger.debug(
511
+ (
512
+ f"Considering the resource located at {get_safe_url(request.url)} "
513
+ "as valid for cache use since the freshness lifetime has been exceeded less than max-stale."
514
+ )
515
+ )
516
+ return response
344
517
 
345
518
  # The max-age request directive indicates that
346
519
  # the client prefers a response whose age is
@@ -349,9 +522,12 @@ class Controller:
349
522
  # the client does not wish to receive a stale response.
350
523
  if request_cache_control.max_age is not None:
351
524
  if request_cache_control.max_age < age:
352
- return None
353
-
354
- if request_cache_control.max_stale is None and not is_fresh:
525
+ logger.debug(
526
+ (
527
+ f"Considering the resource located at {get_safe_url(request.url)} "
528
+ "as invalid for cache use since the age of the response exceeds the max-age directive."
529
+ )
530
+ )
355
531
  return None
356
532
 
357
533
  # the stored response is one of the following:
@@ -359,8 +535,20 @@ class Controller:
359
535
  # allowed to be served stale (see Section 4.2.4), or
360
536
  # successfully validated (see Section 4.3).
361
537
  if is_fresh:
538
+ logger.debug(
539
+ (
540
+ f"Considering the resource located at {get_safe_url(request.url)} "
541
+ "as valid for cache use since it is fresh."
542
+ )
543
+ )
362
544
  return response
363
545
  else:
546
+ logger.debug(
547
+ (
548
+ f"Considering the resource located at {get_safe_url(request.url)} "
549
+ "as needing revalidation since it is not fresh."
550
+ )
551
+ )
364
552
  # Otherwise, make a conditional request
365
553
  self._make_request_conditional(request=request, response=response)
366
554
  return request
hishel/_utils.py CHANGED
@@ -6,6 +6,7 @@ from hashlib import blake2b
6
6
 
7
7
  import anyio
8
8
  import httpcore
9
+ import httpx
9
10
 
10
11
  HEADERS_ENCODING = "iso-8859-1"
11
12
 
@@ -33,6 +34,16 @@ def normalized_url(url: tp.Union[httpcore.URL, str, bytes]) -> str:
33
34
  assert False, "Invalid type for `normalized_url`" # pragma: no cover
34
35
 
35
36
 
37
+ def get_safe_url(url: httpcore.URL) -> str:
38
+ httpx_url = httpx.URL(bytes(url).decode("ascii"))
39
+
40
+ schema = httpx_url.scheme
41
+ host = httpx_url.host
42
+ path = httpx_url.path
43
+
44
+ return f"{schema}://{host}{path}"
45
+
46
+
36
47
  def generate_key(request: httpcore.Request, body: bytes = b"") -> str:
37
48
  encoded_url = normalized_url(request.url).encode("ascii")
38
49
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: hishel
3
- Version: 0.0.32
3
+ Version: 0.0.33
4
4
  Summary: Persistent cache implementation for httpx and httpcore
5
5
  Project-URL: Homepage, https://hishel.com
6
6
  Project-URL: Source, https://github.com/karpetrosyan/hishel
@@ -175,6 +175,10 @@ Help us grow and continue developing good software for you ❤️
175
175
 
176
176
  # Changelog
177
177
 
178
+ ## 0.0.33 (4th Oct, 2024)
179
+
180
+ - Added a [Logging](https://hishel.com/advanced/logging/) section to the documentation.
181
+
178
182
  ## 0.0.32 (27th Sep, 2024)
179
183
 
180
184
  - Don't raise an exception if the `Date` header is not present. (#273)
@@ -1,5 +1,5 @@
1
- hishel/__init__.py,sha256=H002jQK0-tviX35x2lvz1TftDXsTEb4zDBh4h9PHABY,369
2
- hishel/_controller.py,sha256=se09qx7f_xR3T-oQvvmICrv3oIgQcYMiCXyRh2OADOM,16038
1
+ hishel/__init__.py,sha256=PqLQGkHTPzUnEDcEGVCMakiBjeU7jvbg4Pu4opBqbrs,369
2
+ hishel/_controller.py,sha256=F7hj1ePUvau2Wj5r6zzdYr8UXPq4osfmMxkmx_Ig2L8,24390
3
3
  hishel/_exceptions.py,sha256=qbg55RNlzwhv5JreWY9Zog_zmmiKdn5degtqJKijuRs,198
4
4
  hishel/_files.py,sha256=7J5uX7Nnzd7QQWfYuDGh8v6XGLG3eUDBjoJZ4aTaY1c,2228
5
5
  hishel/_headers.py,sha256=TWuHi7sRoeS2xxdNGujKmqWtgncUqfhNGCgHKYpRU-I,7329
@@ -7,7 +7,7 @@ hishel/_lfu_cache.py,sha256=GBxToQI8u_a9TzYnLlZMLhgZ8Lb83boPHzTvIgqV6pA,2707
7
7
  hishel/_s3.py,sha256=JqRlygITK5uAryviC15HZKQlKY7etUOPWcazTJeYKBI,3736
8
8
  hishel/_serializers.py,sha256=gepVb8JC4aBkGw9kLcbAsyo-1XgK_lzTssLr_8av4SQ,11640
9
9
  hishel/_synchronization.py,sha256=xOmU9_8KAWTAv3r8EpqPISrtSF3slyh1J0Sc7ZQO1rg,897
10
- hishel/_utils.py,sha256=cgLGjBI7H-T_DkYBXiHiEEf8SGjGeZ6Cc2IHbLAZybU,2501
10
+ hishel/_utils.py,sha256=lnnFmDnKNDb_-OxlZncOJK5IEbtJeTywpITpYFf6WOk,2736
11
11
  hishel/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  hishel/_async/__init__.py,sha256=_oAltH4emAtUF7_9SSxz_KKGYzSXL34o_EGgGvoPG7E,187
13
13
  hishel/_async/_client.py,sha256=AkVSSbNTTHmK0gX6PRYVQ-3aDbuCX2Im4VKbLkwLiBU,1101
@@ -21,7 +21,7 @@ hishel/_sync/_mock.py,sha256=im88tZr-XhP9BpzvIt3uOjndAlNcJvFP7Puv3H-6lKU,1430
21
21
  hishel/_sync/_pool.py,sha256=VcAknzyAL2i4-zcyE2fOTmTjfBZ2wkBVNYTvSw0OjVQ,7940
22
22
  hishel/_sync/_storages.py,sha256=RYzYXqnv0o2JO3RoEmlEUp0yOg_ungXfz4dLN7UTpIQ,27909
23
23
  hishel/_sync/_transports.py,sha256=G3_8SdPwlnrHZRvE1gqFLE4oZadVqNgg5mvxghDMih0,10838
24
- hishel-0.0.32.dist-info/METADATA,sha256=7YzjYw0A7BojC44b7J7IKqxd_1_-crwZJiKsNwViil0,11578
25
- hishel-0.0.32.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
26
- hishel-0.0.32.dist-info/licenses/LICENSE,sha256=1qQj7pE0V2O9OIedvyOgLGLvZLaPd3nFEup3IBEOZjQ,1493
27
- hishel-0.0.32.dist-info/RECORD,,
24
+ hishel-0.0.33.dist-info/METADATA,sha256=fVzp5e0XfMlqDkvEzZFfS3F_QfjrUA9wi-A696LNjkc,11694
25
+ hishel-0.0.33.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
26
+ hishel-0.0.33.dist-info/licenses/LICENSE,sha256=1qQj7pE0V2O9OIedvyOgLGLvZLaPd3nFEup3IBEOZjQ,1493
27
+ hishel-0.0.33.dist-info/RECORD,,