Skip to content

Add default values #1490

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Feb 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
446f238
added parse_mode parameter in Updater and in Bot class to set a defau…
ak4zh Sep 27, 2018
72db7b6
Resovle conflicts
Bibo-Joshi Aug 28, 2019
265b283
DefaultValue
Bibo-Joshi Aug 28, 2019
04de5a5
Add default parse_mode everywhere
Bibo-Joshi Aug 28, 2019
bfe773b
Renome to default_parse_mode
Bibo-Joshi Aug 28, 2019
4c23bc8
Test def parse_mode for send_*, edit_message_*
Bibo-Joshi Aug 28, 2019
8c9fb7c
Remove duplicate code in Input* classes
Bibo-Joshi Oct 14, 2019
0af02eb
Merge branch 'master' into default_parse_mode
Bibo-Joshi Oct 14, 2019
c129a95
Improve handling of def_p_m for Bot methods
Bibo-Joshi Oct 17, 2019
85296c6
Remove unneeded deletion of kwargs
Bibo-Joshi Oct 19, 2019
5e6cc25
Make @log preserve signature, add bots with defaults to tests
Bibo-Joshi Oct 26, 2019
7ad277d
Fix Codacy
Bibo-Joshi Oct 26, 2019
9f9dc3e
Fix Travis Error
Bibo-Joshi Oct 26, 2019
57306ff
Add default_disable_notification
Bibo-Joshi Oct 27, 2019
6ef7d76
Add default_disable_web_page_preview
Bibo-Joshi Oct 27, 2019
d073813
Add default_disable_timeout
Bibo-Joshi Oct 27, 2019
b83376a
Merge remote-tracking branch 'origin/master' into default_parse_mode
Bibo-Joshi Nov 15, 2019
23af7ad
Merge remote-tracking branch 'origin/master' into default_parse_mode
Bibo-Joshi Jan 19, 2020
4a5a96f
add default_disable_web_page_preview for InputTextMessageContent
Bibo-Joshi Jan 20, 2020
222edb8
add default_quote
Bibo-Joshi Jan 20, 2020
3d3daa7
Try fixing test_pin_and_unpin
Bibo-Joshi Jan 20, 2020
5e42eac
Merge master
Bibo-Joshi Jan 30, 2020
b838c28
Merge branch 'master' into default_parse_mode
Bibo-Joshi Jan 30, 2020
1baa91a
Just run 3.5 tests for paranoia
Bibo-Joshi Feb 2, 2020
65a04d5
add tests for Defaults
Bibo-Joshi Feb 2, 2020
ee66f54
Revert "Just run 3.5 tests for paranoia"
Bibo-Joshi Feb 2, 2020
a08b473
Tidy up parameters, move Defaults to ext
Bibo-Joshi Feb 2, 2020
85c2049
Stage new files, because im an idiot
Bibo-Joshi Feb 2, 2020
9665ea1
Merge branch 'master' into default_parse_mode
Bibo-Joshi Feb 2, 2020
07cb9c7
remove debug prints
Bibo-Joshi Feb 2, 2020
f657f70
change equality checks for DEFAULT_NONE
Bibo-Joshi Feb 2, 2020
18d6edd
Merge branch 'master' into default_parse_mode
Eldinnie Feb 2, 2020
9edec23
Some last changes
Bibo-Joshi Feb 3, 2020
44535ae
fix S&R error so that tests actually run
Bibo-Joshi Feb 4, 2020
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
6 changes: 6 additions & 0 deletions docs/source/telegram.ext.defaults.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
telegram.ext.Defaults
=====================

.. autoclass:: telegram.ext.Defaults
:members:
:show-inheritance:
1 change: 1 addition & 0 deletions docs/source/telegram.ext.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ telegram.ext package
telegram.ext.messagequeue
telegram.ext.delayqueue
telegram.ext.callbackcontext
telegram.ext.defaults

Handlers
--------
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ future>=0.16.0
certifi
tornado>=5.1
cryptography
decorator>=4.4.0
102 changes: 95 additions & 7 deletions telegram/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"""This module contains an object that represents a Telegram Bot."""

import functools
import inspect
from decorator import decorate

try:
import ujson as json
Expand All @@ -39,7 +41,7 @@
PhotoSize, Audio, Document, Sticker, Video, Animation, Voice, VideoNote,
Location, Venue, Contact, InputFile, Poll)
from telegram.error import InvalidToken, TelegramError
from telegram.utils.helpers import to_timestamp
from telegram.utils.helpers import to_timestamp, DEFAULT_NONE
from telegram.utils.request import Request

logging.getLogger(__name__).addHandler(logging.NullHandler())
Expand All @@ -57,18 +59,17 @@ def decorator(self, *args, **kwargs):
return decorator


def log(func):
def log(func, *args, **kwargs):
logger = logging.getLogger(func.__module__)

