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 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):

View file

@ -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,

View file

@ -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