diff --git a/.coveragerc b/.coveragerc
index e7bff6bf..bead6599 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -2,6 +2,7 @@
branch = True
omit =
tqdm/tests/*
+ tqdm/contrib/discord.py
tqdm/contrib/telegram.py
[report]
show_missing = True
diff --git a/.meta/.readme.rst b/.meta/.readme.rst
index b2949ebd..4a57fd88 100644
--- a/.meta/.readme.rst
+++ b/.meta/.readme.rst
@@ -428,6 +428,7 @@ The ``tqdm.contrib`` package also contains experimental modules:
- ``tqdm.contrib.itertools``: Thin wrappers around ``itertools``
- ``tqdm.contrib.concurrent``: Thin wrappers around ``concurrent.futures``
+- ``tqdm.contrib.discord``: Posts to `Discord `__ bots
- ``tqdm.contrib.telegram``: Posts to `Telegram `__ bots
Examples and Advanced Usage
diff --git a/README.rst b/README.rst
index 7688f516..ac9943d1 100644
--- a/README.rst
+++ b/README.rst
@@ -616,6 +616,7 @@ The ``tqdm.contrib`` package also contains experimental modules:
- ``tqdm.contrib.itertools``: Thin wrappers around ``itertools``
- ``tqdm.contrib.concurrent``: Thin wrappers around ``concurrent.futures``
+- ``tqdm.contrib.discord``: Posts to `Discord `__ bots
- ``tqdm.contrib.telegram``: Posts to `Telegram `__ bots
Examples and Advanced Usage
diff --git a/tqdm/contrib/discord.py b/tqdm/contrib/discord.py
new file mode 100644
index 00000000..7c23ec3f
--- /dev/null
+++ b/tqdm/contrib/discord.py
@@ -0,0 +1,131 @@
+"""
+Sends updates to a Discord bot.
+"""
+from __future__ import absolute_import
+
+from concurrent.futures import ThreadPoolExecutor
+try:
+ from disco.client import Client, ClientConfig
+except ImportError:
+ raise ImportError("Please `pip install disco-py`")
+
+from tqdm.auto import tqdm as tqdm_auto
+from tqdm.utils import _range
+__author__ = {"github.com/": ["casperdcl"]}
+__all__ = ['DiscordIO', 'tqdm_discord', 'tdrange', 'tqdm', 'trange']
+
+
+class DiscordIO():
+ """Non-blocking file-like IO to using a Discord Bot."""
+ def __init__(self, token, channel_id):
+ """Creates a new message in the given `channel_id`."""
+ config = ClientConfig()
+ config.token = token
+ client = Client(config)
+ self.text = self.__class__.__name__
+ self.pool = ThreadPoolExecutor()
+ self.futures = []
+ try:
+ self.msg = client.api.channels_messages_create(
+ channel_id, self.text)
+ except Exception as e:
+ tqdm_auto.write(str(e))
+
+ def write(self, s):
+ """Replaces internal `message_id`'s text with `s`."""
+ if not s:
+ return
+ s = s.strip().replace('\r', '')
+ if s == self.text:
+ return # avoid duplicate message Bot error
+ self.text = s
+ try:
+ f = self.pool.submit(self.msg.edit, '`' + s + '`')
+ except Exception as e:
+ tqdm_auto.write(str(e))
+ else:
+ self.futures.append(f)
+ return f
+
+ def flush(self):
+ """Ensure the last `write` has been processed."""
+ [f.cancel() for f in self.futures[-2::-1]]
+ try:
+ return self.futures[-1].result()
+ except IndexError:
+ pass
+ finally:
+ self.futures = []
+
+ def __del__(self):
+ self.flush()
+
+
+class tqdm_discord(tqdm_auto):
+ """
+ Standard `tqdm.auto.tqdm` but also sends updates to a Discord bot.
+ May take a few seconds to create (`__init__`) and clear (`__del__`).
+
+ >>> from tqdm.contrib.discord import tqdm, trange
+ >>> for i in tqdm(
+ ... iterable,
+ ... token='THIS1SSOMETOKEN0BTAINeDfrOmD1SC0rd',
+ ... channel_id=0246813579):
+ """
+ def __init__(self, *args, **kwargs):
+ """
+ Parameters
+ ----------
+ token : str, required. Telegram token.
+ chat_id : str, required. Telegram chat ID.
+ mininterval : float, optional.
+ Minimum of [default: 1.5] to avoid rate limit.
+
+ See `tqdm.auto.tqdm.__init__` for other parameters.
+ """
+ self.dio = DiscordIO(kwargs.pop('token'), kwargs.pop('channel_id'))
+ kwargs['mininterval'] = max(1.5, kwargs.get('mininterval', 1.5))
+ super(tqdm_discord, self).__init__(*args, **kwargs)
+
+ def display(self, **kwargs):
+ super(tqdm_discord, self).display(**kwargs)
+ fmt = self.format_dict
+ if 'bar_format' in fmt and fmt['bar_format']:
+ fmt['bar_format'] = fmt['bar_format'].replace('', '{bar}')
+ else:
+ fmt['bar_format'] = '{l_bar}{bar}{r_bar}'
+ fmt['bar_format'] = fmt['bar_format'].replace('{bar}', '{bar:10u}')
+ self.dio.write(self.format_meter(**fmt))
+
+ def __new__(cls, *args, **kwargs):
+ """
+ Workaround for mixed-class same-stream nested progressbars.
+ See [#509](https://github.com/tqdm/tqdm/issues/509)
+ """
+ with cls.get_lock():
+ try:
+ cls._instances = tqdm_auto._instances
+ except AttributeError:
+ pass
+ instance = super(tqdm_discord, cls).__new__(cls, *args, **kwargs)
+ with cls.get_lock():
+ try:
+ # `tqdm_auto` may have been changed so update
+ cls._instances.update(tqdm_auto._instances)
+ except AttributeError:
+ pass
+ tqdm_auto._instances = cls._instances
+ return instance
+
+
+def tdrange(*args, **kwargs):
+ """
+ A shortcut for `tqdm.contrib.discord.tqdm(xrange(*args), **kwargs)`.
+ On Python3+, `range` is used instead of `xrange`.
+ """
+ return tqdm_discord(_range(*args), **kwargs)
+
+
+# Aliases
+tqdm = tqdm_discord
+trange = tdrange