- 新增图像生成接口,支持试用、积分和自定义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): 添加示例系统日志文件 - 记录用户请求、验证码发送成功与失败的日志信息
422 lines
11 KiB
Python
422 lines
11 KiB
Python
"""HTTP related errors."""
|
|
|
|
import asyncio
|
|
import warnings
|
|
from typing import TYPE_CHECKING, Optional, Tuple, Union
|
|
|
|
from multidict import MultiMapping
|
|
|
|
from .typedefs import StrOrURL
|
|
|
|
if TYPE_CHECKING:
|
|
import ssl
|
|
|
|
SSLContext = ssl.SSLContext
|
|
else:
|
|
try:
|
|
import ssl
|
|
|
|
SSLContext = ssl.SSLContext
|
|
except ImportError: # pragma: no cover
|
|
ssl = SSLContext = None # type: ignore[assignment]
|
|
|
|
if TYPE_CHECKING:
|
|
from .client_reqrep import ClientResponse, ConnectionKey, Fingerprint, RequestInfo
|
|
from .http_parser import RawResponseMessage
|
|
else:
|
|
RequestInfo = ClientResponse = ConnectionKey = RawResponseMessage = None
|
|
|
|
__all__ = (
|
|
"ClientError",
|
|
"ClientConnectionError",
|
|
"ClientConnectionResetError",
|
|
"ClientOSError",
|
|
"ClientConnectorError",
|
|
"ClientProxyConnectionError",
|
|
"ClientSSLError",
|
|
"ClientConnectorDNSError",
|
|
"ClientConnectorSSLError",
|
|
"ClientConnectorCertificateError",
|
|
"ConnectionTimeoutError",
|
|
"SocketTimeoutError",
|
|
"ServerConnectionError",
|
|
"ServerTimeoutError",
|
|
"ServerDisconnectedError",
|
|
"ServerFingerprintMismatch",
|
|
"ClientResponseError",
|
|
"ClientHttpProxyError",
|
|
"WSServerHandshakeError",
|
|
"ContentTypeError",
|
|
"ClientPayloadError",
|
|
"InvalidURL",
|
|
"InvalidUrlClientError",
|
|
"RedirectClientError",
|
|
"NonHttpUrlClientError",
|
|
"InvalidUrlRedirectClientError",
|
|
"NonHttpUrlRedirectClientError",
|
|
"WSMessageTypeError",
|
|
)
|
|
|
|
|
|
class ClientError(Exception):
|
|
"""Base class for client connection errors."""
|
|
|
|
|
|
class ClientResponseError(ClientError):
|
|
"""Base class for exceptions that occur after getting a response.
|
|
|
|
request_info: An instance of RequestInfo.
|
|
history: A sequence of responses, if redirects occurred.
|
|
status: HTTP status code.
|
|
message: Error message.
|
|
headers: Response headers.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
request_info: RequestInfo,
|
|
history: Tuple[ClientResponse, ...],
|
|
*,
|
|
code: Optional[int] = None,
|
|
status: Optional[int] = None,
|
|
message: str = "",
|
|
headers: Optional[MultiMapping[str]] = None,
|
|
) -> None:
|
|
self.request_info = request_info
|
|
if code is not None:
|
|
if status is not None:
|
|
raise ValueError(
|
|
"Both code and status arguments are provided; "
|
|
"code is deprecated, use status instead"
|
|
)
|
|
warnings.warn(
|
|
"code argument is deprecated, use status instead",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
if status is not None:
|
|
self.status = status
|
|
elif code is not None:
|
|
self.status = code
|
|
else:
|
|
self.status = 0
|
|
self.message = message
|
|
self.headers = headers
|
|
self.history = history
|
|
self.args = (request_info, history)
|
|
|
|
def __str__(self) -> str:
|
|
return "{}, message={!r}, url={!r}".format(
|
|
self.status,
|
|
self.message,
|
|
str(self.request_info.real_url),
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
args = f"{self.request_info!r}, {self.history!r}"
|
|
if self.status != 0:
|
|
args += f", status={self.status!r}"
|
|
if self.message != "":
|
|
args += f", message={self.message!r}"
|
|
if self.headers is not None:
|
|
args += f", headers={self.headers!r}"
|
|
return f"{type(self).__name__}({args})"
|
|
|
|
@property
|
|
def code(self) -> int:
|
|
warnings.warn(
|
|
"code property is deprecated, use status instead",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
return self.status
|
|
|
|
@code.setter
|
|
def code(self, value: int) -> None:
|
|
warnings.warn(
|
|
"code property is deprecated, use status instead",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
self.status = value
|
|
|
|
|
|
class ContentTypeError(ClientResponseError):
|
|
"""ContentType found is not valid."""
|
|
|
|
|
|
class WSServerHandshakeError(ClientResponseError):
|
|
"""websocket server handshake error."""
|
|
|
|
|
|
class ClientHttpProxyError(ClientResponseError):
|
|
"""HTTP proxy error.
|
|
|
|
Raised in :class:`aiohttp.connector.TCPConnector` if
|
|
proxy responds with status other than ``200 OK``
|
|
on ``CONNECT`` request.
|
|
"""
|
|
|
|
|
|
class TooManyRedirects(ClientResponseError):
|
|
"""Client was redirected too many times."""
|
|
|
|
|
|
class ClientConnectionError(ClientError):
|
|
"""Base class for client socket errors."""
|
|
|
|
|
|
class ClientConnectionResetError(ClientConnectionError, ConnectionResetError):
|
|
"""ConnectionResetError"""
|
|
|
|
|
|
class ClientOSError(ClientConnectionError, OSError):
|
|
"""OSError error."""
|
|
|
|
|
|
class ClientConnectorError(ClientOSError):
|
|
"""Client connector error.
|
|
|
|
Raised in :class:`aiohttp.connector.TCPConnector` if
|
|
a connection can not be established.
|
|
"""
|
|
|
|
def __init__(self, connection_key: ConnectionKey, os_error: OSError) -> None:
|
|
self._conn_key = connection_key
|
|
self._os_error = os_error
|
|
super().__init__(os_error.errno, os_error.strerror)
|
|
self.args = (connection_key, os_error)
|
|
|
|
@property
|
|
def os_error(self) -> OSError:
|
|
return self._os_error
|
|
|
|
@property
|
|
def host(self) -> str:
|
|
return self._conn_key.host
|
|
|
|
@property
|
|
def port(self) -> Optional[int]:
|
|
return self._conn_key.port
|
|
|
|
@property
|
|
def ssl(self) -> Union[SSLContext, bool, "Fingerprint"]:
|
|
return self._conn_key.ssl
|
|
|
|
def __str__(self) -> str:
|
|
return "Cannot connect to host {0.host}:{0.port} ssl:{1} [{2}]".format(
|
|
self, "default" if self.ssl is True else self.ssl, self.strerror
|
|
)
|
|
|
|
# OSError.__reduce__ does too much black magick
|
|
__reduce__ = BaseException.__reduce__
|
|
|
|
|
|
class ClientConnectorDNSError(ClientConnectorError):
|
|
"""DNS resolution failed during client connection.
|
|
|
|
Raised in :class:`aiohttp.connector.TCPConnector` if
|
|
DNS resolution fails.
|
|
"""
|
|
|
|
|
|
class ClientProxyConnectionError(ClientConnectorError):
|
|
"""Proxy connection error.
|
|
|
|
Raised in :class:`aiohttp.connector.TCPConnector` if
|
|
connection to proxy can not be established.
|
|
"""
|
|
|
|
|
|
class UnixClientConnectorError(ClientConnectorError):
|
|
"""Unix connector error.
|
|
|
|
Raised in :py:class:`aiohttp.connector.UnixConnector`
|
|
if connection to unix socket can not be established.
|
|
"""
|
|
|
|
def __init__(
|
|
self, path: str, connection_key: ConnectionKey, os_error: OSError
|
|
) -> None:
|
|
self._path = path
|
|
super().__init__(connection_key, os_error)
|
|
|
|
@property
|
|
def path(self) -> str:
|
|
return self._path
|
|
|
|
def __str__(self) -> str:
|
|
return "Cannot connect to unix socket {0.path} ssl:{1} [{2}]".format(
|
|
self, "default" if self.ssl is True else self.ssl, self.strerror
|
|
)
|
|
|
|
|
|
class ServerConnectionError(ClientConnectionError):
|
|
"""Server connection errors."""
|
|
|
|
|
|
class ServerDisconnectedError(ServerConnectionError):
|
|
"""Server disconnected."""
|
|
|
|
def __init__(self, message: Union[RawResponseMessage, str, None] = None) -> None:
|
|
if message is None:
|
|
message = "Server disconnected"
|
|
|
|
self.args = (message,)
|
|
self.message = message
|
|
|
|
|
|
class ServerTimeoutError(ServerConnectionError, asyncio.TimeoutError):
|
|
"""Server timeout error."""
|
|
|
|
|
|
class ConnectionTimeoutError(ServerTimeoutError):
|
|
"""Connection timeout error."""
|
|
|
|
|
|
class SocketTimeoutError(ServerTimeoutError):
|
|
"""Socket timeout error."""
|
|
|
|
|
|
class ServerFingerprintMismatch(ServerConnectionError):
|
|
"""SSL certificate does not match expected fingerprint."""
|
|
|
|
def __init__(self, expected: bytes, got: bytes, host: str, port: int) -> None:
|
|
self.expected = expected
|
|
self.got = got
|
|
self.host = host
|
|
self.port = port
|
|
self.args = (expected, got, host, port)
|
|
|
|
def __repr__(self) -> str:
|
|
return "<{} expected={!r} got={!r} host={!r} port={!r}>".format(
|
|
self.__class__.__name__, self.expected, self.got, self.host, self.port
|
|
)
|
|
|
|
|
|
class ClientPayloadError(ClientError):
|
|
"""Response payload error."""
|
|
|
|
|
|
class InvalidURL(ClientError, ValueError):
|
|
"""Invalid URL.
|
|
|
|
URL used for fetching is malformed, e.g. it doesn't contains host
|
|
part.
|
|
"""
|
|
|
|
# Derive from ValueError for backward compatibility
|
|
|
|
def __init__(self, url: StrOrURL, description: Union[str, None] = None) -> None:
|
|
# The type of url is not yarl.URL because the exception can be raised
|
|
# on URL(url) call
|
|
self._url = url
|
|
self._description = description
|
|
|
|
if description:
|
|
super().__init__(url, description)
|
|
else:
|
|
super().__init__(url)
|
|
|
|
@property
|
|
def url(self) -> StrOrURL:
|
|
return self._url
|
|
|
|
@property
|
|
def description(self) -> "str | None":
|
|
return self._description
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<{self.__class__.__name__} {self}>"
|
|
|
|
def __str__(self) -> str:
|
|
if self._description:
|
|
return f"{self._url} - {self._description}"
|
|
return str(self._url)
|
|
|
|
|
|
class InvalidUrlClientError(InvalidURL):
|
|
"""Invalid URL client error."""
|
|
|
|
|
|
class RedirectClientError(ClientError):
|
|
"""Client redirect error."""
|
|
|
|
|
|
class NonHttpUrlClientError(ClientError):
|
|
"""Non http URL client error."""
|
|
|
|
|
|
class InvalidUrlRedirectClientError(InvalidUrlClientError, RedirectClientError):
|
|
"""Invalid URL redirect client error."""
|
|
|
|
|
|
class NonHttpUrlRedirectClientError(NonHttpUrlClientError, RedirectClientError):
|
|
"""Non http URL redirect client error."""
|
|
|
|
|
|
class ClientSSLError(ClientConnectorError):
|
|
"""Base error for ssl.*Errors."""
|
|
|
|
|
|
if ssl is not None:
|
|
cert_errors = (ssl.CertificateError,)
|
|
cert_errors_bases = (
|
|
ClientSSLError,
|
|
ssl.CertificateError,
|
|
)
|
|
|
|
ssl_errors = (ssl.SSLError,)
|
|
ssl_error_bases = (ClientSSLError, ssl.SSLError)
|
|
else: # pragma: no cover
|
|
cert_errors = tuple()
|
|
cert_errors_bases = (
|
|
ClientSSLError,
|
|
ValueError,
|
|
)
|
|
|
|
ssl_errors = tuple()
|
|
ssl_error_bases = (ClientSSLError,)
|
|
|
|
|
|
class ClientConnectorSSLError(*ssl_error_bases): # type: ignore[misc]
|
|
"""Response ssl error."""
|
|
|
|
|
|
class ClientConnectorCertificateError(*cert_errors_bases): # type: ignore[misc]
|
|
"""Response certificate error."""
|
|
|
|
def __init__(
|
|
self, connection_key: ConnectionKey, certificate_error: Exception
|
|
) -> None:
|
|
self._conn_key = connection_key
|
|
self._certificate_error = certificate_error
|
|
self.args = (connection_key, certificate_error)
|
|
|
|
@property
|
|
def certificate_error(self) -> Exception:
|
|
return self._certificate_error
|
|
|
|
@property
|
|
def host(self) -> str:
|
|
return self._conn_key.host
|
|
|
|
@property
|
|
def port(self) -> Optional[int]:
|
|
return self._conn_key.port
|
|
|
|
@property
|
|
def ssl(self) -> bool:
|
|
return self._conn_key.is_ssl
|
|
|
|
def __str__(self) -> str:
|
|
return (
|
|
"Cannot connect to host {0.host}:{0.port} ssl:{0.ssl} "
|
|
"[{0.certificate_error.__class__.__name__}: "
|
|
"{0.certificate_error.args}]".format(self)
|
|
)
|
|
|
|
|
|
class WSMessageTypeError(TypeError):
|
|
"""WebSocket message type is not valid."""
|