hishel 1.0.0.dev0__py3-none-any.whl → 1.0.0.dev1__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/_utils.py CHANGED
@@ -2,19 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  import calendar
4
4
  import hashlib
5
- import json
6
- import sqlite3
7
5
  import time
8
6
  import typing as tp
9
- from datetime import date
10
7
  from email.utils import parsedate_tz
11
- from typing import Any, AsyncIterator, Generator, Iterable, Iterator, TypeVar
8
+ from typing import AsyncIterator, Generator, Iterable, Iterator, TypeVar
12
9
 
13
- import anyio
14
- import anysqlite
15
10
  import httpcore
16
11
  import httpx
17
- from anyio import from_thread, to_thread
18
12
 
19
13
  HEADERS_ENCODING = "iso-8859-1"
20
14
 
@@ -116,10 +110,6 @@ def parse_date(date: str) -> tp.Optional[int]:
116
110
  return timestamp
117
111
 
118
112
 
119
- async def asleep(seconds: tp.Union[int, float]) -> None:
120
- await anyio.sleep(seconds)
121
-
122
-
123
113
  def sleep(seconds: tp.Union[int, float]) -> None:
124
114
  time.sleep(seconds)
125
115
 
@@ -155,66 +145,6 @@ def partition(iterable: tp.Iterable[T], predicate: tp.Callable[[T], bool]) -> tp
155
145
  return matching, non_matching
156
146
 
157
147
 
158
- def async_iterator_to_sync(iterator: AsyncIterator[bytes]) -> Iterator[bytes]:
159
- """
160
- Convert an asynchronous byte iterator to a synchronous one.
161
- This function takes an asynchronous iterator that yields bytes and converts it into
162
- a synchronous iterator.
163
-
164
- Args:
165
- iterator (AsyncIterator[bytes]): The asynchronous byte iterator to be converted.
166
- Returns:
167
- Iterator[bytes]: A synchronous iterator that yields the same byte chunks as the input iterator.
168
- Example:
169
- ```python
170
- async_iter = some_async_byte_stream()
171
- sync_iter = async_iterator_to_sync(async_iter)
172
- for chunk in sync_iter:
173
- process_bytes(chunk)
174
- ```
175
- """
176
-
177
- while True:
178
- try:
179
- chunk = from_thread.run(iterator.__anext__)
180
- except StopAsyncIteration:
181
- break
182
- yield chunk
183
-
184
-
185
- def _call_next(iterator: Iterator[bytes]) -> bytes:
186
- try:
187
- return iterator.__next__()
188
- except StopIteration:
189
- raise StopAsyncIteration
190
-
191
-
192
- async def sync_iterator_to_async(iterator: Iterator[bytes]) -> AsyncIterator[bytes]:
193
- """
194
- Converts a synchronous bytes iterator to an asynchronous one.
195
- This function takes a synchronous iterator that yields bytes and converts it into an
196
- asynchronous iterator, allowing it to be used in async contexts without blocking.
197
- Args:
198
- iterator (Iterator[bytes]): A synchronous iterator yielding bytes objects.
199
- Returns:
200
- AsyncIterator[bytes]: An asynchronous iterator yielding the same bytes objects.
201
- Example:
202
- ```
203
- sync_iter = iter([b'data1', b'data2'])
204
- async for chunk in sync_iterator_to_async(sync_iter):
205
- await process_chunk(chunk)
206
- ```
207
- """
208
-
209
- while True:
210
- try:
211
- chunk = await to_thread.run_sync(_call_next, iterator)
212
- except StopAsyncIteration:
213
- break
214
-
215
- yield chunk
216
-
217
-
218
148
  async def make_async_iterator(iterable: Iterable[bytes]) -> AsyncIterator[bytes]:
219
149
  for item in iterable:
220
150
  yield item
@@ -286,173 +216,3 @@ class GeneratorWithReturnValue:
286
216
  self.value = exc.value
287
217
  raise
288
218
  return chunk
