From 1f73588e286717aa8d746a796410750b43c2e510 Mon Sep 17 00:00:00 2001 From: luoshuijs Date: Wed, 6 Sep 2023 11:12:46 +0800 Subject: [PATCH] :sparkles: Replace aiohttp with httpx in enkanetwork.py --- core/services/players/services.py | 7 +-- plugins/genshin/player_cards.py | 7 +-- utils/enkanetwork.py | 99 ++++++++++++++++++++++++++++++- 3 files changed, 103 insertions(+), 10 deletions(-) diff --git a/core/services/players/services.py b/core/services/players/services.py index cdaed6fc..bb1f636d 100644 --- a/core/services/players/services.py +++ b/core/services/players/services.py @@ -3,11 +3,11 @@ from typing import Optional from aiohttp import ClientConnectorError from enkanetwork import ( - EnkaNetworkAPI, VaildateUIDError, HTTPException, EnkaPlayerNotFound, PlayerInfo as EnkaPlayerInfo, + TimedOut, ) from core.base_service import BaseService @@ -15,9 +15,8 @@ from core.config import config from core.dependence.redisdb import RedisDB from core.services.players.models import PlayersDataBase as Player, PlayerInfoSQLModel, PlayerInfo from core.services.players.repositories import PlayerInfoRepository -from utils.enkanetwork import RedisCache +from utils.enkanetwork import RedisCache, EnkaNetworkAPI from utils.log import logger -from utils.patch.aiohttp import AioHttpTimeoutException from gram_core.services.players.services import PlayersService @@ -50,7 +49,7 @@ class PlayerInfoService(BaseService): return response.player except (VaildateUIDError, EnkaPlayerNotFound, HTTPException) as exc: logger.warning("EnkaNetwork 请求失败: %s", str(exc)) - except AioHttpTimeoutException as exc: + except TimedOut as exc: logger.warning("EnkaNetwork 请求超时: %s", str(exc)) except ClientConnectorError as exc: logger.warning("EnkaNetwork 请求错误: %s", str(exc)) diff --git a/plugins/genshin/player_cards.py b/plugins/genshin/player_cards.py index 19ad123c..3d6039e9 100644 --- a/plugins/genshin/player_cards.py +++ b/plugins/genshin/player_cards.py @@ -3,7 +3,6 @@ from typing import Any, List, Tuple, Union, Optional, TYPE_CHECKING from enkanetwork import ( DigitType, - EnkaNetworkAPI, EnkaNetworkResponse, EnkaServerError, Equipments, @@ -16,6 +15,7 @@ from enkanetwork import ( EnkaServerUnknown, EnkaServerRateLimit, EnkaPlayerNotFound, + TimedOut, ) from pydantic import BaseModel from telegram import InlineKeyboardButton, InlineKeyboardMarkup @@ -33,10 +33,9 @@ from core.services.template.services import TemplateService from metadata.shortname import roleToName from modules.playercards.file import PlayerCardsFile from modules.playercards.helpers import ArtifactStatsTheory -from utils.enkanetwork import RedisCache +from utils.enkanetwork import RedisCache, EnkaNetworkAPI from utils.helpers import download_resource from utils.log import logger -from utils.patch.aiohttp import AioHttpTimeoutException from utils.uid import mask_number if TYPE_CHECKING: @@ -77,7 +76,7 @@ class PlayerCards(Plugin): data = await self.player_cards_file.merge_info(uid, data) await self.cache.set(uid, data) return EnkaNetworkResponse.parse_obj(data) - except AioHttpTimeoutException: + except TimedOut: error = "Enka.Network 服务请求超时,请稍后重试" except EnkaServerRateLimit: error = "Enka.Network 已对此API进行速率限制,请稍后重试" diff --git a/utils/enkanetwork.py b/utils/enkanetwork.py index 50c47aa7..8ec7c843 100644 --- a/utils/enkanetwork.py +++ b/utils/enkanetwork.py @@ -1,13 +1,21 @@ +import logging +import warnings from typing import Dict, Any, Optional, TYPE_CHECKING -from enkanetwork import Cache +from cachetools import TTLCache +from enkanetwork.assets import Assets +from enkanetwork.cache import Cache +from enkanetwork.client import EnkaNetworkAPI as _EnkaNetworkAPI +from enkanetwork.config import Config +from enkanetwork.exception import TimedOut, NetworkError, EnkaServerError, ERROR_ENKA +from enkanetwork.http import HTTPClient as _HTTPClient, Route +from httpx import AsyncClient, TimeoutException, HTTPError, Timeout try: import ujson as jsonlib except ImportError: import json as jsonlib - if TYPE_CHECKING: from redis import asyncio as aioredis @@ -43,3 +51,90 @@ class RedisCache(Cache): async def ttl(self, key) -> int: qname = self.get_qname(key) return await self.redis.ttl(qname) + + +class HTTPClient(_HTTPClient): + async def close(self) -> None: + if not self.client.is_closed: + await self.client.aclose() + + def __init__( + self, *, key: Optional[str] = None, agent: Optional[str] = None, timeout: Optional[Any] = None + ) -> None: + if timeout is None: + timeout = Timeout( + connect=5.0, + read=5.0, + write=5.0, + pool=1.0, + ) + + if agent is not None: + Config.init_user_agent(agent) + agent = agent or Config.USER_AGENT + if key is None: + warnings.warn("'key' has depercated.") + self.client = AsyncClient(timeout=timeout, headers={"User-Agent": agent}) + + async def request(self, route: Route, **kwargs: Any) -> Any: + method = route.method + url = route.url + username = route.username + + try: + response = await self.client.request(method, url, **kwargs) + except TimeoutException as e: + raise TimedOut from e + except HTTPError as e: + raise NetworkError from e + + _host = response.url.host + + if response.is_error: + if _host == Config.ENKA_URL: + err = ERROR_ENKA.get(response.status_code, None) + if err: + raise err[0](err[1].format(uid=username)) + raise EnkaServerError(f"Server error status code: {response.status_code}") + + return {"status": response.status_code, "content": response.content} + + +class StaticCache(Cache): + def __init__(self, maxsize: int, ttl: int) -> None: + self.cache = TTLCache(maxsize, ttl) + + async def get(self, key) -> Dict[str, Any]: + data = self.cache.get(key) + return jsonlib.loads(data) if data is not None else data + + async def set(self, key, value) -> None: + self.cache[key] = jsonlib.dumps(value) + + +class EnkaNetworkAPI(_EnkaNetworkAPI): + def __init__( + self, + *, + lang: str = "en", + debug: bool = False, + key: str = "", + cache: bool = True, + user_agent: str = "", + timeout: int = 10, + ) -> None: # noqa: E501 + # Logging + logging.basicConfig() + logging.getLogger("enkanetwork").setLevel(logging.DEBUG if debug else logging.ERROR) # noqa: E501 + + # Set language and load config + self.assets = Assets(lang) + + # Cache + self._enable_cache = cache + if self._enable_cache: + Config.init_cache(StaticCache(1024, 60 * 1)) + + # http client + self.__http = HTTPClient(key=key, agent=user_agent, timeout=timeout) # skipcq: PTC-W0037 + self._closed = False