aury-boot 0.0.2__py3-none-any.whl → 0.0.4__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.
- aury/boot/__init__.py +66 -0
- aury/boot/_version.py +2 -2
- aury/boot/application/__init__.py +120 -0
- aury/boot/application/app/__init__.py +39 -0
- aury/boot/application/app/base.py +511 -0
- aury/boot/application/app/components.py +434 -0
- aury/boot/application/app/middlewares.py +101 -0
- aury/boot/application/config/__init__.py +44 -0
- aury/boot/application/config/settings.py +663 -0
- aury/boot/application/constants/__init__.py +19 -0
- aury/boot/application/constants/components.py +50 -0
- aury/boot/application/constants/scheduler.py +28 -0
- aury/boot/application/constants/service.py +29 -0
- aury/boot/application/errors/__init__.py +55 -0
- aury/boot/application/errors/chain.py +80 -0
- aury/boot/application/errors/codes.py +67 -0
- aury/boot/application/errors/exceptions.py +238 -0
- aury/boot/application/errors/handlers.py +320 -0
- aury/boot/application/errors/response.py +120 -0
- aury/boot/application/interfaces/__init__.py +76 -0
- aury/boot/application/interfaces/egress.py +224 -0
- aury/boot/application/interfaces/ingress.py +98 -0
- aury/boot/application/middleware/__init__.py +22 -0
- aury/boot/application/middleware/logging.py +451 -0
- aury/boot/application/migrations/__init__.py +13 -0
- aury/boot/application/migrations/manager.py +685 -0
- aury/boot/application/migrations/setup.py +237 -0
- aury/boot/application/rpc/__init__.py +63 -0
- aury/boot/application/rpc/base.py +108 -0
- aury/boot/application/rpc/client.py +294 -0
- aury/boot/application/rpc/discovery.py +218 -0
- aury/boot/application/scheduler/__init__.py +13 -0
- aury/boot/application/scheduler/runner.py +123 -0
- aury/boot/application/server/__init__.py +296 -0
- aury/boot/commands/__init__.py +30 -0
- aury/boot/commands/add.py +76 -0
- aury/boot/commands/app.py +105 -0
- aury/boot/commands/config.py +177 -0
- aury/boot/commands/docker.py +367 -0
- aury/boot/commands/docs.py +284 -0
- aury/boot/commands/generate.py +1277 -0
- aury/boot/commands/init.py +892 -0
- aury/boot/commands/migrate/__init__.py +37 -0
- aury/boot/commands/migrate/app.py +54 -0
- aury/boot/commands/migrate/commands.py +303 -0
- aury/boot/commands/scheduler.py +124 -0
- aury/boot/commands/server/__init__.py +21 -0
- aury/boot/commands/server/app.py +541 -0
- aury/boot/commands/templates/generate/api.py.tpl +105 -0
- aury/boot/commands/templates/generate/model.py.tpl +17 -0
- aury/boot/commands/templates/generate/repository.py.tpl +19 -0
- aury/boot/commands/templates/generate/schema.py.tpl +29 -0
- aury/boot/commands/templates/generate/service.py.tpl +48 -0
- aury/boot/commands/templates/project/CLI.md.tpl +92 -0
- aury/boot/commands/templates/project/DEVELOPMENT.md.tpl +1397 -0
- aury/boot/commands/templates/project/README.md.tpl +111 -0
- aury/boot/commands/templates/project/admin_console_init.py.tpl +50 -0
- aury/boot/commands/templates/project/config.py.tpl +30 -0
- aury/boot/commands/templates/project/conftest.py.tpl +26 -0
- aury/boot/commands/templates/project/env.example.tpl +213 -0
- aury/boot/commands/templates/project/gitignore.tpl +128 -0
- aury/boot/commands/templates/project/main.py.tpl +41 -0
- aury/boot/commands/templates/project/modules/api.py.tpl +19 -0
- aury/boot/commands/templates/project/modules/exceptions.py.tpl +84 -0
- aury/boot/commands/templates/project/modules/schedules.py.tpl +18 -0
- aury/boot/commands/templates/project/modules/tasks.py.tpl +20 -0
- aury/boot/commands/worker.py +143 -0
- aury/boot/common/__init__.py +35 -0
- aury/boot/common/exceptions/__init__.py +114 -0
- aury/boot/common/i18n/__init__.py +16 -0
- aury/boot/common/i18n/translator.py +272 -0
- aury/boot/common/logging/__init__.py +716 -0
- aury/boot/contrib/__init__.py +10 -0
- aury/boot/contrib/admin_console/__init__.py +18 -0
- aury/boot/contrib/admin_console/auth.py +137 -0
- aury/boot/contrib/admin_console/discovery.py +69 -0
- aury/boot/contrib/admin_console/install.py +172 -0
- aury/boot/contrib/admin_console/utils.py +44 -0
- aury/boot/domain/__init__.py +79 -0
- aury/boot/domain/exceptions/__init__.py +132 -0
- aury/boot/domain/models/__init__.py +51 -0
- aury/boot/domain/models/base.py +69 -0
- aury/boot/domain/models/mixins.py +135 -0
- aury/boot/domain/models/models.py +96 -0
- aury/boot/domain/pagination/__init__.py +279 -0
- aury/boot/domain/repository/__init__.py +23 -0
- aury/boot/domain/repository/impl.py +423 -0
- aury/boot/domain/repository/interceptors.py +47 -0
- aury/boot/domain/repository/interface.py +106 -0
- aury/boot/domain/repository/query_builder.py +348 -0
- aury/boot/domain/service/__init__.py +11 -0
- aury/boot/domain/service/base.py +73 -0
- aury/boot/domain/transaction/__init__.py +404 -0
- aury/boot/infrastructure/__init__.py +104 -0
- aury/boot/infrastructure/cache/__init__.py +31 -0
- aury/boot/infrastructure/cache/backends.py +348 -0
- aury/boot/infrastructure/cache/base.py +68 -0
- aury/boot/infrastructure/cache/exceptions.py +37 -0
- aury/boot/infrastructure/cache/factory.py +94 -0
- aury/boot/infrastructure/cache/manager.py +274 -0
- aury/boot/infrastructure/database/__init__.py +39 -0
- aury/boot/infrastructure/database/config.py +71 -0
- aury/boot/infrastructure/database/exceptions.py +44 -0
- aury/boot/infrastructure/database/manager.py +317 -0
- aury/boot/infrastructure/database/query_tools/__init__.py +164 -0
- aury/boot/infrastructure/database/strategies/__init__.py +198 -0
- aury/boot/infrastructure/di/__init__.py +15 -0
- aury/boot/infrastructure/di/container.py +393 -0
- aury/boot/infrastructure/events/__init__.py +33 -0
- aury/boot/infrastructure/events/bus.py +362 -0
- aury/boot/infrastructure/events/config.py +52 -0
- aury/boot/infrastructure/events/consumer.py +134 -0
- aury/boot/infrastructure/events/middleware.py +51 -0
- aury/boot/infrastructure/events/models.py +63 -0
- aury/boot/infrastructure/monitoring/__init__.py +529 -0
- aury/boot/infrastructure/scheduler/__init__.py +19 -0
- aury/boot/infrastructure/scheduler/exceptions.py +37 -0
- aury/boot/infrastructure/scheduler/manager.py +478 -0
- aury/boot/infrastructure/storage/__init__.py +38 -0
- aury/boot/infrastructure/storage/base.py +164 -0
- aury/boot/infrastructure/storage/exceptions.py +37 -0
- aury/boot/infrastructure/storage/factory.py +88 -0
- aury/boot/infrastructure/tasks/__init__.py +24 -0
- aury/boot/infrastructure/tasks/config.py +45 -0
- aury/boot/infrastructure/tasks/constants.py +37 -0
- aury/boot/infrastructure/tasks/exceptions.py +37 -0
- aury/boot/infrastructure/tasks/manager.py +490 -0
- aury/boot/testing/__init__.py +24 -0
- aury/boot/testing/base.py +122 -0
- aury/boot/testing/client.py +163 -0
- aury/boot/testing/factory.py +154 -0
- aury/boot/toolkit/__init__.py +21 -0
- aury/boot/toolkit/http/__init__.py +367 -0
- {aury_boot-0.0.2.dist-info → aury_boot-0.0.4.dist-info}/METADATA +3 -2
- aury_boot-0.0.4.dist-info/RECORD +137 -0
- aury_boot-0.0.2.dist-info/RECORD +0 -5
- {aury_boot-0.0.2.dist-info → aury_boot-0.0.4.dist-info}/WHEEL +0 -0
- {aury_boot-0.0.2.dist-info → aury_boot-0.0.4.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""缓存后端实现。
|
|
2
|
+
|
|
3
|
+
提供 Redis、Memory、Memcached 等缓存后端的实现。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from datetime import timedelta
|
|
11
|
+
import json
|
|
12
|
+
import pickle
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from redis.asyncio import Redis
|
|
16
|
+
|
|
17
|
+
from aury.boot.common.logging import logger
|
|
18
|
+
|
|
19
|
+
from .base import ICache
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RedisCache(ICache):
|
|
23
|
+
"""Redis缓存实现。"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, url: str, *, serializer: str = "json"):
|
|
26
|
+
"""初始化Redis缓存。
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
url: Redis连接URL
|
|
30
|
+
serializer: 序列化方式(json/pickle)
|
|
31
|
+
"""
|
|
32
|
+
self._url = url
|
|
33
|
+
self._serializer = serializer
|
|
34
|
+
self._redis: Redis | None = None
|
|
35
|
+
|
|
36
|
+
async def initialize(self) -> None:
|
|
37
|
+
"""初始化连接。"""
|
|
38
|
+
try:
|
|
39
|
+
self._redis = Redis.from_url(
|
|
40
|
+
self._url,
|
|
41
|
+
encoding="utf-8",
|
|
42
|
+
decode_responses=False,
|
|
43
|
+
socket_connect_timeout=5,
|
|
44
|
+
socket_timeout=5,
|
|
45
|
+
)
|
|
46
|
+
await self._redis.ping()
|
|
47
|
+
logger.info("Redis缓存初始化成功")
|
|
48
|
+
except Exception as exc:
|
|
49
|
+
logger.error(f"Redis连接失败: {exc}")
|
|
50
|
+
raise
|
|
51
|
+
|
|
52
|
+
async def get(self, key: str, default: Any = None) -> Any:
|
|
53
|
+
"""获取缓存。"""
|
|
54
|
+
if not self._redis:
|
|
55
|
+
return default
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
data = await self._redis.get(key)
|
|
59
|
+
if data is None:
|
|
60
|
+
return default
|
|
61
|
+
|
|
62
|
+
# 使用函数式编程处理序列化器
|
|
63
|
+
deserializers: dict[str, Callable[[bytes], Any]] = {
|
|
64
|
+
"json": lambda d: json.loads(d.decode()),
|
|
65
|
+
"pickle": pickle.loads,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
deserializer = deserializers.get(self._serializer)
|
|
69
|
+
if deserializer:
|
|
70
|
+
return deserializer(data)
|
|
71
|
+
return data.decode()
|
|
72
|
+
except Exception as exc:
|
|
73
|
+
logger.error(f"Redis获取失败: {key}, {exc}")
|
|
74
|
+
return default
|
|
75
|
+
|
|
76
|
+
async def set(
|
|
77
|
+
self,
|
|
78
|
+
key: str,
|
|
79
|
+
value: Any,
|
|
80
|
+
expire: int | timedelta | None = None,
|
|
81
|
+
) -> bool:
|
|
82
|
+
"""设置缓存。"""
|
|
83
|
+
if not self._redis:
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
# 使用函数式编程处理序列化器
|
|
88
|
+
serializers: dict[str, Callable[[Any], bytes]] = {
|
|
89
|
+
"json": lambda v: json.dumps(v).encode(),
|
|
90
|
+
"pickle": pickle.dumps,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
serializer = serializers.get(self._serializer)
|
|
94
|
+
if serializer:
|
|
95
|
+
data = serializer(value)
|
|
96
|
+
else:
|
|
97
|
+
data = str(value).encode()
|
|
98
|
+
|
|
99
|
+
# 转换过期时间
|
|
100
|
+
if isinstance(expire, timedelta):
|
|
101
|
+
expire = int(expire.total_seconds())
|
|
102
|
+
|
|
103
|
+
await self._redis.set(key, data, ex=expire)
|
|
104
|
+
return True
|
|
105
|
+
except Exception as exc:
|
|
106
|
+
logger.error(f"Redis设置失败: {key}, {exc}")
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
async def delete(self, *keys: str) -> int:
|
|
110
|
+
"""删除缓存。"""
|
|
111
|
+
if not self._redis or not keys:
|
|
112
|
+
return 0
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
return await self._redis.delete(*keys)
|
|
116
|
+
except Exception as exc:
|
|
117
|
+
logger.error(f"Redis删除失败: {keys}, {exc}")
|
|
118
|
+
return 0
|
|
119
|
+
|
|
120
|
+
async def exists(self, *keys: str) -> int:
|
|
121
|
+
"""检查缓存是否存在。"""
|
|
122
|
+
if not self._redis or not keys:
|
|
123
|
+
return 0
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
return await self._redis.exists(*keys)
|
|
127
|
+
except Exception as exc:
|
|
128
|
+
logger.error(f"Redis检查失败: {keys}, {exc}")
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
async def clear(self) -> None:
|
|
132
|
+
"""清空所有缓存。"""
|
|
133
|
+
if self._redis:
|
|
134
|
+
await self._redis.flushdb()
|
|
135
|
+
logger.info("Redis缓存已清空")
|
|
136
|
+
|
|
137
|
+
async def close(self) -> None:
|
|
138
|
+
"""关闭连接。"""
|
|
139
|
+
if self._redis:
|
|
140
|
+
await self._redis.close()
|
|
141
|
+
logger.info("Redis连接已关闭")
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def redis(self) -> Redis | None:
|
|
145
|
+
"""获取Redis客户端。"""
|
|
146
|
+
return self._redis
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class MemoryCache(ICache):
|
|
150
|
+
"""内存缓存实现。"""
|
|
151
|
+
|
|
152
|
+
def __init__(self, max_size: int = 1000):
|
|
153
|
+
"""初始化内存缓存。
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
max_size: 最大缓存项数
|
|
157
|
+
"""
|
|
158
|
+
self._max_size = max_size
|
|
159
|
+
self._cache: dict[str, tuple[Any, float | None]] = {}
|
|
160
|
+
self._lock = asyncio.Lock()
|
|
161
|
+
|
|
162
|
+
async def get(self, key: str, default: Any = None) -> Any:
|
|
163
|
+
"""获取缓存。"""
|
|
164
|
+
async with self._lock:
|
|
165
|
+
if key not in self._cache:
|
|
166
|
+
return default
|
|
167
|
+
|
|
168
|
+
value, expire_at = self._cache[key]
|
|
169
|
+
|
|
170
|
+
# 检查过期
|
|
171
|
+
if expire_at is not None and asyncio.get_event_loop().time() > expire_at:
|
|
172
|
+
del self._cache[key]
|
|
173
|
+
return default
|
|
174
|
+
|
|
175
|
+
return value
|
|
176
|
+
|
|
177
|
+
async def set(
|
|
178
|
+
self,
|
|
179
|
+
key: str,
|
|
180
|
+
value: Any,
|
|
181
|
+
expire: int | timedelta | None = None,
|
|
182
|
+
) -> bool:
|
|
183
|
+
"""设置缓存。"""
|
|
184
|
+
async with self._lock:
|
|
185
|
+
# 转换过期时间
|
|
186
|
+
expire_at = None
|
|
187
|
+
if expire:
|
|
188
|
+
if isinstance(expire, timedelta):
|
|
189
|
+
expire_seconds = expire.total_seconds()
|
|
190
|
+
else:
|
|
191
|
+
expire_seconds = expire
|
|
192
|
+
expire_at = asyncio.get_event_loop().time() + expire_seconds
|
|
193
|
+
|
|
194
|
+
# 如果超出容量,删除最旧的
|
|
195
|
+
if len(self._cache) >= self._max_size and key not in self._cache:
|
|
196
|
+
# 简单策略:删除第一个
|
|
197
|
+
first_key = next(iter(self._cache))
|
|
198
|
+
del self._cache[first_key]
|
|
199
|
+
|
|
200
|
+
self._cache[key] = (value, expire_at)
|
|
201
|
+
return True
|
|
202
|
+
|
|
203
|
+
async def delete(self, *keys: str) -> int:
|
|
204
|
+
"""删除缓存。"""
|
|
205
|
+
async with self._lock:
|
|
206
|
+
count = 0
|
|
207
|
+
for key in keys:
|
|
208
|
+
if key in self._cache:
|
|
209
|
+
del self._cache[key]
|
|
210
|
+
count += 1
|
|
211
|
+
return count
|
|
212
|
+
|
|
213
|
+
async def exists(self, *keys: str) -> int:
|
|
214
|
+
"""检查缓存是否存在。"""
|
|
215
|
+
async with self._lock:
|
|
216
|
+
count = 0
|
|
217
|
+
for key in keys:
|
|
218
|
+
if key in self._cache:
|
|
219
|
+
_value, expire_at = self._cache[key]
|
|
220
|
+
# 检查是否过期
|
|
221
|
+
if expire_at is None or asyncio.get_event_loop().time() <= expire_at:
|
|
222
|
+
count += 1
|
|
223
|
+
return count
|
|
224
|
+
|
|
225
|
+
async def clear(self) -> None:
|
|
226
|
+
"""清空所有缓存。"""
|
|
227
|
+
async with self._lock:
|
|
228
|
+
self._cache.clear()
|
|
229
|
+
logger.info("内存缓存已清空")
|
|
230
|
+
|
|
231
|
+
async def close(self) -> None:
|
|
232
|
+
"""关闭连接(内存缓存无需关闭)。"""
|
|
233
|
+
await self.clear()
|
|
234
|
+
|
|
235
|
+
async def size(self) -> int:
|
|
236
|
+
"""获取缓存大小。"""
|
|
237
|
+
return len(self._cache)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class MemcachedCache(ICache):
|
|
241
|
+
"""Memcached缓存实现(可选)。"""
|
|
242
|
+
|
|
243
|
+
def __init__(self, servers: list[str]):
|
|
244
|
+
"""初始化Memcached缓存。
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
servers: Memcached服务器列表,如 ["127.0.0.1:11211"]
|
|
248
|
+
"""
|
|
249
|
+
self._servers = servers
|
|
250
|
+
self._client = None
|
|
251
|
+
|
|
252
|
+
async def initialize(self) -> None:
|
|
253
|
+
"""初始化连接。"""
|
|
254
|
+
try:
|
|
255
|
+
# 需要安装 python-memcached 或 aiomcache
|
|
256
|
+
try:
|
|
257
|
+
import aiomcache
|
|
258
|
+
self._client = aiomcache.Client(
|
|
259
|
+
self._servers[0].split(":")[0],
|
|
260
|
+
int(self._servers[0].split(":")[1]) if ":" in self._servers[0] else 11211,
|
|
261
|
+
)
|
|
262
|
+
logger.info("Memcached缓存初始化成功")
|
|
263
|
+
except ImportError:
|
|
264
|
+
logger.error("请安装 aiomcache: pip install aiomcache")
|
|
265
|
+
raise
|
|
266
|
+
except Exception as exc:
|
|
267
|
+
logger.error(f"Memcached连接失败: {exc}")
|
|
268
|
+
raise
|
|
269
|
+
|
|
270
|
+
async def get(self, key: str, default: Any = None) -> Any:
|
|
271
|
+
"""获取缓存。"""
|
|
272
|
+
if not self._client:
|
|
273
|
+
return default
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
data = await self._client.get(key.encode())
|
|
277
|
+
if data is None:
|
|
278
|
+
return default
|
|
279
|
+
return json.loads(data.decode())
|
|
280
|
+
except Exception as exc:
|
|
281
|
+
logger.error(f"Memcached获取失败: {key}, {exc}")
|
|
282
|
+
return default
|
|
283
|
+
|
|
284
|
+
async def set(
|
|
285
|
+
self,
|
|
286
|
+
key: str,
|
|
287
|
+
value: Any,
|
|
288
|
+
expire: int | timedelta | None = None,
|
|
289
|
+
) -> bool:
|
|
290
|
+
"""设置缓存。"""
|
|
291
|
+
if not self._client:
|
|
292
|
+
return False
|
|
293
|
+
|
|
294
|
+
try:
|
|
295
|
+
if isinstance(expire, timedelta):
|
|
296
|
+
expire = int(expire.total_seconds())
|
|
297
|
+
|
|
298
|
+
data = json.dumps(value).encode()
|
|
299
|
+
return await self._client.set(key.encode(), data, exptime=expire or 0)
|
|
300
|
+
except Exception as exc:
|
|
301
|
+
logger.error(f"Memcached设置失败: {key}, {exc}")
|
|
302
|
+
return False
|
|
303
|
+
|
|
304
|
+
async def delete(self, *keys: str) -> int:
|
|
305
|
+
"""删除缓存。"""
|
|
306
|
+
if not self._client or not keys:
|
|
307
|
+
return 0
|
|
308
|
+
|
|
309
|
+
count = 0
|
|
310
|
+
for key in keys:
|
|
311
|
+
try:
|
|
312
|
+
if await self._client.delete(key.encode()):
|
|
313
|
+
count += 1
|
|
314
|
+
except Exception as exc:
|
|
315
|
+
logger.error(f"Memcached删除失败: {key}, {exc}")
|
|
316
|
+
return count
|
|
317
|
+
|
|
318
|
+
async def exists(self, *keys: str) -> int:
|
|
319
|
+
"""检查缓存是否存在。"""
|
|
320
|
+
if not self._client or not keys:
|
|
321
|
+
return 0
|
|
322
|
+
|
|
323
|
+
count = 0
|
|
324
|
+
for key in keys:
|
|
325
|
+
try:
|
|
326
|
+
if await self._client.get(key.encode()) is not None:
|
|
327
|
+
count += 1
|
|
328
|
+
except Exception:
|
|
329
|
+
pass
|
|
330
|
+
return count
|
|
331
|
+
|
|
332
|
+
async def clear(self) -> None:
|
|
333
|
+
"""清空所有缓存(Memcached不支持)。"""
|
|
334
|
+
logger.warning("Memcached不支持清空所有缓存")
|
|
335
|
+
|
|
336
|
+
async def close(self) -> None:
|
|
337
|
+
"""关闭连接。"""
|
|
338
|
+
if self._client:
|
|
339
|
+
self._client.close()
|
|
340
|
+
logger.info("Memcached连接已关闭")
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
__all__ = [
|
|
344
|
+
"MemcachedCache",
|
|
345
|
+
"MemoryCache",
|
|
346
|
+
"RedisCache",
|
|
347
|
+
]
|
|
348
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""缓存系统基础接口和类型定义。
|
|
2
|
+
|
|
3
|
+
提供缓存接口和枚举类型。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from datetime import timedelta
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CacheBackend(str, Enum):
|
|
15
|
+
"""缓存后端类型。"""
|
|
16
|
+
|
|
17
|
+
REDIS = "redis"
|
|
18
|
+
MEMORY = "memory"
|
|
19
|
+
MEMCACHED = "memcached"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ICache(ABC):
|
|
23
|
+
"""缓存接口。
|
|
24
|
+
|
|
25
|
+
所有缓存后端必须实现此接口。
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
async def get(self, key: str, default: Any = None) -> Any:
|
|
30
|
+
"""获取缓存。"""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
async def set(
|
|
35
|
+
self,
|
|
36
|
+
key: str,
|
|
37
|
+
value: Any,
|
|
38
|
+
expire: int | timedelta | None = None,
|
|
39
|
+
) -> bool:
|
|
40
|
+
"""设置缓存。"""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
async def delete(self, *keys: str) -> int:
|
|
45
|
+
"""删除缓存。"""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
async def exists(self, *keys: str) -> int:
|
|
50
|
+
"""检查缓存是否存在。"""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
async def clear(self) -> None:
|
|
55
|
+
"""清空所有缓存。"""
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
@abstractmethod
|
|
59
|
+
async def close(self) -> None:
|
|
60
|
+
"""关闭连接。"""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
__all__ = [
|
|
65
|
+
"CacheBackend",
|
|
66
|
+
"ICache",
|
|
67
|
+
]
|
|
68
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""缓存相关异常定义。
|
|
2
|
+
|
|
3
|
+
Infrastructure 层异常,继承自 FoundationError。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from aury.boot.common.exceptions import FoundationError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CacheError(FoundationError):
|
|
12
|
+
"""缓存相关错误基类。
|
|
13
|
+
|
|
14
|
+
所有缓存相关的异常都应该继承此类。
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CacheMissError(CacheError):
|
|
21
|
+
"""缓存未命中错误(可选,通常不抛出)。"""
|
|
22
|
+
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CacheBackendError(CacheError):
|
|
27
|
+
"""缓存后端错误。"""
|
|
28
|
+
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"CacheBackendError",
|
|
34
|
+
"CacheError",
|
|
35
|
+
"CacheMissError",
|
|
36
|
+
]
|
|
37
|
+
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""缓存工厂 - 注册机制。
|
|
2
|
+
|
|
3
|
+
通过注册机制支持多种缓存后端。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import ClassVar
|
|
9
|
+
|
|
10
|
+
from aury.boot.common.logging import logger
|
|
11
|
+
|
|
12
|
+
from .backends import (
|
|
13
|
+
MemcachedCache,
|
|
14
|
+
MemoryCache,
|
|
15
|
+
RedisCache,
|
|
16
|
+
)
|
|
17
|
+
from .base import ICache
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CacheFactory:
|
|
21
|
+
"""缓存工厂 - 注册机制。
|
|
22
|
+
|
|
23
|
+
类似Flask-Cache的设计,通过注册机制支持多种后端。
|
|
24
|
+
|
|
25
|
+
使用示例:
|
|
26
|
+
# 注册后端
|
|
27
|
+
CacheFactory.register("redis", RedisCache)
|
|
28
|
+
CacheFactory.register("memory", MemoryCache)
|
|
29
|
+
|
|
30
|
+
# 创建缓存实例
|
|
31
|
+
cache = await CacheFactory.create("redis", url="redis://localhost:6379")
|
|
32
|
+
cache = await CacheFactory.create("memory", max_size=1000)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
_backends: ClassVar[dict[str, type[ICache]]] = {}
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def register(cls, name: str, backend_class: type[ICache]) -> None:
|
|
39
|
+
"""注册缓存后端。
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
name: 后端名称
|
|
43
|
+
backend_class: 后端类
|
|
44
|
+
"""
|
|
45
|
+
cls._backends[name] = backend_class
|
|
46
|
+
logger.debug(f"注册缓存后端: {name} -> {backend_class.__name__}")
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
async def create(cls, backend_name: str, **config) -> ICache:
|
|
50
|
+
"""创建缓存实例。
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
backend_name: 后端名称
|
|
54
|
+
**config: 配置参数
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
ICache: 缓存实例
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
ValueError: 后端未注册
|
|
61
|
+
"""
|
|
62
|
+
if backend_name not in cls._backends:
|
|
63
|
+
available = ", ".join(cls._backends.keys())
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"缓存后端 '{backend_name}' 未注册。"
|
|
66
|
+
f"可用后端: {available}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
backend_class = cls._backends[backend_name]
|
|
70
|
+
instance = backend_class(**config)
|
|
71
|
+
|
|
72
|
+
# 如果后端需要初始化
|
|
73
|
+
if hasattr(instance, "initialize"):
|
|
74
|
+
await instance.initialize()
|
|
75
|
+
|
|
76
|
+
logger.info(f"创建缓存实例: {backend_name}")
|
|
77
|
+
return instance
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def get_registered(cls) -> list[str]:
|
|
81
|
+
"""获取已注册的后端名称。"""
|
|
82
|
+
return list(cls._backends.keys())
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# 注册默认后端
|
|
86
|
+
CacheFactory.register("redis", RedisCache)
|
|
87
|
+
CacheFactory.register("memory", MemoryCache)
|
|
88
|
+
CacheFactory.register("memcached", MemcachedCache)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
__all__ = [
|
|
92
|
+
"CacheFactory",
|
|
93
|
+
]
|
|
94
|
+
|