Repository: python-telegram-bot/python-telegram-bot
Pull request: Add default values
Supersedes/Closes #1226 and closes #1527

To meet the requirements stated by @Eldinnie in #1226, I added a new class `DefaultValue` to the `helpers` module. It's purpose is to allow to differentiate check if a value is just the default or was set specifically:

With `def f(arg=None)` we don't know, if `arg is None` results from `f()` or from `f(None)`.
With `def f(arg=DefaultValue(None)`, we can handle those differently.

This makes it necessary to add some stuff to the `InlineQueryResult*` for `bot.answer_inline_query` to parse the new `default_parse_mode` correctly. But it does the job :) Maybe `DefaultValue` also comes in handy with a future use case …
How do the @python-telegram-bot/maintainers feel about this?

For the tests, I tried and added them for the `send_*` and `edit_message_*` methods. I'm still not too familiar with `pytest`, so please tell if those can be improved.
However, I couldn't find a clever way to test the `default_parse_mode` for `answer_inline_query` since I'd somehow have to get hold of the resulting message. Any idea is appreciated :)


Problem statement:
[FEATURE] Enter missing defaults at runtime
This was an idea we had in mind (and discussed) somewhere a while back. Users should be able to pass defaults to a convenient method, so all the requests to the bot API which have this paramater defaults to the one the user provided. Examples would be parse mode, disable notification, hide link preview. This shall be edited with a sufficient list of parameters and where exactly we want to implement this (updater, dispatcher, bot instance?) in the future, this is merely a reminder for now.

Hints from issue discussion:
So, the referred PR is implementing parse_mode as a default option. This issue will track its succesfull implementing secondarly, but primarly will stay up to track the implementing of other defaults we want to have. Current list:

- [x] parse mode
- [ ] timeout
- [ ] disable notification
- [ ] hide link preview

Commit message: Improve handling of def_p_m for Bot methods

Patch to review:
diff --git a/telegram/bot.py b/telegram/bot.py
index bb9b3ecf5b2..671469b6b4c 100644
--- a/telegram/bot.py
+++ b/telegram/bot.py
@@ -21,6 +21,7 @@
 """This module contains an object that represents a Telegram Bot."""
 
 import functools
+import inspect
 
 try:
     import ujson as json
@@ -39,7 +40,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, Defaults, DEFAULT_NONE
 from telegram.utils.request import Request
 
 logging.getLogger(__name__).addHandler(logging.NullHandler())
@@ -82,13 +83,49 @@ 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.
+        default_parse_mode (:obj:`str`, optional): Default parse mode used if not set explicitly in
+            method call. See the constants in :class:`telegram.ParseMode` for the available modes.
 
     """
 
+    def __new__(cls, *args, **kwargs):
+        # Handle default_... kwargs for bot methods
+        # Transform default_x=y kwargs into Defaults.x=y
+        defaults = Defaults()
+        for kwarg in list(kwargs.keys()):
+            if kwarg.startswith('default_'):
+                setattr(defaults, kwarg[8:], kwargs[kwarg])
+                del kwargs[kwarg]
+
+        # Make an instance of the class
+        instance = super(Bot, cls).__new__(cls)
+
+        # 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 kwarg_name in defaults.__dict__.keys()
+            ]
+            # ... make a dict of kwarg name and the default value
+            default_kwargs = {
+                kwarg_name: getattr(defaults, kwarg_name) for kwarg_name in needs_default
+            }
+            # ... 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):
+                 private_key_password=None, default_parse_mode=None):
         self.token = self._validate_token(token)
 
+        # Gather default
+        self.defaults = Defaults(parse_mode=default_parse_mode)
+
         if base_url is None:
             base_url = 'https://api.telegram.org/bot'
 
@@ -120,6 +157,9 @@ 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 is DEFAULT_NONE):
+            data['media'].parse_mode = self.defaults.parse_mode
+
         result = self._request.post(url, data, timeout=timeout)
 
         if result is True:
@@ -978,6 +1018,10 @@ def send_media_group(self,
 
         data = {'chat_id': chat_id, 'media': media}
 
+        for m in data['media']:
+            if m.parse_mode is DEFAULT_NONE:
+                m.parse_mode = self.defaults.parse_mode
+
         if reply_to_message_id:
             data['reply_to_message_id'] = reply_to_message_id
         if disable_notification:
@@ -1447,6 +1491,13 @@ 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 is 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 is DEFAULT_NONE):
+                    res.input_message_content.parse_mode = self.defaults.parse_mode
         results = [res.to_dict() for res in results]
 
         data = {'inline_query_id': inline_query_id, 'results': results}
diff --git a/telegram/ext/updater.py b/telegram/ext/updater.py
index 8b6af368417..6cb3516b8b4 100644
--- a/telegram/ext/updater.py
+++ b/telegram/ext/updater.py
@@ -58,6 +58,8 @@ class Updater(object):
         persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to
             store data that should be persistent over restarts.
         use_context (:obj:`bool`, optional): ``True`` if using context based callbacks.
+        default_parse_mode (:obj:`str`, optional): Default parse mode used if not set explicitly in
+            method call. See the constants in :class:`telegram.ParseMode` for the available modes.
 
     Args:
         token (:obj:`str`, optional): The bot's token given by the @BotFather.
@@ -81,6 +83,8 @@ class Updater(object):
             set this to ``True``.
         persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence class to
             store data that should be persistent over restarts.
+        default_parse_mode (:obj:`str`, optional): Default parse mode used if not set explicitly in
+            method call. See the constants in :class:`telegram.ParseMode` for the available modes.
 
     Note:
         You must supply either a :attr:`bot` or a :attr:`token` argument.
@@ -102,6 +106,7 @@ def __init__(self,
                  user_sig_handler=None,
                  request_kwargs=None,
                  persistence=None,
+                 default_parse_mode=None,
                  use_context=False):
 
         if (token is None) and (bot is None):
@@ -134,7 +139,8 @@ def __init__(self,
                 request_kwargs['con_pool_size'] = con_pool_size
             self._request = Request(**request_kwargs)
             self.bot = Bot(token, base_url, request=self._request, private_key=private_key,
-                           private_key_password=private_key_password)
+                           private_key_password=private_key_password,
+                           default_parse_mode=default_parse_mode)
         self.user_sig_handler = user_sig_handler
         self.update_queue = Queue()
         self.job_queue = JobQueue()
diff --git a/telegram/files/inputmedia.py b/telegram/files/inputmedia.py
index e8187ba544a..2760223bc33 100644
--- a/telegram/files/inputmedia.py
+++ b/telegram/files/inputmedia.py
@@ -19,6 +19,7 @@
 """Base class for Telegram InputMedia Objects."""
 
 from telegram import TelegramObject, InputFile, PhotoSize, Animation, Video, Audio, Document
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InputMedia(TelegramObject):
@@ -77,7 +78,13 @@ class InputMediaAnimation(InputMedia):
         arguments.
     """
 
