hishel 0.1.4__py3-none-any.whl → 1.0.0b1__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.
Files changed (56) hide show
  1. hishel/__init__.py +59 -52
  2. hishel/_async_cache.py +213 -0
  3. hishel/_async_httpx.py +236 -0
  4. hishel/_core/_headers.py +646 -0
  5. hishel/{beta/_core → _core}/_spec.py +270 -136
  6. hishel/_core/_storages/_async_base.py +71 -0
  7. hishel/_core/_storages/_async_sqlite.py +420 -0
  8. hishel/_core/_storages/_packing.py +144 -0
  9. hishel/_core/_storages/_sync_base.py +71 -0
  10. hishel/_core/_storages/_sync_sqlite.py +420 -0
  11. hishel/{beta/_core → _core}/models.py +100 -37
  12. hishel/_policies.py +49 -0
  13. hishel/_sync_cache.py +213 -0
  14. hishel/_sync_httpx.py +236 -0
  15. hishel/_utils.py +37 -366
  16. hishel/asgi.py +400 -0
  17. hishel/fastapi.py +263 -0
  18. hishel/httpx.py +12 -0
  19. hishel/{beta/requests.py → requests.py} +41 -30
  20. hishel-1.0.0b1.dist-info/METADATA +509 -0
  21. hishel-1.0.0b1.dist-info/RECORD +24 -0
  22. hishel/_async/__init__.py +0 -5
  23. hishel/_async/_client.py +0 -30
  24. hishel/_async/_mock.py +0 -43
  25. hishel/_async/_pool.py +0 -201
  26. hishel/_async/_storages.py +0 -768
  27. hishel/_async/_transports.py +0 -282
  28. hishel/_controller.py +0 -581
  29. hishel/_exceptions.py +0 -10
  30. hishel/_files.py +0 -54
  31. hishel/_headers.py +0 -215
  32. hishel/_lfu_cache.py +0 -71
  33. hishel/_lmdb_types_.pyi +0 -53
  34. hishel/_s3.py +0 -122
  35. hishel/_serializers.py +0 -329
  36. hishel/_sync/__init__.py +0 -5
  37. hishel/_sync/_client.py +0 -30
  38. hishel/_sync/_mock.py +0 -43
  39. hishel/_sync/_pool.py +0 -201
  40. hishel/_sync/_storages.py +0 -768
  41. hishel/_sync/_transports.py +0 -282
  42. hishel/_synchronization.py +0 -37
  43. hishel/beta/__init__.py +0 -59
  44. hishel/beta/_async_cache.py +0 -167
  45. hishel/beta/_core/__init__.py +0 -0
  46. hishel/beta/_core/_async/_storages/_sqlite.py +0 -411
  47. hishel/beta/_core/_base/_storages/_base.py +0 -260
  48. hishel/beta/_core/_base/_storages/_packing.py +0 -165
  49. hishel/beta/_core/_headers.py +0 -301
  50. hishel/beta/_core/_sync/_storages/_sqlite.py +0 -411
  51. hishel/beta/_sync_cache.py +0 -167
  52. hishel/beta/httpx.py +0 -317
  53. hishel-0.1.4.dist-info/METADATA +0 -404
  54. hishel-0.1.4.dist-info/RECORD +0 -41
  55. {hishel-0.1.4.dist-info → hishel-1.0.0b1.dist-info}/WHEEL +0 -0
  56. {hishel-0.1.4.dist-info → hishel-1.0.0b1.dist-info}/licenses/LICENSE +0 -0
