2018-11-30 23:39:13 +03:00
|
|
|
import csv
|
2018-04-14 14:56:39 +03:00
|
|
|
|
2018-04-14 18:22:39 +03:00
|
|
|
from ..utils import snake_to_camel_case
|
2018-04-14 14:56:39 +03:00
|
|
|
|
|
|
|
# Core base classes depending on the integer error code
|
|
|
|
KNOWN_BASE_CLASSES = {
|
|
|
|
303: 'InvalidDCError',
|
|
|
|
400: 'BadRequestError',
|
|
|
|
401: 'UnauthorizedError',
|
|
|
|
403: 'ForbiddenError',
|
|
|
|
404: 'NotFoundError',
|
|
|
|
406: 'AuthKeyError',
|
|
|
|
420: 'FloodError',
|
|
|
|
500: 'ServerError',
|
|
|
|
}
|
|
|
|
|
|
|
|
# Give better semantic names to some captures
|
2018-11-30 23:39:13 +03:00
|
|
|
# TODO Move this to the CSV?
|
2018-04-14 14:56:39 +03:00
|
|
|
CAPTURE_NAMES = {
|
|
|
|
'FloodWaitError': 'seconds',
|
|
|
|
'FloodTestPhoneWaitError': 'seconds',
|
2018-06-27 10:46:14 +03:00
|
|
|
'TakeoutInitDelayError': 'seconds',
|
2018-04-14 14:56:39 +03:00
|
|
|
'FileMigrateError': 'new_dc',
|
|
|
|
'NetworkMigrateError': 'new_dc',
|
|
|
|
'PhoneMigrateError': 'new_dc',
|
|
|
|
'UserMigrateError': 'new_dc',
|
|
|
|
'FilePartMissingError': 'which'
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def _get_class_name(error_code):
|
|
|
|
"""
|
|
|
|
Gets the corresponding class name for the given error code,
|
|
|
|
this either being an integer (thus base error name) or str.
|
|
|
|
"""
|
|
|
|
if isinstance(error_code, int):
|
|
|
|
return KNOWN_BASE_CLASSES.get(
|
|
|
|
error_code, 'RPCError' + str(error_code).replace('-', 'Neg')
|
|
|
|
)
|
|
|
|
|
2018-04-14 20:04:07 +03:00
|
|
|
return snake_to_camel_case(
|
|
|
|
error_code.replace('FIRSTNAME', 'FIRST_NAME').lower(), suffix='Error')
|
2018-04-14 14:56:39 +03:00
|
|
|
|
|
|
|
|
|
|
|
class Error:
|
2018-11-30 23:39:13 +03:00
|
|
|
def __init__(self, codes, name, description):
|
|
|
|
# TODO Some errors have the same name but different integer codes
|
2018-04-14 14:56:39 +03:00
|
|
|
# Should these be split into different files or doesn't really matter?
|
|
|
|
# Telegram isn't exactly consistent with returned errors anyway.
|
2018-11-30 23:39:13 +03:00
|
|
|
self.int_code = codes[0]
|
|
|
|
self.str_code = name
|
|
|
|
self.subclass = _get_class_name(codes[0])
|
|
|
|
self.subclass_exists = codes[0] in KNOWN_BASE_CLASSES
|
2018-04-14 14:56:39 +03:00
|
|
|
self.description = description
|
|
|
|
|
2018-11-30 23:39:13 +03:00
|
|
|
self.has_captures = '_X' in name
|
2018-04-14 14:56:39 +03:00
|
|
|
if self.has_captures:
|
2018-11-30 23:39:13 +03:00
|
|
|
self.name = _get_class_name(name.replace('_X', ''))
|
|
|
|
self.pattern = name.replace('_X', r'_(\d+)')
|
2018-04-14 14:56:39 +03:00
|
|
|
self.capture_name = CAPTURE_NAMES.get(self.name, 'x')
|
|
|
|
else:
|
2018-11-30 23:39:13 +03:00
|
|
|
self.name = _get_class_name(name)
|
|
|
|
self.pattern = name
|
2018-04-14 14:56:39 +03:00
|
|
|
self.capture_name = None
|
|
|
|
|
|
|
|
|
2018-11-30 23:39:13 +03:00
|
|
|
def parse_errors(csv_file):
|
2018-04-14 14:56:39 +03:00
|
|
|
"""
|
2018-11-30 23:39:13 +03:00
|
|
|
Parses the input CSV file with columns (name, error codes, description)
|
|
|
|
and yields `Error` instances as a result.
|
2018-04-14 14:56:39 +03:00
|
|
|
"""
|
2018-11-30 23:39:13 +03:00
|
|
|
with open(csv_file, newline='') as f:
|
|
|
|
f = csv.reader(f)
|
|
|
|
next(f, None) # header
|
|
|
|
for line, (name, codes, description) in enumerate(f, start=2):
|
|
|
|
try:
|
|
|
|
codes = [int(x) for x in codes.split()] or [400]
|
|
|
|
except ValueError:
|
|
|
|
raise ValueError('Not all codes are integers '
|
|
|
|
'(line {})'.format(line)) from None
|
|
|
|
|
|
|
|
yield Error([int(x) for x in codes], name, description)
|