hishel 1.0.0.dev3__tar.gz → 1.1.1__tar.gz

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 (26) hide show
  1. {hishel-1.0.0.dev3 → hishel-1.1.1}/CHANGELOG.md +39 -0
  2. {hishel-1.0.0.dev3 → hishel-1.1.1}/PKG-INFO +139 -16
  3. {hishel-1.0.0.dev3 → hishel-1.1.1}/README.md +99 -15
  4. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/__init__.py +11 -3
  5. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/_async_cache.py +70 -37
  6. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/_async_httpx.py +8 -15
  7. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/_core/_spec.py +17 -40
  8. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/_core/_storages/_async_sqlite.py +5 -2
  9. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/_core/_storages/_sync_sqlite.py +5 -2
  10. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/_core/models.py +87 -11
  11. hishel-1.1.1/hishel/_policies.py +49 -0
  12. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/_sync_cache.py +70 -37
  13. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/_sync_httpx.py +8 -15
  14. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/_utils.py +2 -2
  15. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/asgi.py +16 -16
  16. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/requests.py +3 -5
  17. {hishel-1.0.0.dev3 → hishel-1.1.1}/pyproject.toml +13 -11
  18. {hishel-1.0.0.dev3 → hishel-1.1.1}/.gitignore +0 -0
  19. {hishel-1.0.0.dev3 → hishel-1.1.1}/LICENSE +0 -0
  20. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/_core/_headers.py +0 -0
  21. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/_core/_storages/_async_base.py +0 -0
  22. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/_core/_storages/_packing.py +0 -0
  23. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/_core/_storages/_sync_base.py +0 -0
  24. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/fastapi.py +0 -0
  25. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/httpx.py +0 -0
  26. {hishel-1.0.0.dev3 → hishel-1.1.1}/hishel/py.typed +0 -0