hishel/_controller.py DELETED
@@ -1,581 +0,0 @@
1
- import logging
2
- import typing as tp
3
-
4
- from httpcore import Request, Response
5
-
6
- from ._headers import Vary, parse_cache_control
7
- from ._utils import (
8
- BaseClock,
9
- Clock,
10
- extract_header_values,
11
- extract_header_values_decoded,
12
- generate_key,
13
- get_safe_url,
14
- header_presents,
15
- parse_date,
16
- )
17
-
18
- logger = logging.getLogger("hishel.controller")
19
-
20
- HEURISTICALLY_CACHEABLE_STATUS_CODES = (200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501)
21
- HTTP_METHODS = ["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"]
22
-
23
- __all__ = ("HEURISTICALLY_CACHEABLE_STATUS_CODES", "Controller")
24
-
25
-
26
- def get_updated_headers(
27
- stored_response_headers: tp.List[tp.Tuple[bytes, bytes]],
28
- new_response_headers: tp.List[tp.Tuple[bytes, bytes]],
29
- ) -> tp.List[tp.Tuple[bytes, bytes]]:
30
- updated_headers = []
31
-
32
- checked = set()
33
-
34
- for key, value in stored_response_headers:
35
- if key not in checked and key.lower() != b"content-length":
36
- checked.add(key)
37
- values = extract_header_values(new_response_headers, key)
38
-
39
- if values:
40
- updated_headers.extend([(key, value) for value in values])
41
- else:
42
- values = extract_header_values(stored_response_headers, key)
43
- updated_headers.extend([(key, value) for value in values])
44
-
45
- for key, value in new_response_headers:
46
- if key not in checked and key.lower() != b"content-length":
47
- values = extract_header_values(new_response_headers, key)
48
- updated_headers.extend([(key, value) for value in values])
49
-
50
- return updated_headers
51
-
52
-
53
- def get_freshness_lifetime(response: Response) -> tp.Optional[int]:
54
- response_cache_control = parse_cache_control(extract_header_values_decoded(response.headers, b"Cache-Control"))
55
-
56
- if response_cache_control.max_age is not None:
57
- return response_cache_control.max_age
58
-
59
- if header_presents(response.headers, b"expires"):
60
- expires = extract_header_values_decoded(response.headers, b"expires", single=True)[0]
61
- expires_timestamp = parse_date(expires)
62
- if expires_timestamp is None:
63
- return None
64
- date = extract_header_values_decoded(response.headers, b"date", single=True)[0]
65
- date_timestamp = parse_date(date)
66
- if date_timestamp is None:
67
- return None
68
-
69
- return expires_timestamp - date_timestamp
70
- return None
71
-
72
-
73
- def get_heuristic_freshness(response: Response, clock: "BaseClock") -> int:
74
- last_modified = extract_header_values_decoded(response.headers, b"last-modified", single=True)
75
-
76
- if last_modified:
77
- last_modified_timestamp = parse_date(last_modified[0])
78
- if last_modified_timestamp is not None:
79
- now = clock.now()
80
-
81
- ONE_WEEK = 604_800
82
-
83
- return min(ONE_WEEK, int((now - last_modified_timestamp) * 0.1))
84
-
85
- ONE_DAY = 86_400
86
- return ONE_DAY
87
-
88
-
89
- def get_age(response: Response, clock: "BaseClock") -> int:
90
- if not header_presents(response.headers, b"date"):
91
- # If the response does not have a date header, then it is impossible to calculate the age.
92
- # Instead of raising an exception, we return infinity to be sure that the response is not considered fresh.
93
- return float("inf") # type: ignore
94
-
95
- date = parse_date(extract_header_values_decoded(response.headers, b"date")[0])
96
- if date is None:
97
- return float("inf") # type: ignore
98
-
99
- now = clock.now()
100
-
101
- apparent_age = max(0, now - date)
102
- return int(apparent_age)
103
-
104
-
105
- def allowed_stale(response: Response) -> bool:
106
- response_cache_control = parse_cache_control(extract_header_values_decoded(response.headers, b"Cache-Control"))
107
-
108
- if response_cache_control.no_cache:
109
- return False
110
-
111
- if response_cache_control.must_revalidate:
112
- return False
113
-
114
- return True
115
-
116
-
117
- class Controller:
118
- def __init__(
119
- self,
120
- cacheable_methods: tp.Optional[tp.List[str]] = None,
121
- cacheable_status_codes: tp.Optional[tp.List[int]] = None,
122
- cache_private: bool = True,
123
- allow_heuristics: bool = False,
124
- clock: tp.Optional[BaseClock] = None,
125
- allow_stale: bool = False,
126
- always_revalidate: bool = False,
127
- force_cache: bool = False,
128
- key_generator: tp.Optional[tp.Callable[[Request, tp.Optional[bytes]], str]] = None,
129
- ):
130
- self._cacheable_methods = []
131
-
132
- if cacheable_methods is None:
133
- self._cacheable_methods.append("GET")
134
- else:
135
- for method in cacheable_methods:
136
- if method.upper() not in HTTP_METHODS:
137
- raise RuntimeError(
138
- f"Hishel does not support the HTTP method `{method}`.\n"
139
- f"Please use the methods from this list: {HTTP_METHODS}"
140
- )
141
- self._cacheable_methods.append(method.upper())
142
-
143
- self._cacheable_status_codes = cacheable_status_codes if cacheable_status_codes else [200, 301, 308]
144
- self._cache_private = cache_private
145
- self._clock = clock if clock else Clock()
146
- self._allow_heuristics = allow_heuristics
147
- self._allow_stale = allow_stale
148
- self._always_revalidate = always_revalidate
149
- self._force_cache = force_cache
150
- self._key_generator = key_generator or generate_key
151
-
152
- def is_cachable(self, request: Request, response: Response) -> bool:
153
- """
154
- Determines whether the response may be cached.
155
-
156
- The only thing this method does is determine whether the
157
- response associated with this request can be cached for later use.
158
- `https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-in-caches`
159
- lists the steps that this method simply follows.
160
- """
161
- method = request.method.decode("ascii")
162
- force_cache = request.extensions.get("force_cache", None)
163
-
164
- if response.status not in self._cacheable_status_codes:
165
- logger.debug(
166
- (
167
- f"Considering the resource located at {get_safe_url(request.url)} "
168
- f"as not cachable since its status code ({response.status})"
169
- " is not in the list of cacheable status codes."
170
- )
171
- )
172
- return False
173
-
174
- if response.status in (301, 308):
175
- logger.debug(
176
- (
177
- f"Considering the resource located at {get_safe_url(request.url)} "
178
- "as cachable since its status code is a permanent redirect."
179
- )
180
- )
181
- return True
182
-
183
- # the request method is understood by the cache
184
- if method not in self._cacheable_methods:
185
- logger.debug(
186
- (
187
- f"Considering the resource located at {get_safe_url(request.url)} "
188
- f"as not cachable since the request method ({method}) is not in the list of cacheable methods."
189
- )
190
- )
191
- return False
192
-
193
- if force_cache if force_cache is not None else self._force_cache:
194
- logger.debug(
195
- (
196
- f"Considering the resource located at {get_safe_url(request.url)} "
197
- "as cachable since the request is forced to use the cache."
198
- )
199
- )
200
- return True
201
-
202
- response_cache_control = parse_cache_control(extract_header_values_decoded(response.headers, b"cache-control"))
203
- request_cache_control = parse_cache_control(extract_header_values_decoded(request.headers, b"cache-control"))
204
-
205
- # the response status code is final
206
- if response.status // 100 == 1:
207
- logger.debug(
208
- (
209
- f"Considering the resource located at {get_safe_url(request.url)} "
210
- "as not cachable since its status code is informational."
211
- )
212
- )
213
- return False
214
-
215
- # the no-store cache directive is not present (see Section 5.2.2.5)
216
- if request_cache_control.no_store:
217
- logger.debug(
218
- (
219
- f"Considering the resource located at {get_safe_url(request.url)} "
220
- "as not cachable since the request contains the no-store directive."
221
- )
222
- )
223
- return False
224
-
225
- # note that the must-understand cache directive overrides
226
- # no-store in certain circumstances; see Section 5.2.2.3.
227
- if response_cache_control.no_store:
228
- if response_cache_control.must_understand:
229
- logger.debug(
230
- (
231
- f"Skipping the no-store directive for the resource located at {get_safe_url(request.url)} "
232
- "since the response contains the must-understand directive."
233
- )
234
- )
235
- else:
236
- logger.debug(
237
- (
238
- f"Considering the resource located at {get_safe_url(request.url)} "
239
- "as not cachable since the response contains the no-store directive."
240
- )
241
- )
242
- return False
243
-
244
- # a shared cache must not store a response with private directive
245
- # Note that we do not implement special handling for the qualified form,
246
- # which would only forbid storing specified headers.
247
- if not self._cache_private and response_cache_control.private:
248
- logger.debug(
249
- (
250
- f"Considering the resource located at {get_safe_url(request.url)} "
251
- "as not cachable since the response contains the private directive."
252
- )
253
- )
254
- return False
255
-
256
- expires_presents = header_presents(response.headers, b"expires")
257
- # the response contains at least one of the following:
258
- # - a public response directive (see Section 5.2.2.9);
259
- # - a private response directive, if the cache is not shared (see Section 5.2.2.7);
260
- # - an Expires header field (see Section 5.3);
261
- # - a max-age response directive (see Section 5.2.2.1);
262
- # - if the cache is shared: an s-maxage response directive (see Section 5.2.2.10);
263
- # - a cache extension that allows it to be cached (see Section 5.2.3); or
264
- # - a status code that is defined as heuristically cacheable (see Section 4.2.2).
265
- if self._allow_heuristics and response.status in HEURISTICALLY_CACHEABLE_STATUS_CODES:
266
- logger.debug(
267
- (
268
- f"Considering the resource located at {get_safe_url(request.url)} "
269
- "as cachable since its status code is heuristically cacheable."
270
- )
271
- )
272
- return True
273
-
274
- if not any(
275
- [
276
- response_cache_control.public,
277
- response_cache_control.private,
278
- expires_presents,
279
- response_cache_control.max_age is not None,
280
- ]
281
- ):
282
- logger.debug(
283
- (
284
- f"Considering the resource located at {get_safe_url(request.url)} "
285
- "as not cachable since it does not contain any of the required cache directives."
286
- )
287
- )
288
- return False
289
-
290
- logger.debug(
291
- (
292
- f"Considering the resource located at {get_safe_url(request.url)} "
293
- "as cachable since it meets the criteria for being stored in the cache."
294
- )
295
- )
296
- # response is a cachable!
297
- return True
298
-
299
- def _make_request_conditional(self, request: Request, response: Response) -> None:
300
- """
301
- Adds the precondition headers needed for response validation.
302
-
303
- This method will use the "Last-Modified" or "Etag" headers
304
- if they are provided in order to create precondition headers.
305
-
306
- See also (https://www.rfc-editor.org/rfc/rfc9111.html#name-sending-a-validation-reques)
307
- """
308
-
309
- if header_presents(response.headers, b"last-modified"):
310
- last_modified = extract_header_values(response.headers, b"last-modified", single=True)[0]
311
- logger.debug(
312
- (
313
- f"Adding the 'If-Modified-Since' header with the value of '{last_modified.decode('ascii')}' "
314
- f"to the request for the resource located at {get_safe_url(request.url)}."
315
- )
316
- )
317
- else:
318
- last_modified = None
319
-
320
- if header_presents(response.headers, b"etag"):
321
- etag = extract_header_values(response.headers, b"etag", single=True)[0]
322
- logger.debug(
323
- (
324
- f"Adding the 'If-None-Match' header with the value of '{etag.decode('ascii')}' "
325
- f"to the request for the resource located at {get_safe_url(request.url)}."
326
- )
327
- )
328
- else:
329
- etag = None
330
-
331
- precondition_headers: tp.List[tp.Tuple[bytes, bytes]] = []
332
- if last_modified:
333
- precondition_headers.append((b"If-Modified-Since", last_modified))
334
- if etag:
335
- precondition_headers.append((b"If-None-Match", etag))
336
-
337
- request.headers.extend(precondition_headers)
338
-
339
- def _validate_vary(self, request: Request, response: Response, original_request: Request) -> bool:
340
- """
341
- Determines whether the "vary" headers in the request and response headers are identical.
342
-
343
- See also (https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-cache-keys-with).
344
- """
345
-
346
- vary_headers = extract_header_values_decoded(response.headers, b"vary")
347
- vary = Vary.from_value(vary_values=vary_headers)
348
- for vary_header in vary._values:
349
- if vary_header == "*":
350
- return False # pragma: no cover
351
-
352
- if extract_header_values(request.headers, vary_header) != extract_header_values(
353
- original_request.headers, vary_header
354
- ):
355
- return False
356
-
357
- return True
358
-
359
- def construct_response_from_cache(
360
- self, request: Request, response: Response, original_request: Request
361
- ) -> tp.Union[Response, Request, None]:
362
- """
363
- Specifies whether the response should be used, skipped, or validated by the cache.
364
-
365
- This method makes a decision regarding what to do with
366
- the stored response when it is retrieved from storage.
367
- It might be ready for use or it might need to be revalidated.
368
- This method mirrors the relevant section from RFC 9111,
369
- see (https://www.rfc-editor.org/rfc/rfc9111.html#name-constructing-responses-from).
370
-
371
- Returns:
372
- Response: This response is applicable to the request.
373
- Request: This response can be used for this request, but it must first be revalidated.
374
- None: It is not possible to use this response for this request.
375
- """
376
-
377
- # Use of responses with status codes 301 and 308 is always
378
- # legal as long as they don't adhere to any caching rules.
379
- if response.status in (301, 308):
380
- logger.debug(
381
- (
382
- f"Considering the resource located at {get_safe_url(request.url)} "
383
- "as valid for cache use since its status code is a permanent redirect."
384
- )
385
- )
386
- return response
387
-
388
- response_cache_control = parse_cache_control(extract_header_values_decoded(response.headers, b"Cache-Control"))
389
- request_cache_control = parse_cache_control(extract_header_values_decoded(request.headers, b"Cache-Control"))
390
-
391
- # request header fields nominated by the stored
392
- # response (if any) match those presented (see Section 4.1)
393
- if not self._validate_vary(request=request, response=response, original_request=original_request):
394
- # If the vary headers does not match, then do not use the response
395
- logger.debug(
396
- (
397
- f"Considering the resource located at {get_safe_url(request.url)} "
398
- "as invalid for cache use since the vary headers do not match."
399
- )
400
- )
401
- return None # pragma: no cover
402
-
403
- # !!! this should be after the "vary" header validation.
404
- force_cache = request.extensions.get("force_cache", None)
405
- if force_cache if force_cache is not None else self._force_cache:
406
- logger.debug(
407
- (
408
- f"Considering the resource located at {get_safe_url(request.url)} "
409
- "as valid for cache use since the request is forced to use the cache."
410
- )
411
- )
412
- return response
413
-
414
- # the stored response does not contain the
415
- # no-cache directive (Section 5.2.2.4), unless
416
- # it is successfully validated (Section 4.3)
417
- if (
418
- self._always_revalidate
419
- or response_cache_control.no_cache
420
- or response_cache_control.must_revalidate
421
- or request_cache_control.no_cache
422
- ):
423
- if self._always_revalidate:
424
- log_text = (
425
- f"Considering the resource located at {get_safe_url(request.url)} "
426
- "as needing revalidation since the cache is set to always revalidate."
427
- )
428
- elif response_cache_control.no_cache:
429
- log_text = (
430
- f"Considering the resource located at {get_safe_url(request.url)} "
431
- "as needing revalidation since the response contains the no-cache directive."
432
- )
433
- elif response_cache_control.must_revalidate:
434
- log_text = (
435
- f"Considering the resource located at {get_safe_url(request.url)} "
436
- "as needing revalidation since the response contains the must-revalidate directive."
437
- )
438
- elif request_cache_control.no_cache:
439
- log_text = (
440
- f"Considering the resource located at {get_safe_url(request.url)} "
441
- "as needing revalidation since the request contains the no-cache directive."
442
- )
443
- else:
444
- assert False, "Unreachable code " # pragma: no cover
445
- logger.debug(log_text)
446
- self._make_request_conditional(request=request, response=response)
447
- return request
448
-
449
- freshness_lifetime = get_freshness_lifetime(response)
450
-
451
- if freshness_lifetime is None:
452
- logger.debug(
453
- (
454
- "Could not determine the freshness lifetime of "
455
- f"the resource located at {get_safe_url(request.url)}, "
456
- "trying to use heuristics to calculate it."
457
- )
458
- )
459
- if self._allow_heuristics and response.status in HEURISTICALLY_CACHEABLE_STATUS_CODES:
460
- freshness_lifetime = get_heuristic_freshness(response=response, clock=self._clock)
461
- logger.debug(
462
- (
463
- f"Successfully calculated the freshness lifetime of the resource located at "
464
- f"{get_safe_url(request.url)} using heuristics."
465
- )
466
- )
467
- else:
468
- logger.debug(
469
- (
470
- "Could not calculate the freshness lifetime of "
471
- f"the resource located at {get_safe_url(request.url)}. "
472
- "Making a conditional request to revalidate the response."
473
- )
474
- )
475
- # If Freshness cannot be calculated, then send the request
476
- self._make_request_conditional(request=request, response=response)
477
- return request
478
-
479
- age = get_age(response, self._clock)
480
- is_fresh = freshness_lifetime > age
481
-
482
- # The min-fresh request directive indicates that the client
483
- # prefers a response whose freshness lifetime is no less than
484
- # its current age plus the specified time in seconds.
485
- # That is, the client wants a response that will still
486
- # be fresh for at least the specified number of seconds.
487
- if request_cache_control.min_fresh is not None:
488
- if freshness_lifetime < (age + request_cache_control.min_fresh):
489
- logger.debug(
490
- (
491
- f"Considering the resource located at {get_safe_url(request.url)} "
492
- "as invalid for cache use since the time left for "
493
- "freshness is less than the min-fresh directive."
494
- )
495
- )
496
- return None
497
-
498
- # The max-stale request directive indicates that the
499
- # client will accept a response that has exceeded its freshness lifetime.
500
- # If a value is present, then the client is willing to accept a response
501
- # that has exceeded its freshness lifetime by no more than the specified
502
- # number of seconds. If no value is assigned to max-stale, then
503
- # the client will accept a stale response of any age.
504
- if not is_fresh and request_cache_control.max_stale is not None:
505
- exceeded_freshness_lifetime = age - freshness_lifetime
506
-
507
- if request_cache_control.max_stale < exceeded_freshness_lifetime:
508
- logger.debug(
509
- (
510
- f"Considering the resource located at {get_safe_url(request.url)} "
511
- "as invalid for cache use since the freshness lifetime has been exceeded more than max-stale."
512
- )
513
- )
514
- return None
515
- else:
516
- logger.debug(
517
- (
518
- f"Considering the resource located at {get_safe_url(request.url)} "
519
- "as valid for cache use since the freshness lifetime has been exceeded less than max-stale."
520
- )
521
- )
522
- return response
523
-
524
- # The max-age request directive indicates that
525
- # the client prefers a response whose age is
526
- # less than or equal to the specified number of seconds.
527
- # Unless the max-stale request directive is also present,
528
- # the client does not wish to receive a stale response.
529
- if request_cache_control.max_age is not None:
530
- if request_cache_control.max_age < age:
531
- logger.debug(
532
- (
533
- f"Considering the resource located at {get_safe_url(request.url)} "
534
- "as invalid for cache use since the age of the response exceeds the max-age directive."
535
- )
536
- )
537
- return None
538
-
539
- # the stored response is one of the following:
540
- # fresh (see Section 4.2), or
541
- # allowed to be served stale (see Section 4.2.4), or
542
- # successfully validated (see Section 4.3).
543
- if is_fresh:
544
- logger.debug(
545
- (
546
- f"Considering the resource located at {get_safe_url(request.url)} "
547
- "as valid for cache use since it is fresh."
548
- )
549
- )
550
- return response
551
- else:
552
- logger.debug(
553
- (
554
- f"Considering the resource located at {get_safe_url(request.url)} "
555
- "as needing revalidation since it is not fresh."
556
- )
557
- )
558
- # Otherwise, make a conditional request
559
- self._make_request_conditional(request=request, response=response)
560
- return request
561
-
562
- def handle_validation_response(self, old_response: Response, new_response: Response) -> Response:
563
- """
564
- Handles incoming validation response.
565
-
566
- This method takes care of what to do with the incoming
567
- validation response; if it is a 304 response, it updates
568
- the headers with the new response and returns it.
569
-
570
- This method mirrors the relevant section from RFC 9111,
571
- see (https://www.rfc-editor.org/rfc/rfc9111.html#name-handling-a-validation-respo).
572
- """
573
- if new_response.status == 304:
574
- headers = get_updated_headers(
575
- stored_response_headers=old_response.headers,
576
- new_response_headers=new_response.headers,
577
- )
578
- old_response.headers = headers
579
- return old_response
580
- else:
581
- return new_response
hishel/_exceptions.py DELETED
@@ -1,10 +0,0 @@
1
- __all__ = ("CacheControlError", "ParseError", "ValidationError")
2
-
3
-
4
- class CacheControlError(Exception): ...
5
-
6
-
7
- class ParseError(CacheControlError): ...
8
-
9
-
10
- class ValidationError(CacheControlError): ...
hishel/_files.py DELETED
@@ -1,54 +0,0 @@
1
- import typing as tp
2
-
3
- import anyio
4
-
5
-
6
- class AsyncBaseFileManager:
7
- def __init__(self, is_binary: bool) -> None:
8
- self.is_binary = is_binary
9
-
10
- async def write_to(self, path: str, data: tp.Union[bytes, str], is_binary: tp.Optional[bool] = None) -> None:
11
- raise NotImplementedError()
12
-
13
- async def read_from(self, path: str, is_binary: tp.Optional[bool] = None) -> tp.Union[bytes, str]:
14
- raise NotImplementedError()
15
-
16
-
17
- class AsyncFileManager(AsyncBaseFileManager):
18
- async def write_to(self, path: str, data: tp.Union[bytes, str], is_binary: tp.Optional[bool] = None) -> None:
19
- is_binary = self.is_binary if is_binary is None else is_binary
20
- mode = "wb" if is_binary else "wt"
21
- async with await anyio.open_file(path, mode) as f: # type: ignore[call-overload]
22
- await f.write(data)
23
-
24
- async def read_from(self, path: str, is_binary: tp.Optional[bool] = None) -> tp.Union[bytes, str]:
25
- is_binary = self.is_binary if is_binary is None else is_binary
26
- mode = "rb" if is_binary else "rt"
27
-
28
- async with await anyio.open_file(path, mode) as f: # type: ignore[call-overload]
29
- return tp.cast(tp.Union[bytes, str], await f.read())
30
-
31
-
32
- class BaseFileManager:
33
- def __init__(self, is_binary: bool) -> None:
34
- self.is_binary = is_binary
35
-
36
- def write_to(self, path: str, data: tp.Union[bytes, str], is_binary: tp.Optional[bool] = None) -> None:
37
- raise NotImplementedError()
38
-
39
- def read_from(self, path: str, is_binary: tp.Optional[bool] = None) -> tp.Union[bytes, str]:
40
- raise NotImplementedError()
41
-
42
-
43
- class FileManager(BaseFileManager):
44
- def write_to(self, path: str, data: tp.Union[bytes, str], is_binary: tp.Optional[bool] = None) -> None:
45
- is_binary = self.is_binary if is_binary is None else is_binary
46
- mode = "wb" if is_binary else "wt"
47
- with open(path, mode) as f:
48
- f.write(data)
49
-
50
- def read_from(self, path: str, is_binary: tp.Optional[bool] = None) -> tp.Union[bytes, str]:
51
- is_binary = self.is_binary if is_binary is None else is_binary
52
- mode = "rb" if is_binary else "rt"
53
- with open(path, mode) as f:
54
- return tp.cast(tp.Union[bytes, str], f.read())