ai_v/venv/Lib/site-packages/redis/event.py

469 lines
14 KiB
Python
Raw Normal View History

feat(api): 实现图像生成及后台同步功能 - 新增图像生成接口,支持试用、积分和自定义API Key模式 - 实现生成图片结果异步上传至MinIO存储,带重试机制 - 优化积分预扣除和异常退还逻辑,保障用户积分准确 - 添加获取生成历史记录接口,支持时间范围和分页 - 提供本地字典配置接口,支持模型、比例、提示模板和尺寸 - 实现图片批量上传接口,支持S3兼容对象存储 feat(admin): 增加管理员角色管理与权限分配接口 - 实现角色列表查询、角色创建、更新及删除功能 - 增加权限列表查询接口 - 实现用户角色分配接口,便于统一管理用户权限 - 增加系统字典增删查改接口,支持分类过滤和排序 - 权限控制全面覆盖管理接口,保证安全访问 feat(auth): 完善用户登录注册及权限相关接口与页面 - 实现手机号验证码发送及校验功能,保障注册安全 - 支持手机号注册、登录及退出接口,集成日志记录 - 增加修改密码功能,验证原密码后更新 - 提供动态导航菜单接口,基于权限展示不同菜单 - 实现管理界面路由及日志、角色、字典管理页面访问权限控制 - 添加系统日志查询接口,支持关键词和等级筛选 feat(app): 初始化Flask应用并配置蓝图与数据库 - 创建应用程序工厂,加载配置,初始化数据库和Redis客户端 - 注册认证、API及管理员蓝图,整合路由 - 根路由渲染主页模板 - 应用上下文中自动创建数据库表,保证运行环境准备完毕 feat(database): 提供数据库创建与迁移支持脚本 - 新增数据库创建脚本,支持自动检测是否已存在 - 添加数据库表初始化脚本,支持创建和删除所有表 - 实现RBAC权限初始化,包含基础权限和角色创建 - 新增字段手动修复脚本,添加用户API Key和积分字段 - 强制迁移脚本支持清理连接和修复表结构,初始化默认数据及角色分配 feat(config): 新增系统配置参数 - 配置数据库、Redis、Session和MinIO相关参数 - 添加AI接口地址及试用Key配置 - 集成阿里云短信服务配置及开发模式相关参数 feat(extensions): 初始化数据库、Redis和MinIO客户端 - 创建全局SQLAlchemy数据库实例和Redis客户端 - 配置基于boto3的MinIO兼容S3客户端 chore(logs): 添加示例系统日志文件 - 记录用户请求、验证码发送成功与失败的日志信息
2026-01-12 00:53:31 +08:00
import asyncio
import threading
from abc import ABC, abstractmethod
from enum import Enum
from typing import Dict, List, Optional, Type, Union
from redis.auth.token import TokenInterface
from redis.credentials import CredentialProvider, StreamingCredentialProvider
class EventListenerInterface(ABC):
"""
Represents a listener for given event object.
"""
@abstractmethod
def listen(self, event: object):
pass
class AsyncEventListenerInterface(ABC):
"""
Represents an async listener for given event object.
"""
@abstractmethod
async def listen(self, event: object):
pass
class EventDispatcherInterface(ABC):
"""
Represents a dispatcher that dispatches events to listeners
associated with given event.
"""
@abstractmethod
def dispatch(self, event: object):
pass
@abstractmethod
async def dispatch_async(self, event: object):
pass
@abstractmethod
def register_listeners(
self,
mappings: Dict[
Type[object],
List[Union[EventListenerInterface, AsyncEventListenerInterface]],
],
):
"""Register additional listeners."""
pass
class EventException(Exception):
"""
Exception wrapper that adds an event object into exception context.
"""
def __init__(self, exception: Exception, event: object):
self.exception = exception
self.event = event
super().__init__(exception)
class EventDispatcher(EventDispatcherInterface):
# TODO: Make dispatcher to accept external mappings.
def __init__(
self,
event_listeners: Optional[
Dict[Type[object], List[EventListenerInterface]]
] = None,
):
"""
Dispatcher that dispatches events to listeners associated with given event.
"""
self._event_listeners_mapping: Dict[
Type[object], List[EventListenerInterface]
] = {
AfterConnectionReleasedEvent: [
ReAuthConnectionListener(),
],
AfterPooledConnectionsInstantiationEvent: [
RegisterReAuthForPooledConnections()
],
AfterSingleConnectionInstantiationEvent: [
RegisterReAuthForSingleConnection()
],
AfterPubSubConnectionInstantiationEvent: [RegisterReAuthForPubSub()],
AfterAsyncClusterInstantiationEvent: [RegisterReAuthForAsyncClusterNodes()],
AsyncAfterConnectionReleasedEvent: [
AsyncReAuthConnectionListener(),
],
}
self._lock = threading.Lock()
self._async_lock = None
if event_listeners:
self.register_listeners(event_listeners)
def dispatch(self, event: object):
with self._lock:
listeners = self._event_listeners_mapping.get(type(event), [])
for listener in listeners:
listener.listen(event)
async def dispatch_async(self, event: object):
if self._async_lock is None:
self._async_lock = asyncio.Lock()
async with self._async_lock:
listeners = self._event_listeners_mapping.get(type(event), [])
for listener in listeners:
await listener.listen(event)
def register_listeners(
self,
mappings: Dict[
Type[object],
List[Union[EventListenerInterface, AsyncEventListenerInterface]],
],
):
with self._lock:
for event_type in mappings:
if event_type in self._event_listeners_mapping:
self._event_listeners_mapping[event_type] = list(
set(
self._event_listeners_mapping[event_type]
+ mappings[event_type]
)
)
else:
self._event_listeners_mapping[event_type] = mappings[event_type]
class AfterConnectionReleasedEvent:
"""
Event that will be fired before each command execution.
"""
def __init__(self, connection):
self._connection = connection
@property
def connection(self):
return self._connection
class AsyncAfterConnectionReleasedEvent(AfterConnectionReleasedEvent):
pass
class ClientType(Enum):
SYNC = ("sync",)
ASYNC = ("async",)
class AfterPooledConnectionsInstantiationEvent:
"""
Event that will be fired after pooled connection instances was created.
"""
def __init__(
self,
connection_pools: List,
client_type: ClientType,
credential_provider: Optional[CredentialProvider] = None,
):
self._connection_pools = connection_pools
self._client_type = client_type
self._credential_provider = credential_provider
@property
def connection_pools(self):
return self._connection_pools
@property
def client_type(self) -> ClientType:
return self._client_type
@property
def credential_provider(self) -> Union[CredentialProvider, None]:
return self._credential_provider
class AfterSingleConnectionInstantiationEvent:
"""
Event that will be fired after single connection instances was created.
:param connection_lock: For sync client thread-lock should be provided,
for async asyncio.Lock
"""
def __init__(
self,
connection,
client_type: ClientType,
connection_lock: Union[threading.RLock, asyncio.Lock],
):
self._connection = connection
self._client_type = client_type
self._connection_lock = connection_lock
@property
def connection(self):
return self._connection
@property
def client_type(self) -> ClientType:
return self._client_type
@property
def connection_lock(self) -> Union[threading.RLock, asyncio.Lock]:
return self._connection_lock
class AfterPubSubConnectionInstantiationEvent:
def __init__(
self,
pubsub_connection,
connection_pool,
client_type: ClientType,
connection_lock: Union[threading.RLock, asyncio.Lock],
):
self._pubsub_connection = pubsub_connection
self._connection_pool = connection_pool
self._client_type = client_type
self._connection_lock = connection_lock
@property
def pubsub_connection(self):
return self._pubsub_connection
@property
def connection_pool(self):
return self._connection_pool
@property
def client_type(self) -> ClientType:
return self._client_type
@property
def connection_lock(self) -> Union[threading.RLock, asyncio.Lock]:
return self._connection_lock
class AfterAsyncClusterInstantiationEvent:
"""
Event that will be fired after async cluster instance was created.
Async cluster doesn't use connection pools,
instead ClusterNode object manages connections.
"""
def __init__(
self,
nodes: dict,
credential_provider: Optional[CredentialProvider] = None,
):
self._nodes = nodes
self._credential_provider = credential_provider
@property
def nodes(self) -> dict:
return self._nodes
@property
def credential_provider(self) -> Union[CredentialProvider, None]:
return self._credential_provider
class OnCommandsFailEvent:
"""
Event fired whenever a command fails during the execution.
"""
def __init__(
self,
commands: tuple,
exception: Exception,
):
self._commands = commands
self._exception = exception
@property
def commands(self) -> tuple:
return self._commands
@property
def exception(self) -> Exception:
return self._exception
class AsyncOnCommandsFailEvent(OnCommandsFailEvent):
pass
class ReAuthConnectionListener(EventListenerInterface):
"""
Listener that performs re-authentication of given connection.
"""
def listen(self, event: AfterConnectionReleasedEvent):
event.connection.re_auth()
class AsyncReAuthConnectionListener(AsyncEventListenerInterface):
"""
Async listener that performs re-authentication of given connection.
"""
async def listen(self, event: AsyncAfterConnectionReleasedEvent):
await event.connection.re_auth()
class RegisterReAuthForPooledConnections(EventListenerInterface):
"""
Listener that registers a re-authentication callback for pooled connections.
Required by :class:`StreamingCredentialProvider`.
"""
def __init__(self):
self._event = None
def listen(self, event: AfterPooledConnectionsInstantiationEvent):
if isinstance(event.credential_provider, StreamingCredentialProvider):
self._event = event
if event.client_type == ClientType.SYNC:
event.credential_provider.on_next(self._re_auth)
event.credential_provider.on_error(self._raise_on_error)
else:
event.credential_provider.on_next(self._re_auth_async)
event.credential_provider.on_error(self._raise_on_error_async)
def _re_auth(self, token):
for pool in self._event.connection_pools:
pool.re_auth_callback(token)
async def _re_auth_async(self, token):
for pool in self._event.connection_pools:
await pool.re_auth_callback(token)
def _raise_on_error(self, error: Exception):
raise EventException(error, self._event)
async def _raise_on_error_async(self, error: Exception):
raise EventException(error, self._event)
class RegisterReAuthForSingleConnection(EventListenerInterface):
"""
Listener that registers a re-authentication callback for single connection.
Required by :class:`StreamingCredentialProvider`.
"""
def __init__(self):
self._event = None
def listen(self, event: AfterSingleConnectionInstantiationEvent):
if isinstance(
event.connection.credential_provider, StreamingCredentialProvider
):
self._event = event
if event.client_type == ClientType.SYNC:
event.connection.credential_provider.on_next(self._re_auth)
event.connection.credential_provider.on_error(self._raise_on_error)
else:
event.connection.credential_provider.on_next(self._re_auth_async)
event.connection.credential_provider.on_error(
self._raise_on_error_async
)
def _re_auth(self, token):
with self._event.connection_lock:
self._event.connection.send_command(
"AUTH", token.try_get("oid"), token.get_value()
)
self._event.connection.read_response()
async def _re_auth_async(self, token):
async with self._event.connection_lock:
await self._event.connection.send_command(
"AUTH", token.try_get("oid"), token.get_value()
)
await self._event.connection.read_response()
def _raise_on_error(self, error: Exception):
raise EventException(error, self._event)
async def _raise_on_error_async(self, error: Exception):
raise EventException(error, self._event)
class RegisterReAuthForAsyncClusterNodes(EventListenerInterface):
def __init__(self):
self._event = None
def listen(self, event: AfterAsyncClusterInstantiationEvent):
if isinstance(event.credential_provider, StreamingCredentialProvider):
self._event = event
event.credential_provider.on_next(self._re_auth)
event.credential_provider.on_error(self._raise_on_error)
async def _re_auth(self, token: TokenInterface):
for key in self._event.nodes:
await self._event.nodes[key].re_auth_callback(token)
async def _raise_on_error(self, error: Exception):
raise EventException(error, self._event)
class RegisterReAuthForPubSub(EventListenerInterface):
def __init__(self):
self._connection = None
self._connection_pool = None
self._client_type = None
self._connection_lock = None
self._event = None
def listen(self, event: AfterPubSubConnectionInstantiationEvent):
if isinstance(
event.pubsub_connection.credential_provider, StreamingCredentialProvider
) and event.pubsub_connection.get_protocol() in [3, "3"]:
self._event = event
self._connection = event.pubsub_connection
self._connection_pool = event.connection_pool
self._client_type = event.client_type
self._connection_lock = event.connection_lock
if self._client_type == ClientType.SYNC:
self._connection.credential_provider.on_next(self._re_auth)
self._connection.credential_provider.on_error(self._raise_on_error)
else:
self._connection.credential_provider.on_next(self._re_auth_async)
self._connection.credential_provider.on_error(
self._raise_on_error_async
)
def _re_auth(self, token: TokenInterface):
with self._connection_lock:
self._connection.send_command(
"AUTH", token.try_get("oid"), token.get_value()
)
self._connection.read_response()
self._connection_pool.re_auth_callback(token)
async def _re_auth_async(self, token: TokenInterface):
async with self._connection_lock:
await self._connection.send_command(
"AUTH", token.try_get("oid"), token.get_value()
)
await self._connection.read_response()
await self._connection_pool.re_auth_callback(token)
def _raise_on_error(self, error: Exception):
raise EventException(error, self._event)
async def _raise_on_error_async(self, error: Exception):
raise EventException(error, self._event)