Skip to content
This repository was archived by the owner on Sep 20, 2023. It is now read-only.

Fixed emoji support for python-3.8, added use_emoji from initializer, added custom disabling_notification #26

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
.cache/
.idea/
.coverage
htmlcov/
htmlcov/
venv/
/python_telegram_handler.egg-info/
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ requests
mock
pytest
pytest-cov
tox
tox
python-telegram-bot
24 changes: 24 additions & 0 deletions telegram_handler/TelegramBotQueue.py
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 1 addition & 2 deletions telegram_handler/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
25 changes: 18 additions & 7 deletions telegram_handler/formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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 = '<code>%(asctime)s</code> <b>%(levelname)s</b>\nFrom %(name)s:%(funcName)s\n%(message)s'
fmt = ('<code>%(asctime)s</code> <b>%(levelname)s</b>\nFrom %(host)s:%('
'name)s:%(funcName)s\n%(message)s')
parse_mode = 'HTML'

def __init__(self, *args, **kwargs):
Expand All @@ -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)
Expand Down
50 changes: 39 additions & 11 deletions telegram_handler/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import requests

from telegram_handler.TelegramBotQueue import MQBot
from telegram_handler.formatters import HtmlFormatter

logger = logging.getLogger(__name__)
Expand All @@ -19,22 +20,29 @@ 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.')
logger.info('Chat id: %s', self.chat_id)

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):
Expand Down Expand Up @@ -70,6 +78,7 @@ def request(self, method, **kwargs):

return response

"""
def send_message(self, text, **kwargs):
data = {'text': text}
data.update(kwargs)
Expand All @@ -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))
4 changes: 3 additions & 1 deletion tests/test_formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
51 changes: 29 additions & 22 deletions tests/test_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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']
Expand Down Expand Up @@ -158,27 +163,29 @@ def test_handler_init_without_chat():

assert handler.level == logging.NOTSET


def test_handler_respects_proxy():
proxies = {
'http': 'http_proxy_sample',
'https': 'https_proxy_sample',
}

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]
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py27,py30,py34,py35,py36,coverage
envlist = py36,py37,py38,py39,coverage

[testenv]
skip_install = True
Expand All @@ -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
Expand Down