openai-sqlite-cache 0.0.1__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.
- cached_openai/__init__.py +175 -0
- cached_openai/_cache.py +145 -0
- cached_openai/_client.py +166 -0
- cached_openai/_fingerprint.py +186 -0
- cached_openai/_http.py +515 -0
- cached_openai/_module_alias.py +86 -0
- cached_openai/_settings.py +122 -0
- cached_openai/_version.py +3 -0
- cached_openai/py.typed +1 -0
- openai_sqlite_cache-0.0.1.dist-info/METADATA +151 -0
- openai_sqlite_cache-0.0.1.dist-info/RECORD +14 -0
- openai_sqlite_cache-0.0.1.dist-info/WHEEL +5 -0
- openai_sqlite_cache-0.0.1.dist-info/licenses/LICENSE +21 -0
- openai_sqlite_cache-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Cache settings and platform-appropriate defaults."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from dataclasses import dataclass, replace
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from threading import RLock
|
|
10
|
+
from typing import Optional, Union
|
|
11
|
+
|
|
12
|
+
PathLike = Union[str, os.PathLike[str]]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def default_cache_path() -> Path:
|
|
16
|
+
"""Return a user-local cache path without requiring another dependency."""
|
|
17
|
+
|
|
18
|
+
configured = os.environ.get("CACHED_OPENAI_CACHE_PATH")
|
|
19
|
+
if configured:
|
|
20
|
+
return Path(configured).expanduser()
|
|
21
|
+
|
|
22
|
+
if sys.platform == "darwin":
|
|
23
|
+
root = Path.home() / "Library" / "Caches"
|
|
24
|
+
elif os.name == "nt":
|
|
25
|
+
root = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local"))
|
|
26
|
+
else:
|
|
27
|
+
root = Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache"))
|
|
28
|
+
return root / "openai-sqlite-cache" / "cache.sqlite3"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _env_flag(name: str, default: bool) -> bool:
|
|
32
|
+
value = os.environ.get(name)
|
|
33
|
+
if value is None:
|
|
34
|
+
return default
|
|
35
|
+
return value.strip().lower() not in {"0", "false", "no", "off"}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _env_ttl() -> Optional[float]:
|
|
39
|
+
value = os.environ.get("CACHED_OPENAI_TTL_SECONDS", "").strip()
|
|
40
|
+
if not value:
|
|
41
|
+
return None
|
|
42
|
+
ttl = float(value)
|
|
43
|
+
if ttl < 0:
|
|
44
|
+
raise ValueError("CACHED_OPENAI_TTL_SECONDS must be zero or greater")
|
|
45
|
+
return ttl
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class CacheSettings:
|
|
50
|
+
path: Path
|
|
51
|
+
ttl_seconds: Optional[float] = None
|
|
52
|
+
enabled: bool = True
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
_settings_lock = RLock()
|
|
56
|
+
_settings: Optional[CacheSettings] = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_settings() -> CacheSettings:
|
|
60
|
+
global _settings
|
|
61
|
+
with _settings_lock:
|
|
62
|
+
if _settings is None:
|
|
63
|
+
_settings = CacheSettings(
|
|
64
|
+
path=default_cache_path(),
|
|
65
|
+
ttl_seconds=_env_ttl(),
|
|
66
|
+
enabled=not _env_flag("CACHED_OPENAI_DISABLE", False),
|
|
67
|
+
)
|
|
68
|
+
return _settings
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
_UNSET = object()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def configure_settings(
|
|
75
|
+
*,
|
|
76
|
+
path: Union[PathLike, object] = _UNSET,
|
|
77
|
+
ttl_seconds: Union[Optional[float], object] = _UNSET,
|
|
78
|
+
enabled: Union[bool, object] = _UNSET,
|
|
79
|
+
) -> CacheSettings:
|
|
80
|
+
global _settings
|
|
81
|
+
with _settings_lock:
|
|
82
|
+
current = get_settings()
|
|
83
|
+
changes = {}
|
|
84
|
+
if path is not _UNSET:
|
|
85
|
+
changes["path"] = Path(path).expanduser() # type: ignore[arg-type]
|
|
86
|
+
if ttl_seconds is not _UNSET:
|
|
87
|
+
if ttl_seconds is not None and float(ttl_seconds) < 0: # type: ignore[arg-type]
|
|
88
|
+
raise ValueError("ttl_seconds must be zero or greater")
|
|
89
|
+
changes["ttl_seconds"] = (
|
|
90
|
+
None if ttl_seconds is None else float(ttl_seconds) # type: ignore[arg-type]
|
|
91
|
+
)
|
|
92
|
+
if enabled is not _UNSET:
|
|
93
|
+
changes["enabled"] = bool(enabled)
|
|
94
|
+
_settings = replace(current, **changes)
|
|
95
|
+
return _settings
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def settings_for_client(
|
|
99
|
+
*,
|
|
100
|
+
cache_path: Union[Optional[PathLike], object] = _UNSET,
|
|
101
|
+
cache_ttl: Union[Optional[float], object] = _UNSET,
|
|
102
|
+
cache_enabled: Union[Optional[bool], object] = _UNSET,
|
|
103
|
+
) -> CacheSettings:
|
|
104
|
+
current = get_settings()
|
|
105
|
+
path = (
|
|
106
|
+
current.path
|
|
107
|
+
if cache_path is _UNSET or cache_path is None
|
|
108
|
+
else Path(cache_path).expanduser()
|
|
109
|
+
)
|
|
110
|
+
ttl = current.ttl_seconds if cache_ttl is _UNSET else cache_ttl
|
|
111
|
+
enabled = (
|
|
112
|
+
current.enabled
|
|
113
|
+
if cache_enabled is _UNSET or cache_enabled is None
|
|
114
|
+
else cache_enabled
|
|
115
|
+
)
|
|
116
|
+
if ttl is not None and float(ttl) < 0:
|
|
117
|
+
raise ValueError("cache_ttl must be zero or greater")
|
|
118
|
+
return CacheSettings(
|
|
119
|
+
path=path,
|
|
120
|
+
ttl_seconds=None if ttl is None else float(ttl),
|
|
121
|
+
enabled=bool(enabled),
|
|
122
|
+
)
|
cached_openai/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openai_sqlite_cache
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: An unofficial drop-in OpenAI Python SDK with a local SQLite response cache
|
|
5
|
+
Author: openai_sqlite_cache contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: openai,cache,sqlite,llm,api
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: openai<3,>=1.66.2
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# openai_sqlite_cache
|
|
25
|
+
|
|
26
|
+
`openai_sqlite_cache` 是非官方的 OpenAI Python SDK 透明 SQLite 缓存层。同一个 API、同一个模型、完全相同的输入再次调用时,会直接返回本机缓存的响应,不再请求上游。
|
|
27
|
+
|
|
28
|
+
PyPI 分发名是 `openai_sqlite_cache`(规范化名称为 `openai-sqlite-cache`);为满足只替换 import 的用法,Python 导入名仍然是 `cached_openai`。
|
|
29
|
+
|
|
30
|
+
## 安装
|
|
31
|
+
|
|
32
|
+
在当前仓库中安装:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install ./openai_sqlite_cache
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
发布到你有权限的 Python 包索引后,安装形式为:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install openai_sqlite_cache
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> 注意:本项目与公共 PyPI 上的 `cached-openai` 分发项目都提供 `cached_openai` 导入包,二者不能安全地安装在同一个 Python 环境中。请只安装其中一个。
|
|
45
|
+
|
|
46
|
+
## 用法
|
|
47
|
+
|
|
48
|
+
现有代码只需替换 import,并保留 `openai` 这个本地别名:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
# 原来:import openai
|
|
52
|
+
import cached_openai as openai
|
|
53
|
+
|
|
54
|
+
client = openai.OpenAI()
|
|
55
|
+
response = client.responses.create(
|
|
56
|
+
model="gpt-5",
|
|
57
|
+
input="Explain SQLite in one sentence.",
|
|
58
|
+
)
|
|
59
|
+
print(response.output_text)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
也可以直接使用包名:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import cached_openai
|
|
66
|
+
|
|
67
|
+
client = cached_openai.OpenAI()
|
|
68
|
+
completion = client.chat.completions.create(
|
|
69
|
+
model="gpt-4.1-mini",
|
|
70
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
以下官方 SDK 用法均保持不变:
|
|
75
|
+
|
|
76
|
+
- `OpenAI`、`AsyncOpenAI`、`AzureOpenAI` 和 `AsyncAzureOpenAI`;
|
|
77
|
+
- 模块级调用,如 `openai.chat.completions.create(...)`;
|
|
78
|
+
- `with_options()`、`with_raw_response`、`with_streaming_response`;
|
|
79
|
+
- 自定义 `base_url` 和自定义同步/异步 `http_client`;
|
|
80
|
+
- 官方 SDK 的响应模型和异常类型。
|
|
81
|
+
|
|
82
|
+
流式响应会在第一次被完整消费时写入缓存;如果流被提前关闭,则不会缓存不完整内容。Realtime/WebSocket 不经过普通 HTTP 请求,因此不会缓存。
|
|
83
|
+
|
|
84
|
+
## 缓存规则
|
|
85
|
+
|
|
86
|
+
缓存键使用 SHA-256 计算,包含:
|
|
87
|
+
|
|
88
|
+
- HTTP 方法和规范化后的完整 API URL;
|
|
89
|
+
- 请求体(JSON 会按键排序,multipart 会忽略随机 boundary 并对文件内容取 hash);
|
|
90
|
+
- 模型名;
|
|
91
|
+
- organization、project、beta 等会影响语义的请求头;
|
|
92
|
+
- API 凭据的不可逆作用域 hash,防止不同 API key 之间共享响应。
|
|
93
|
+
|
|
94
|
+
请求原文和 API key 都不会写入数据库。数据库只保存 hash、成功响应体、必要响应头和命中统计。默认永久保留,只缓存 2xx 响应。
|
|
95
|
+
|
|
96
|
+
为避免有副作用的操作被错误去重,目前只缓存这些推理/生成端点:Responses、Chat Completions、Completions、Embeddings、Moderations、Images、Audio 和 Videos。Files、Uploads、Batches、Fine-tuning、Vector Stores 等管理类 API 会原样请求上游。
|
|
97
|
+
|
|
98
|
+
## 配置
|
|
99
|
+
|
|
100
|
+
默认数据库位置:
|
|
101
|
+
|
|
102
|
+
- macOS:`~/Library/Caches/openai-sqlite-cache/cache.sqlite3`
|
|
103
|
+
- Linux:`$XDG_CACHE_HOME/openai-sqlite-cache/cache.sqlite3`,未设置时使用 `~/.cache/...`
|
|
104
|
+
- Windows:`%LOCALAPPDATA%/openai-sqlite-cache/cache.sqlite3`
|
|
105
|
+
|
|
106
|
+
环境变量:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
export CACHED_OPENAI_CACHE_PATH=/path/to/cache.sqlite3
|
|
110
|
+
export CACHED_OPENAI_TTL_SECONDS=86400
|
|
111
|
+
export CACHED_OPENAI_DISABLE=0
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
也可以在创建客户端前配置:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import cached_openai as openai
|
|
118
|
+
|
|
119
|
+
openai.configure_cache(
|
|
120
|
+
path="./.cache/openai.sqlite3",
|
|
121
|
+
ttl_seconds=24 * 60 * 60,
|
|
122
|
+
)
|
|
123
|
+
client = openai.OpenAI()
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
单个客户端可以覆盖全局配置:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
client = openai.OpenAI(
|
|
130
|
+
cache_path="./project-cache.sqlite3",
|
|
131
|
+
cache_ttl=3600,
|
|
132
|
+
cache_enabled=True,
|
|
133
|
+
)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
维护接口:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
print(openai.cache_info())
|
|
140
|
+
removed = openai.clear_cache()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
缓存文件可能包含模型响应中的敏感内容,应像其他本地应用数据一样保护。包会尽量把目录和数据库权限分别设为 `0700` 和 `0600`。
|
|
144
|
+
|
|
145
|
+
## 开发与测试
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
cd openai_sqlite_cache
|
|
149
|
+
python -m unittest discover -s tests -v
|
|
150
|
+
python -m pip wheel --no-deps . -w dist
|
|
151
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
cached_openai/__init__.py,sha256=SrjO8Mntmt7OiPUaQlR9xCoQcWPX6xAUhrfYzlRWNs0,4703
|
|
2
|
+
cached_openai/_cache.py,sha256=TJizYukVUsBonsvTnqi7Bmx62B9sLi0ZbzC-hGUuTmo,4978
|
|
3
|
+
cached_openai/_client.py,sha256=oDZokr-B6TT1gfz-u0ANFDEC2zfL1QCTjFN4PYNDCX8,4618
|
|
4
|
+
cached_openai/_fingerprint.py,sha256=yhkt5ZixD7TO80-xD7MklmiIZuhUjeiuyPhfE-6GPv0,6234
|
|
5
|
+
cached_openai/_http.py,sha256=5_Ky-YPrD-s6kcg8x_sdvOSQYwdhKH6k-8md9mWY56U,17488
|
|
6
|
+
cached_openai/_module_alias.py,sha256=QYHlNKyds6eD-Mfic81-9aOKKEGebXefzmH0mnBsw08,2973
|
|
7
|
+
cached_openai/_settings.py,sha256=ZpOVq4ngCKjnmzTxbB-b48N0WLaEI4w4HfgXmhgwyDw,3657
|
|
8
|
+
cached_openai/_version.py,sha256=yXe71I5Ds7WgI-daxeCN0TpK2xgujsfxH7z7GjPCZqA,62
|
|
9
|
+
cached_openai/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
10
|
+
openai_sqlite_cache-0.0.1.dist-info/licenses/LICENSE,sha256=3nKN1iGgSEww3R8uNaFoxf54TmEQVaJr7Fw3draZZVI,1089
|
|
11
|
+
openai_sqlite_cache-0.0.1.dist-info/METADATA,sha256=zdvmq8DWv_nrn2grSd4AFWmZlCNh-DvUnvAy1NuBTz0,4847
|
|
12
|
+
openai_sqlite_cache-0.0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
13
|
+
openai_sqlite_cache-0.0.1.dist-info/top_level.txt,sha256=DlzMut0JtFH9dwVNMedzyoKmV3m03J_dUFWq2LHYpDA,14
|
|
14
|
+
openai_sqlite_cache-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 openai_sqlite_cache contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cached_openai
|