diff --git a/.gitignore b/.gitignore
index 4b3f0e8..3bf63b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,6 @@
.cache/
.idea/
.coverage
-htmlcov/
\ No newline at end of file
+htmlcov/
+venv/
+/python_telegram_handler.egg-info/
diff --git a/.travis.yml b/.travis.yml
index cd6c9a8..c3a248f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,10 +2,10 @@ language: python
cache: pip
python:
- - "2.7"
- - "3.4"
- - "3.5"
- "3.6"
+ - "3.7"
+ - "3.8"
+ - "3.9"
install:
- pip install tox-travis codecov
diff --git a/requirements-dev.txt b/requirements-dev.txt
index c22fcf4..58dfb10 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -2,4 +2,5 @@ requests
mock
pytest
pytest-cov
-tox
\ No newline at end of file
+tox
+python-telegram-bot
\ No newline at end of file
diff --git a/telegram_handler/TelegramBotQueue.py b/telegram_handler/TelegramBotQueue.py
new file mode 100644
index 0000000..55e8b58
--- /dev/null
+++ b/telegram_handler/TelegramBotQueue.py
@@ -0,0 +1,24 @@
+import telegram
+from telegram.ext import MessageQueue
+from telegram.ext.messagequeue import queuedmessage
+
+
+class MQBot(telegram.bot.Bot):
+ '''A subclass of Bot which delegates send method handling to MQ'''
+ def __init__(self, *args, **kwargs):
+ super(MQBot, self).__init__(*args, **kwargs)
+ # below 2 attributes should be provided for decorator usage
+ self._is_messages_queued_default = kwargs.get('is_queued_def', True)
+ self._msg_queue = kwargs.get('mqueue') or MessageQueue()
+
+ def __del__(self):
+ try:
+ self._msg_queue.stop()
+ except:
+ pass
+
+ @queuedmessage
+ def send_message(self, *args, **kwargs):
+ '''Wrapped method would accept new `queued` and `isgroup`
+ OPTIONAL arguments'''
+ return super(MQBot, self).send_message(*args, **kwargs)
\ No newline at end of file
diff --git a/telegram_handler/__init__.py b/telegram_handler/__init__.py
index 9cb97d3..be62d98 100644
--- a/telegram_handler/__init__.py
+++ b/telegram_handler/__init__.py
@@ -1,5 +1,4 @@
-from telegram_handler.formatters import *
-from telegram_handler.handlers import *
+from telegram_handler.handlers import TelegramHandler
def main(): # pragma: no cover
diff --git a/telegram_handler/formatters.py b/telegram_handler/formatters.py
index d63aaa9..fffbfeb 100644
--- a/telegram_handler/formatters.py
+++ b/telegram_handler/formatters.py
@@ -8,7 +8,8 @@
class TelegramFormatter(logging.Formatter):
"""Base formatter class suitable for use with `TelegramHandler`"""
- fmt = "%(asctime)s %(levelname)s\n[%(name)s:%(funcName)s]\n%(message)s"
+ fmt = ("%(asctime)s %(levelname)s\n[%(host)s:%(name)s:%(funcName)s]\n%("
+ "message)s")
parse_mode = None
def __init__(self, fmt=None, *args, **kwargs):
@@ -17,7 +18,8 @@ def __init__(self, fmt=None, *args, **kwargs):
class MarkdownFormatter(TelegramFormatter):
"""Markdown formatter for telegram."""
- fmt = '`%(asctime)s` *%(levelname)s*\n[%(name)s:%(funcName)s]\n%(message)s'
+ fmt = ('`%(asctime)s` *%(levelname)s*\n[%(host)s:%(name)s:%('
+ 'funcName)s]\n%(message)s')
parse_mode = 'Markdown'
def formatException(self, *args, **kwargs):
@@ -26,14 +28,17 @@ def formatException(self, *args, **kwargs):
class EMOJI:
- WHITE_CIRCLE = '\xE2\x9A\xAA'
- BLUE_CIRCLE = '\xF0\x9F\x94\xB5'
- RED_CIRCLE = '\xF0\x9F\x94\xB4'
+ WHITE_CIRCLE = '\U000026AA'
+ BLUE_CIRCLE = '\U0001F535'
+ YELLOW_CIRCLE = '\U0001f7e1'
+ RED_CIRCLE = '\U0001F534'
+ BLACK_CIRCLE = '\u26AB'
class HtmlFormatter(TelegramFormatter):
"""HTML formatter for telegram."""
- fmt = '%(asctime)s %(levelname)s\nFrom %(name)s:%(funcName)s\n%(message)s'
+ fmt = ('%(asctime)s %(levelname)s\nFrom %(host)s:%('
+ 'name)s:%(funcName)s\n%(message)s')
parse_mode = 'HTML'
def __init__(self, *args, **kwargs):
@@ -52,13 +57,19 @@ def format(self, record):
record.name = escape_html(str(record.name))
if record.msg:
record.msg = escape_html(record.getMessage())
+ if record.message:
+ record.message = escape_html(record.message)
if self.use_emoji:
if record.levelno == logging.DEBUG:
record.levelname += ' ' + EMOJI.WHITE_CIRCLE
elif record.levelno == logging.INFO:
record.levelname += ' ' + EMOJI.BLUE_CIRCLE
- else:
+ elif record.levelno == logging.WARNING:
+ record.levelname += ' ' + EMOJI.YELLOW_CIRCLE
+ elif record.levelno == logging.ERROR:
record.levelname += ' ' + EMOJI.RED_CIRCLE
+ else:
+ record.levelname += ' ' + EMOJI.BLACK_CIRCLE
if hasattr(self, '_style'):
return self._style.format(record)
diff --git a/telegram_handler/handlers.py b/telegram_handler/handlers.py
index f471806..f2cf3e7 100644
--- a/telegram_handler/handlers.py
+++ b/telegram_handler/handlers.py
@@ -3,6 +3,7 @@
import requests
+from telegram_handler.TelegramBotQueue import MQBot
from telegram_handler.formatters import HtmlFormatter
logger = logging.getLogger(__name__)
@@ -19,14 +20,20 @@ class TelegramHandler(logging.Handler):
API_ENDPOINT = 'https://api.telegram.org'
last_response = None
- def __init__(self, token, chat_id=None, level=logging.NOTSET, timeout=2, disable_notification=False,
- disable_web_page_preview=False, proxies=None):
+ def __init__(self, token, chat_id=None, level=logging.WARNING, timeout=2, disable_notification=False,
+ disable_notification_logging_level=logging.ERROR,
+ disable_web_page_preview=False, proxies=None, **kwargs):
self.token = token
self.disable_web_page_preview = disable_web_page_preview
+ # self.disable_notification = kwargs.get('custom_disable_notification', disable_notification)
self.disable_notification = disable_notification
+ if 'custom_enable_notification' in kwargs:
+ self.disable_notification = not kwargs.get('custom_enable_notification')
+ self.disable_notification_logging_level = disable_notification_logging_level
self.timeout = timeout
self.proxies = proxies
self.chat_id = chat_id or self.get_chat_id()
+ level = kwargs.get('custom_logging_level', level)
if not self.chat_id:
level = logging.NOTSET
logger.error('Did not get chat id. Setting handler logging level to NOTSET.')
@@ -34,7 +41,8 @@ def __init__(self, token, chat_id=None, level=logging.NOTSET, timeout=2, disable
super(TelegramHandler, self).__init__(level=level)
- self.setFormatter(HtmlFormatter())
+ self.setFormatter(HtmlFormatter(use_emoji=kwargs.get('use_emoji', True)))
+ self.bot = MQBot(token=self.token)
@classmethod
def format_url(cls, token, method):
@@ -70,6 +78,7 @@ def request(self, method, **kwargs):
return response
+ """
def send_message(self, text, **kwargs):
data = {'text': text}
data.update(kwargs)
@@ -79,23 +88,42 @@ def send_document(self, text, document, **kwargs):
data = {'caption': text}
data.update(kwargs)
return self.request('sendDocument', data=data, files={'document': ('traceback.txt', document, 'text/plain')})
+ """
def emit(self, record):
text = self.format(record)
-
+ disable_notification = (record.levelno is None or record.levelno < self.disable_notification_logging_level) or \
+ self.disable_notification
data = {
'chat_id': self.chat_id,
'disable_web_page_preview': self.disable_web_page_preview,
- 'disable_notification': self.disable_notification,
+ 'disable_notification': disable_notification,
}
if getattr(self.formatter, 'parse_mode', None):
data['parse_mode'] = self.formatter.parse_mode
- if len(text) < MAX_MESSAGE_LEN:
- response = self.send_message(text, **data)
- else:
- response = self.send_document(text[:1000], document=BytesIO(text.encode()), **data)
+ kwargs = dict()
+ if self.timeout is not None:
+ kwargs.setdefault('timeout', self.timeout)
+ if self.proxies is not None:
+ kwargs.setdefault('proxies', self.proxies)
+
+
- if response and not response.get('ok', False):
- logger.warning('Telegram responded with ok=false status! {}'.format(response))
+ try:
+ if len(text) < MAX_MESSAGE_LEN:
+ response = self.bot.send_message(text=text, api_kwargs=kwargs, **data)
+ else:
+ del data['disable_web_page_preview']
+ response = self.bot.send_document(caption=text[:1000], api_kwargs=kwargs, document=BytesIO(text.encode()),
+ filename="traceback.txt",
+ **data)
+ if not response:
+ logger.warning(
+ 'Telegram responded with ok=false status! {}'.format(
+ response))
+ except Exception as e:
+ logger.exception("Error while sending message to telegram, "
+ f"{str(e)}")
+ logger.debug(str(kwargs))
diff --git a/tests/test_formatters.py b/tests/test_formatters.py
index 27aecf0..7ef4bc8 100644
--- a/tests/test_formatters.py
+++ b/tests/test_formatters.py
@@ -42,7 +42,9 @@ def test_html_formatter_emoji():
emoji_level_map = {
formatters.EMOJI.WHITE_CIRCLE: [logging.DEBUG],
formatters.EMOJI.BLUE_CIRCLE: [logging.INFO],
- formatters.EMOJI.RED_CIRCLE: [logging.WARNING, logging.ERROR]
+ formatters.EMOJI.YELLOW_CIRCLE: [logging.WARNING],
+ formatters.EMOJI.RED_CIRCLE: [logging.ERROR],
+ formatters.EMOJI.BLACK_CIRCLE: [logging.CRITICAL]
}
for emoji, levels in emoji_level_map.items():
diff --git a/tests/test_handlers.py b/tests/test_handlers.py
index 49e7a55..ca10a34 100644
--- a/tests/test_handlers.py
+++ b/tests/test_handlers.py
@@ -4,6 +4,10 @@
import mock
import pytest
import requests
+from _pytest.monkeypatch import MonkeyPatch
+
+message_queue_patch = MonkeyPatch()
+message_queue_patch.setattr("telegram_handler.TelegramBotQueue.MQBot.__init__", lambda *_, **kwargs: None)
import telegram_handler.handlers
@@ -52,21 +56,22 @@ def handler():
def test_emit(handler):
record = logging.makeLogRecord({'msg': 'hello'})
- with mock.patch('requests.post') as patch:
+ with mock.patch('telegram_handler.TelegramBotQueue.MQBot.send_message') as patch:
handler.emit(record)
assert patch.called
assert patch.call_count == 1
- assert patch.call_args[1]['json']['chat_id'] == 'bar'
- assert 'hello' in patch.call_args[1]['json']['text']
- assert patch.call_args[1]['json']['parse_mode'] == 'HTML'
+ assert patch.call_args[1]['chat_id'] == 'bar'
+ assert 'hello' in patch.call_args[1]['text']
+ assert patch.call_args[1]['parse_mode'] == 'HTML'
+
def test_emit_big_message(handler):
message = '*' * telegram_handler.handlers.MAX_MESSAGE_LEN
record = logging.makeLogRecord({'msg': message})
- with mock.patch('requests.post') as patch:
+ with mock.patch('telegram_handler.TelegramBotQueue.MQBot.send_document') as patch:
handler.emit(record)
assert patch.called
@@ -76,25 +81,25 @@ def test_emit_big_message(handler):
def test_emit_http_exception(handler):
record = logging.makeLogRecord({'msg': 'hello'})
- with mock.patch('requests.post') as patch:
- response = requests.Response()
- response.status_code = 500
- response._content = 'Server error'.encode()
- patch.return_value = response
+ with mock.patch('telegram_handler.TelegramBotQueue.MQBot.send_message') as patch:
+ # response = requests.Response()
+ # response.status_code = 500
+ # response._content = 'Server error'.encode()
+ patch.return_value = None
handler.emit(record)
- assert telegram_handler.handlers.logger.handlers[0].messages['error']
- assert telegram_handler.handlers.logger.handlers[0].messages['debug']
+ assert telegram_handler.handlers.logger.handlers[0].messages['warning']
+ # assert telegram_handler.handlers.logger.handlers[0].messages['debug']
def test_emit_telegram_error(handler):
record = logging.makeLogRecord({'msg': 'hello'})
- with mock.patch('requests.post') as patch:
- response = requests.Response()
- response.status_code = 200
- response._content = json.dumps({'ok': False}).encode()
- patch.return_value = response
+ with mock.patch('telegram_handler.TelegramBotQueue.MQBot.send_message') as patch:
+ #response = requests.Response()
+ #response.status_code = 200
+ #response._content = json.dumps({'ok': False}).encode()
+ patch.return_value = None
handler.emit(record)
assert telegram_handler.handlers.logger.handlers[0].messages['warning']
@@ -158,6 +163,7 @@ def test_handler_init_without_chat():
assert handler.level == logging.NOTSET
+
def test_handler_respects_proxy():
proxies = {
'http': 'http_proxy_sample',
@@ -165,20 +171,21 @@ def test_handler_respects_proxy():
}
handler = telegram_handler.handlers.TelegramHandler('foo', 'bar', level=logging.INFO, proxies=proxies)
-
+
record = logging.makeLogRecord({'msg': 'hello'})
- with mock.patch('requests.post') as patch:
+ with mock.patch('telegram_handler.TelegramBotQueue.MQBot.send_message') as patch:
handler.emit(record)
- assert patch.call_args[1]['proxies'] == proxies
+ assert patch.call_args[1]['api_kwargs']['proxies'] == proxies
+
def test_custom_formatter(handler):
handler.setFormatter(logging.Formatter())
record = logging.makeLogRecord({'msg': 'hello'})
- with mock.patch('requests.post') as patch:
+ with mock.patch('telegram_handler.TelegramBotQueue.MQBot.send_message') as patch:
handler.emit(record)
- assert 'parse_mode' not in patch.call_args[1]['json']
+ assert 'parse_mode' not in patch.call_args[1]
diff --git a/tox.ini b/tox.ini
index ebece3b..8a13c84 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py27,py30,py34,py35,py36,coverage
+envlist = py36,py37,py38,py39,coverage
[testenv]
skip_install = True
@@ -9,7 +9,7 @@ deps=
commands=py.test tests --cov-fail-under 90 --color=auto --cov=telegram_handler --cov-report=term-missing
[testenv:coverage]
-basepython = python3.5
+basepython = python3.6
passenv = CI TRAVIS_BUILD_ID TRAVIS TRAVIS_BRANCH TRAVIS_JOB_NUMBER TRAVIS_PULL_REQUEST TRAVIS_JOB_ID TRAVIS_REPO_SLUG TRAVIS_COMMIT
deps =
codecov>=1.4.0