diff --git a/requirements.txt b/requirements.txt index 6e4ca40..a38fc09 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ py==1.4.29 pytest==3.0.2 requests==2.20.0 -six==1.9.0 wheel==0.24.0 diff --git a/setup.py b/setup.py index f176609..b3e8158 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ setup(name='pyTelegramBotAPI', packages=['telebot'], license='GPL2', keywords='telegram bot api tools', - install_requires=['requests', 'six'], + install_requires=['requests'], extras_require={ 'json': 'ujson', 'redis': 'redis>=3.4.1' diff --git a/telebot/__init__.py b/telebot/__init__.py index 063163e..3ca0cfe 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -7,8 +7,6 @@ import sys import threading import time -import six - logger = logging.getLogger('TeleBot') formatter = logging.Formatter( '%(asctime)s (%(filename)s:%(lineno)d %(threadName)s) %(levelname)s - %(name)s: "%(message)s"' @@ -42,6 +40,15 @@ class Handler: return getattr(self, item) +class ExceptionHandler: + """ + Class for handling exceptions while Polling + """ + + def handle(self, exception): + return False + + class TeleBot: """ This is TeleBot Class Methods: @@ -86,7 +93,7 @@ class TeleBot: def __init__( self, token, parse_mode=None, threaded=True, skip_pending=False, num_threads=2, - next_step_backend=None, reply_backend=None + next_step_backend=None, reply_backend=None, exception_handler=None ): """ :param token: bot API token @@ -111,6 +118,8 @@ class TeleBot: if not self.reply_backend: self.reply_backend = MemoryHandlerBackend() + self.exception_handler = exception_handler + self.message_handlers = [] self.edited_message_handlers = [] self.channel_post_handlers = [] @@ -123,20 +132,20 @@ class TeleBot: self.poll_handlers = [] self.poll_answer_handlers = [] - self.typed_middleware_handlers = { - 'message': [], - 'edited_message': [], - 'channel_post': [], - 'edited_channel_post': [], - 'inline_query': [], - 'chosen_inline_result': [], - 'callback_query': [], - 'shipping_query': [], - 'pre_checkout_query': [], - 'poll': [], - } - - self.default_middleware_handlers = [] + if apihelper.ENABLE_MIDDLEWARE: + self.typed_middleware_handlers = { + 'message': [], + 'edited_message': [], + 'channel_post': [], + 'edited_channel_post': [], + 'inline_query': [], + 'chosen_inline_result': [], + 'callback_query': [], + 'shipping_query': [], + 'pre_checkout_query': [], + 'poll': [], + } + self.default_middleware_handlers = [] self.threaded = threaded if self.threaded: @@ -286,17 +295,22 @@ class TeleBot: self.process_new_updates(updates) def process_new_updates(self, updates): - new_messages = [] - new_edited_messages = [] - new_channel_posts = [] - new_edited_channel_posts = [] - new_inline_querys = [] - new_chosen_inline_results = [] - new_callback_querys = [] - new_shipping_querys = [] - new_pre_checkout_querys = [] - new_polls = [] - new_poll_answers = [] + upd_count = len(updates) + logger.debug('Received {0} new updates'.format(upd_count)) + if (upd_count == 0): + return + + new_messages = None + new_edited_messages = None + new_channel_posts = None + new_edited_channel_posts = None + new_inline_queries = None + new_chosen_inline_results = None + new_callback_queries = None + new_shipping_queries = None + new_pre_checkout_queries = None + new_polls = None + new_poll_answers = None for update in updates: if apihelper.ENABLE_MIDDLEWARE: @@ -305,50 +319,60 @@ class TeleBot: if update.update_id > self.last_update_id: self.last_update_id = update.update_id if update.message: + if new_messages is None: new_messages = [] new_messages.append(update.message) if update.edited_message: + if new_edited_messages is None: new_edited_messages = [] new_edited_messages.append(update.edited_message) if update.channel_post: + if new_channel_posts is None: new_channel_posts = [] new_channel_posts.append(update.channel_post) if update.edited_channel_post: + if new_edited_channel_posts is None: new_edited_channel_posts = [] new_edited_channel_posts.append(update.edited_channel_post) if update.inline_query: - new_inline_querys.append(update.inline_query) + if new_inline_queries is None: new_inline_queries = [] + new_inline_queries.append(update.inline_query) if update.chosen_inline_result: + if new_chosen_inline_results is None: new_chosen_inline_results = [] new_chosen_inline_results.append(update.chosen_inline_result) if update.callback_query: - new_callback_querys.append(update.callback_query) + if new_callback_queries is None: new_callback_queries = [] + new_callback_queries.append(update.callback_query) if update.shipping_query: - new_shipping_querys.append(update.shipping_query) + if new_shipping_queries is None: new_shipping_queries = [] + new_shipping_queries.append(update.shipping_query) if update.pre_checkout_query: - new_pre_checkout_querys.append(update.pre_checkout_query) + if new_pre_checkout_queries is None: new_pre_checkout_queries = [] + new_pre_checkout_queries.append(update.pre_checkout_query) if update.poll: + if new_polls is None: new_polls = [] new_polls.append(update.poll) if update.poll_answer: + if new_poll_answers is None: new_poll_answers = [] new_poll_answers.append(update.poll_answer) - logger.debug('Received {0} new updates'.format(len(updates))) - if len(new_messages) > 0: + if new_messages: self.process_new_messages(new_messages) - if len(new_edited_messages) > 0: + if new_edited_messages: self.process_new_edited_messages(new_edited_messages) - if len(new_channel_posts) > 0: + if new_channel_posts: self.process_new_channel_posts(new_channel_posts) - if len(new_edited_channel_posts) > 0: + if new_edited_channel_posts: self.process_new_edited_channel_posts(new_edited_channel_posts) - if len(new_inline_querys) > 0: - self.process_new_inline_query(new_inline_querys) - if len(new_chosen_inline_results) > 0: + if new_inline_queries: + self.process_new_inline_query(new_inline_queries) + if new_chosen_inline_results: self.process_new_chosen_inline_query(new_chosen_inline_results) - if len(new_callback_querys) > 0: - self.process_new_callback_query(new_callback_querys) - if len(new_shipping_querys) > 0: - self.process_new_shipping_query(new_shipping_querys) - if len(new_pre_checkout_querys) > 0: - self.process_new_pre_checkout_query(new_pre_checkout_querys) - if len(new_polls) > 0: + if new_callback_queries: + self.process_new_callback_query(new_callback_queries) + if new_shipping_queries: + self.process_new_shipping_query(new_shipping_queries) + if new_pre_checkout_queries: + self.process_new_pre_checkout_query(new_pre_checkout_queries) + if new_polls: self.process_new_poll(new_polls) - if len(new_poll_answers) > 0: + if new_poll_answers: self.process_new_poll_answer(new_poll_answers) def process_new_messages(self, new_messages): @@ -398,6 +422,8 @@ class TeleBot: default_middleware_handler(self, update) def __notify_update(self, new_messages): + if len(self.update_listener) == 0: + return for listener in self.update_listener: self._exec_task(listener, new_messages) @@ -452,20 +478,41 @@ class TeleBot: error_interval = 0.25 except apihelper.ApiException as e: - logger.error(e) - if not none_stop: - self.__stop_polling.set() - logger.info("Exception occurred. Stopping.") + if self.exception_handler is not None: + handled = self.exception_handler.handle(e) + else: + handled = False + + if not handled: + logger.error(e) + if not none_stop: + self.__stop_polling.set() + logger.info("Exception occurred. Stopping.") + else: + polling_thread.clear_exceptions() + self.worker_pool.clear_exceptions() + logger.info("Waiting for {0} seconds until retry".format(error_interval)) + time.sleep(error_interval) + error_interval *= 2 else: polling_thread.clear_exceptions() self.worker_pool.clear_exceptions() - logger.info("Waiting for {0} seconds until retry".format(error_interval)) time.sleep(error_interval) - error_interval *= 2 except KeyboardInterrupt: logger.info("KeyboardInterrupt received.") self.__stop_polling.set() break + except Exception as e: + if self.exception_handler is not None: + handled = self.exception_handler.handle(e) + else: + handled = False + if not handled: + raise e + else: + polling_thread.clear_exceptions() + self.worker_pool.clear_exceptions() + time.sleep(error_interval) polling_thread.stop() logger.info('Stopped polling.') @@ -480,18 +527,35 @@ class TeleBot: self.__retrieve_updates(timeout) error_interval = 0.25 except apihelper.ApiException as e: - logger.error(e) - if not none_stop: - self.__stop_polling.set() - logger.info("Exception occurred. Stopping.") + if self.exception_handler is not None: + handled = self.exception_handler.handle(e) + else: + handled = False + + if not handled: + logger.error(e) + if not none_stop: + self.__stop_polling.set() + logger.info("Exception occurred. Stopping.") + else: + logger.info("Waiting for {0} seconds until retry".format(error_interval)) + time.sleep(error_interval) + error_interval *= 2 else: - logger.info("Waiting for {0} seconds until retry".format(error_interval)) time.sleep(error_interval) - error_interval *= 2 except KeyboardInterrupt: logger.info("KeyboardInterrupt received.") self.__stop_polling.set() break + except Exception as e: + if self.exception_handler is not None: + handled = self.exception_handler.handle(e) + else: + handled = False + if not handled: + raise e + else: + time.sleep(error_interval) logger.info('Stopped polling.') @@ -1063,7 +1127,6 @@ class TeleBot: """ return apihelper.set_chat_administrator_custom_title(self.token, chat_id, user_id, custom_title) - def set_chat_permissions(self, chat_id, permissions): """ Use this method to set default chat permissions for all members. @@ -1278,7 +1341,7 @@ class TeleBot: def send_invoice(self, chat_id, title, description, invoice_payload, provider_token, currency, prices, start_parameter, photo_url=None, photo_size=None, photo_width=None, photo_height=None, need_name=None, need_phone_number=None, need_email=None, need_shipping_address=None, - send_phone_number_to_provider = None, send_email_to_provider = None, is_flexible=None, + send_phone_number_to_provider=None, send_email_to_provider=None, is_flexible=None, disable_notification=None, reply_to_message_id=None, reply_markup=None, provider_data=None, timeout=None): """ Sends invoice @@ -1394,7 +1457,7 @@ class TeleBot: :return: """ parse_mode = self.parse_mode if not parse_mode else parse_mode - + result = apihelper.edit_message_caption(self.token, caption, chat_id, message_id, inline_message_id, parse_mode, reply_markup) if type(result) == bool: @@ -1542,8 +1605,9 @@ class TeleBot: for message in new_messages: if hasattr(message, "reply_to_message") and message.reply_to_message is not None: handlers = self.reply_backend.get_handlers(message.reply_to_message.message_id) - for handler in handlers: - self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) + if handlers: + for handler in handlers: + self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) def register_next_step_handler(self, message, callback, *args, **kwargs): """ @@ -1615,11 +1679,12 @@ class TeleBot: for i, message in enumerate(new_messages): need_pop = False handlers = self.next_step_backend.get_handlers(message.chat.id) - for handler in handlers: - need_pop = True - self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) + if handlers: + for handler in handlers: + need_pop = True + self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) if need_pop: - new_messages.pop(i) # removing message that detects with next_step_handler + new_messages.pop(i) # removing message that was detected with next_step_handler @staticmethod def _build_handler_dict(handler, **filters): @@ -1721,9 +1786,7 @@ class TeleBot: func=func, content_types=content_types, **kwargs) - self.add_message_handler(handler_dict) - return handler return decorator @@ -1845,6 +1908,7 @@ class TeleBot: :param kwargs: :return: """ + def decorator(handler): handler_dict = self._build_handler_dict(handler, func=func, **kwargs) self.add_inline_handler(handler_dict) @@ -1867,6 +1931,7 @@ class TeleBot: :param kwargs: :return: """ + def decorator(handler): handler_dict = self._build_handler_dict(handler, func=func, **kwargs) self.add_chosen_inline_handler(handler_dict) @@ -1889,6 +1954,7 @@ class TeleBot: :param kwargs: :return: """ + def decorator(handler): handler_dict = self._build_handler_dict(handler, func=func, **kwargs) self.add_callback_query_handler(handler_dict) @@ -1911,6 +1977,7 @@ class TeleBot: :param kwargs: :return: """ + def decorator(handler): handler_dict = self._build_handler_dict(handler, func=func, **kwargs) self.add_shipping_query_handler(handler_dict) @@ -1933,6 +2000,7 @@ class TeleBot: :param kwargs: :return: """ + def decorator(handler): handler_dict = self._build_handler_dict(handler, func=func, **kwargs) self.add_pre_checkout_query_handler(handler_dict) @@ -1955,6 +2023,7 @@ class TeleBot: :param kwargs: :return: """ + def decorator(handler): handler_dict = self._build_handler_dict(handler, func=func, **kwargs) self.add_poll_handler(handler_dict) @@ -1977,6 +2046,7 @@ class TeleBot: :param kwargs: :return: """ + def decorator(handler): handler_dict = self._build_handler_dict(handler, func=func, **kwargs) self.add_poll_answer_handler(handler_dict) @@ -1999,7 +2069,7 @@ class TeleBot: :param message: :return: """ - for message_filter, filter_value in six.iteritems(message_handler['filters']): + for message_filter, filter_value in message_handler['filters'].items(): if filter_value is None: continue @@ -2033,6 +2103,8 @@ class TeleBot: :param new_messages: :return: """ + if len(handlers) == 0: + return for message in new_messages: for message_handler in handlers: if self._test_message_handler(message_handler, message): diff --git a/telebot/apihelper.py b/telebot/apihelper.py index e75ed53..70c3f26 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1005,6 +1005,7 @@ def send_invoice( :param reply_to_message_id: If the message is a reply, ID of the original message :param reply_markup: A JSON-serialized object for an inline keyboard. If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button :param provider_data: A JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider. + :param timeout: :return: """ method_url = r'sendInvoice' @@ -1318,7 +1319,7 @@ class ApiTelegramException(ApiException): """ def __init__(self, function_name, result, result_json): super(ApiTelegramException, self).__init__( - "Error code: {0} Description: {1}" \ + "Error code: {0}. Description: {1}" \ .format(result_json['error_code'], result_json['description']), function_name, result) diff --git a/telebot/handler_backends.py b/telebot/handler_backends.py index e71fb24..a10d13c 100644 --- a/telebot/handler_backends.py +++ b/telebot/handler_backends.py @@ -32,10 +32,13 @@ class MemoryHandlerBackend(HandlerBackend): self.handlers[handler_group_id] = [handler] def clear_handlers(self, handler_group_id): - self.handlers.pop(handler_group_id, []) + self.handlers.pop(handler_group_id, None) def get_handlers(self, handler_group_id): - return self.handlers.pop(handler_group_id, []) + return self.handlers.pop(handler_group_id, None) + + def load_handlers(self, filename, del_file_after_loading): + raise NotImplementedError() class FileHandlerBackend(HandlerBackend): @@ -50,19 +53,15 @@ class FileHandlerBackend(HandlerBackend): self.handlers[handler_group_id].append(handler) else: self.handlers[handler_group_id] = [handler] - self.start_save_timer() def clear_handlers(self, handler_group_id): - self.handlers.pop(handler_group_id, []) - + self.handlers.pop(handler_group_id, None) self.start_save_timer() def get_handlers(self, handler_group_id): - handlers = self.handlers.pop(handler_group_id, []) - + handlers = self.handlers.pop(handler_group_id, None) self.start_save_timer() - return handlers def start_save_timer(self): @@ -136,10 +135,9 @@ class RedisHandlerBackend(HandlerBackend): self.redis.delete(self._key(handler_group_id)) def get_handlers(self, handler_group_id): - handlers = [] + handlers = None value = self.redis.get(self._key(handler_group_id)) if value: handlers = pickle.loads(value) self.clear_handlers(handler_group_id) - return handlers diff --git a/telebot/types.py b/telebot/types.py index 4ef068d..d00cb6d 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -7,8 +7,6 @@ try: except ImportError: import json -import six - from telebot import util DISABLE_KEYLEN_ERROR = False @@ -81,13 +79,13 @@ class JsonDeserializable(object): def __str__(self): d = {} - for x, y in six.iteritems(self.__dict__): + for x, y in self.__dict__.items(): if hasattr(y, '__dict__'): d[x] = y.__dict__ else: d[x] = y - return six.text_type(d) + return str(d) class Update(JsonDeserializable): diff --git a/telebot/util.py b/telebot/util.py index 67c6abf..099828a 100644 --- a/telebot/util.py +++ b/telebot/util.py @@ -2,21 +2,12 @@ import random import re import string -import sys import threading import traceback import warnings import functools -import six -from six import string_types - -# Python3 queue support. - -try: - import Queue -except ImportError: - import queue as Queue +import queue as Queue import logging try: @@ -51,7 +42,7 @@ class WorkerThread(threading.Thread): self.continue_event = threading.Event() self.exception_callback = exception_callback - self.exc_info = None + self.exception_info = None self._running = True self.start() @@ -73,11 +64,11 @@ class WorkerThread(threading.Thread): pass except Exception as e: logger.error(type(e).__name__ + " occurred, args=" + str(e.args) + "\n" + traceback.format_exc()) - self.exc_info = sys.exc_info() + self.exception_info = e self.exception_event.set() if self.exception_callback: - self.exception_callback(self, self.exc_info) + self.exception_callback(self, self.exception_info) self.continue_event.wait() def put(self, task, *args, **kwargs): @@ -85,7 +76,7 @@ class WorkerThread(threading.Thread): def raise_exceptions(self): if self.exception_event.is_set(): - six.reraise(self.exc_info[0], self.exc_info[1], self.exc_info[2]) + raise self.exception_info def clear_exceptions(self): self.exception_event.clear() @@ -103,19 +94,19 @@ class ThreadPool: self.num_threads = num_threads self.exception_event = threading.Event() - self.exc_info = None + self.exception_info = None def put(self, func, *args, **kwargs): self.tasks.put((func, args, kwargs)) def on_exception(self, worker_thread, exc_info): - self.exc_info = exc_info + self.exception_info = exc_info self.exception_event.set() worker_thread.continue_event.set() def raise_exceptions(self): if self.exception_event.is_set(): - six.reraise(self.exc_info[0], self.exc_info[1], self.exc_info[2]) + raise self.exception_info def clear_exceptions(self): self.exception_event.clear() @@ -140,15 +131,15 @@ class AsyncTask: def _run(self): try: self.result = self.target(*self.args, **self.kwargs) - except: - self.result = sys.exc_info() + except Exception as e: + self.result = e self.done = True def wait(self): if not self.done: self.thread.join() if isinstance(self.result, BaseException): - six.reraise(self.result[0], self.result[1], self.result[2]) + raise self.result else: return self.result @@ -164,7 +155,7 @@ def async_dec(): def is_string(var): - return isinstance(var, string_types) + return isinstance(var, str) def is_dict(var): return isinstance(var, dict)