hishel 1.0.0.dev2__py3-none-any.whl → 1.1.0__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 +26 -17
- hishel/_async_cache.py +104 -65
- hishel/_async_httpx.py +236 -0
- hishel/_core/_headers.py +11 -1
- hishel/_core/_spec.py +101 -120
- hishel/_core/_storages/_async_base.py +71 -0
- hishel/_core/{_async/_storages/_sqlite.py → _storages/_async_sqlite.py} +100 -134
- hishel/_core/_storages/_packing.py +144 -0
- hishel/_core/_storages/_sync_base.py +71 -0
- hishel/_core/{_sync/_storages/_sqlite.py → _storages/_sync_sqlite.py} +100 -134
- hishel/_core/models.py +93 -33
- hishel/_policies.py +49 -0
- hishel/_sync_cache.py +104 -65
- hishel/_sync_httpx.py +236 -0
- hishel/_utils.py +49 -2
- hishel/asgi.py +400 -0
- hishel/fastapi.py +263 -0
- hishel/httpx.py +3 -326
- hishel/requests.py +28 -22
- {hishel-1.0.0.dev2.dist-info → hishel-1.1.0.dist-info}/METADATA +225 -18
- hishel-1.1.0.dist-info/RECORD +24 -0
- hishel/_core/__init__.py +0 -59
- hishel/_core/_base/_storages/_base.py +0 -272
- hishel/_core/_base/_storages/_packing.py +0 -165
- hishel-1.0.0.dev2.dist-info/RECORD +0 -19
- {hishel-1.0.0.dev2.dist-info → hishel-1.1.0.dist-info}/WHEEL +0 -0
- {hishel-1.0.0.dev2.dist-info → hishel-1.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hishel
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
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
|
|
@@ -29,6 +29,8 @@ Requires-Dist: typing-extensions>=4.14.1
|
|
|
29
29
|
Provides-Extra: async
|
|
30
30
|
Requires-Dist: anyio>=4.9.0; extra == 'async'
|
|
31
31
|
Requires-Dist: anysqlite>=0.0.5; extra == 'async'
|
|
32
|
+
Provides-Extra: fastapi
|
|
33
|
+
Requires-Dist: fastapi>=0.119.1; extra == 'fastapi'
|
|
32
34
|
Provides-Extra: httpx
|
|
33
35
|
Requires-Dist: anyio>=4.9.0; extra == 'httpx'
|
|
34
36
|
Requires-Dist: anysqlite>=0.0.5; extra == 'httpx'
|
|
@@ -37,6 +39,7 @@ Provides-Extra: requests
|
|
|
37
39
|
Requires-Dist: requests>=2.32.5; extra == 'requests'
|
|
38
40
|
Description-Content-Type: text/markdown
|
|
39
41
|
|
|
42
|
+
|
|
40
43
|
<p align="center">
|
|
41
44
|
<img alt="Hishel Logo" width="350" src="https://raw.githubusercontent.com/karpetrosyan/hishel/master/docs/static/Shelkopryad_350x250_yellow.png#gh-dark-mode-only">
|
|
42
45
|
<img alt="Hishel Logo" width="350" src="https://raw.githubusercontent.com/karpetrosyan/hishel/master/docs/static/Shelkopryad_350x250_black.png#gh-light-mode-only">
|
|
@@ -73,14 +76,16 @@ Description-Content-Type: text/markdown
|
|
|
73
76
|
## ✨ Features
|
|
74
77
|
|
|
75
78
|
- 🎯 **RFC 9111 Compliant** - Fully compliant with the latest HTTP caching specification
|
|
76
|
-
- 🔌 **Easy Integration** - Drop-in support for HTTPX and
|
|
79
|
+
- 🔌 **Easy Integration** - Drop-in support for HTTPX, Requests, ASGI, FastAPI, and BlackSheep
|
|
77
80
|
- 💾 **Flexible Storage** - SQLite backend with more coming soon
|
|
78
81
|
- ⚡ **High Performance** - Efficient caching with minimal overhead
|
|
79
82
|
- 🔄 **Async & Sync** - Full support for both synchronous and asynchronous workflows
|
|
80
83
|
- 🎨 **Type Safe** - Fully typed with comprehensive type hints
|
|
81
84
|
- 🧪 **Well Tested** - Extensive test coverage and battle-tested
|
|
82
|
-
- 🎛️ **Configurable** - Fine-grained control over caching behavior
|
|
83
|
-
-
|
|
85
|
+
- 🎛️ **Configurable** - Fine-grained control over caching behavior with flexible policies
|
|
86
|
+
- 💨 **Memory Efficient** - Streaming support prevents loading large payloads into memory
|
|
87
|
+
- 🌐 **Universal** - Works with any ASGI application (Starlette, Litestar, BlackSheep, etc.)
|
|
88
|
+
- 🎯 **GraphQL Support** - Cache GraphQL queries with body-sensitive content caching
|
|
84
89
|
|
|
85
90
|
## 📦 Installation
|
|
86
91
|
|
|
@@ -90,19 +95,23 @@ pip install hishel
|
|
|
90
95
|
|
|
91
96
|
### Optional Dependencies
|
|
92
97
|
|
|
93
|
-
Install with specific
|
|
98
|
+
Install with specific integration support:
|
|
94
99
|
|
|
95
100
|
```bash
|
|
96
101
|
pip install hishel[httpx] # For HTTPX support
|
|
97
102
|
pip install hishel[requests] # For Requests support
|
|
103
|
+
pip install hishel[fastapi] # For FastAPI support (includes ASGI)
|
|
98
104
|
```
|
|
99
105
|
|
|
100
|
-
Or install
|
|
106
|
+
Or install multiple:
|
|
101
107
|
|
|
102
108
|
```bash
|
|
103
|
-
pip install hishel[httpx,requests]
|
|
109
|
+
pip install hishel[httpx,requests,fastapi]
|
|
104
110
|
```
|
|
105
111
|
|
|
112
|
+
> [!NOTE]
|
|
113
|
+
> ASGI middleware has no extra dependencies - it's included in the base installation.
|
|
114
|
+
|
|
106
115
|
## 🚀 Quick Start
|
|
107
116
|
|
|
108
117
|
### With HTTPX
|
|
@@ -156,23 +165,112 @@ response = session.get("https://api.example.com/data")
|
|
|
156
165
|
print(response.headers.get("X-Hishel-From-Cache")) # "True"
|
|
157
166
|
```
|
|
158
167
|
|
|
168
|
+
### With ASGI Applications
|
|
169
|
+
|
|
170
|
+
Add caching middleware to any ASGI application:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from hishel.asgi import ASGICacheMiddleware
|
|
174
|
+
|
|
175
|
+
# Wrap your ASGI app
|
|
176
|
+
app = ASGICacheMiddleware(app)
|
|
177
|
+
|
|
178
|
+
# Or configure with options
|
|
179
|
+
from hishel import AsyncSqliteStorage, CacheOptions, SpecificationPolicy
|
|
180
|
+
|
|
181
|
+
app = ASGICacheMiddleware(
|
|
182
|
+
app,
|
|
183
|
+
storage=AsyncSqliteStorage(),
|
|
184
|
+
policy=SpecificationPolicy(
|
|
185
|
+
cache_options=CacheOptions(shared=True)
|
|
186
|
+
),
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### With FastAPI
|
|
191
|
+
|
|
192
|
+
Add Cache-Control headers using the `cache()` dependency:
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
from fastapi import FastAPI
|
|
196
|
+
from hishel.fastapi import cache
|
|
197
|
+
|
|
198
|
+
app = FastAPI()
|
|
199
|
+
|
|
200
|
+
@app.get("/api/data", dependencies=[cache(max_age=300, public=True)])
|
|
201
|
+
async def get_data():
|
|
202
|
+
# Cache-Control: public, max-age=300
|
|
203
|
+
return {"data": "cached for 5 minutes"}
|
|
204
|
+
|
|
205
|
+
# Optionally wrap with ASGI middleware for local caching according to specified rules
|
|
206
|
+
from hishel.asgi import ASGICacheMiddleware
|
|
207
|
+
from hishel import AsyncSqliteStorage
|
|
208
|
+
|
|
209
|
+
app = ASGICacheMiddleware(app, storage=AsyncSqliteStorage())
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### With BlackSheep
|
|
213
|
+
|
|
214
|
+
Use BlackSheep's native `cache_control` decorator with Hishel's ASGI middleware:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from blacksheep import Application, get
|
|
218
|
+
from blacksheep.server.headers.cache import cache_control
|
|
219
|
+
|
|
220
|
+
app = Application()
|
|
221
|
+
|
|
222
|
+
@get("/api/data")
|
|
223
|
+
@cache_control(max_age=300, public=True)
|
|
224
|
+
async def get_data():
|
|
225
|
+
# Cache-Control: public, max-age=300
|
|
226
|
+
return {"data": "cached for 5 minutes"}
|
|
227
|
+
```
|
|
228
|
+
|
|
159
229
|
## 🎛️ Advanced Configuration
|
|
160
230
|
|
|
161
|
-
###
|
|
231
|
+
### Caching Policies
|
|
232
|
+
|
|
233
|
+
Hishel supports two types of caching policies:
|
|
234
|
+
|
|
235
|
+
**SpecificationPolicy** - RFC 9111 compliant HTTP caching (default):
|
|
162
236
|
|
|
163
237
|
```python
|
|
164
|
-
from hishel import CacheOptions
|
|
238
|
+
from hishel import CacheOptions, SpecificationPolicy
|
|
165
239
|
from hishel.httpx import SyncCacheClient
|
|
166
240
|
|
|
167
241
|
client = SyncCacheClient(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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()]
|
|
172
268
|
)
|
|
173
269
|
)
|
|
174
270
|
```
|
|
175
271
|
|
|
272
|
+
[Learn more about policies →](https://hishel.com/dev/policies/)
|
|
273
|
+
|
|
176
274
|
### Custom Storage Backend
|
|
177
275
|
|
|
178
276
|
```python
|
|
@@ -188,6 +286,60 @@ storage = SyncSqliteStorage(
|
|
|
188
286
|
client = SyncCacheClient(storage=storage)
|
|
189
287
|
```
|
|
190
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
|
+
|
|
191
343
|
## 🏗️ Architecture
|
|
192
344
|
|
|
193
345
|
Hishel uses a **sans-I/O state machine** architecture that separates HTTP caching logic from I/O operations:
|
|
@@ -199,13 +351,11 @@ Hishel uses a **sans-I/O state machine** architecture that separates HTTP cachin
|
|
|
199
351
|
|
|
200
352
|
## 🔮 Roadmap
|
|
201
353
|
|
|
202
|
-
|
|
354
|
+
We're actively working on:
|
|
203
355
|
|
|
204
|
-
- 🎯 Additional HTTP client integrations
|
|
205
|
-
- 🎯 Server-side caching support
|
|
206
|
-
- 🎯 More storage backends
|
|
207
|
-
- 🎯 Advanced caching strategies
|
|
208
356
|
- 🎯 Performance optimizations
|
|
357
|
+
- 🎯 More integrations
|
|
358
|
+
- 🎯 Partial responses support
|
|
209
359
|
|
|
210
360
|
## 📚 Documentation
|
|
211
361
|
|
|
@@ -214,7 +364,12 @@ Comprehensive documentation is available at [https://hishel.com/dev](https://his
|
|
|
214
364
|
- [Getting Started](https://hishel.com)
|
|
215
365
|
- [HTTPX Integration](https://hishel.com/dev/integrations/httpx)
|
|
216
366
|
- [Requests Integration](https://hishel.com/dev/integrations/requests)
|
|
367
|
+
- [ASGI Integration](https://hishel.com/dev/asgi)
|
|
368
|
+
- [FastAPI Integration](https://hishel.com/dev/fastapi)
|
|
369
|
+
- [BlackSheep Integration](https://hishel.com/dev/integrations/blacksheep)
|
|
370
|
+
- [GraphQL Integration](https://hishel.com/dev/integrations/graphql)
|
|
217
371
|
- [Storage Backends](https://hishel.com/dev/storages)
|
|
372
|
+
- [Request/Response Metadata](https://hishel.com/dev/metadata)
|
|
218
373
|
- [RFC 9111 Specification](https://hishel.com/dev/specification)
|
|
219
374
|
|
|
220
375
|
## 🤝 Contributing
|
|
@@ -255,10 +410,62 @@ Hishel is inspired by and builds upon the excellent work in the Python HTTP ecos
|
|
|
255
410
|
|
|
256
411
|
All notable changes to this project will be documented in this file.
|
|
257
412
|
|
|
413
|
+
## 1.1.0 - 2025-10-31
|
|
414
|
+
### ⚙️ Miscellaneous Tasks
|
|
415
|
+
- Add in memory example
|
|
416
|
+
|
|
417
|
+
### 🐛 Bug Fixes
|
|
418
|
+
- Pass any response with non-expected status code on revalidation to client
|
|
419
|
+
|
|
420
|
+
### 🚀 Features
|
|
421
|
+
- Allow setting storage base with via `database_path` for sqlite storage
|
|
422
|
+
|
|
423
|
+
## 1.0.0 - 2025-10-28
|
|
424
|
+
### ⚙️ Miscellaneous Tasks
|
|
425
|
+
- Add examples, improve docs
|
|
426
|
+
|
|
427
|
+
## 1.0.0b1 - 2025-10-28
|
|
428
|
+
### ♻️ Refactoring
|
|
429
|
+
- Add policies
|
|
430
|
+
|
|
431
|
+
### ⚙️ Miscellaneous Tasks
|
|
432
|
+
- Improve sans-io diagram colors
|
|
433
|
+
- Add graphql docs
|
|
434
|
+
|
|
435
|
+
### 🐛 Bug Fixes
|
|
436
|
+
- Body-sensitive responses caching
|
|
437
|
+
- Filter out `Transfer-Encoding` header for asgi responses
|
|
438
|
+
|
|
439
|
+
### 🚀 Features
|
|
440
|
+
- Add global `use_body_key` setting
|
|
441
|
+
|
|
442
|
+
## 1.0.0.dev3 - 2025-10-26
|
|
443
|
+
### ♻️ Refactoring
|
|
444
|
+
- Replace pairs with entries, simplify storage API
|
|
445
|
+
- Automatically generate httpx sync integration from async
|
|
446
|
+
|
|
447
|
+
### ⚙️ Miscellaneous Tasks
|
|
448
|
+
- Simplify metadata docs
|
|
449
|
+
- Add custom integrations docs
|
|
450
|
+
- More robust compressed response caching
|
|
451
|
+
|
|
452
|
+
### 🐛 Bug Fixes
|
|
453
|
+
- Add missing permissions into `publish.yml`
|
|
454
|
+
- Raise on consumed httpx streams, which we can't store as is (it's already decoded)
|
|
455
|
+
- Fix compressed data caching for requests
|
|
456
|
+
- Handle httpx iterable usage instead of iterator correctly
|
|
457
|
+
- Add date header for proper age calculation
|
|
458
|
+
|
|
459
|
+
### 🚀 Features
|
|
460
|
+
- Add integrations with fastapi and asgi
|
|
461
|
+
- Add blacksheep integration examples
|
|
462
|
+
- Add logging for asgi
|
|
463
|
+
|
|
258
464
|
## 1.0.0.dev2 - 2025-10-21
|
|
259
465
|
### ⚙️ Miscellaneous Tasks
|
|
260
466
|
- Remove redundant utils and tests
|
|
261
467
|
- Add import without extras check in ci
|
|
468
|
+
- Fix time travel date, explicitly specify the timezone
|
|
262
469
|
|
|
263
470
|
### 🐛 Bug Fixes
|
|
264
471
|
- Fix check for storing auth requests
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
hishel/__init__.py,sha256=XYnAlT2Wkrg0Cw5u4DLJAsC3TjWDOQJr5kqQMuXxEJI,1796
|
|
2
|
+
hishel/_async_cache.py,sha256=4wm6YBL9ClNOg4WbEkALTJVCIXuzQU80MVem2C3hHrQ,8785
|
|
3
|
+
hishel/_async_httpx.py,sha256=WB35o7CseRDz89dpqoWNLXYEkDxqeSo709eR3oPXP7Q,7536
|
|
4
|
+
hishel/_policies.py,sha256=1ae_rmDF7oaG91-lQyOGVaTrRX8uI2GImmu5gN6WJa4,1135
|
|
5
|
+
hishel/_sync_cache.py,sha256=k0AN0M--yR4Jc6SiAreaxPUFiwEt5Dx7wi9jqW9sy50,8510
|
|
6
|
+
hishel/_sync_httpx.py,sha256=FbnJrdoLftPDVNUzYkAtz-JOTNtiBPJ2c1ZOcU0JnmE,7368
|
|
7
|
+
hishel/_utils.py,sha256=kR7RnhFqLzFRmB-YNnZteQVP0iDPUouCscA0_FHHFls,3837
|
|
8
|
+
hishel/asgi.py,sha256=ocXzqrrYGazeJxlKFcz1waoKvKGOqJ7YBEAmly4Towk,14998
|
|
9
|
+
hishel/fastapi.py,sha256=CVWCyXTxBPwG_XALo-Oldekv4lqMgH2-W-PPZ9rZjXg,10826
|
|
10
|
+
hishel/httpx.py,sha256=99a8X9COPiPHSgGW61O2uMWMZB7dY93Ty9DTCJ9C18Q,467
|
|
11
|
+
hishel/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
hishel/requests.py,sha256=FbOBMvgNSWkH0Bh0qZdr-dhCYG6siR5Lmxcu5eMay3s,6409
|
|
13
|
+
hishel/_core/_headers.py,sha256=hGaT6o1F-gs1pm5RpdGb0IMQL3uJYDH1xpwJLy28Cys,17514
|
|
14
|
+
hishel/_core/_spec.py,sha256=YfZfMYS4--o0v-AQZ37vVzHfc-Mh-SEHcl9SrBM9Y7Y,102963
|
|
15
|
+
hishel/_core/models.py,sha256=EabP2qnjYVzhPWhQer3QFmdDE6TDbqEBEqPHzv25VnA,7978
|
|
16
|
+
hishel/_core/_storages/_async_base.py,sha256=iZ6Mb30P0ho5h4UU5bgOrcsSMZ1427j9tht-tupZs68,2106
|
|
17
|
+
hishel/_core/_storages/_async_sqlite.py,sha256=QZEWroGZKGhCMf7CcYpZfKZdKd88R8elQCoPge0Khxc,15674
|
|
18
|
+
hishel/_core/_storages/_packing.py,sha256=mC8LMFQ5uPfFOgingKm2WKFO_DwcZ1OjTgI6xc0hfJI,3708
|
|
19
|
+
hishel/_core/_storages/_sync_base.py,sha256=qfOvcFY5qvrzSh4ztV2Trlxft-BF7An5SFsLlEb8EeE,2075
|
|
20
|
+
hishel/_core/_storages/_sync_sqlite.py,sha256=P3vIp636tTtM553-sBTZkam3-sNv0EEkh1rRSd1Qw90,15167
|
|
21
|
+
hishel-1.1.0.dist-info/METADATA,sha256=08BqqoUdS_tMtsQyo4JTS5mOuASXLW1cs5S_PrSAuGM,15759
|
|
22
|
+
hishel-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
23
|
+
hishel-1.1.0.dist-info/licenses/LICENSE,sha256=1qQj7pE0V2O9OIedvyOgLGLvZLaPd3nFEup3IBEOZjQ,1493
|
|
24
|
+
hishel-1.1.0.dist-info/RECORD,,
|
hishel/_core/__init__.py
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
from hishel._core._async._storages._sqlite import AsyncSqliteStorage
|
|
2
|
-
from hishel._core._base._storages._base import (
|
|
3
|
-
AsyncBaseStorage as AsyncBaseStorage,
|
|
4
|
-
SyncBaseStorage as SyncBaseStorage,
|
|
5
|
-
)
|
|
6
|
-
from hishel._core._headers import Headers as Headers
|
|
7
|
-
from hishel._core._spec import (
|
|
8
|
-
AnyState as AnyState,
|
|
9
|
-
CacheMiss as CacheMiss,
|
|
10
|
-
CacheOptions as CacheOptions,
|
|
11
|
-
CouldNotBeStored as CouldNotBeStored,
|
|
12
|
-
FromCache as FromCache,
|
|
13
|
-
IdleClient as IdleClient,
|
|
14
|
-
NeedRevalidation as NeedRevalidation,
|
|
15
|
-
NeedToBeUpdated as NeedToBeUpdated,
|
|
16
|
-
State as State,
|
|
17
|
-
StoreAndUse as StoreAndUse,
|
|
18
|
-
create_idle_state as create_idle_state,
|
|
19
|
-
)
|
|
20
|
-
from hishel._core._sync._storages._sqlite import SyncSqliteStorage
|
|
21
|
-
from hishel._core.models import (
|
|
22
|
-
CompletePair as CompletePair,
|
|
23
|
-
IncompletePair as IncompletePair,
|
|
24
|
-
Pair as Pair,
|
|
25
|
-
PairMeta as PairMeta,
|
|
26
|
-
Request as Request,
|
|
27
|
-
Response,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
__all__ = (
|
|
31
|
-
# New API
|
|
32
|
-
## States
|
|
33
|
-
"AnyState",
|
|
34
|
-
"IdleClient",
|
|
35
|
-
"CacheMiss",
|
|
36
|
-
"FromCache",
|
|
37
|
-
"NeedRevalidation",
|
|
38
|
-
"AnyState",
|
|
39
|
-
"CacheOptions",
|
|
40
|
-
"NeedToBeUpdated",
|
|
41
|
-
"State",
|
|
42
|
-
"StoreAndUse",
|
|
43
|
-
"CouldNotBeStored",
|
|
44
|
-
"create_idle_state",
|
|
45
|
-
## Models
|
|
46
|
-
"Request",
|
|
47
|
-
"Response",
|
|
48
|
-
"Pair",
|
|
49
|
-
"IncompletePair",
|
|
50
|
-
"CompletePair",
|
|
51
|
-
"PairMeta",
|
|
52
|
-
## Headers
|
|
53
|
-
"Headers",
|
|
54
|
-
## Storages
|
|
55
|
-
"SyncBaseStorage",
|
|
56
|
-
"AsyncBaseStorage",
|
|
57
|
-
"SyncSqliteStorage",
|
|
58
|
-
"AsyncSqliteStorage",
|
|
59
|
-
)
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import abc
|
|
4
|
-
import time
|
|
5
|
-
import typing as tp
|
|
6
|
-
import uuid
|
|
7
|
-
from abc import ABC
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
from hishel._core.models import CompletePair, IncompletePair, Request, Response
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class SyncBaseStorage(ABC):
|
|
14
|
-
@abc.abstractmethod
|
|
15
|
-
def create_pair(
|
|
16
|
-
self,
|
|
17
|
-
request: Request,
|
|
18
|
-
id: uuid.UUID | None = None,
|
|
19
|
-
) -> IncompletePair:
|
|
20
|
-
"""
|
|
21
|
-
Store a request in the backend under the given key.
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
request: The request object to store.
|
|
25
|
-
|
|
26
|
-
Returns:
|
|
27
|
-
The created IncompletePair object representing the stored request.
|
|
28
|
-
|
|
29
|
-
Raises:
|
|
30
|
-
NotImplementedError: Must be implemented in subclasses.
|
|
31
|
-
"""
|
|
32
|
-
raise NotImplementedError()
|
|
33
|
-
|
|
34
|
-
@abc.abstractmethod
|
|
35
|
-
def add_response(self, pair_id: uuid.UUID, response: Response, key: str | bytes) -> CompletePair:
|
|
36
|
-
"""
|
|
37
|
-
Add a response to an existing request pair.
|
|
38
|
-
|
|
39
|
-
Args:
|
|
40
|
-
pair_id: The unique identifier of the request pair.
|
|
41
|
-
response: The response object to add.
|
|
42
|
-
key: The cache key associated with the request pair.
|
|
43
|
-
|
|
44
|
-
Returns:
|
|
45
|
-
The updated response object.
|
|
46
|
-
|
|
47
|
-
Raises:
|
|
48
|
-
NotImplementedError: Must be implemented in subclasses.
|
|
49
|
-
"""
|
|
50
|
-
raise NotImplementedError()
|
|
51
|
-
|
|
52
|
-
@abc.abstractmethod
|
|
53
|
-
def get_pairs(self, key: str) -> tp.List[CompletePair]:
|
|
54
|
-
"""
|
|
55
|
-
Retrieve all responses associated with a given key.
|
|
56
|
-
|
|
57
|
-
Args:
|
|
58
|
-
key: The unique identifier for the request pairs.
|
|
59
|
-
complete_only: If True, only return pairs with responses. If False,
|
|
60
|
-
only return pairs without responses. If None, return all pairs.
|
|
61
|
-
"""
|
|
62
|
-
raise NotImplementedError()
|
|
63
|
-
|
|
64
|
-
@abc.abstractmethod
|
|
65
|
-
def update_pair(
|
|
66
|
-
self,
|
|
67
|
-
id: uuid.UUID,
|
|
68
|
-
new_pair: tp.Union[CompletePair, tp.Callable[[CompletePair], CompletePair]],
|
|
69
|
-
) -> tp.Optional[CompletePair]:
|
|
70
|
-
"""
|
|
71
|
-
Update an existing request pair.
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
id: The unique identifier of the request pair to update.
|
|
75
|
-
new_pair: The new pair data or a callable that takes the current pair
|
|
76
|
-
and returns the updated pair.
|
|
77
|
-
"""
|
|
78
|
-
raise NotImplementedError()
|
|
79
|
-
|
|
80
|
-
@abc.abstractmethod
|
|
81
|
-
def remove(self, id: uuid.UUID) -> None:
|
|
82
|
-
"""
|
|
83
|
-
Remove a request pair from the storage.
|
|
84
|
-
|
|
85
|
-
Args:
|
|
86
|
-
id: The unique identifier of the request pair to remove.
|
|
87
|
-
"""
|
|
88
|
-
raise NotImplementedError()
|
|
89
|
-
|
|
90
|
-
def close(self) -> None:
|
|
91
|
-
"""
|
|
92
|
-
Close any resources held by the storage backend.
|
|
93
|
-
"""
|
|
94
|
-
pass
|
|
95
|
-
|
|
96
|
-
def is_soft_deleted(self, pair: IncompletePair | CompletePair) -> bool:
|
|
97
|
-
"""
|
|
98
|
-
Check if a pair is soft deleted based on its metadata.
|
|
99
|
-
|
|
100
|
-
Args:
|
|
101
|
-
pair: The request pair to check.
|
|
102
|
-
|
|
103
|
-
Returns:
|
|
104
|
-
True if the pair is soft deleted, False otherwise.
|
|
105
|
-
"""
|
|
106
|
-
return pair.meta.deleted_at is not None and pair.meta.deleted_at > 0
|
|
107
|
-
|
|
108
|
-
def is_safe_to_hard_delete(self, pair: IncompletePair | CompletePair) -> bool:
|
|
109
|
-
"""
|
|
110
|
-
Check if a pair is safe to hard delete based on its metadata.
|
|
111
|
-
|
|
112
|
-
If the pair has been soft deleted for more than 1 hour, it is considered safe to hard delete.
|
|
113
|
-
|
|
114
|
-
Args:
|
|
115
|
-
pair: The request pair to check.
|
|
116
|
-
|
|
117
|
-
Returns:
|
|
118
|
-
True if the pair is safe to hard delete, False otherwise.
|
|
119
|
-
"""
|
|
120
|
-
return bool(pair.meta.deleted_at is not None and (pair.meta.deleted_at + 3600 < time.time()))
|
|
121
|
-
|
|
122
|
-
@tp.overload
|
|
123
|
-
def mark_pair_as_deleted(self, pair: CompletePair) -> CompletePair: ...
|
|
124
|
-
@tp.overload
|
|
125
|
-
def mark_pair_as_deleted(self, pair: IncompletePair) -> IncompletePair: ...
|
|
126
|
-
def mark_pair_as_deleted(self, pair: CompletePair | IncompletePair) -> CompletePair | IncompletePair:
|
|
127
|
-
"""
|
|
128
|
-
Mark a pair as soft deleted by setting its deleted_at timestamp.
|
|
129
|
-
|
|
130
|
-
Args:
|
|
131
|
-
pair: The request pair to mark as deleted.
|
|
132
|
-
Returns:
|
|
133
|
-
The updated request pair with the deleted_at timestamp set.
|
|
134
|
-
"""
|
|
135
|
-
pair.meta.deleted_at = time.time()
|
|
136
|
-
return pair
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
class AsyncBaseStorage(ABC):
|
|
140
|
-
@abc.abstractmethod
|
|
141
|
-
async def create_pair(
|
|
142
|
-
self,
|
|
143
|
-
request: Request,
|
|
144
|
-
id: uuid.UUID | None = None,
|
|
145
|
-
) -> IncompletePair:
|
|
146
|
-
"""
|
|
147
|
-
Store a request in the backend under the given key.
|
|
148
|
-
|
|
149
|
-
Args:
|
|
150
|
-
request: The request object to store.
|
|
151
|
-
|
|
152
|
-
Returns:
|
|
153
|
-
The created IncompletePair object representing the stored request.
|
|
154
|
-
|
|
155
|
-
Raises:
|
|
156
|
-
NotImplementedError: Must be implemented in subclasses.
|
|
157
|
-
"""
|
|
158
|
-
raise NotImplementedError()
|
|
159
|
-
|
|
160
|
-
@abc.abstractmethod
|
|
161
|
-
async def add_response(self, pair_id: uuid.UUID, response: Response, key: str | bytes) -> CompletePair:
|
|
162
|
-
"""
|
|
163
|
-
Add a response to an existing request pair.
|
|
164
|
-
|
|
165
|
-
Args:
|
|
166
|
-
pair_id: The unique identifier of the request pair.
|
|
167
|
-
response: The response object to add.
|
|
168
|
-
key: The cache key associated with the request pair.
|
|
169
|
-
|
|
170
|
-
Returns:
|
|
171
|
-
The updated response object.
|
|
172
|
-
|
|
173
|
-
Raises:
|
|
174
|
-
NotImplementedError: Must be implemented in subclasses.
|
|
175
|
-
"""
|
|
176
|
-
raise NotImplementedError()
|
|
177
|
-
|
|
178
|
-
@abc.abstractmethod
|
|
179
|
-
async def get_pairs(self, key: str) -> tp.List[CompletePair]:
|
|
180
|
-
"""
|
|
181
|
-
Retrieve all responses associated with a given key.
|
|
182
|
-
|
|
183
|
-
Args:
|
|
184
|
-
key: The unique identifier for the request pairs.
|
|
185
|
-
"""
|
|
186
|
-
raise NotImplementedError()
|
|
187
|
-
|
|
188
|
-
@abc.abstractmethod
|
|
189
|
-
async def update_pair(
|
|
190
|
-
self,
|
|
191
|
-
id: uuid.UUID,
|
|
192
|
-
new_pair: tp.Union[CompletePair, tp.Callable[[CompletePair], CompletePair]],
|
|
193
|
-
) -> tp.Optional[CompletePair]:
|
|
194
|
-
"""
|
|
195
|
-
Update an existing request pair.
|
|
196
|
-
|
|
197
|
-
Args:
|
|
198
|
-
id: The unique identifier of the request pair to update.
|
|
199
|
-
new_pair: The new pair data or a callable that takes the current pair
|
|
200
|
-
and returns the updated pair.
|
|
201
|
-
"""
|
|
202
|
-
raise NotImplementedError()
|
|
203
|
-
|
|
204
|
-
@abc.abstractmethod
|
|
205
|
-
async def remove(self, id: uuid.UUID) -> None:
|
|
206
|
-
"""
|
|
207
|
-
Remove a request pair from the storage.
|
|
208
|
-
|
|
209
|
-
Args:
|
|
210
|
-
id: The unique identifier of the request pair to remove.
|
|
211
|
-
"""
|
|
212
|
-
raise NotImplementedError()
|
|
213
|
-
|
|
214
|
-
async def close(self) -> None:
|
|
215
|
-
"""
|
|
216
|
-
Close any resources held by the storage backend.
|
|
217
|
-
"""
|
|
218
|
-
pass
|
|
219
|
-
|
|
220
|
-
def is_soft_deleted(self, pair: IncompletePair | CompletePair) -> bool:
|
|
221
|
-
"""
|
|
222
|
-
Check if a pair is soft deleted based on its metadata.
|
|
223
|
-
|
|
224
|
-
Args:
|
|
225
|
-
pair: The request pair to check.
|
|
226
|
-
|
|
227
|
-
Returns:
|
|
228
|
-
True if the pair is soft deleted, False otherwise.
|
|
229
|
-
"""
|
|
230
|
-
return pair.meta.deleted_at is not None and pair.meta.deleted_at > 0
|
|
231
|
-
|
|
232
|
-
def is_safe_to_hard_delete(self, pair: IncompletePair | CompletePair) -> bool:
|
|
233
|
-
"""
|
|
234
|
-
Check if a pair is safe to hard delete based on its metadata.
|
|
235
|
-
|
|
236
|
-
If the pair has been soft deleted for more than 1 hour, it is considered safe to hard delete.
|
|
237
|
-
|
|
238
|
-
Args:
|
|
239
|
-
pair: The request pair to check.
|
|
240
|
-
|
|
241
|
-
Returns:
|
|
242
|
-
True if the pair is safe to hard delete, False otherwise.
|
|
243
|
-
"""
|
|
244
|
-
return bool(pair.meta.deleted_at is not None and (pair.meta.deleted_at + 3600 < time.time()))
|
|
245
|
-
|
|
246
|
-
@tp.overload
|
|
247
|
-
def mark_pair_as_deleted(self, pair: CompletePair) -> CompletePair: ...
|
|
248
|
-
@tp.overload
|
|
249
|
-
def mark_pair_as_deleted(self, pair: IncompletePair) -> IncompletePair: ...
|
|
250
|
-
def mark_pair_as_deleted(self, pair: CompletePair | IncompletePair) -> CompletePair | IncompletePair:
|
|
251
|
-
"""
|
|
252
|
-
Mark a pair as soft deleted by setting its deleted_at timestamp.
|
|
253
|
-
|
|
254
|
-
Args:
|
|
255
|
-
pair: The request pair to mark as deleted.
|
|
256
|
-
Returns:
|
|
257
|
-
The updated request pair with the deleted_at timestamp set.
|
|
258
|
-
"""
|
|
259
|
-
pair.meta.deleted_at = time.time()
|
|
260
|
-
return pair
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def ensure_cache_dict(base_path: str | None = None) -> Path:
|
|
264
|
-
_base_path = Path(base_path) if base_path is not None else Path(".cache/hishel")
|
|
265
|
-
_gitignore_file = _base_path / ".gitignore"
|
|
266
|
-
|
|
267
|
-
_base_path.mkdir(parents=True, exist_ok=True)
|
|
268
|
-
|
|
269
|
-
if not _gitignore_file.is_file():
|
|
270
|
-
with open(_gitignore_file, "w", encoding="utf-8") as f:
|
|
271
|
-
f.write("# Automatically created by Hishel\n*")
|
|
272
|
-
return _base_path
|