From 7b0e7d26e763b3f770510e758651c1673a9faa63 Mon Sep 17 00:00:00 2001 From: "M307 (Mac)" Date: Tue, 15 Aug 2023 00:52:08 +0700 Subject: [PATCH] feat: Add new exception and little bi format code --- enkanetwork/client.py | 54 +++++++++++++++++-------------- enkanetwork/exception.py | 69 +++++++++++++++++++++++++++++----------- enkanetwork/http.py | 55 ++++++++++++++++++-------------- enkanetwork/utils.py | 6 ++-- example/cache.py | 3 +- 5 files changed, 117 insertions(+), 70 deletions(-) diff --git a/enkanetwork/client.py b/enkanetwork/client.py index f99f69e..8018abc 100644 --- a/enkanetwork/client.py +++ b/enkanetwork/client.py @@ -30,16 +30,20 @@ class EnkaNetworkAPI: - """ A library for API wrapper player by UID / Username from https://enka.network + """ + + A library for API wrapper player by UID / Username + from https://enka.network Parameters ------------ lang: :class:`str` Init default language debug: :class:`bool` - If set to `True`. In request data or get assets. It's will be shown log processing + If set to `True`. In request data or get assets. + It's will be shown log processing key: :class:`str` - Depercated + Depercated cache: :class:`bool` If set to `True`. In response data will be cache data user_agent: :class:`str` @@ -54,7 +58,7 @@ class EnkaNetworkAPI: http: :class:`HTTPClient` HTTP for request and handle data lang: :class:`Language` - A default language + A default language Example ------------ @@ -175,19 +179,19 @@ async def fetch_user_by_uid( The response player data """ - # Loda cache + # Loda cache cache = await self.__get_cache(uid) if cache: return EnkaNetworkResponse.parse_obj(cache) data = await self.__http.fetch_user_by_uid(uid, info=info) data = self.__format_json(data) - + # Return data self.LOGGER.debug("Parsing data...") # Store cache - await self.__store_cache(uid,data) + await self.__store_cache(uid, data) if "owner" in data: data["owner"] = { @@ -229,7 +233,7 @@ async def fetch_user_by_username( :class:`EnkaNetworkProfileResponse` The response profile / hoyos and builds data """ - # Loda cache + # Loda cache cache = await self.__get_cache(profile_id) if cache: return EnkaNetworkProfileResponse.parse_obj(cache) @@ -240,7 +244,7 @@ async def fetch_user_by_username( self.LOGGER.debug("Parsing data...") # Store cache - await self.__store_cache(profile_id,data) + await self.__store_cache(profile_id, data) # Fetch hoyos and build(s) data = { @@ -281,7 +285,7 @@ async def fetch_hoyos_by_username( """ key = profile_id + ":hoyos" - # Loda cache + # Loda cache cache = await self.__get_cache(key) if cache: return self.__format_hoyos(profile_id, cache) @@ -308,7 +312,7 @@ async def fetch_builds( profile_id: Optional[:class:`str`] Username / patreon ID has subscriptions in Enka.Network metaname: Optional[:class:`str`] - Metaname from hoyos data or owner tag in hash field + Metaname from hoyos data or owner tag in hash field Raises ------------ @@ -329,12 +333,13 @@ async def fetch_builds( A response builds data """ key = profile_id + ":hoyos:" + metaname + ":builds" - # Loda cache + # Loda cache cache = await self.__get_cache(key) if cache: return Builds.parse_obj(cache) - data = await self.__http.fetch_hoyos_by_username(profile_id, metaname, True) + data = await self.__http.fetch_hoyos_by_username( + profile_id, metaname, True) data = self.__format_json(data) self.LOGGER.debug("Parsing data...") @@ -343,24 +348,24 @@ async def fetch_builds( return Builds.parse_obj(data) - async def fetch_raw_data(self, uid: Union[str, int], *, info: bool = False) -> Dict[str, Any]: + async def fetch_raw_data(self, uid: Union[str, int], *, info: bool = False) -> Dict[str, Any]: # noqa """Fetches raw data for a user with the given UID. """ - # Loda cache + # Loda cache cache = await self.__get_cache(uid) if cache: return cache - + data = await self.__http.fetch_user_by_uid(uid, info=info) data = self.__format_json(data) # Store cache - await self.__store_cache(uid,data, cache=cache) + await self.__store_cache(uid, data, cache=cache) return data - async def sync_build(self, uid: Union[str, int], old_data: Dict[str, Any]) -> Dict[str, Any]: - """ Sync build data + async def sync_build(self, uid: Union[str, int], old_data: Dict[str, Any]) -> Dict[str, Any]: # noqa + """ Sync build data Parameters ---------- @@ -391,16 +396,18 @@ async def update_assets(self) -> None: self.LOGGER.debug(f"Writing {folder} file {filename}...") # dumps to json file - with open(os.path.join(path[folder], filename), "w", encoding="utf-8") as f: + with open(os.path.join(path[folder], filename), "w", + encoding="utf-8") as f: json.dump(json.loads(data["content"]), f, ensure_ascii=False, indent=4) # Reload config self.assets.reload_assets() - async def __format_hoyos(self, username: str, data: List[Any]) -> List[PlayerHoyos]: + async def __format_hoyos(self, username: str, data: List[Any]) -> List[PlayerHoyos]: # noqa return [PlayerHoyos.parse_obj({ - "builds": await self.fetch_builds(profile_id=username, metaname=data[key]["hash"]), + "builds": await self.fetch_builds(profile_id=username, + metaname=data[key]["hash"]), **data[key] }) for key in data] @@ -430,7 +437,8 @@ async def __store_cache(self, key: str, data: Any, *, cache: Any = None): if cache is None: await Config.CACHE.set(key, data) else: - await Config.CACHE.set(key, await self.merge_raw_data(data, cache_data=cache)) + await Config.CACHE.set(key, await self.merge_raw_data(data, + cache_data=cache)) # noqa # Concept by genshin.py python library fetch_user = fetch_user_by_uid diff --git a/enkanetwork/exception.py b/enkanetwork/exception.py index cb26663..6a04ace 100644 --- a/enkanetwork/exception.py +++ b/enkanetwork/exception.py @@ -1,38 +1,71 @@ +""" + System exception +""" + + class VaildateUIDError(Exception): """ Raised when the UID is not valid. """ -class ProfileNotFounded(Exception): - """ Raised when the profile is not found. """ -class BuildNotPublicData(Exception): - """ Raised when the profile hoyos has public to hidden """ +""" + EnkaNetwork network exception +""" -class HTTPException(Exception): - """ Exception that's raised when an HTTP request operation fails. """ -class EnkaValidateFailed(HTTPException): - """ Exception that's raised for when status code 400 occurs.""" +class EnkaNetworError(Exception): + """Base class for EnkaNetwork errors.""" -class EnkaPlayerNotFound(Exception): - """ Raised when the UID is not found. """ -class EnkaServerError(HTTPException): +class NetworkError(EnkaNetworError): + """Base class for exceptions due to networking errors.""" + + +class TimedOut(NetworkError): + """Raised when a request took too long to finish.""" + + +class EnkaServerError(EnkaNetworError): """ Exception that's raised for when status code 500 occurs.""" -class EnkaServerMaintanance(HTTPException): + +class EnkaServerMaintanance(EnkaNetworError): """ Exception that's raised when status code 424 occurs. """ -class EnkaServerRateLimit(HTTPException): + +class EnkaServerRateLimit(EnkaNetworError): """ Exception that's raised when status code 429 occurs.""" -class EnkaServerUnknown(HTTPException): + +class EnkaServerUnknown(EnkaNetworError): """ Exception that's raised when status code 503 occurs. """ + +""" + EnkaNetwork response error +""" + + +class ProfileNotFounded(Exception): + """ Raised when the profile is not found. """ + + +class BuildNotPublicData(Exception): + """ Raised when the profile hoyos has public to hidden """ + + +class EnkaValidateFailed(EnkaNetworError): + """ Exception that's raised for when status code 400 occurs.""" + + +class EnkaPlayerNotFound(Exception): + """ Raised when the UID is not found. """ + + ERROR_ENKA = { 400: [VaildateUIDError, "Validate UID {uid} failed."], - 404: [EnkaPlayerNotFound, "Player ID {uid} not found. Please check your UID / Username"], + 404: [EnkaPlayerNotFound, "Player ID {uid} not found. Please check your UID / Username"], # noqa 429: [EnkaServerRateLimit, "Enka.network has been rate limit this path"], - 424: [EnkaServerMaintanance, "Enka.Network doing maintenance server. Please wait took 5-8 hours or 1 day"], - 500: [EnkaServerError, "Enka.network server has down or Genshin server broken."], + 424: [EnkaServerMaintanance, "Enka.Network doing maintenance server. Please wait took 5-8 hours or 1 day"], # noqa + 500: [EnkaServerError, "Enka.network server has down or Genshin server broken."], # noqa 503: [EnkaServerUnknown, "I screwed up massively"] -} \ No newline at end of file +} diff --git a/enkanetwork/http.py b/enkanetwork/http.py index c81fad7..86de92c 100644 --- a/enkanetwork/http.py +++ b/enkanetwork/http.py @@ -46,6 +46,7 @@ HTTPException, EnkaServerError, EnkaServerUnknown, + TimedOut, ERROR_ENKA ) @@ -69,16 +70,19 @@ def __init__( self.method = method self.url = '' self.username = username - + if endpoint == 'enka': - self.url: str = Config.ENKA_PROTOCOL + "://" + Config.ENKA_URL + path + self.url: str = Config.ENKA_PROTOCOL + "://" + Config.ENKA_URL + + path else: - self.url: str = Config.ASSETS_PROTOCOL + "://" + Config.ASSETS_URL + path + self.url: str = Config.ASSETS_PROTOCOL + "://" + Config.ASSETS_URL + + path + class HTTPClient: LOGGER = logging.getLogger(__name__) - def __init__(self, *, key: str = '', agent: str = '', timeout: int = 5) -> None: + def __init__(self, *, key: str = '', agent: str = '', timeout: int = 5) -> None: # noqa self.__session: aiohttp.ClientSession = MISSING self.__headers: Dict = {} self.__timeout = timeout or 10 @@ -87,7 +91,6 @@ def __init__(self, *, key: str = '', agent: str = '', timeout: int = 5) -> None: if agent != '': Config.init_user_agent(agent) - if key != '': warnings.warn("'key' has depercated.") @@ -114,17 +117,19 @@ async def request(self, route: Route, **kwargs: Any) -> Any: data: Optional[Union[Dict[str, Any], str]] = None if self.__session is MISSING: - self.__session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=self.__timeout)) + self.__session = aiohttp.ClientSession( + timeout=aiohttp.ClientTimeout(total=self.__timeout)) for tries in range(RETRY_MAX): try: - async with self.__session.request(method, url, **kwargs) as response: + async with self.__session.request(method, url, **kwargs) as response: # noqa _host = response.host if 300 > response.status >= 200: data = await utils.to_data(response) - - self.LOGGER.debug('%s %s has received %s', method, url, data) + + self.LOGGER.debug( + '%s %s has received %s', method, url, data) return data if _host == Config.ENKA_URL: @@ -132,10 +137,12 @@ async def request(self, route: Route, **kwargs: Any) -> Any: if err: raise err[0](err[1].format(uid=username)) - raise EnkaServerUnknown(f"Unknow error HTTP status: {response.status}") - + raise EnkaServerUnknown( + f"Unknow error HTTP status: {response.status}") + if response.status >= 400: - self.LOGGER.warning(f"Failure to fetch {url} ({response.status}) Retry {tries} / {RETRY_MAX}") + self.LOGGER.warning( + f"Failure to fetch {url} ({response.status}) Retry {tries} / {RETRY_MAX}") # noqa if tries > RETRY_MAX: raise HTTPException(f"Failed to download {url}") await asyncio.sleep(1) # 1 + tries * 2 @@ -148,7 +155,7 @@ async def request(self, route: Route, **kwargs: Any) -> Any: if tries < 4 and e.errno in (54, 10054): await asyncio.sleep(1 + tries * 2) continue - raise + raise TimedOut("Timeout from enka.network") if response is not None: # We've run out of retries, raise. @@ -160,24 +167,25 @@ async def request(self, route: Route, **kwargs: Any) -> Any: raise RuntimeError('Unreachable code in HTTP handling') def fetch_user_by_uid( - self, + self, uid: Union[str, int], *, info: bool = False ) -> Response[EnkaNetworkPayload]: if not utils.validate_uid(str(uid)): - raise VaildateUIDError("Validate UID failed. Please check your UID.") + raise VaildateUIDError( + "Validate UID failed. Please check your UID.") r = Route( 'GET', - f'/api/uid/{uid}' + (f"?info" if info else ""), + f'/api/uid/{uid}' + ("?info" if info else ""), endpoint='enka', username=uid ) return self.request(r) def fetch_user_by_username( - self, + self, username: Union[str, int] ) -> Response[EnkaNetworkPayload]: r = Route( @@ -189,14 +197,14 @@ def fetch_user_by_username( return self.request(r) def fetch_hoyos_by_username( - self, - username: Union[str, int], + self, + username: Union[str, int], metaname: str = "", show_build: bool = False ): r = Route( 'GET', - f'/api/profile/{username}/hoyos' + f'/api/profile/{username}/hoyos' + (f"/{metaname}" if metaname != '' else '') + ('/builds' if (show_build and metaname != '') else ''), endpoint='enka', @@ -204,11 +212,10 @@ def fetch_hoyos_by_username( ) return self.request(r) - - def fetch_asset(self, folder: str, filename: str) -> Response[DefaultPayload]: + def fetch_asset(self, folder: str, filename: str) -> Response[DefaultPayload]: # noqa r = Route( 'GET', - f'/mrwan200/enkanetwork.py-data/master/exports/{folder}/{filename}', + f'/mrwan200/enkanetwork.py-data/master/exports/{folder}/{filename}', # noqa endpoint='assets' ) return self.request(r) @@ -220,4 +227,4 @@ async def read_from_url(self, url: str) -> bytes: elif resp.status == 404: raise HTTPException(resp, 'asset not found') else: - raise HTTPException(resp, 'failed to get asset') \ No newline at end of file + raise HTTPException(resp, 'failed to get asset') diff --git a/enkanetwork/utils.py b/enkanetwork/utils.py index 412497f..80d1171 100644 --- a/enkanetwork/utils.py +++ b/enkanetwork/utils.py @@ -58,13 +58,11 @@ def validate_uid(uid: str) -> bool: """ Validate UID """ - return len(uid) == 9 and uid.isdigit() and re.match(r"([1,2,5-9])\d{8}", uid) + return len(uid) == 9 and uid.isdigit() and re.match( + r"([1,2,5-9])\d{8}", uid) def get_default_header(): - # Get python version - python_version = sys.version_info - return { "User-Agent": get_user_agent(), } diff --git a/example/cache.py b/example/cache.py index cd21a62..a4d2fe1 100644 --- a/example/cache.py +++ b/example/cache.py @@ -4,6 +4,7 @@ client = EnkaNetworkAPI(lang="th", cache=True) + async def main(): async with client: data = await client.fetch_user(843715177) @@ -12,4 +13,4 @@ async def main(): data_catched = await client.fetch_user(843715177) print("TTL: %s" % data_catched.ttl) -asyncio.run(main()) \ No newline at end of file +asyncio.run(main())