@@ -2,6 +2,45 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 1.1.1 - 2025-11-01
6
+ ### ⚙️ Miscellaneous Tasks
7
+ - Bump the python-packages group with 10 updates (#396)
8
+
9
+ ### 📦 Dependencies
10
+ - Bump actions/upload-artifact from 4 to 5 (#395)
11
+ - Bump actions/download-artifact from 4 to 6 (#394)
12
+ - Bump astral-sh/setup-uv from 5 to 7 (#393)
13
+
14
+ ## 1.1.0 - 2025-10-31
15
+ ### ⚙️ Miscellaneous Tasks
16
+ - Add in memory example
17
+
18
+ ### 🐛 Bug Fixes
19
+ - Pass any response with non-expected status code on revalidation to client
20
+ - Pass any response with non-expected status code on revalidation to client
21
+
22
+ ### 🚀 Features
23
+ - Allow setting storage base with via `database_path` for sqlite storage
24
+
25
+ ## 1.0.0 - 2025-10-28
26
+ ### ⚙️ Miscellaneous Tasks
27
+ - Add examples, improve docs
28
+
29
+ ## 1.0.0b1 - 2025-10-28
30
+ ### ♻️ Refactoring
31
+ - Add policies
32
+
33
+ ### ⚙️ Miscellaneous Tasks
34
+ - Improve sans-io diagram colors
35
+ - Add graphql docs
36
+
37
+ ### 🐛 Bug Fixes
38
+ - Body-sensitive responses caching
39
+ - Filter out `Transfer-Encoding` header for asgi responses
40
+
41
+ ### 🚀 Features
42
+ - Add global `use_body_key` setting
43
+
5
44
  ## 1.0.0.dev3 - 2025-10-26
6
45
  ### ♻️ Refactoring
7
46
  - Replace pairs with entries, simplify storage API
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hishel
3
- Version: 1.0.0.dev3
3
+ Version: 1.1.1
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
@@ -82,9 +82,10 @@ Description-Content-Type: text/markdown
82
82
  - 🔄 **Async & Sync** - Full support for both synchronous and asynchronous workflows
83
83
  - 🎨 **Type Safe** - Fully typed with comprehensive type hints
84
84
  - 🧪 **Well Tested** - Extensive test coverage and battle-tested
85
- - 🎛️ **Configurable** - Fine-grained control over caching behavior
86
- - **Memory Efficient** - Streaming support prevents loading large payloads into memory
85
+ - 🎛️ **Configurable** - Fine-grained control over caching behavior with flexible policies
86
+ - 💨 **Memory Efficient** - Streaming support prevents loading large payloads into memory
87
87
  - 🌐 **Universal** - Works with any ASGI application (Starlette, Litestar, BlackSheep, etc.)
88
+ - 🎯 **GraphQL Support** - Cache GraphQL queries with body-sensitive content caching
88
89
 
89
90
  ## 📦 Installation
90
91
 
@@ -175,12 +176,14 @@ from hishel.asgi import ASGICacheMiddleware
175
176
  app = ASGICacheMiddleware(app)
176
177
 
177
178
  # Or configure with options
178
- from hishel import AsyncSqliteStorage, CacheOptions
179
+ from hishel import AsyncSqliteStorage, CacheOptions, SpecificationPolicy
179
180
 
180
181
  app = ASGICacheMiddleware(
181
182
  app,
182
183
  storage=AsyncSqliteStorage(),
183
- cache_options=CacheOptions(shared=True)
184
+ policy=SpecificationPolicy(
185
+ cache_options=CacheOptions(shared=True)
186
+ ),
184
187
  )
185
188
  ```
186
189
 
@@ -225,21 +228,49 @@ async def get_data():
225
228
 
226
229
  ## 🎛️ Advanced Configuration
227
230
 
228
- ### Custom Cache Options
231
+ ### Caching Policies
232
+
233
+ Hishel supports two types of caching policies:
234
+
235
+ **SpecificationPolicy** - RFC 9111 compliant HTTP caching (default):
229
236
 
230
237
  ```python
231
- from hishel import CacheOptions
238
+ from hishel import CacheOptions, SpecificationPolicy
232
239
  from hishel.httpx import SyncCacheClient
233
240
 
234
241
  client = SyncCacheClient(
235
- cache_options=CacheOptions(
236
- shared=False, # Use as private cache (browser-like)
237
- supported_methods=["GET", "HEAD", "POST"], # Cache GET, HEAD, and POST
238
- allow_stale=True # Allow serving stale responses
242
+ policy=SpecificationPolicy(
243
+ cache_options=CacheOptions(
244
+ shared=False, # Use as private cache (browser-like)
245
+ supported_methods=["GET", "HEAD", "POST"], # Cache GET, HEAD, and POST
246
+ allow_stale=True # Allow serving stale responses
247
+ )
248
+ )
249
+ )
250
+ ```
251
+
252
+ **FilterPolicy** - Custom filtering logic for fine-grained control:
253
+
254
+ ```python
255
+ from hishel import FilterPolicy, BaseFilter, Request
256
+ from hishel.httpx import AsyncCacheClient
257
+
258
+ class CacheOnlyAPIRequests(BaseFilter[Request]):
259
+ def needs_body(self) -> bool:
260
+ return False
261
+
262
+ def apply(self, item: Request, body: bytes | None) -> bool:
263
+ return "/api/" in str(item.url)
264
+
265
+ client = AsyncCacheClient(
266
+ policy=FilterPolicy(
267
+ request_filters=[CacheOnlyAPIRequests()]
239
268
  )
240
269
  )
241
270
  ```
242
271
 
272
+ [Learn more about policies →](https://hishel.com/dev/policies/)
273
+
243
274
  ### Custom Storage Backend
244
275
 
245
276
  ```python
@@ -255,6 +286,60 @@ storage = SyncSqliteStorage(
255
286
  client = SyncCacheClient(storage=storage)
256
287
  ```
257
288
 
289
+ ### GraphQL and Body-Sensitive Caching
290
+
291
+ Cache GraphQL queries and other POST requests by including the request body in the cache key.
292
+
293
+ **Using per-request header:**
294
+
295
+ ```python
296
+ from hishel import FilterPolicy
297
+ from hishel.httpx import SyncCacheClient
298
+
299
+ client = SyncCacheClient(
300
+ policy=FilterPolicy()
301
+ )
302
+
303
+ # Cache GraphQL queries - different queries get different cache entries
304
+ graphql_query = """
305
+ query GetUser($id: ID!) {
306
+ user(id: $id) {
307
+ name
308
+ email
309
+ }
310
+ }
311
+ """
312
+
313
+ response = client.post(
314
+ "https://api.example.com/graphql",
315
+ json={"query": graphql_query, "variables": {"id": "123"}},
316
+ headers={"X-Hishel-Body-Key": "true"} # Enable body-based caching
317
+ )
318
+
319
+ # Different query will be cached separately
320
+ response = client.post(
321
+ "https://api.example.com/graphql",
322
+ json={"query": graphql_query, "variables": {"id": "456"}},
323
+ headers={"X-Hishel-Body-Key": "true"}
324
+ )
325
+ ```
326
+
327
+ **Using global configuration:**
328
+
329
+ ```python
330
+ from hishel.httpx import SyncCacheClient
331
+ from hishel import FilterPolicy
332
+
333
+ # Enable body-based caching for all requests
334
+ client = SyncCacheClient(policy=FilterPolicy(use_body_key=True))
335
+
336
+ # All POST requests automatically include body in cache key
337
+ response = client.post(
338
+ "https://api.example.com/graphql",
339
+ json={"query": graphql_query, "variables": {"id": "123"}}
340
+ )
341
+ ```
342
+
258
343
  ## 🏗️ Architecture
259
344
 
260
345
  Hishel uses a **sans-I/O state machine** architecture that separates HTTP caching logic from I/O operations:
@@ -266,13 +351,11 @@ Hishel uses a **sans-I/O state machine** architecture that separates HTTP cachin
266
351
 
267
352
  ## 🔮 Roadmap
268
353
 
269
- While Hishel currently supports HTTPX and Requests, we're actively working on:
354
+ We're actively working on:
270
355
 
271
- - 🎯 Additional HTTP client integrations
272
- - 🎯 Server-side caching support
273
- - 🎯 More storage backends
274
- - 🎯 Advanced caching strategies
275
356
  - 🎯 Performance optimizations
357
+ - 🎯 More integrations
358
+ - 🎯 Partial responses support
276
359
 
277
360
  ## 📚 Documentation
278
361
 
@@ -284,6 +367,7 @@ Comprehensive documentation is available at [https://hishel.com/dev](https://his
284
367
  - [ASGI Integration](https://hishel.com/dev/asgi)
285
368
  - [FastAPI Integration](https://hishel.com/dev/fastapi)
286
369
  - [BlackSheep Integration](https://hishel.com/dev/integrations/blacksheep)
370
+ - [GraphQL Integration](https://hishel.com/dev/integrations/graphql)
287
371
  - [Storage Backends](https://hishel.com/dev/storages)
288
372
  - [Request/Response Metadata](https://hishel.com/dev/metadata)
289
373
  - [RFC 9111 Specification](https://hishel.com/dev/specification)
@@ -326,6 +410,45 @@ Hishel is inspired by and builds upon the excellent work in the Python HTTP ecos
326
410
 
327
411
  All notable changes to this project will be documented in this file.
328
412
 
413
+ ## 1.1.1 - 2025-11-01
414
+ ### ⚙️ Miscellaneous Tasks
415
+ - Bump the python-packages group with 10 updates (#396)
416
+
417
+ ### 📦 Dependencies
418
+ - Bump actions/upload-artifact from 4 to 5 (#395)
419
+ - Bump actions/download-artifact from 4 to 6 (#394)
420
+ - Bump astral-sh/setup-uv from 5 to 7 (#393)
421
+
422
+ ## 1.1.0 - 2025-10-31
423
+ ### ⚙️ Miscellaneous Tasks
424
+ - Add in memory example
425
+
426
+ ### 🐛 Bug Fixes
427
+ - Pass any response with non-expected status code on revalidation to client
428
+ - Pass any response with non-expected status code on revalidation to client
429
+
430
+ ### 🚀 Features
431
+ - Allow setting storage base with via `database_path` for sqlite storage
432
+
433
+ ## 1.0.0 - 2025-10-28
434
+ ### ⚙️ Miscellaneous Tasks
435
+ - Add examples, improve docs
436
+
437
+ ## 1.0.0b1 - 2025-10-28
438
+ ### ♻️ Refactoring
439
+ - Add policies
440
+
441
+ ### ⚙️ Miscellaneous Tasks
442
+ - Improve sans-io diagram colors
443
+ - Add graphql docs
444
+
445
+ ### 🐛 Bug Fixes
446
+ - Body-sensitive responses caching
447
+ - Filter out `Transfer-Encoding` header for asgi responses
448
+
449
+ ### 🚀 Features
450
+ - Add global `use_body_key` setting
451
+
329
452
  ## 1.0.0.dev3 - 2025-10-26
330
453
  ### ♻️ Refactoring
331
454
  - Replace pairs with entries, simplify storage API
@@ -41,9 +41,10 @@
41
41
  - 🔄 **Async & Sync** - Full support for both synchronous and asynchronous workflows
42
42
  - 🎨 **Type Safe** - Fully typed with comprehensive type hints
43
43
  - 🧪 **Well Tested** - Extensive test coverage and battle-tested
44
- - 🎛️ **Configurable** - Fine-grained control over caching behavior
45
- - **Memory Efficient** - Streaming support prevents loading large payloads into memory
44
+ - 🎛️ **Configurable** - Fine-grained control over caching behavior with flexible policies
45
+ - 💨 **Memory Efficient** - Streaming support prevents loading large payloads into memory
46
46
  - 🌐 **Universal** - Works with any ASGI application (Starlette, Litestar, BlackSheep, etc.)
47
+ - 🎯 **GraphQL Support** - Cache GraphQL queries with body-sensitive content caching
47
48
 
48
49
  ## 📦 Installation
49
50
 
@@ -134,12 +135,14 @@ from hishel.asgi import ASGICacheMiddleware
134
135
  app = ASGICacheMiddleware(app)
135
136
 
136
137
  # Or configure with options
137
- from hishel import AsyncSqliteStorage, CacheOptions
138
+ from hishel import AsyncSqliteStorage, CacheOptions, SpecificationPolicy
138
139
 
139
140
  app = ASGICacheMiddleware(
140
141
  app,
141
142
  storage=AsyncSqliteStorage(),
142
- cache_options=CacheOptions(shared=True)
143
+ policy=SpecificationPolicy(
144
+ cache_options=CacheOptions(shared=True)
145
+ ),
143
146
  )
144
147
  ```
145
148
 
@@ -184,21 +187,49 @@ async def get_data():
184
187
 
185
188
  ## 🎛️ Advanced Configuration
186
189
 
187
- ### Custom Cache Options
190
+ ### Caching Policies
191
+
192
+ Hishel supports two types of caching policies:
193
+
194
+ **SpecificationPolicy** - RFC 9111 compliant HTTP caching (default):
188
195
 
189
196
  ```python
190
- from hishel import CacheOptions
197
+ from hishel import CacheOptions, SpecificationPolicy
191
198
  from hishel.httpx import SyncCacheClient
192
199
 
193
200
  client = SyncCacheClient(
194
- cache_options=CacheOptions(
195
- shared=False, # Use as private cache (browser-like)
196
- supported_methods=["GET", "HEAD", "POST"], # Cache GET, HEAD, and POST
197
- allow_stale=True # Allow serving stale responses
201
+ policy=SpecificationPolicy(
202
+ cache_options=CacheOptions(
203
+ shared=False, # Use as private cache (browser-like)
204
+ supported_methods=["GET", "HEAD", "POST"], # Cache GET, HEAD, and POST
205
+ allow_stale=True # Allow serving stale responses
206
+ )
207
+ )
208
+ )
209
+ ```
210
+
211
+ **FilterPolicy** - Custom filtering logic for fine-grained control:
212
+
213
+ ```python
214
+ from hishel import FilterPolicy, BaseFilter, Request
215
+ from hishel.httpx import AsyncCacheClient
216
+
217
+ class CacheOnlyAPIRequests(BaseFilter[Request]):
218
+ def needs_body(self) -> bool:
219
+ return False
220
+
221
+ def apply(self, item: Request, body: bytes | None) -> bool:
222
+ return "/api/" in str(item.url)
223
+
224
+ client = AsyncCacheClient(
225
+ policy=FilterPolicy(
226
+ request_filters=[CacheOnlyAPIRequests()]
198
227
  )
199
228
  )
200
229
  ```
201
230
 
231
+ [Learn more about policies →](https://hishel.com/dev/policies/)
232
+
202
233
  ### Custom Storage Backend
203
234
 
204
235
  ```python
@@ -214,6 +245,60 @@ storage = SyncSqliteStorage(
214
245
  client = SyncCacheClient(storage=storage)
215
246
  ```
216
247
 
248
+ ### GraphQL and Body-Sensitive Caching
249
+
250
+ Cache GraphQL queries and other POST requests by including the request body in the cache key.
251
+
252
+ **Using per-request header:**
253
+
254
+ ```python
255
+ from hishel import FilterPolicy
256
+ from hishel.httpx import SyncCacheClient
257
+
258
+ client = SyncCacheClient(
259
+ policy=FilterPolicy()
260
+ )
261
+
262
+ # Cache GraphQL queries - different queries get different cache entries
263
+ graphql_query = """
264
+ query GetUser($id: ID!) {
265
+ user(id: $id) {
266
+ name
267
+ email
268
+ }
269
+ }
270
+ """
271
+
272
+ response = client.post(
273
+ "https://api.example.com/graphql",
274
+ json={"query": graphql_query, "variables": {"id": "123"}},
275
+ headers={"X-Hishel-Body-Key": "true"} # Enable body-based caching
276
+ )
277
+
278
+ # Different query will be cached separately
279
+ response = client.post(
280
+ "https://api.example.com/graphql",
281
+ json={"query": graphql_query, "variables": {"id": "456"}},
282
+ headers={"X-Hishel-Body-Key": "true"}
283
+ )
284
+ ```
285
+
286
+ **Using global configuration:**
287
+
288
+ ```python
289
+ from hishel.httpx import SyncCacheClient
290
+ from hishel import FilterPolicy
291
+
292
+ # Enable body-based caching for all requests
293
+ client = SyncCacheClient(policy=FilterPolicy(use_body_key=True))
294
+
295
+ # All POST requests automatically include body in cache key
296
+ response = client.post(
297
+ "https://api.example.com/graphql",
298
+ json={"query": graphql_query, "variables": {"id": "123"}}
299
+ )
300
+ ```
301
+
217
302
  ## 🏗️ Architecture
218
303
 
219
304
  Hishel uses a **sans-I/O state machine** architecture that separates HTTP caching logic from I/O operations:
@@ -225,13 +310,11 @@ Hishel uses a **sans-I/O state machine** architecture that separates HTTP cachin
225
310
 
226
311
  ## 🔮 Roadmap
227
312
 
228
- While Hishel currently supports HTTPX and Requests, we're actively working on:
313
+ We're actively working on:
229
314
 
230
- - 🎯 Additional HTTP client integrations
231
- - 🎯 Server-side caching support
232
- - 🎯 More storage backends
233
- - 🎯 Advanced caching strategies
234
315
  - 🎯 Performance optimizations
316
+ - 🎯 More integrations
317
+ - 🎯 Partial responses support
235
318
 
236
319
  ## 📚 Documentation
237
320
 
@@ -243,6 +326,7 @@ Comprehensive documentation is available at [https://hishel.com/dev](https://his
243
326
  - [ASGI Integration](https://hishel.com/dev/asgi)
244
327
  - [FastAPI Integration](https://hishel.com/dev/fastapi)
245
328
  - [BlackSheep Integration](https://hishel.com/dev/integrations/blacksheep)
329
+ - [GraphQL Integration](https://hishel.com/dev/integrations/graphql)
246
330
  - [Storage Backends](https://hishel.com/dev/storages)
247
331
  - [Request/Response Metadata](https://hishel.com/dev/metadata)
248
332
  - [RFC 9111 Specification](https://hishel.com/dev/specification)
@@ -15,17 +15,20 @@ from hishel._core._spec import (
15
15
  NeedToBeUpdated as NeedToBeUpdated,
16
16
  State as State,
17
17
  StoreAndUse as StoreAndUse,
18
- create_idle_state as create_idle_state,
19
18
  )
20
19
  from hishel._core.models import (
21
20
  Entry as Entry,
22
21
  EntryMeta as EntryMeta,
23
22
  Request as Request,
24
- Response,
23
+ Response as Response,
24
+ ResponseMetadata as ResponseMetadata,
25
+ RequestMetadata as RequestMetadata,
25
26
  )
26
27
  from hishel._async_cache import AsyncCacheProxy as AsyncCacheProxy
27
28
  from hishel._sync_cache import SyncCacheProxy as SyncCacheProxy
28
29
 
30
+ from hishel._policies import SpecificationPolicy, FilterPolicy, CachePolicy
31
+
29
32
  __all__ = (
30
33
  # New API
31
34
  ## States
@@ -41,12 +44,13 @@ __all__ = (
41
44
  "StoreAndUse",
42
45
  "CouldNotBeStored",
43
46
  "InvalidateEntries",
44
- "create_idle_state",
45
47
  ## Models
46
48
  "Request",
47
49
  "Response",
48
50
  "Entry",
49
51
  "EntryMeta",
52
+ "RequestMetadata",
53
+ "ResponseMetadata",
50
54
  ## Headers
51
55
  "Headers",
52
56
  ## Storages
@@ -57,4 +61,8 @@ __all__ = (
57
61
  # Proxy
58
62
  "AsyncCacheProxy",
59
63
  "SyncCacheProxy",
64
+ # Policies
65
+ "CachePolicy",
66
+ "SpecificationPolicy",
67
+ "FilterPolicy",
60
68
  )