Make more graceful exceptions

This commit is contained in:
Alex Root Junior 2017-08-11 05:58:27 +03:00
parent e66194dbf3
commit 06c509cda8
3 changed files with 79 additions and 45 deletions

View file

@ -1,9 +1,12 @@
import asyncio
import logging import logging
import os import os
from http import HTTPStatus
import aiohttp import aiohttp
from ..exceptions import ValidationError, TelegramAPIError from ..exceptions import ValidationError, TelegramAPIError, BadRequest, Unauthorized, NetworkError, RetryAfter, \
MigrateToChat
from ..utils import json from ..utils import json
from ..utils.helper import Helper, HelperMode, Item 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 :param response: The returned response of the method request
:return: The result parsed to a JSON dictionary. :return: The result parsed to a JSON dictionary.
""" """
if response.status != 200: body = await response.text()
body = await response.text() log.debug(f"Response for {method_name}: {body}")
raise TelegramAPIError("The server returned HTTP {0}. Response body:\n[{1}]".format(
response.status, body),
method_name, response.status, 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'): message = result_json.get('description') or body
body = await response.text()
code = result_json.get('error_code') if response.status >= HTTPStatus.INTERNAL_SERVER_ERROR:
description = result_json.get('description') raise TelegramAPIError(message)
raise TelegramAPIError("Error code: {0} Description {1}".format(code, description),
method_name, response.status, body) if 'retry_after' in result_json:
log.debug("Response for '{0}': {1}".format(method_name, result_json)) raise RetryAfter(result_json['retry_after'])
return result_json.get('result') 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): def _guess_filename(obj):
@ -104,7 +119,7 @@ def _compose_data(params=None, files=None):
return data 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 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 method: API method
:param data: request payload :param data: request payload
:param files: files :param files: files
:param continue_retry:
:return: bool or dict :return: bool or dict
""" """
log.debug("Make request: '{0}' with data: {1} and files {2}".format( log.debug("Make request: '{0}' with data: {1} and files {2}".format(
method, data or {}, files or {})) method, data or {}, files or {}))
data = _compose_data(data, files) data = _compose_data(data, files)
url = Methods.api_url(token=token, method=method) url = Methods.api_url(token=token, method=method)
async with session.post(url, data=data, **kwargs) as response: try:
return await _check_result(method, response) 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): class Methods(Helper):

View file

@ -24,7 +24,7 @@ class BaseBot:
def __init__(self, token: String, def __init__(self, token: String,
loop: Optional[Union[asyncio.BaseEventLoop, asyncio.AbstractEventLoop]] = None, 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 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.__token = token
self.proxy = proxy self.proxy = proxy
self.proxy_auth = proxy_auth self.proxy_auth = proxy_auth
self.continue_retry = continue_retry
if loop is None: if loop is None:
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
@ -98,7 +99,8 @@ class BaseBot:
:raise: :class:`aiogram.exceptions.TelegramApiError` :raise: :class:`aiogram.exceptions.TelegramApiError`
""" """
return await api.request(self.session, self.__token, method, data, files, 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, async def download_file(self, file_path: String,
destination: Optional[InputFile] = None, destination: Optional[InputFile] = None,

View file

@ -1,30 +1,38 @@
from .utils import json def _clean_message(text):
return text. \
lstrip('Error: '). \
class ValidationError(Exception): lstrip('[Error]: '). \
pass lstrip('Bad Request: ')
class TelegramAPIError(Exception): class TelegramAPIError(Exception):
def __init__(self, message, method, status, body): def __init__(self, message):
super(TelegramAPIError, self).__init__( super(TelegramAPIError, self).__init__(_clean_message(message))
"A request to the Telegram API was unsuccessful.\n" + message)
self.method = method
self.status = status
self.body = body
def json(self):
if not self.body:
return None
try:
data = json.dumps(self.body)
except Exception:
data = None
return data
@property class ValidationError(TelegramAPIError):
def parameters(self): pass
from .types import ResponseParameters
data = self.json()
if data and 'parameters' in data: class BadRequest(TelegramAPIError):
return ResponseParameters.deserialize(data['parameters']) 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