289
-
290
-
291
- def print_sqlite_state(conn: sqlite3.Connection) -> str:
292
- """
293
- Print all tables and their rows in a pretty format suitable for inline snapshots.
294
-
295
- Args:
296
- conn: SQLite database connection
297
-
298
- Returns:
299
- Formatted string representation of the database state
300
- """
301
- cursor = conn.cursor()
302
-
303
- # Get all table names
304
- cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
305
- tables = [row[0] for row in cursor.fetchall()]
306
-
307
- output_lines = []
308
- output_lines.append("=" * 80)
309
- output_lines.append("DATABASE SNAPSHOT")
310
- output_lines.append("=" * 80)
311
-
312
- for table_name in tables:
313
- # Get column information
314
- cursor.execute(f"PRAGMA table_info({table_name})")
315
- columns = cursor.fetchall()
316
- column_names = [col[1] for col in columns]
317
- column_types = {col[1]: col[2] for col in columns}
318
-
319
- # Get all rows
320
- cursor.execute(f"SELECT * FROM {table_name}")
321
- rows = cursor.fetchall()
322
-
323
- output_lines.append("")
324
- output_lines.append(f"TABLE: {table_name}")
325
- output_lines.append("-" * 80)
326
- output_lines.append(f"Rows: {len(rows)}")
327
- output_lines.append("")
328
-
329
- if not rows:
330
- output_lines.append(" (empty)")
331
- continue
332
-
333
- # Format each row
334
- for idx, row in enumerate(rows, 1):
335
- output_lines.append(f" Row {idx}:")
336
-
337
- for col_name, value in zip(column_names, row):
338
- col_type = column_types[col_name]
339
- formatted_value = format_value(value, col_name, col_type)
340
- output_lines.append(f" {col_name:15} = {formatted_value}")
341
-
342
- if idx < len(rows):
343
- output_lines.append("")
344
-
345
- output_lines.append("")
346
- output_lines.append("=" * 80)
347
-
348
- result = "\n".join(output_lines)
349
- return result
350
-
351
-
352
- async def aprint_sqlite_state(conn: anysqlite.Connection) -> str:
353
- """
354
- Print all tables and their rows in a pretty format suitable for inline snapshots.
355
-
356
- Args:
357
- conn: SQLite database connection
358
-
359
- Returns:
360
- Formatted string representation of the database state
361
- """
362
- cursor = await conn.cursor()
363
-
364
- # Get all table names
365
- await cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
366
- tables = [row[0] for row in await cursor.fetchall()]
367
-
368
- output_lines = []
369
- output_lines.append("=" * 80)
370
- output_lines.append("DATABASE SNAPSHOT")
371
- output_lines.append("=" * 80)
372
-
373
- for table_name in tables:
374
- # Get column information
375
- await cursor.execute(f"PRAGMA table_info({table_name})")
376
- columns = await cursor.fetchall()
377
- column_names = [col[1] for col in columns]
378
- column_types = {col[1]: col[2] for col in columns}
379
-
380
- # Get all rows
381
- await cursor.execute(f"SELECT * FROM {table_name}")
382
- rows = await cursor.fetchall()
383
-
384
- output_lines.append("")
385
- output_lines.append(f"TABLE: {table_name}")
386
- output_lines.append("-" * 80)
387
- output_lines.append(f"Rows: {len(rows)}")
388
- output_lines.append("")
389
-
390
- if not rows:
391
- output_lines.append(" (empty)")
392
- continue
393
-
394
- # Format each row
395
- for idx, row in enumerate(rows, 1):
396
- output_lines.append(f" Row {idx}:")
397
-
398
- for col_name, value in zip(column_names, row):
399
- col_type = column_types[col_name]
400
- formatted_value = format_value(value, col_name, col_type)
401
- output_lines.append(f" {col_name:15} = {formatted_value}")
402
-
403
- if idx < len(rows):
404
- output_lines.append("")
405
-
406
- output_lines.append("")
407
- output_lines.append("=" * 80)
408
-
409
- result = "\n".join(output_lines)
410
- return result
411
-
412
-
413
- def format_value(value: Any, col_name: str, col_type: str) -> str:
414
- """Format a value for display based on its type and column name."""
415
-
416
- if value is None:
417
- return "NULL"
418
-
419
- # Handle BLOB columns
420
- if col_type.upper() == "BLOB":
421
- if isinstance(value, bytes):
422
- # Try to decode as UTF-8 string first
423
- try:
424
- decoded = value.decode("utf-8")
425
- # Check if it looks like JSON
426
- if decoded.strip().startswith("{") or decoded.strip().startswith("["):
427
- try:
428
- parsed = json.loads(decoded)
429
- return f"(JSON) {json.dumps(parsed, indent=2)}"
430
- except json.JSONDecodeError:
431
- pass
432
- # Show string if it's printable
433
- if all(32 <= ord(c) <= 126 or c in "\n\r\t" for c in decoded):
434
- return f"(str) '{decoded}'"
435
- except UnicodeDecodeError:
436
- pass
437
-
438
- # Show hex representation for binary data
439
- hex_str = value.hex()
440
- if len(hex_str) > 64:
441
- return f"(bytes) 0x{hex_str[:60]}... ({len(value)} bytes)"
442
- return f"(bytes) 0x{hex_str} ({len(value)} bytes)"
443
- return repr(value)
444
-
445
- # Handle timestamps - ONLY show date, not the raw timestamp
446
- if col_name.endswith("_at") and isinstance(value, (int, float)):
447
- try:
448
- dt = date.fromtimestamp(value)
449
- return dt.isoformat() # Changed: removed the timestamp prefix
450
- except (ValueError, OSError):
451
- return str(value)
452
-
453
- # Handle TEXT columns
454
- if col_type.upper() == "TEXT":
455
- return f"'{value}'"
456
-
457
- # Handle other types
458
- return str(value)
hishel/httpx.py CHANGED
@@ -4,8 +4,6 @@ import ssl
4
4
  import typing as t
