From 06c509cda836b8c50ff6712de27080308b817f71 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Fri, 11 Aug 2017 05:58:27 +0300 Subject: [PATCH] Make more graceful exceptions --- aiogram/bot/api.py | 60 ++++++++++++++++++++++++++++++------------- aiogram/bot/base.py | 6 +++-- aiogram/exceptions.py | 58 +++++++++++++++++++++++------------------ 3 files changed, 79 insertions(+), 45 deletions(-) diff --git a/aiogram/bot/api.py b/aiogram/bot/api.py index e4a59f91..6b14cbe5 100644 --- a/aiogram/bot/api.py +++ b/aiogram/bot/api.py @@ -1,9 +1,12 @@ +import asyncio import logging import os +from http import HTTPStatus import aiohttp -from ..exceptions import ValidationError, TelegramAPIError +from ..exceptions import ValidationError, TelegramAPIError, BadRequest, Unauthorized, NetworkError, RetryAfter, \ + MigrateToChat from ..utils import json from ..utils.helper import Helper, HelperMode, Item @@ -45,22 +48,34 @@ async def _check_result(method_name, response): :param response: The returned response of the method request :return: The result parsed to a JSON dictionary. """ - if response.status != 200: - body = await response.text() - raise TelegramAPIError("The server returned HTTP {0}. Response body:\n[{1}]".format( - response.status, body), - method_name, response.status, body) + body = await response.text() + log.debug(f"Response for {method_name}: {body}") - result_json = await response.json(loads=json.loads) + try: + result_json = await response.json(loads=json.loads) + except ValueError: + result_json = {} - if not result_json.get('ok'): - body = await response.text() - code = result_json.get('error_code') - description = result_json.get('description') - raise TelegramAPIError("Error code: {0} Description {1}".format(code, description), - method_name, response.status, body) - log.debug("Response for '{0}': {1}".format(method_name, result_json)) - return result_json.get('result') + message = result_json.get('description') or body + + if response.status >= HTTPStatus.INTERNAL_SERVER_ERROR: + raise TelegramAPIError(message) + + if 'retry_after' in result_json: + raise RetryAfter(result_json['retry_after']) + elif 'migrate_to_chat_id' in result_json: + raise MigrateToChat(result_json['migrate_to_chat_id']) + elif HTTPStatus.OK <= response.status <= HTTPStatus.IM_USED: + return result_json.get('result') + elif response.status == HTTPStatus.BAD_REQUEST: + raise BadRequest(message) + elif response.status in [HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN]: + raise Unauthorized(message) + elif response.status == HTTPStatus.REQUEST_ENTITY_TOO_LARGE: + raise NetworkError('File too large for uploading. ' + 'Check telegram api limits https://core.telegram.org/bots/api#senddocument') + else: + raise TelegramAPIError(f"{message} [{response.status}]") def _guess_filename(obj): @@ -104,7 +119,7 @@ def _compose_data(params=None, files=None): return data -async def request(session, token, method, data=None, files=None, **kwargs) -> bool or dict: +async def request(session, token, method, data=None, files=None, continue_retry=False, **kwargs) -> bool or dict: """ Make request to API @@ -119,14 +134,23 @@ async def request(session, token, method, data=None, files=None, **kwargs) -> bo :param method: API method :param data: request payload :param files: files + :param continue_retry: :return: bool or dict """ log.debug("Make request: '{0}' with data: {1} and files {2}".format( method, data or {}, files or {})) data = _compose_data(data, files) url = Methods.api_url(token=token, method=method) - async with session.post(url, data=data, **kwargs) as response: - return await _check_result(method, response) + try: + async with session.post(url, data=data, **kwargs) as response: + return await _check_result(method, response) + except aiohttp.ClientError as e: + raise TelegramAPIError(f"aiohttp client throws an error: {e.__class__.__name__}: {e}") + except RetryAfter as e: + if continue_retry: + await asyncio.sleep(e.timeout) + return await request(session, token, method, data, files, **kwargs) + raise class Methods(Helper): diff --git a/aiogram/bot/base.py b/aiogram/bot/base.py index c897cb9d..5ece448a 100644 --- a/aiogram/bot/base.py +++ b/aiogram/bot/base.py @@ -24,7 +24,7 @@ class BaseBot: def __init__(self, token: String, loop: Optional[Union[asyncio.BaseEventLoop, asyncio.AbstractEventLoop]] = None, - connections_limit: Optional[Integer] = 10, proxy=None, proxy_auth=None): + connections_limit: Optional[Integer] = 10, proxy=None, proxy_auth=None, continue_retry=False): """ Instructions how to get Bot token is found here: https://core.telegram.org/bots#3-how-do-i-create-a-bot @@ -36,6 +36,7 @@ class BaseBot: self.__token = token self.proxy = proxy self.proxy_auth = proxy_auth + self.continue_retry = continue_retry if loop is None: loop = asyncio.get_event_loop() @@ -98,7 +99,8 @@ class BaseBot: :raise: :class:`aiogram.exceptions.TelegramApiError` """ return await api.request(self.session, self.__token, method, data, files, - proxy=self.proxy, proxy_auth=self.proxy_auth) + proxy=self.proxy, proxy_auth=self.proxy_auth, + continue_retry=self.continue_retry) async def download_file(self, file_path: String, destination: Optional[InputFile] = None, diff --git a/aiogram/exceptions.py b/aiogram/exceptions.py index 86358878..4fbb4cf9 100644 --- a/aiogram/exceptions.py +++ b/aiogram/exceptions.py @@ -1,30 +1,38 @@ -from .utils import json - - -class ValidationError(Exception): - pass +def _clean_message(text): + return text. \ + lstrip('Error: '). \ + lstrip('[Error]: '). \ + lstrip('Bad Request: ') class TelegramAPIError(Exception): - def __init__(self, message, method, status, body): - super(TelegramAPIError, self).__init__( - "A request to the Telegram API was unsuccessful.\n" + message) - self.method = method - self.status = status - self.body = body + def __init__(self, message): + super(TelegramAPIError, self).__init__(_clean_message(message)) - def json(self): - if not self.body: - return None - try: - data = json.dumps(self.body) - except Exception: - data = None - return data - @property - def parameters(self): - from .types import ResponseParameters - data = self.json() - if data and 'parameters' in data: - return ResponseParameters.deserialize(data['parameters']) +class ValidationError(TelegramAPIError): + pass + + +class BadRequest(TelegramAPIError): + pass + + +class Unauthorized(TelegramAPIError): + pass + + +class NetworkError(TelegramAPIError): + pass + + +class RetryAfter(TelegramAPIError): + def __init__(self, retry_after): + super(RetryAfter, self).__init__(f"Flood control exceeded. Retry in {retry_after} seconds") + self.timeout = retry_after + + +class MigrateToChat(TelegramAPIError): + def __init__(self, chat_id): + super(MigrateToChat, self).__init__(f"The group has been migrated to a supergroup. New id: {chat_id}") + self.migrate_to_chat_id = chat_id