@functools.wraps(func)
def decorator(self, *args, **kwargs):
logger.debug('Entering: %s', func.__name__)
result = func(self, *args, **kwargs)
result = func(*args, **kwargs)
logger.debug(result)
logger.debug('Exiting: %s', func.__name__)
return result

return decorator
return decorate(func, decorator)


class Bot(TelegramObject):
Expand All @@ -82,13 +83,55 @@ class Bot(TelegramObject):
:obj:`telegram.utils.request.Request`.
private_key (:obj:`bytes`, optional): Private key for decryption of telegram passport data.
private_key_password (:obj:`bytes`, optional): Password for above private key.
defaults (:class:`telegram.ext.Defaults`, optional): An object containing default values to
be used if not set explicitly in the bot methods.

"""

def __init__(self, token, base_url=None, base_file_url=None, request=None, private_key=None,
private_key_password=None):
def __new__(cls, *args, **kwargs):
# Get default values from kwargs
defaults = kwargs.get('defaults')

# Make an instance of the class
instance = super(Bot, cls).__new__(cls)

if not defaults:
return instance

# For each method ...
for method_name, method in inspect.getmembers(instance, predicate=inspect.ismethod):
# ... get kwargs
argspec = inspect.getargspec(method)
kwarg_names = argspec.args[-len(argspec.defaults or []):]
# ... check if Defaults has a attribute that matches the kwarg name
needs_default = [
kwarg_name for kwarg_name in kwarg_names if hasattr(defaults, kwarg_name)
]
# ... make a dict of kwarg name and the default value
default_kwargs = {
kwarg_name: getattr(defaults, kwarg_name) for kwarg_name in needs_default if (
getattr(defaults, kwarg_name) is not DEFAULT_NONE
)
}
# ... apply the defaults using a partial
if default_kwargs:
setattr(instance, method_name, functools.partial(method, **default_kwargs))

return instance

def __init__(self,
token,
base_url=None,
base_file_url=None,
request=None,
private_key=None,
private_key_password=None,
defaults=None):
self.token = self._validate_token(token)

# Gather default
self.defaults = defaults

if base_url is None:
base_url = 'https://api.telegram.org/bot'

Expand Down Expand Up @@ -120,11 +163,20 @@ def _message(self, url, data, reply_to_message_id=None, disable_notification=Non
else:
data['reply_markup'] = reply_markup

if data.get('media') and (data['media'].parse_mode == DEFAULT_NONE):
if self.defaults:
data['media'].parse_mode = self.defaults.parse_mode
else:
data['media'].parse_mode = None

result = self._request.post(url, data, timeout=timeout)

if result is True:
return result

if self.defaults:
result['default_quote'] = self.defaults.quote

return Message.de_json(result, self)

@property
Expand Down Expand Up @@ -978,13 +1030,24 @@ def send_media_group(self,

data = {'chat_id': chat_id, 'media': media}

for m in data['media']:
if m.parse_mode == DEFAULT_NONE:
if self.defaults:
m.parse_mode = self.defaults.parse_mode
else:
m.parse_mode = None

if reply_to_message_id:
data['reply_to_message_id'] = reply_to_message_id
if disable_notification:
data['disable_notification'] = disable_notification

result = self._request.post(url, data, timeout=timeout)

if self.defaults:
for res in result:
res['default_quote'] = self.defaults.quote

return [Message.de_json(res, self) for res in result]

@log
Expand Down Expand Up @@ -1447,6 +1510,24 @@ def answer_inline_query(self,
"""
url = '{0}/answerInlineQuery'.format(self.base_url)

for res in results:
if res._has_parse_mode and res.parse_mode == DEFAULT_NONE:
res.parse_mode = self.defaults.parse_mode
if res._has_input_message_content and res.input_message_content:
if (res.input_message_content._has_parse_mode
and res.input_message_content.parse_mode == DEFAULT_NONE):
if self.defaults:
res.input_message_content.parse_mode = self.defaults.parse_mode
else:
res.input_message_content.parse_mode = None
if (res.input_message_content._has_disable_web_page_preview
and res.input_message_content.disable_web_page_preview == DEFAULT_NONE):
if self.defaults:
res.input_message_content.disable_web_page_preview = \
self.defaults.disable_web_page_preview
else:
res.input_message_content.disable_web_page_preview = None

results = [res.to_dict() for res in results]