5
5
  from typing import AsyncIterator, Iterable, Iterator, Union, overload
6
6
 
7
- import httpx
8
-
9
7
  from hishel import Headers, Request, Response
10
8
  from hishel._async_cache import AsyncCacheProxy
11
9
  from hishel._core._base._storages._base import AsyncBaseStorage, SyncBaseStorage
@@ -15,6 +13,15 @@ from hishel._core._spec import (
15
13
  from hishel._core.models import AnyIterable
16
14
  from hishel._sync_cache import SyncCacheProxy
17
15
 
16
+ try:
17
+ import httpx
18
+ except ImportError as e:
19
+ raise ImportError(
20
+ "httpx is required to use hishel.httpx module. "
21
+ "Please install hishel with the 'httpx' extra, "
22
+ "e.g., 'pip install hishel[httpx]'."
23
+ ) from e
24
+
18
25
  SOCKET_OPTION = t.Union[
19
26
  t.Tuple[int, int, int],
20
27
  t.Tuple[int, int, t.Union[bytes, bytearray]],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hishel
3
- Version: 1.0.0.dev0
3
+ Version: 1.0.0.dev1
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
@@ -24,12 +24,14 @@ Classifier: Programming Language :: Python :: 3.13
24
24
  Classifier: Programming Language :: Python :: 3.14
25
25
  Classifier: Topic :: Internet :: WWW/HTTP
26
26
  Requires-Python: >=3.9
27
- Requires-Dist: anyio>=4.9.0
28
- Requires-Dist: anysqlite>=0.0.5
29
- Requires-Dist: httpx>=0.28.0
30
27
  Requires-Dist: msgpack>=1.1.2
31
28
  Requires-Dist: typing-extensions>=4.14.1
29
+ Provides-Extra: async
30
+ Requires-Dist: anyio>=4.9.0; extra == 'async'
31
+ Requires-Dist: anysqlite>=0.0.5; extra == 'async'
32
32
  Provides-Extra: httpx
33
+ Requires-Dist: anyio>=4.9.0; extra == 'httpx'
34
+ Requires-Dist: anysqlite>=0.0.5; extra == 'httpx'
33
35
  Requires-Dist: httpx>=0.28.1; extra == 'httpx'
34
36
  Provides-Extra: requests
35
37
  Requires-Dist: requests>=2.32.5; extra == 'requests'
@@ -249,73 +251,48 @@ Hishel is inspired by and builds upon the excellent work in the Python HTTP ecos
249
251
  <strong>Made with ❤️ by <a href="https://github.com/karpetrosyan">Kar Petrosyan</a></strong>
250
252
  </p>
251
253
 
252
- ## [1.0.0dev0] - 2025-10-19
254
+ # Changelog
253
255
 
254
- ### ⚙️ Miscellaneous Tasks
256
+ All notable changes to this project will be documented in this file.
255
257
 
256
- - *(docs)* Use mike powered versioning
257
- - *(docs)* Improve docs versioning, deploy dev doc on ci
258
- ## [0.1.5] - 2025-10-18
258
+ ## 1.0.0dev1 - 2025-10-21
259
+ ### <!-- 7 -->⚙️ Miscellaneous Tasks
260
+ - Remove some redundant utils methods
259
261
 
260
- ### 🚀 Features
262
+ ## 1.0.0.dev0 - 2025-10-19
263
+ ### <!-- 7 -->⚙️ Miscellaneous Tasks
264
+ - Use mike powered versioning
265
+ - Improve docs versioning, deploy dev doc on ci
261
266
 
262
- - *(perf)* Set chunk size to 128KB for httpx to reduce SQLite read/writes
267
+ ## 0.1.5 - 2025-10-18
268
+ ### <!-- 0 -->🚀 Features
269
+ - Set chunk size to 128KB for httpx to reduce SQLite read/writes
263
270
  - Better cache-control parsing
264
271
  - Add close method to storages API (#384)
265
- - *(perf)* Increase requests buffer size to 128KB, disable charset detection
272
+ - Increase requests buffer size to 128KB, disable charset detection
266
273
 
267
- ### 🐛 Bug Fixes
268
-
269
- - *(docs)* Fix some line breaks
270
-
271
- ### ⚙️ Miscellaneous Tasks
274
+ ### <!-- 1 -->🐛 Bug Fixes
275
+ - Fix some line breaks
272
276
 
277
+ ### <!-- 7 -->⚙️ Miscellaneous Tasks
273
278
  - Remove some redundant files from repo
274
- ## [0.1.4] - 2025-10-14
275
-
276
- ### 🚀 Features
277
279
 
280
+ ## 0.1.4 - 2025-10-14
281
+ ### <!-- 0 -->🚀 Features
278
282
  - Add support for a sans-IO API (#366)
279
283
  - Allow already consumed streams with `CacheTransport` (#377)
280
284
  - Add sqlite storage for beta storages
281
285
  - Get rid of some locks from sqlite storage
282
286
  - Better async implemetation for sqlite storage
283
287
 
284
- ### 🐛 Bug Fixes
285
-
288
+ ### <!-- 1 -->🐛 Bug Fixes
286
289
  - Create an sqlite file in a cache folder
287
290
  - Fix beta imports
288
291
 
289
- ### ⚙️ Miscellaneous Tasks
290
-
292
+ ### <!-- 7 -->⚙️ Miscellaneous Tasks
291
293
  - Improve CI (#369)
292
- - *(internal)* Remove src folder (#373)
293
- - *(internal)* Temporary remove python3.14 from CI
294
- - *(tests)* Add sqlite tests for new storage
295
- - *(tests)* Move some tests to beta
296
- ## [0.1.3] - 2025-07-06
297
-
298
- ### 🚀 Features
299
-
300
- - Support providing a path prefix to S3 storage (#342)
301
-
302
- ### 📚 Documentation
303
-
304
- - Update link to httpx transports page (#337)
305
- ## [0.1.2] - 2025-04-04
306
-
307
- ### 🐛 Bug Fixes
308
-
309
- - Requirements.txt to reduce vulnerabilities (#263)
310
- ## [0.0.30] - 2024-07-12
311
-
312
- ### 🐛 Bug Fixes
313
-
314
- - Requirements.txt to reduce vulnerabilities (#245)
315
- - Requirements.txt to reduce vulnerabilities (#255)
316
- ## [0.0.27] - 2024-05-31
317
-
318
- ### 🐛 Bug Fixes
294
+ - Remove src folder (#373)
295
+ - Temporary remove python3.14 from CI
296
+ - Add sqlite tests for new storage
297
+ - Move some tests to beta
319
298
 
320
- - *(redis)* Do not update metadata with negative ttl (#231)
321
- ## [0.0.1] - 2023-07-22
@@ -1,19 +1,19 @@
1
1
  hishel/__init__.py,sha256=byj_IhCjFMaBcp6R8iyRlQV-3R4uTfH44PQzB4lVe1g,1447
2
2
  hishel/_async_cache.py,sha256=gE5CygC7FG9htBMfxul7carRRNph8zcMlSoOcB_LNTY,6792
3
3
  hishel/_sync_cache.py,sha256=lfkWHJFK527peESMaufjKSbXBriidc09tOwBwub2t34,6538
4
- hishel/_utils.py,sha256=uO8PcY_E1sHSgBGzZ2GNB4kpKqAlzmnzPCc3s-yDd44,13826
5
- hishel/httpx.py,sha256=HcJ5iO9PgkEOp92ti8013N6m1IotLajwd9M_DLsmrX0,10997
4
+ hishel/_utils.py,sha256=hQURFuNdS7sjHeVGu12MQzJewjFdnVrdOki3CjBfZvs,6162
5
+ hishel/httpx.py,sha256=vscNB426VIhh0f5qQVkGA_WpwVvaKxbI6gnsHrBA_D0,11226
6
6
  hishel/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  hishel/requests.py,sha256=eiWcwCId04DucnquCsU12tj9cDZcn-cjZ9MYniVuNeo,6429
8
8
  hishel/_core/__init__.py,sha256=byj_IhCjFMaBcp6R8iyRlQV-3R4uTfH44PQzB4lVe1g,1447
9
9
  hishel/_core/_headers.py,sha256=ii4x2L6GoQFpqpgg28OtFh7p2DoM9mhE4q6CjW6xUWc,17473
10
10
  hishel/_core/_spec.py,sha256=d2ZnTXttyT4zuVq9xHAO86VGJxAEBxD2a8WMyEgOuAo,102702
11
11
  hishel/_core/models.py,sha256=5qwo1WifrDeZdXag7M5rh0hJuVsm1N-sF3UagQ5LcLc,5519
12
- hishel/_core/_async/_storages/_sqlite.py,sha256=wIO0UaFzal9qoVqDVczzcsW0kGUjBQD-ikauc_MN414,14704
12
+ hishel/_core/_async/_storages/_sqlite.py,sha256=QPbNtNMA7vYjpt8bSPFIZ4u4c3UCH6eDfUWnH6WprTU,17787
13
13
  hishel/_core/_base/_storages/_base.py,sha256=xLJGTBlFK8DVrQMgRMtGXJnYRUmNB-iYkk7S-BtMx8s,8516
14
14
  hishel/_core/_base/_storages/_packing.py,sha256=NFMpSvYYTDBNkzwpjj5l4w-JOPLc19oAEDqDEQJ7VZI,4873
15
- hishel/_core/_sync/_storages/_sqlite.py,sha256=TDm9jXIWtd54m4_8AiVApxZVmbBoeFVi3E6s-vGzDjs,14138
16
- hishel-1.0.0.dev0.dist-info/METADATA,sha256=EpqEHRIGfzVXqMiRefCa_NZ9AlbjzVToXfnK-GBrs9o,9993
17
- hishel-1.0.0.dev0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
- hishel-1.0.0.dev0.dist-info/licenses/LICENSE,sha256=1qQj7pE0V2O9OIedvyOgLGLvZLaPd3nFEup3IBEOZjQ,1493
19
- hishel-1.0.0.dev0.dist-info/RECORD,,
15
+ hishel/_core/_sync/_storages/_sqlite.py,sha256=kvAcV2FttZstWEfCczBxj30-PYdj1y9lzh0x3hNusKY,17210
16
+ hishel-1.0.0.dev1.dist-info/METADATA,sha256=IW6_UTqzUl-k8Pcyil_6brlEgd-gKomu1OyeZxrN3dU,9727
17
+ hishel-1.0.0.dev1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
+ hishel-1.0.0.dev1.dist-info/licenses/LICENSE,sha256=1qQj7pE0V2O9OIedvyOgLGLvZLaPd3nFEup3IBEOZjQ,1493
19
+ hishel-1.0.0.dev1.dist-info/RECORD,,