-    def __init__(self, media, thumb=None, caption=None, parse_mode=None, width=None, height=None,
+    def __init__(self,
+                 media,
+                 thumb=None,
+                 caption=None,
+                 parse_mode=DEFAULT_NONE,
+                 width=None,
+                 height=None,
                  duration=None):
         self.type = 'animation'
 
@@ -98,8 +105,7 @@ def __init__(self, media, thumb=None, caption=None, parse_mode=None, width=None,
 
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if width:
             self.width = width
         if height:
@@ -133,7 +139,7 @@ class InputMediaPhoto(InputMedia):
             in :class:`telegram.ParseMode` for the available modes.
     """
 
-    def __init__(self, media, caption=None, parse_mode=None):
+    def __init__(self, media, caption=None, parse_mode=DEFAULT_NONE):
         self.type = 'photo'
 
         if isinstance(media, PhotoSize):
@@ -145,8 +151,7 @@ def __init__(self, media, caption=None, parse_mode=None):
 
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
 
 
 class InputMediaVideo(InputMedia):
@@ -198,7 +203,7 @@ class InputMediaVideo(InputMedia):
     """
 
     def __init__(self, media, caption=None, width=None, height=None, duration=None,
-                 supports_streaming=None, parse_mode=None, thumb=None):
+                 supports_streaming=None, parse_mode=DEFAULT_NONE, thumb=None):
         self.type = 'video'
 
         if isinstance(media, Video):
@@ -218,8 +223,7 @@ def __init__(self, media, caption=None, width=None, height=None, duration=None,
 
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if width:
             self.width = width
         if height:
@@ -276,7 +280,7 @@ class InputMediaAudio(InputMedia):
         optional arguments.
     """
 
-    def __init__(self, media, thumb=None, caption=None, parse_mode=None,
+    def __init__(self, media, thumb=None, caption=None, parse_mode=DEFAULT_NONE,
                  duration=None, performer=None, title=None):
         self.type = 'audio'
 
@@ -297,8 +301,7 @@ def __init__(self, media, thumb=None, caption=None, parse_mode=None,
 
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if duration:
             self.duration = duration
         if performer:
@@ -340,7 +343,7 @@ class InputMediaDocument(InputMedia):
             is passed as a string or file_id.
     """
 
-    def __init__(self, media, thumb=None, caption=None, parse_mode=None):
+    def __init__(self, media, thumb=None, caption=None, parse_mode=DEFAULT_NONE):
         self.type = 'document'
 
         if isinstance(media, Document):
@@ -357,5 +360,4 @@ def __init__(self, media, thumb=None, caption=None, parse_mode=None):
 
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
diff --git a/telegram/inline/inlinequeryresult.py b/telegram/inline/inlinequeryresult.py
index 6feaf56512d..a2ec5e18056 100644
--- a/telegram/inline/inlinequeryresult.py
+++ b/telegram/inline/inlinequeryresult.py
@@ -41,3 +41,11 @@ def __init__(self, type, id, **kwargs):
         self.id = str(id)
 
         self._id_attrs = (self.id,)
+
+    @property
+    def _has_parse_mode(self):
+        return hasattr(self, 'parse_mode')
+
+    @property
+    def _has_input_message_content(self):
+        return hasattr(self, 'input_message_content')
diff --git a/telegram/inline/inlinequeryresultaudio.py b/telegram/inline/inlinequeryresultaudio.py
index 0bd2c3ce584..13f3c08f43b 100644
--- a/telegram/inline/inlinequeryresultaudio.py
+++ b/telegram/inline/inlinequeryresultaudio.py
@@ -19,6 +19,7 @@
 """This module contains the classes that represent Telegram InlineQueryResultAudio."""
 
 from telegram import InlineQueryResult
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InlineQueryResultAudio(InlineQueryResult):
@@ -70,7 +71,7 @@ def __init__(self,
                  caption=None,
                  reply_markup=None,
                  input_message_content=None,
-                 parse_mode=None,
+                 parse_mode=DEFAULT_NONE,
                  **kwargs):
 
         # Required
@@ -85,8 +86,7 @@ def __init__(self,
             self.audio_duration = audio_duration
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if reply_markup:
             self.reply_markup = reply_markup
         if input_message_content:
diff --git a/telegram/inline/inlinequeryresultcachedaudio.py b/telegram/inline/inlinequeryresultcachedaudio.py
index d225f4129e5..034029f1fef 100644
--- a/telegram/inline/inlinequeryresultcachedaudio.py
+++ b/telegram/inline/inlinequeryresultcachedaudio.py
@@ -19,6 +19,7 @@
 """This module contains the classes that represent Telegram InlineQueryResultCachedAudio."""
 
 from telegram import InlineQueryResult
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InlineQueryResultCachedAudio(InlineQueryResult):
@@ -61,7 +62,7 @@ def __init__(self,
                  caption=None,
                  reply_markup=None,
                  input_message_content=None,
-                 parse_mode=None,
+                 parse_mode=DEFAULT_NONE,
                  **kwargs):
         # Required
         super(InlineQueryResultCachedAudio, self).__init__('audio', id)
@@ -70,8 +71,7 @@ def __init__(self,
         # Optionals
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if reply_markup:
             self.reply_markup = reply_markup
         if input_message_content:
diff --git a/telegram/inline/inlinequeryresultcacheddocument.py b/telegram/inline/inlinequeryresultcacheddocument.py
index 28606949ab5..fef116d97ee 100644
--- a/telegram/inline/inlinequeryresultcacheddocument.py
+++ b/telegram/inline/inlinequeryresultcacheddocument.py
@@ -19,6 +19,7 @@
 """This module contains the classes that represent Telegram InlineQueryResultCachedDocument."""
 
 from telegram import InlineQueryResult
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InlineQueryResultCachedDocument(InlineQueryResult):
@@ -67,7 +68,7 @@ def __init__(self,
                  caption=None,
                  reply_markup=None,
                  input_message_content=None,
-                 parse_mode=None,
+                 parse_mode=DEFAULT_NONE,
                  **kwargs):
         # Required
         super(InlineQueryResultCachedDocument, self).__init__('document', id)
@@ -79,8 +80,7 @@ def __init__(self,
             self.description = description
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if reply_markup:
             self.reply_markup = reply_markup
         if input_message_content:
diff --git a/telegram/inline/inlinequeryresultcachedgif.py b/telegram/inline/inlinequeryresultcachedgif.py
index bfdb5bb9789..404c33b2265 100644
--- a/telegram/inline/inlinequeryresultcachedgif.py
+++ b/telegram/inline/inlinequeryresultcachedgif.py
@@ -19,6 +19,7 @@
 """This module contains the classes that represent Telegram InlineQueryResultCachedGif."""
 
 from telegram import InlineQueryResult
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InlineQueryResultCachedGif(InlineQueryResult):
@@ -65,7 +66,7 @@ def __init__(self,
                  caption=None,
                  reply_markup=None,
                  input_message_content=None,
-                 parse_mode=None,
+                 parse_mode=DEFAULT_NONE,
                  **kwargs):
         # Required
         super(InlineQueryResultCachedGif, self).__init__('gif', id)
@@ -76,8 +77,7 @@ def __init__(self,
             self.title = title
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if reply_markup:
             self.reply_markup = reply_markup
         if input_message_content:
diff --git a/telegram/inline/inlinequeryresultcachedmpeg4gif.py b/telegram/inline/inlinequeryresultcachedmpeg4gif.py
index 4dfbd8d9e44..1f870ea58cb 100644
--- a/telegram/inline/inlinequeryresultcachedmpeg4gif.py
+++ b/telegram/inline/inlinequeryresultcachedmpeg4gif.py
@@ -19,6 +19,7 @@
 """This module contains the classes that represent Telegram InlineQueryResultMpeg4Gif."""
 
 from telegram import InlineQueryResult
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
@@ -65,7 +66,7 @@ def __init__(self,
                  caption=None,
                  reply_markup=None,
                  input_message_content=None,
-                 parse_mode=None,
+                 parse_mode=DEFAULT_NONE,
                  **kwargs):
         # Required
         super(InlineQueryResultCachedMpeg4Gif, self).__init__('mpeg4_gif', id)
@@ -76,8 +77,7 @@ def __init__(self,
             self.title = title
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if reply_markup:
             self.reply_markup = reply_markup
         if input_message_content:
diff --git a/telegram/inline/inlinequeryresultcachedphoto.py b/telegram/inline/inlinequeryresultcachedphoto.py
index dd2c5845141..6828f49d44a 100644
--- a/telegram/inline/inlinequeryresultcachedphoto.py
+++ b/telegram/inline/inlinequeryresultcachedphoto.py
@@ -19,6 +19,7 @@
 """This module contains the classes that represent Telegram InlineQueryResultPhoto"""
 
 from telegram import InlineQueryResult
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InlineQueryResultCachedPhoto(InlineQueryResult):
@@ -68,7 +69,7 @@ def __init__(self,
                  caption=None,
                  reply_markup=None,
                  input_message_content=None,
-                 parse_mode=None,
+                 parse_mode=DEFAULT_NONE,
                  **kwargs):
         # Required
         super(InlineQueryResultCachedPhoto, self).__init__('photo', id)
@@ -81,8 +82,7 @@ def __init__(self,
             self.description = description
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if reply_markup:
             self.reply_markup = reply_markup
         if input_message_content:
diff --git a/telegram/inline/inlinequeryresultcachedvideo.py b/telegram/inline/inlinequeryresultcachedvideo.py
index 0038adf8c6f..a9f6a8ca686 100644
--- a/telegram/inline/inlinequeryresultcachedvideo.py
+++ b/telegram/inline/inlinequeryresultcachedvideo.py
@@ -19,6 +19,7 @@
 """This module contains the classes that represent Telegram InlineQueryResultCachedVideo."""
 
 from telegram import InlineQueryResult
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InlineQueryResultCachedVideo(InlineQueryResult):
@@ -68,7 +69,7 @@ def __init__(self,
                  caption=None,
                  reply_markup=None,
                  input_message_content=None,
-                 parse_mode=None,
+                 parse_mode=DEFAULT_NONE,
                  **kwargs):
         # Required
         super(InlineQueryResultCachedVideo, self).__init__('video', id)
@@ -80,8 +81,7 @@ def __init__(self,
             self.description = description
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if reply_markup:
             self.reply_markup = reply_markup
         if input_message_content:
diff --git a/telegram/inline/inlinequeryresultcachedvoice.py b/telegram/inline/inlinequeryresultcachedvoice.py
index 687be9baeb5..fe295b44d45 100644
--- a/telegram/inline/inlinequeryresultcachedvoice.py
+++ b/telegram/inline/inlinequeryresultcachedvoice.py
@@ -19,6 +19,7 @@
 """This module contains the classes that represent Telegram InlineQueryResultCachedVoice."""
 
 from telegram import InlineQueryResult
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InlineQueryResultCachedVoice(InlineQueryResult):
@@ -64,7 +65,7 @@ def __init__(self,
                  caption=None,
                  reply_markup=None,
                  input_message_content=None,
-                 parse_mode=None,
+                 parse_mode=DEFAULT_NONE,
                  **kwargs):
         # Required
         super(InlineQueryResultCachedVoice, self).__init__('voice', id)
@@ -74,8 +75,7 @@ def __init__(self,
         # Optionals
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if reply_markup:
             self.reply_markup = reply_markup
         if input_message_content:
diff --git a/telegram/inline/inlinequeryresultdocument.py b/telegram/inline/inlinequeryresultdocument.py
index bb480e4d519..c3335916986 100644
--- a/telegram/inline/inlinequeryresultdocument.py
+++ b/telegram/inline/inlinequeryresultdocument.py
@@ -19,6 +19,7 @@
 """This module contains the classes that represent Telegram InlineQueryResultDocument"""
 
 from telegram import InlineQueryResult
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InlineQueryResultDocument(InlineQueryResult):
@@ -82,7 +83,7 @@ def __init__(self,
                  thumb_url=None,
                  thumb_width=None,
                  thumb_height=None,
-                 parse_mode=None,
+                 parse_mode=DEFAULT_NONE,
                  **kwargs):
         # Required
         super(InlineQueryResultDocument, self).__init__('document', id)
@@ -93,8 +94,7 @@ def __init__(self,
         # Optionals
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if description:
             self.description = description
         if reply_markup:
diff --git a/telegram/inline/inlinequeryresultgif.py b/telegram/inline/inlinequeryresultgif.py
index 53b863f06ad..ba6ddb8d3f7 100644
--- a/telegram/inline/inlinequeryresultgif.py
+++ b/telegram/inline/inlinequeryresultgif.py
@@ -19,6 +19,7 @@
 """This module contains the classes that represent Telegram InlineQueryResultGif."""
 
 from telegram import InlineQueryResult
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InlineQueryResultGif(InlineQueryResult):
@@ -76,7 +77,7 @@ def __init__(self,
                  reply_markup=None,
                  input_message_content=None,
                  gif_duration=None,
-                 parse_mode=None,
+                 parse_mode=DEFAULT_NONE,
                  **kwargs):
 
         # Required
@@ -95,8 +96,7 @@ def __init__(self,
             self.title = title
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if reply_markup:
             self.reply_markup = reply_markup
         if input_message_content:
diff --git a/telegram/inline/inlinequeryresultmpeg4gif.py b/telegram/inline/inlinequeryresultmpeg4gif.py
index 63610b43a36..49eff09488f 100644
--- a/telegram/inline/inlinequeryresultmpeg4gif.py
+++ b/telegram/inline/inlinequeryresultmpeg4gif.py
@@ -19,6 +19,7 @@
 """This module contains the classes that represent Telegram InlineQueryResultMpeg4Gif."""
 
 from telegram import InlineQueryResult
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InlineQueryResultMpeg4Gif(InlineQueryResult):
@@ -77,7 +78,7 @@ def __init__(self,
                  reply_markup=None,
                  input_message_content=None,
                  mpeg4_duration=None,
-                 parse_mode=None,
+                 parse_mode=DEFAULT_NONE,
                  **kwargs):
 
         # Required
@@ -96,8 +97,7 @@ def __init__(self,
             self.title = title
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if reply_markup:
             self.reply_markup = reply_markup
         if input_message_content:
diff --git a/telegram/inline/inlinequeryresultphoto.py b/telegram/inline/inlinequeryresultphoto.py
index 243e6f43a53..a5d6e21e8d9 100644
--- a/telegram/inline/inlinequeryresultphoto.py
+++ b/telegram/inline/inlinequeryresultphoto.py
@@ -19,6 +19,7 @@
 """This module contains the classes that represent Telegram InlineQueryResultPhoto."""
 
 from telegram import InlineQueryResult
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InlineQueryResultPhoto(InlineQueryResult):
@@ -78,7 +79,7 @@ def __init__(self,
                  caption=None,
                  reply_markup=None,
                  input_message_content=None,
-                 parse_mode=None,
+                 parse_mode=DEFAULT_NONE,
                  **kwargs):
         # Required
         super(InlineQueryResultPhoto, self).__init__('photo', id)
@@ -96,8 +97,7 @@ def __init__(self,
             self.description = description
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if reply_markup:
             self.reply_markup = reply_markup
         if input_message_content:
diff --git a/telegram/inline/inlinequeryresultvideo.py b/telegram/inline/inlinequeryresultvideo.py
index ac885dee4f5..279415bfc2b 100644
--- a/telegram/inline/inlinequeryresultvideo.py
+++ b/telegram/inline/inlinequeryresultvideo.py
@@ -19,6 +19,7 @@
 """This module contains the classes that represent Telegram InlineQueryResultVideo."""
 
 from telegram import InlineQueryResult
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InlineQueryResultVideo(InlineQueryResult):
@@ -83,7 +84,7 @@ def __init__(self,
                  description=None,
                  reply_markup=None,
                  input_message_content=None,
-                 parse_mode=None,
+                 parse_mode=DEFAULT_NONE,
                  **kwargs):
 
         # Required
@@ -96,8 +97,7 @@ def __init__(self,
         # Optional
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if video_width:
             self.video_width = video_width
         if video_height:
diff --git a/telegram/inline/inlinequeryresultvoice.py b/telegram/inline/inlinequeryresultvoice.py
index 535d8b6c635..78caff3de3a 100644
--- a/telegram/inline/inlinequeryresultvoice.py
+++ b/telegram/inline/inlinequeryresultvoice.py
@@ -19,6 +19,7 @@
 """This module contains the classes that represent Telegram InlineQueryResultVoice."""
 
 from telegram import InlineQueryResult
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InlineQueryResultVoice(InlineQueryResult):
@@ -68,7 +69,7 @@ def __init__(self,
                  caption=None,
                  reply_markup=None,
                  input_message_content=None,
-                 parse_mode=None,
+                 parse_mode=DEFAULT_NONE,
                  **kwargs):
 
         # Required
@@ -81,8 +82,7 @@ def __init__(self,
             self.voice_duration = voice_duration
         if caption:
             self.caption = caption
-        if parse_mode:
-            self.parse_mode = parse_mode
+        self.parse_mode = parse_mode
         if reply_markup:
             self.reply_markup = reply_markup
         if input_message_content:
diff --git a/telegram/inline/inputmessagecontent.py b/telegram/inline/inputmessagecontent.py
index f7b706580d2..e1fea9fc188 100644
--- a/telegram/inline/inputmessagecontent.py
+++ b/telegram/inline/inputmessagecontent.py
@@ -29,4 +29,6 @@ class InputMessageContent(TelegramObject):
     :class:`telegram.InputVenueMessageContent` for more details.
 
     """
-    pass
+    @property
+    def _has_parse_mode(self):
+        return hasattr(self, 'parse_mode')
diff --git a/telegram/inline/inputtextmessagecontent.py b/telegram/inline/inputtextmessagecontent.py
index 3e08d6b978b..de7af87c97b 100644
--- a/telegram/inline/inputtextmessagecontent.py
+++ b/telegram/inline/inputtextmessagecontent.py
@@ -19,6 +19,7 @@
 """This module contains the classes that represent Telegram InputTextMessageContent."""
 
 from telegram import InputMessageContent
+from telegram.utils.helpers import DEFAULT_NONE
 
 
 class InputTextMessageContent(InputMessageContent):
@@ -43,7 +44,11 @@ class InputTextMessageContent(InputMessageContent):
 
     """
 
-    def __init__(self, message_text, parse_mode=None, disable_web_page_preview=None, **kwargs):
+    def __init__(self,
+                 message_text,
+                 parse_mode=DEFAULT_NONE,
+                 disable_web_page_preview=None,
+                 **kwargs):
         # Required
         self.message_text = message_text
         # Optionals
diff --git a/telegram/utils/helpers.py b/telegram/utils/helpers.py
index ca149e55f71..0bed738a902 100644
--- a/telegram/utils/helpers.py
+++ b/telegram/utils/helpers.py
@@ -256,3 +256,75 @@ def decode_user_chat_data_from_json(data):
                 pass
             tmp[user][key] = value
     return tmp
+
+
+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.
+
+    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.
+    """
+    def __init__(self, parse_mode=None):
+        self.parse_mode = parse_mode
+
+
+class DefaultValue:
+    """Wrapper for immutable default arguments that allows to check, if the default value was set
+    explicitly. Usage::
+
+        DefaultOne = DefaultValue(1)
+        def f(arg=DefaultOne):
+            if arg is DefaultOne:
+                print('`arg` is the default')
+                arg = arg.value
+            else:
+                print('`arg` was set explicitly')
+            print('`arg` = ' + str(arg))
+
+    This yields::
+
+        >>> f()
+        `arg` is the default
+        `arg` = 1
+        >>> f(1)
+        `arg` was set explicitly
+        `arg` = 1
+        >>> f(2)
+        `arg` was set explicitly
+        `arg` = 2
+
+    Also allows to evaluate truthiness::
+
+        default = DefaultValue(value)
+        if default:
+            ...
+
+    is equivalent to::
+
+        default = DefaultValue(value)
+        if value:
+            ...
+
+    Attributes:
+        value (:obj:`obj`): The value of the default argument
+
+    Args:
+        value (:obj:`obj`): The value of the default argument
+    """
+    def __init__(self, value=None):
+        self.value = value
+
+    def __bool__(self):
+        return bool(self.value)
+
+    # For Python 2.x
+    __nonzero__ = __bool__
+
+
+DEFAULT_NONE = DefaultValue(None)
+""":class:`DefaultValue`: Default `None`"""
diff --git a/tests/test_animation.py b/tests/test_animation.py
index ae694fde565..03cabec176a 100644
--- a/tests/test_animation.py
+++ b/tests/test_animation.py
@@ -19,9 +19,11 @@
 
 import os
 import pytest
+import functools
 from flaky import flaky
 
 from telegram import PhotoSize, Animation, Voice, TelegramError
+from telegram.utils.helpers import escape_markdown
 
 
 @pytest.fixture(scope='function')
@@ -107,6 +109,45 @@ def test_send_animation_url_file(self, bot, chat_id, animation):
         assert message.animation.mime_type == animation.mime_type
         assert message.animation.file_size == animation.file_size
 
+    flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_animation_default_parse_mode_1(self, monkeypatch, bot, chat_id, animation_file):
+        monkeypatch.setattr('telegram.Bot.send_animation', functools.partial(bot.send_animation,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_string = 'Italic Bold Code'
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_animation(chat_id, animation_file, caption=test_markdown_string)
+        assert message.caption_markdown == test_markdown_string
+        assert message.caption == test_string
+
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_animation_default_parse_mode_2(self, monkeypatch, bot, chat_id, animation_file):
+        monkeypatch.setattr('telegram.Bot.send_animation', functools.partial(bot.send_animation,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_animation(chat_id, animation_file, caption=test_markdown_string,
+                                     parse_mode=None)
+        assert message.caption == test_markdown_string
+        assert message.caption_markdown == escape_markdown(test_markdown_string)
+
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_animation_default_parse_mode_3(self, monkeypatch, bot, chat_id, animation_file):
+        monkeypatch.setattr('telegram.Bot.send_animation', functools.partial(bot.send_animation,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_animation(chat_id, animation_file, caption=test_markdown_string,
+                                     parse_mode='HTML')
+        assert message.caption == test_markdown_string
+        assert message.caption_markdown == escape_markdown(test_markdown_string)
+
     @flaky(3, 1)
     @pytest.mark.timeout(10)
     def test_resend(self, bot, chat_id, animation):
diff --git a/tests/test_audio.py b/tests/test_audio.py
index 9433edbd7bc..8e7d27fdf06 100644
--- a/tests/test_audio.py
+++ b/tests/test_audio.py
@@ -17,11 +17,13 @@
 # You should have received a copy of the GNU Lesser Public License
 # along with this program.  If not, see [http://www.gnu.org/licenses/].
 import os
+import functools
 
 import pytest
 from flaky import flaky
 
 from telegram import Audio, TelegramError, Voice
+from telegram.utils.helpers import escape_markdown
 
 
 @pytest.fixture(scope='function')
@@ -132,6 +134,60 @@ def test(_, url, data, **kwargs):
         message = bot.send_audio(audio=audio, chat_id=chat_id)
         assert message
 
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_audio_default_parse_mode_1(self,
+                                             monkeypatch,
+                                             bot,
+                                             chat_id,
+                                             audio_file,
+                                             thumb_file):
+        monkeypatch.setattr('telegram.Bot.send_audio', functools.partial(bot.send_audio,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_string = 'Italic Bold Code'
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_audio(chat_id, audio_file, caption=test_markdown_string)
+        assert message.caption_markdown == test_markdown_string
+        assert message.caption == test_string
+
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_audio_default_parse_mode_2(self,
+                                             monkeypatch,
+                                             bot,
+                                             chat_id,
+                                             audio_file,
+                                             thumb_file):
+        monkeypatch.setattr('telegram.Bot.send_audio', functools.partial(bot.send_audio,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_audio(chat_id, audio_file, caption=test_markdown_string,
+                                 parse_mode=None)
+        assert message.caption == test_markdown_string
+        assert message.caption_markdown == escape_markdown(test_markdown_string)
+
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_audio_default_parse_mode_3(self,
+                                             monkeypatch,
+                                             bot,
+                                             chat_id,
+                                             audio_file,
+                                             thumb_file):
+        monkeypatch.setattr('telegram.Bot.send_audio', functools.partial(bot.send_audio,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_audio(chat_id, audio_file, caption=test_markdown_string,
+                                 parse_mode='HTML')
+        assert message.caption == test_markdown_string
+        assert message.caption_markdown == escape_markdown(test_markdown_string)
+
     def test_de_json(self, bot, audio):
         json_dict = {'file_id': 'not a file id',
                      'duration': self.duration,
diff --git a/tests/test_bot.py b/tests/test_bot.py
index 5eb58136050..937f72a44b2 100644
--- a/tests/test_bot.py
+++ b/tests/test_bot.py
@@ -19,6 +19,7 @@
 import os
 import sys
 import time
+import functools
 from datetime import datetime
 from platform import python_implementation
 
@@ -30,7 +31,7 @@
                       InlineKeyboardButton, InlineQueryResultArticle, InputTextMessageContent,
                       ShippingOption, LabeledPrice, ChatPermissions, Poll)
 from telegram.error import BadRequest, InvalidToken, NetworkError, RetryAfter
-from telegram.utils.helpers import from_timestamp
+from telegram.utils.helpers import from_timestamp, escape_markdown
 
 BASE_TIME = time.time()
 HIGHSCORE_DELTA = 1450000000
@@ -308,6 +309,36 @@ def test_edit_message_text(self, bot, message):
 
         assert message.text == 'new_text'
 
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_edit_message_text_default_parse_mode(self, monkeypatch, bot, message):
+        monkeypatch.setattr('telegram.Bot.edit_message_text',
+                            functools.partial(bot.edit_message_text, **{'parse_mode': 'Markdown'}))
+
+        test_string = 'Italic Bold Code'
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.edit_message_text(text=test_markdown_string, chat_id=message.chat_id,
+                                        message_id=message.message_id,
+                                        disable_web_page_preview=True)
+        assert message.text_markdown == test_markdown_string
+        assert message.text == test_string
+
+        message = bot.edit_message_text(text=test_markdown_string, chat_id=message.chat_id,
+                                        message_id=message.message_id, parse_mode=None,
+                                        disable_web_page_preview=True)
+        assert message.text == test_markdown_string
+        assert message.text_markdown == escape_markdown(test_markdown_string)
+
+        message = bot.edit_message_text(text=test_markdown_string, chat_id=message.chat_id,
+                                        message_id=message.message_id,
+                                        disable_web_page_preview=True)
+        message = bot.edit_message_text(text=test_markdown_string, chat_id=message.chat_id,
+                                        message_id=message.message_id, parse_mode='HTML',
+                                        disable_web_page_preview=True)
+        assert message.text == test_markdown_string
+        assert message.text_markdown == escape_markdown(test_markdown_string)
+
     @pytest.mark.skip(reason='need reference to an inline message')
     def test_edit_message_text_inline(self):
         pass
@@ -322,6 +353,37 @@ def test_edit_message_caption(self, bot, media_message):
 
     # edit_message_media is tested in test_inputmedia
 
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_edit_message_caption_default_parse_mode(self, monkeypatch, bot, media_message):
+        monkeypatch.setattr('telegram.Bot.edit_message_caption',
+                            functools.partial(bot.edit_message_caption,
+                                              **{'parse_mode': 'Markdown'}))
+
+        test_string = 'Italic Bold Code'
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.edit_message_caption(caption=test_markdown_string,
+                                           chat_id=media_message.chat_id,
+                                           message_id=media_message.message_id)
+        assert message.caption_markdown == test_markdown_string
+        assert message.caption == test_string
+
+        message = bot.edit_message_caption(caption=test_markdown_string,
+                                           chat_id=media_message.chat_id,
+                                           message_id=media_message.message_id, parse_mode=None)
+        assert message.caption == test_markdown_string
+        assert message.caption_markdown == escape_markdown(test_markdown_string)
+
+        message = bot.edit_message_caption(caption=test_markdown_string,
+                                           chat_id=media_message.chat_id,
+                                           message_id=media_message.message_id)
+        message = bot.edit_message_caption(caption=test_markdown_string,
+                                           chat_id=media_message.chat_id,
+                                           message_id=media_message.message_id, parse_mode='HTML')
+        assert message.caption == test_markdown_string
+        assert message.caption_markdown == escape_markdown(test_markdown_string)
+
     @flaky(3, 1)
     @pytest.mark.timeout(10)
     def test_edit_message_caption_with_parse_mode(self, bot, media_message):
@@ -721,3 +783,22 @@ def request_wrapper(*args, **kwargs):
         # Test file uploading
         with pytest.raises(OkException):
             bot.send_photo(chat_id, open('tests/data/telegram.jpg', 'rb'))
+
+    def test_send_message_default_parse_mode(self, monkeypatch, bot, chat_id):
+        monkeypatch.setattr('telegram.Bot.send_message', functools.partial(bot.send_message,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_string = 'Italic Bold Code'
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_message(chat_id, test_markdown_string)
+        assert message.text_markdown == test_markdown_string
+        assert message.text == test_string
+
+        message = bot.send_message(chat_id, test_markdown_string, parse_mode=None)
+        assert message.text == test_markdown_string
+        assert message.text_markdown == escape_markdown(test_markdown_string)
+
+        message = bot.send_message(chat_id, test_markdown_string, parse_mode='HTML')
+        assert message.text == test_markdown_string
+        assert message.text_markdown == escape_markdown(test_markdown_string)
diff --git a/tests/test_document.py b/tests/test_document.py
index 2aa4d5dd7fb..8a36f37af50 100644
--- a/tests/test_document.py
+++ b/tests/test_document.py
@@ -17,11 +17,13 @@
 # You should have received a copy of the GNU Lesser Public License
 # along with this program.  If not, see [http://www.gnu.org/licenses/].
 import os
+import functools
 
 import pytest
 from flaky import flaky
 
 from telegram import Document, PhotoSize, TelegramError, Voice
+from telegram.utils.helpers import escape_markdown
 
 
 @pytest.fixture(scope='function')
@@ -123,6 +125,45 @@ def test(_, url, data, **kwargs):
 
         assert message
 
+    flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_document_default_parse_mode_1(self, monkeypatch, bot, chat_id, document):
+        monkeypatch.setattr('telegram.Bot.send_document', functools.partial(bot.send_document,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_string = 'Italic Bold Code'
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_document(chat_id, document, caption=test_markdown_string)
+        assert message.caption_markdown == test_markdown_string
+        assert message.caption == test_string
+
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_document_default_parse_mode_2(self, monkeypatch, bot, chat_id, document):
+        monkeypatch.setattr('telegram.Bot.send_document', functools.partial(bot.send_document,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_document(chat_id, document, caption=test_markdown_string,
+                                    parse_mode=None)
+        assert message.caption == test_markdown_string
+        assert message.caption_markdown == escape_markdown(test_markdown_string)
+
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_document_default_parse_mode_3(self, monkeypatch, bot, chat_id, document):
+        monkeypatch.setattr('telegram.Bot.send_document', functools.partial(bot.send_document,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_document(chat_id, document, caption=test_markdown_string,
+                                    parse_mode='HTML')
+        assert message.caption == test_markdown_string
+        assert message.caption_markdown == escape_markdown(test_markdown_string)
+
     def test_de_json(self, bot, document):
         json_dict = {'file_id': 'not a file id',
                      'thumb': document.thumb.to_dict(),
diff --git a/tests/test_photo.py b/tests/test_photo.py
index 9a3d8aee6b9..ecf407618c2 100644
--- a/tests/test_photo.py
+++ b/tests/test_photo.py
@@ -17,12 +17,14 @@
 # You should have received a copy of the GNU Lesser Public License
 # along with this program.  If not, see [http://www.gnu.org/licenses/].
 import os
+import functools
 from io import BytesIO
 
 import pytest
 from flaky import flaky
 
 from telegram import Sticker, TelegramError, PhotoSize, InputFile
+from telegram.utils.helpers import escape_markdown
 
 
 @pytest.fixture(scope='function')
@@ -139,6 +141,63 @@ def test_send_photo_parse_mode_html(self, bot, chat_id, photo_file, thumb, photo
         assert message.caption == TestPhoto.caption.replace('<b>', '').replace('</b>', '')
         assert len(message.caption_entities) == 1
 
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_photo_default_parse_mode_1(self,
+                                             monkeypatch,
+                                             bot,
+                                             chat_id,
+                                             photo_file,
+                                             thumb,
+                                             photo):
+        monkeypatch.setattr('telegram.Bot.send_photo', functools.partial(bot.send_photo,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_string = 'Italic Bold Code'
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_photo(chat_id, photo_file, caption=test_markdown_string)
+        assert message.caption_markdown == test_markdown_string
+        assert message.caption == test_string
+
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_photo_default_parse_mode_2(self,
+                                             monkeypatch,
+                                             bot,
+                                             chat_id,
+                                             photo_file,
+                                             thumb,
+                                             photo):
+        monkeypatch.setattr('telegram.Bot.send_photo', functools.partial(bot.send_photo,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_photo(chat_id, photo_file, caption=test_markdown_string,
+                                 parse_mode=None)
+        assert message.caption == test_markdown_string
+        assert message.caption_markdown == escape_markdown(test_markdown_string)
+
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_photo_default_parse_mode_3(self,
+                                             monkeypatch,
+                                             bot,
+                                             chat_id,
+                                             photo_file,
+                                             thumb,
+                                             photo):
+        monkeypatch.setattr('telegram.Bot.send_photo', functools.partial(bot.send_photo,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_photo(chat_id, photo_file, caption=test_markdown_string,
+                                 parse_mode='HTML')
+        assert message.caption == test_markdown_string
+        assert message.caption_markdown == escape_markdown(test_markdown_string)
+
     @flaky(3, 1)
     @pytest.mark.timeout(10)
     def test_get_and_download(self, bot, photo):
diff --git a/tests/test_video.py b/tests/test_video.py
index 2e009ff68b1..dd921eee859 100644
--- a/tests/test_video.py
+++ b/tests/test_video.py
@@ -17,11 +17,13 @@
 # You should have received a copy of the GNU Lesser Public License
 # along with this program.  If not, see [http://www.gnu.org/licenses/].
 import os
+import functools
 
 import pytest
 from flaky import flaky
 
 from telegram import Video, TelegramError, Voice, PhotoSize
+from telegram.utils.helpers import escape_markdown
 
 
 @pytest.fixture(scope='function')
@@ -141,6 +143,45 @@ def test(_, url, data, **kwargs):
         message = bot.send_video(chat_id, video=video)
         assert message
 
+    flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_video_default_parse_mode_1(self, monkeypatch, bot, chat_id, video):
+        monkeypatch.setattr('telegram.Bot.send_video', functools.partial(bot.send_video,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_string = 'Italic Bold Code'
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_video(chat_id, video, caption=test_markdown_string)
+        assert message.caption_markdown == test_markdown_string
+        assert message.caption == test_string
+
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_video_default_parse_mode_2(self, monkeypatch, bot, chat_id, video):
+        monkeypatch.setattr('telegram.Bot.send_video', functools.partial(bot.send_video,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_video(chat_id, video, caption=test_markdown_string,
+                                 parse_mode=None)
+        assert message.caption == test_markdown_string
+        assert message.caption_markdown == escape_markdown(test_markdown_string)
+
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_video_default_parse_mode_3(self, monkeypatch, bot, chat_id, video):
+        monkeypatch.setattr('telegram.Bot.send_video', functools.partial(bot.send_video,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_video(chat_id, video, caption=test_markdown_string,
+                                 parse_mode='HTML')
+        assert message.caption == test_markdown_string
+        assert message.caption_markdown == escape_markdown(test_markdown_string)
+
     def test_de_json(self, bot):
         json_dict = {
             'file_id': 'not a file id',
diff --git a/tests/test_voice.py b/tests/test_voice.py
index 407bf66f708..40369d560a8 100644
--- a/tests/test_voice.py
+++ b/tests/test_voice.py
@@ -17,11 +17,13 @@
 # You should have received a copy of the GNU Lesser Public License
 # along with this program.  If not, see [http://www.gnu.org/licenses/].
 import os
+import functools
 
 import pytest
 from flaky import flaky
 
 from telegram import Audio, Voice, TelegramError
+from telegram.utils.helpers import escape_markdown
 
 
 @pytest.fixture(scope='function')
@@ -111,6 +113,45 @@ def test(_, url, data, **kwargs):
         message = bot.send_voice(chat_id, voice=voice)
         assert message
 
+    flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_voice_default_parse_mode_1(self, monkeypatch, bot, chat_id, voice):
+        monkeypatch.setattr('telegram.Bot.send_voice', functools.partial(bot.send_voice,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_string = 'Italic Bold Code'
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_voice(chat_id, voice, caption=test_markdown_string)
+        assert message.caption_markdown == test_markdown_string
+        assert message.caption == test_string
+
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_voice_default_parse_mode_2(self, monkeypatch, bot, chat_id, voice):
+        monkeypatch.setattr('telegram.Bot.send_voice', functools.partial(bot.send_voice,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_voice(chat_id, voice, caption=test_markdown_string,
+                                 parse_mode=None)
+        assert message.caption == test_markdown_string
+        assert message.caption_markdown == escape_markdown(test_markdown_string)
+
+    @flaky(3, 1)
+    @pytest.mark.timeout(10)
+    def test_send_voice_default_parse_mode_3(self, monkeypatch, bot, chat_id, voice):
+        monkeypatch.setattr('telegram.Bot.send_voice', functools.partial(bot.send_voice,
+                            **{'parse_mode': 'Markdown'}))
+
+        test_markdown_string = '_Italic_ *Bold* `Code`'
+
+        message = bot.send_voice(chat_id, voice, caption=test_markdown_string,
+                                 parse_mode='HTML')
+        assert message.caption == test_markdown_string
+        assert message.caption_markdown == escape_markdown(test_markdown_string)
+
     def test_de_json(self, bot):
         json_dict = {
             'file_id': 'not a file id',


Code review comments:
diff_hunk: @@ -82,13 +83,49 @@ 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.
+        default_parse_mode (:obj:`str`, optional): Default parse mode used if not set explicitly in
+            method call. See the constants in :class:`telegram.ParseMode` for the available modes.
 
     """
 
+    def __new__(cls, *args, **kwargs):
+        # Handle default_... kwargs for bot methods
+        # Transform default_x=y kwargs into Defaults.x=y
+        defaults = Defaults()
+        for kwarg in list(kwargs.keys()):
+            if kwarg.startswith('default_'):
+                setattr(defaults, kwarg[8:], kwargs[kwarg])
+                del kwargs[kwarg]
+
+        # Make an instance of the class
+        instance = super(Bot, cls).__new__(cls)
+
+        # 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 kwarg_name in defaults.__dict__.keys()
+            ]
+            # ... make a dict of kwarg name and the default value
+            default_kwargs = {
+                kwarg_name: getattr(defaults, kwarg_name) for kwarg_name in needs_default
+            }
+            # ... 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):
+                 private_key_password=None, default_parse_mode=None):
path: telegram/bot.py
text: @user1:
This probably won't work cause `del kwargs[kwarg]` (line 98) deletes this kwarg if i'm not mistaken.

A better way instead of `self.defaults = Defaults(parse_mode=default_parse_mode)` below, might be to simply do a `instance.defaults = defaults` in `__new__` above.

@author:
Actually it does. Running

```
from telegram import Bot
bot = Bot('TOKEN', default_parse_mode='Markdown')
print(bot.defaults.parse_mode)
```

prints `Markdown` as expected. If I understand correctly, `__new__` can not directly alter the arguments passed to `__init__`. Instead for this to work one would have to overwrite the `__call__` method or something (see [here](https://bytes.com/topic/python/answers/751865-modify-arguments-between-__new__-__init__)).

Why did you add the `del kwargs[kwarg]` in the first place, if I may ask? The described behaviour in mind I think we can savely delete it (all tests pass without it, too).

@user1:
Originally i had `instance = super(Bot, cls).__new__(cls, *args, **kwargs)` note the args and kwargs. so i had to strip out the kwargs that `object.__new__` didn't want. I would prefer to have it like that, as it should provide better integration with custom Bot classes as far as I understand (instead of just ignoring the args and kwargs we actually pass them up according to the MRO)

@author:
Okay, I see your point about passing the arguments. A solution for the problem that `object` takes no parameters (mentioned in the dev chat) could be to overwrite `TelegramObjects` (which inherits from `object`) `__new__` method to

```
def __new__(cls, *args, **kwargs):
    return super(TelegramObject, cls).__new__(cls)
```

i.e. stop the passing of args here. This passes all our tests but I'm not enough of a pythonian to tell if this might cause trouble later …

Still, I don't really see why the `default_` args should be deleted (and none of the other) before passing them along to `TelegramObject`. What am I missing?

@user1:
Bot inherits from `object` and should continue to do so. I was under the impression that it would be possible to override the MRO so that super would go up to a user defined class that does accept `__new__` but now that i think of it, that's not possible.
So as long as Bot inherits from object there's no need to pass the arguments.