data = {'inline_query_id': inline_query_id, 'results': results}
Expand Down Expand Up @@ -1987,6 +2068,10 @@ def get_updates(self,
else:
self.logger.debug('No new updates found.')

if self.defaults:
for u in result:
u['default_quote'] = self.defaults.quote

return [Update.de_json(u, self) for u in result]

@log
Expand Down Expand Up @@ -2167,6 +2252,9 @@ def get_chat(self, chat_id, timeout=None, **kwargs):

result = self._request.post(url, data, timeout=timeout)

if self.defaults:
result['default_quote'] = self.defaults.quote

return Chat.de_json(result, self)

@log
Expand Down
5 changes: 4 additions & 1 deletion telegram/callbackquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ def de_json(cls, data, bot):
data = super(CallbackQuery, cls).de_json(data, bot)

data['from_user'] = User.de_json(data.get('from'), bot)
data['message'] = Message.de_json(data.get('message'), bot)
message = data.get('message')
if message:
message['default_quote'] = data.get('default_quote')
data['message'] = Message.de_json(message, bot)

return cls(bot=bot, **data)

Expand Down
5 changes: 4 additions & 1 deletion telegram/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@ def de_json(cls, data, bot):

data['photo'] = ChatPhoto.de_json(data.get('photo'), bot)
from telegram import Message
data['pinned_message'] = Message.de_json(data.get('pinned_message'), bot)
pinned_message = data.get('pinned_message')
if pinned_message:
pinned_message['default_quote'] = data.get('default_quote')
data['pinned_message'] = Message.de_json(pinned_message, bot)
data['permissions'] = ChatPermissions.de_json(data.get('permissions'), bot)

return cls(bot=bot, **data)
Expand Down
3 changes: 2 additions & 1 deletion telegram/ext/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@
from .shippingqueryhandler import ShippingQueryHandler
from .messagequeue import MessageQueue
from .messagequeue import DelayQueue
from .defaults import Defaults

__all__ = ('Dispatcher', 'JobQueue', 'Job', 'Updater', 'CallbackQueryHandler',
'ChosenInlineResultHandler', 'CommandHandler', 'Handler', 'InlineQueryHandler',
'MessageHandler', 'BaseFilter', 'Filters', 'RegexHandler', 'StringCommandHandler',
'StringRegexHandler', 'TypeHandler', 'ConversationHandler',
'PreCheckoutQueryHandler', 'ShippingQueryHandler', 'MessageQueue', 'DelayQueue',
'DispatcherHandlerStop', 'run_async', 'CallbackContext', 'BasePersistence',
'PicklePersistence', 'DictPersistence', 'PrefixHandler')
'PicklePersistence', 'DictPersistence', 'PrefixHandler', 'Defaults')
127 changes: 127 additions & 0 deletions telegram/ext/defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the class Defaults, which allows to pass default values to Updater."""

from telegram.utils.helpers import DEFAULT_NONE


class Defaults:
"""Convenience Class to gather all parameters with a (user defined) default value

Attributes:
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
bold, italic, fixed-width toxt or URLs in your bot's message.
disable_notification (:obj:`bool`): Optional. Sends the message silently. Users will
receive a notification with no sound.
disable_web_page_preview (:obj:`bool`): Optional. Disables link previews for links in this
message.
timeout (:obj:`int` | :obj:`float`): Optional. If this value is specified, use it as the
read timeout from the server (instead of the one specified during creation of the
connection pool).
quote (:obj:`bool`): Optional. If set to ``True``, the reply is sent as an actual reply to
the message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter will
be ignored. Default: ``True`` in group chats and ``False`` in private chats.

Parameters:
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
bold, italic, fixed-width toxt or URLs in your bot's message.
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
receive a notification with no sound.
disable_web_page_preview (:obj:`bool`, optional): Disables link previews for links in this
message.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as the
read timeout from the server (instead of the one specified during creation of the
connection pool).
quote (:obj:`bool`, opitonal): If set to ``True``, the reply is sent as an actual reply to
the message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter will
be ignored. Default: ``True`` in group chats and ``False`` in private chats.
"""
def __init__(self,
parse_mode=None,
disable_notification=None,
disable_web_page_preview=None,
# Timeout needs special treatment, since the bot methods have two different
# default values for timeout (None and 20s)
timeout=DEFAULT_NONE,
quote=None):
self._parse_mode = parse_mode
self._disable_notification = disable_notification
self._disable_web_page_preview = disable_web_page_preview
self._timeout = timeout
self._quote = quote

@property
def parse_mode(self):
return self._parse_mode

@parse_mode.setter
def parse_mode(self, value):
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")

@property
def disable_notification(self):
return self._disable_notification

@disable_notification.setter
def disable_notification(self, value):
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")

@property
def disable_web_page_preview(self):
return self._disable_web_page_preview

@disable_web_page_preview.setter
def disable_web_page_preview(self, value):
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")

@property
def timeout(self):
return self._timeout

@timeout.setter
def timeout(self, value):
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")

@property
def quote(self):
return self._quote

@quote.setter
def quote(self, value):
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")

def __hash__(self):
return hash((self._parse_mode,
self._disable_notification,
self._disable_web_page_preview,
self._timeout,
self._quote))

def __eq__(self, other):
if isinstance(other, Defaults):
return self.__dict__ == other.__dict__
return False

def __ne__(self, other):
return not self == other
Loading