mirror of
https://gitflic.ru/project/photopea-v2/photopea-v-2.git
synced 2026-05-19 16:20:37 +00:00
Normal update
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
Thin wrappers around common functions.
|
||||
|
||||
Subpackages contain potentially unstable extensions.
|
||||
"""
|
||||
import sys
|
||||
from functools import wraps
|
||||
|
||||
from ..auto import tqdm as tqdm_auto
|
||||
from ..std import tqdm
|
||||
from ..utils import ObjectWrapper
|
||||
|
||||
__author__ = {"github.com/": ["casperdcl"]}
|
||||
__all__ = ['tenumerate', 'tzip', 'tmap']
|
||||
|
||||
|
||||
class DummyTqdmFile(ObjectWrapper):
|
||||
"""Dummy file-like that will write to tqdm"""
|
||||
|
||||
def __init__(self, wrapped):
|
||||
super(DummyTqdmFile, self).__init__(wrapped)
|
||||
self._buf = []
|
||||
|
||||
def write(self, x, nolock=False):
|
||||
nl = b"\n" if isinstance(x, bytes) else "\n"
|
||||
pre, sep, post = x.rpartition(nl)
|
||||
if sep:
|
||||
blank = type(nl)()
|
||||
tqdm.write(blank.join(self._buf + [pre, sep]),
|
||||
end=blank, file=self._wrapped, nolock=nolock)
|
||||
self._buf = [post]
|
||||
else:
|
||||
self._buf.append(x)
|
||||
|
||||
def __del__(self):
|
||||
if self._buf:
|
||||
blank = type(self._buf[0])()
|
||||
try:
|
||||
tqdm.write(blank.join(self._buf), end=blank, file=self._wrapped)
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def builtin_iterable(func):
|
||||
"""Wraps `func()` output in a `list()` in py2"""
|
||||
if sys.version_info[:1] < (3,):
|
||||
@wraps(func)
|
||||
def inner(*args, **kwargs):
|
||||
return list(func(*args, **kwargs))
|
||||
return inner
|
||||
return func
|
||||
|
||||
|
||||
def tenumerate(iterable, start=0, total=None, tqdm_class=tqdm_auto, **tqdm_kwargs):
|
||||
"""
|
||||
Equivalent of `numpy.ndenumerate` or builtin `enumerate`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tqdm_class : [default: tqdm.auto.tqdm].
|
||||
"""
|
||||
try:
|
||||
import numpy as np
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
if isinstance(iterable, np.ndarray):
|
||||
return tqdm_class(np.ndenumerate(iterable), total=total or iterable.size,
|
||||
**tqdm_kwargs)
|
||||
return enumerate(tqdm_class(iterable, total=total, **tqdm_kwargs), start)
|
||||
|
||||
|
||||
@builtin_iterable
|
||||
def tzip(iter1, *iter2plus, **tqdm_kwargs):
|
||||
"""
|
||||
Equivalent of builtin `zip`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tqdm_class : [default: tqdm.auto.tqdm].
|
||||
"""
|
||||
kwargs = tqdm_kwargs.copy()
|
||||
tqdm_class = kwargs.pop("tqdm_class", tqdm_auto)
|
||||
for i in zip(tqdm_class(iter1, **kwargs), *iter2plus):
|
||||
yield i
|
||||
|
||||
|
||||
@builtin_iterable
|
||||
def tmap(function, *sequences, **tqdm_kwargs):
|
||||
"""
|
||||
Equivalent of builtin `map`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tqdm_class : [default: tqdm.auto.tqdm].
|
||||
"""
|
||||
for i in tzip(*sequences, **tqdm_kwargs):
|
||||
yield function(*i)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Even more features than `tqdm.auto` (all the bells & whistles):
|
||||
|
||||
- `tqdm.auto`
|
||||
- `tqdm.tqdm.pandas`
|
||||
- `tqdm.contrib.telegram`
|
||||
+ uses `${TQDM_TELEGRAM_TOKEN}` and `${TQDM_TELEGRAM_CHAT_ID}`
|
||||
- `tqdm.contrib.discord`
|
||||
+ uses `${TQDM_DISCORD_TOKEN}` and `${TQDM_DISCORD_CHANNEL_ID}`
|
||||
"""
|
||||
__all__ = ['tqdm', 'trange']
|
||||
import warnings
|
||||
from os import getenv
|
||||
|
||||
if getenv("TQDM_SLACK_TOKEN") and getenv("TQDM_SLACK_CHANNEL"):
|
||||
from .slack import tqdm, trange
|
||||
elif getenv("TQDM_TELEGRAM_TOKEN") and getenv("TQDM_TELEGRAM_CHAT_ID"):
|
||||
from .telegram import tqdm, trange
|
||||
elif getenv("TQDM_DISCORD_TOKEN") and getenv("TQDM_DISCORD_CHANNEL_ID"):
|
||||
from .discord import tqdm, trange
|
||||
else:
|
||||
from ..auto import tqdm, trange
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", category=FutureWarning)
|
||||
tqdm.pandas()
|
||||
@@ -0,0 +1,130 @@
|
||||
"""
|
||||
Thin wrappers around `concurrent.futures`.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
from ..auto import tqdm as tqdm_auto
|
||||
from ..std import TqdmWarning
|
||||
|
||||
try:
|
||||
from operator import length_hint
|
||||
except ImportError:
|
||||
def length_hint(it, default=0):
|
||||
"""Returns `len(it)`, falling back to `default`"""
|
||||
try:
|
||||
return len(it)
|
||||
except TypeError:
|
||||
return default
|
||||
try:
|
||||
from os import cpu_count
|
||||
except ImportError:
|
||||
try:
|
||||
from multiprocessing import cpu_count
|
||||
except ImportError:
|
||||
def cpu_count():
|
||||
return 4
|
||||
import sys
|
||||
|
||||
__author__ = {"github.com/": ["casperdcl"]}
|
||||
__all__ = ['thread_map', 'process_map']
|
||||
|
||||
|
||||
@contextmanager
|
||||
def ensure_lock(tqdm_class, lock_name=""):
|
||||
"""get (create if necessary) and then restore `tqdm_class`'s lock"""
|
||||
old_lock = getattr(tqdm_class, '_lock', None) # don't create a new lock
|
||||
lock = old_lock or tqdm_class.get_lock() # maybe create a new lock
|
||||
lock = getattr(lock, lock_name, lock) # maybe subtype
|
||||
tqdm_class.set_lock(lock)
|
||||
yield lock
|
||||
if old_lock is None:
|
||||
del tqdm_class._lock
|
||||
else:
|
||||
tqdm_class.set_lock(old_lock)
|
||||
|
||||
|
||||
def _executor_map(PoolExecutor, fn, *iterables, **tqdm_kwargs):
|
||||
"""
|
||||
Implementation of `thread_map` and `process_map`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tqdm_class : [default: tqdm.auto.tqdm].
|
||||
max_workers : [default: min(32, cpu_count() + 4)].
|
||||
chunksize : [default: 1].
|
||||
lock_name : [default: "":str].
|
||||
"""
|
||||
kwargs = tqdm_kwargs.copy()
|
||||
if "total" not in kwargs:
|
||||
kwargs["total"] = length_hint(iterables[0])
|
||||
tqdm_class = kwargs.pop("tqdm_class", tqdm_auto)
|
||||
max_workers = kwargs.pop("max_workers", min(32, cpu_count() + 4))
|
||||
chunksize = kwargs.pop("chunksize", 1)
|
||||
lock_name = kwargs.pop("lock_name", "")
|
||||
with ensure_lock(tqdm_class, lock_name=lock_name) as lk:
|
||||
pool_kwargs = {'max_workers': max_workers}
|
||||
sys_version = sys.version_info[:2]
|
||||
if sys_version >= (3, 7):
|
||||
# share lock in case workers are already using `tqdm`
|
||||
pool_kwargs.update(initializer=tqdm_class.set_lock, initargs=(lk,))
|
||||
map_args = {}
|
||||
if not (3, 0) < sys_version < (3, 5):
|
||||
map_args.update(chunksize=chunksize)
|
||||
with PoolExecutor(**pool_kwargs) as ex:
|
||||
return list(tqdm_class(ex.map(fn, *iterables, **map_args), **kwargs))
|
||||
|
||||
|
||||
def thread_map(fn, *iterables, **tqdm_kwargs):
|
||||
"""
|
||||
Equivalent of `list(map(fn, *iterables))`
|
||||
driven by `concurrent.futures.ThreadPoolExecutor`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tqdm_class : optional
|
||||
`tqdm` class to use for bars [default: tqdm.auto.tqdm].
|
||||
max_workers : int, optional
|
||||
Maximum number of workers to spawn; passed to
|
||||
`concurrent.futures.ThreadPoolExecutor.__init__`.
|
||||
[default: max(32, cpu_count() + 4)].
|
||||
"""
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
return _executor_map(ThreadPoolExecutor, fn, *iterables, **tqdm_kwargs)
|
||||
|
||||
|
||||
def process_map(fn, *iterables, **tqdm_kwargs):
|
||||
"""
|
||||
Equivalent of `list(map(fn, *iterables))`
|
||||
driven by `concurrent.futures.ProcessPoolExecutor`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tqdm_class : optional
|
||||
`tqdm` class to use for bars [default: tqdm.auto.tqdm].
|
||||
max_workers : int, optional
|
||||
Maximum number of workers to spawn; passed to
|
||||
`concurrent.futures.ProcessPoolExecutor.__init__`.
|
||||
[default: min(32, cpu_count() + 4)].
|
||||
chunksize : int, optional
|
||||
Size of chunks sent to worker processes; passed to
|
||||
`concurrent.futures.ProcessPoolExecutor.map`. [default: 1].
|
||||
lock_name : str, optional
|
||||
Member of `tqdm_class.get_lock()` to use [default: mp_lock].
|
||||
"""
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
if iterables and "chunksize" not in tqdm_kwargs:
|
||||
# default `chunksize=1` has poor performance for large iterables
|
||||
# (most time spent dispatching items to workers).
|
||||
longest_iterable_len = max(map(length_hint, iterables))
|
||||
if longest_iterable_len > 1000:
|
||||
from warnings import warn
|
||||
warn("Iterable length %d > 1000 but `chunksize` is not set."
|
||||
" This may seriously degrade multiprocess performance."
|
||||
" Set `chunksize=1` or more." % longest_iterable_len,
|
||||
TqdmWarning, stacklevel=2)
|
||||
if "lock_name" not in tqdm_kwargs:
|
||||
tqdm_kwargs = tqdm_kwargs.copy()
|
||||
tqdm_kwargs["lock_name"] = "mp_lock"
|
||||
return _executor_map(ProcessPoolExecutor, fn, *iterables, **tqdm_kwargs)
|
||||
@@ -0,0 +1,125 @@
|
||||
"""
|
||||
Sends updates to a Discord bot.
|
||||
|
||||
Usage:
|
||||
>>> from tqdm.contrib.discord import tqdm, trange
|
||||
>>> for i in trange(10, token='{token}', channel_id='{channel_id}'):
|
||||
... ...
|
||||
|
||||

|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
from os import getenv
|
||||
|
||||
try:
|
||||
from disco.client import Client, ClientConfig
|
||||
except ImportError:
|
||||
raise ImportError("Please `pip install disco-py`")
|
||||
|
||||
from ..auto import tqdm as tqdm_auto
|
||||
from ..utils import _range
|
||||
from .utils_worker import MonoWorker
|
||||
|
||||
__author__ = {"github.com/": ["casperdcl"]}
|
||||
__all__ = ['DiscordIO', 'tqdm_discord', 'tdrange', 'tqdm', 'trange']
|
||||
|
||||
|
||||
class DiscordIO(MonoWorker):
|
||||
"""Non-blocking file-like IO using a Discord Bot."""
|
||||
def __init__(self, token, channel_id):
|
||||
"""Creates a new message in the given `channel_id`."""
|
||||
super(DiscordIO, self).__init__()
|
||||
config = ClientConfig()
|
||||
config.token = token
|
||||
client = Client(config)
|
||||
self.text = self.__class__.__name__
|
||||
try:
|
||||
self.message = client.api.channels_messages_create(channel_id, self.text)
|
||||
except Exception as e:
|
||||
tqdm_auto.write(str(e))
|
||||
self.message = None
|
||||
|
||||
def write(self, s):
|
||||
"""Replaces internal `message`'s text with `s`."""
|
||||
if not s:
|
||||
s = "..."
|
||||
s = s.replace('\r', '').strip()
|
||||
if s == self.text:
|
||||
return # skip duplicate message
|
||||
message = self.message
|
||||
if message is None:
|
||||
return
|
||||
self.text = s
|
||||
try:
|
||||
future = self.submit(message.edit, '`' + s + '`')
|
||||
except Exception as e:
|
||||
tqdm_auto.write(str(e))
|
||||
else:
|
||||
return future
|
||||
|
||||
|
||||
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__`).
|
||||
|
||||
- create a discord bot (not public, no requirement of OAuth2 code
|
||||
grant, only send message permissions) & invite it to a channel:
|
||||
<https://discordpy.readthedocs.io/en/latest/discord.html>
|
||||
- copy the bot `{token}` & `{channel_id}` and paste below
|
||||
|
||||
>>> from tqdm.contrib.discord import tqdm, trange
|
||||
>>> for i in tqdm(iterable, token='{token}', channel_id='{channel_id}'):
|
||||
... ...
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
token : str, required. Discord token
|
||||
[default: ${TQDM_DISCORD_TOKEN}].
|
||||
channel_id : int, required. Discord channel ID
|
||||
[default: ${TQDM_DISCORD_CHANNEL_ID}].
|
||||
mininterval : float, optional.
|
||||
Minimum of [default: 1.5] to avoid rate limit.
|
||||
|
||||
See `tqdm.auto.tqdm.__init__` for other parameters.
|
||||
"""
|
||||
if not kwargs.get('disable'):
|
||||
kwargs = kwargs.copy()
|
||||
logging.getLogger("HTTPClient").setLevel(logging.WARNING)
|
||||
self.dio = DiscordIO(
|
||||
kwargs.pop('token', getenv("TQDM_DISCORD_TOKEN")),
|
||||
kwargs.pop('channel_id', getenv("TQDM_DISCORD_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 fmt.get('bar_format', None):
|
||||
fmt['bar_format'] = fmt['bar_format'].replace(
|
||||
'<bar/>', '{bar:10u}').replace('{bar}', '{bar:10u}')
|
||||
else:
|
||||
fmt['bar_format'] = '{l_bar}{bar:10u}{r_bar}'
|
||||
self.dio.write(self.format_meter(**fmt))
|
||||
|
||||
def clear(self, *args, **kwargs):
|
||||
super(tqdm_discord, self).clear(*args, **kwargs)
|
||||
if not self.disable:
|
||||
self.dio.write("")
|
||||
|
||||
|
||||
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
|
||||
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
Thin wrappers around `itertools`.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import itertools
|
||||
|
||||
from ..auto import tqdm as tqdm_auto
|
||||
|
||||
__author__ = {"github.com/": ["casperdcl"]}
|
||||
__all__ = ['product']
|
||||
|
||||
|
||||
def product(*iterables, **tqdm_kwargs):
|
||||
"""
|
||||
Equivalent of `itertools.product`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tqdm_class : [default: tqdm.auto.tqdm].
|
||||
"""
|
||||
kwargs = tqdm_kwargs.copy()
|
||||
tqdm_class = kwargs.pop("tqdm_class", tqdm_auto)
|
||||
try:
|
||||
lens = list(map(len, iterables))
|
||||
except TypeError:
|
||||
total = None
|
||||
else:
|
||||
total = 1
|
||||
for i in lens:
|
||||
total *= i
|
||||
kwargs.setdefault("total", total)
|
||||
with tqdm_class(**kwargs) as t:
|
||||
it = itertools.product(*iterables)
|
||||
for i in it:
|
||||
yield i
|
||||
t.update()
|
||||
@@ -0,0 +1,128 @@
|
||||
"""
|
||||
Helper functionality for interoperability with stdlib `logging`.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
|
||||
try:
|
||||
from typing import Iterator, List, Optional, Type # pylint: disable=unused-import
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from ..std import tqdm as std_tqdm
|
||||
|
||||
|
||||
class _TqdmLoggingHandler(logging.StreamHandler):
|
||||
def __init__(
|
||||
self,
|
||||
tqdm_class=std_tqdm # type: Type[std_tqdm]
|
||||
):
|
||||
super(_TqdmLoggingHandler, self).__init__()
|
||||
self.tqdm_class = tqdm_class
|
||||
|
||||
def emit(self, record):
|
||||
try:
|
||||
msg = self.format(record)
|
||||
self.tqdm_class.write(msg, file=self.stream)
|
||||
self.flush()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except: # noqa pylint: disable=bare-except
|
||||
self.handleError(record)
|
||||
|
||||
|
||||
def _is_console_logging_handler(handler):
|
||||
return (isinstance(handler, logging.StreamHandler)
|
||||
and handler.stream in {sys.stdout, sys.stderr})
|
||||
|
||||
|
||||
def _get_first_found_console_logging_handler(handlers):
|
||||
for handler in handlers:
|
||||
if _is_console_logging_handler(handler):
|
||||
return handler
|
||||
|
||||
|
||||
@contextmanager
|
||||
def logging_redirect_tqdm(
|
||||
loggers=None, # type: Optional[List[logging.Logger]],
|
||||
tqdm_class=std_tqdm # type: Type[std_tqdm]
|
||||
):
|
||||
# type: (...) -> Iterator[None]
|
||||
"""
|
||||
Context manager redirecting console logging to `tqdm.write()`, leaving
|
||||
other logging handlers (e.g. log files) unaffected.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
loggers : list, optional
|
||||
Which handlers to redirect (default: [logging.root]).
|
||||
tqdm_class : optional
|
||||
|
||||
Example
|
||||
-------
|
||||
```python
|
||||
import logging
|
||||
from tqdm import trange
|
||||
from tqdm.contrib.logging import logging_redirect_tqdm
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
with logging_redirect_tqdm():
|
||||
for i in trange(9):
|
||||
if i == 4:
|
||||
LOG.info("console logging redirected to `tqdm.write()`")
|
||||
# logging restored
|
||||
```
|
||||
"""
|
||||
if loggers is None:
|
||||
loggers = [logging.root]
|
||||
original_handlers_list = [logger.handlers for logger in loggers]
|
||||
try:
|
||||
for logger in loggers:
|
||||
tqdm_handler = _TqdmLoggingHandler(tqdm_class)
|
||||
orig_handler = _get_first_found_console_logging_handler(logger.handlers)
|
||||
if orig_handler is not None:
|
||||
tqdm_handler.setFormatter(orig_handler.formatter)
|
||||
tqdm_handler.stream = orig_handler.stream
|
||||
logger.handlers = [
|
||||
handler for handler in logger.handlers
|
||||
if not _is_console_logging_handler(handler)] + [tqdm_handler]
|
||||
yield
|
||||
finally:
|
||||
for logger, original_handlers in zip(loggers, original_handlers_list):
|
||||
logger.handlers = original_handlers
|
||||
|
||||
|
||||
@contextmanager
|
||||
def tqdm_logging_redirect(
|
||||
*args,
|
||||
# loggers=None, # type: Optional[List[logging.Logger]]
|
||||
# tqdm=None, # type: Optional[Type[tqdm.tqdm]]
|
||||
**kwargs
|
||||
):
|
||||
# type: (...) -> Iterator[None]
|
||||
"""
|
||||
Convenience shortcut for:
|
||||
```python
|
||||
with tqdm_class(*args, **tqdm_kwargs) as pbar:
|
||||
with logging_redirect_tqdm(loggers=loggers, tqdm_class=tqdm_class):
|
||||
yield pbar
|
||||
```
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tqdm_class : optional, (default: tqdm.std.tqdm).
|
||||
loggers : optional, list.
|
||||
**tqdm_kwargs : passed to `tqdm_class`.
|
||||
"""
|
||||
tqdm_kwargs = kwargs.copy()
|
||||
loggers = tqdm_kwargs.pop('loggers', None)
|
||||
tqdm_class = tqdm_kwargs.pop('tqdm_class', std_tqdm)
|
||||
with tqdm_class(*args, **tqdm_kwargs) as pbar:
|
||||
with logging_redirect_tqdm(loggers=loggers, tqdm_class=tqdm_class):
|
||||
yield pbar
|
||||
@@ -0,0 +1,126 @@
|
||||
"""
|
||||
Sends updates to a Slack app.
|
||||
|
||||
Usage:
|
||||
>>> from tqdm.contrib.slack import tqdm, trange
|
||||
>>> for i in trange(10, token='{token}', channel='{channel}'):
|
||||
... ...
|
||||
|
||||

|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
from os import getenv
|
||||
|
||||
try:
|
||||
from slack_sdk import WebClient
|
||||
except ImportError:
|
||||
raise ImportError("Please `pip install slack-sdk`")
|
||||
|
||||
from ..auto import tqdm as tqdm_auto
|
||||
from ..utils import _range
|
||||
from .utils_worker import MonoWorker
|
||||
|
||||
__author__ = {"github.com/": ["0x2b3bfa0", "casperdcl"]}
|
||||
__all__ = ['SlackIO', 'tqdm_slack', 'tsrange', 'tqdm', 'trange']
|
||||
|
||||
|
||||
class SlackIO(MonoWorker):
|
||||
"""Non-blocking file-like IO using a Slack app."""
|
||||
def __init__(self, token, channel):
|
||||
"""Creates a new message in the given `channel`."""
|
||||
super(SlackIO, self).__init__()
|
||||
self.client = WebClient(token=token)
|
||||
self.text = self.__class__.__name__
|
||||
try:
|
||||
self.message = self.client.chat_postMessage(channel=channel, text=self.text)
|
||||
except Exception as e:
|
||||
tqdm_auto.write(str(e))
|
||||
self.message = None
|
||||
|
||||
def write(self, s):
|
||||
"""Replaces internal `message`'s text with `s`."""
|
||||
if not s:
|
||||
s = "..."
|
||||
s = s.replace('\r', '').strip()
|
||||
if s == self.text:
|
||||
return # skip duplicate message
|
||||
message = self.message
|
||||
if message is None:
|
||||
return
|
||||
self.text = s
|
||||
try:
|
||||
future = self.submit(self.client.chat_update, channel=message['channel'],
|
||||
ts=message['ts'], text='`' + s + '`')
|
||||
except Exception as e:
|
||||
tqdm_auto.write(str(e))
|
||||
else:
|
||||
return future
|
||||
|
||||
|
||||
class tqdm_slack(tqdm_auto):
|
||||
"""
|
||||
Standard `tqdm.auto.tqdm` but also sends updates to a Slack app.
|
||||
May take a few seconds to create (`__init__`).
|
||||
|
||||
- create a Slack app with the `chat:write` scope & invite it to a
|
||||
channel: <https://api.slack.com/authentication/basics>
|
||||
- copy the bot `{token}` & `{channel}` and paste below
|
||||
>>> from tqdm.contrib.slack import tqdm, trange
|
||||
>>> for i in tqdm(iterable, token='{token}', channel='{channel}'):
|
||||
... ...
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
token : str, required. Slack token
|
||||
[default: ${TQDM_SLACK_TOKEN}].
|
||||
channel : int, required. Slack channel
|
||||
[default: ${TQDM_SLACK_CHANNEL}].
|
||||
mininterval : float, optional.
|
||||
Minimum of [default: 1.5] to avoid rate limit.
|
||||
|
||||
See `tqdm.auto.tqdm.__init__` for other parameters.
|
||||
"""
|
||||
if not kwargs.get('disable'):
|
||||
kwargs = kwargs.copy()
|
||||
logging.getLogger("HTTPClient").setLevel(logging.WARNING)
|
||||
self.sio = SlackIO(
|
||||
kwargs.pop('token', getenv("TQDM_SLACK_TOKEN")),
|
||||
kwargs.pop('channel', getenv("TQDM_SLACK_CHANNEL")))
|
||||
kwargs['mininterval'] = max(1.5, kwargs.get('mininterval', 1.5))
|
||||
super(tqdm_slack, self).__init__(*args, **kwargs)
|
||||
|
||||
def display(self, **kwargs):
|
||||
super(tqdm_slack, self).display(**kwargs)
|
||||
fmt = self.format_dict
|
||||
if fmt.get('bar_format', None):
|
||||
fmt['bar_format'] = fmt['bar_format'].replace(
|
||||
'<bar/>', '`{bar:10}`').replace('{bar}', '`{bar:10u}`')
|
||||
else:
|
||||
fmt['bar_format'] = '{l_bar}`{bar:10}`{r_bar}'
|
||||
if fmt['ascii'] is False:
|
||||
fmt['ascii'] = [":black_square:", ":small_blue_diamond:", ":large_blue_diamond:",
|
||||
":large_blue_square:"]
|
||||
fmt['ncols'] = 336
|
||||
self.sio.write(self.format_meter(**fmt))
|
||||
|
||||
def clear(self, *args, **kwargs):
|
||||
super(tqdm_slack, self).clear(*args, **kwargs)
|
||||
if not self.disable:
|
||||
self.sio.write("")
|
||||
|
||||
|
||||
def tsrange(*args, **kwargs):
|
||||
"""
|
||||
A shortcut for `tqdm.contrib.slack.tqdm(xrange(*args), **kwargs)`.
|
||||
On Python3+, `range` is used instead of `xrange`.
|
||||
"""
|
||||
return tqdm_slack(_range(*args), **kwargs)
|
||||
|
||||
|
||||
# Aliases
|
||||
tqdm = tqdm_slack
|
||||
trange = tsrange
|
||||
@@ -0,0 +1,159 @@
|
||||
"""
|
||||
Sends updates to a Telegram bot.
|
||||
|
||||
Usage:
|
||||
>>> from tqdm.contrib.telegram import tqdm, trange
|
||||
>>> for i in trange(10, token='{token}', chat_id='{chat_id}'):
|
||||
... ...
|
||||
|
||||

|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from os import getenv
|
||||
from warnings import warn
|
||||
|
||||
from requests import Session
|
||||
|
||||
from ..auto import tqdm as tqdm_auto
|
||||
from ..std import TqdmWarning
|
||||
from ..utils import _range
|
||||
from .utils_worker import MonoWorker
|
||||
|
||||
__author__ = {"github.com/": ["casperdcl"]}
|
||||
__all__ = ['TelegramIO', 'tqdm_telegram', 'ttgrange', 'tqdm', 'trange']
|
||||
|
||||
|
||||
class TelegramIO(MonoWorker):
|
||||
"""Non-blocking file-like IO using a Telegram Bot."""
|
||||
API = 'https://api.telegram.org/bot'
|
||||
|
||||
def __init__(self, token, chat_id):
|
||||
"""Creates a new message in the given `chat_id`."""
|
||||
super(TelegramIO, self).__init__()
|
||||
self.token = token
|
||||
self.chat_id = chat_id
|
||||
self.session = Session()
|
||||
self.text = self.__class__.__name__
|
||||
self.message_id
|
||||
|
||||
@property
|
||||
def message_id(self):
|
||||
if hasattr(self, '_message_id'):
|
||||
return self._message_id
|
||||
try:
|
||||
res = self.session.post(
|
||||
self.API + '%s/sendMessage' % self.token,
|
||||
data={'text': '`' + self.text + '`', 'chat_id': self.chat_id,
|
||||
'parse_mode': 'MarkdownV2'}).json()
|
||||
except Exception as e:
|
||||
tqdm_auto.write(str(e))
|
||||
else:
|
||||
if res.get('error_code') == 429:
|
||||
warn("Creation rate limit: try increasing `mininterval`.",
|
||||
TqdmWarning, stacklevel=2)
|
||||
else:
|
||||
self._message_id = res['result']['message_id']
|
||||
return self._message_id
|
||||
|
||||
def write(self, s):
|
||||
"""Replaces internal `message_id`'s text with `s`."""
|
||||
if not s:
|
||||
s = "..."
|
||||
s = s.replace('\r', '').strip()
|
||||
if s == self.text:
|
||||
return # avoid duplicate message Bot error
|
||||
message_id = self.message_id
|
||||
if message_id is None:
|
||||
return
|
||||
self.text = s
|
||||
try:
|
||||
future = self.submit(
|
||||
self.session.post, self.API + '%s/editMessageText' % self.token,
|
||||
data={'text': '`' + s + '`', 'chat_id': self.chat_id,
|
||||
'message_id': message_id, 'parse_mode': 'MarkdownV2'})
|
||||
except Exception as e:
|
||||
tqdm_auto.write(str(e))
|
||||
else:
|
||||
return future
|
||||
|
||||
def delete(self):
|
||||
"""Deletes internal `message_id`."""
|
||||
try:
|
||||
future = self.submit(
|
||||
self.session.post, self.API + '%s/deleteMessage' % self.token,
|
||||
data={'chat_id': self.chat_id, 'message_id': self.message_id})
|
||||
except Exception as e:
|
||||
tqdm_auto.write(str(e))
|
||||
else:
|
||||
return future
|
||||
|
||||
|
||||
class tqdm_telegram(tqdm_auto):
|
||||
"""
|
||||
Standard `tqdm.auto.tqdm` but also sends updates to a Telegram Bot.
|
||||
May take a few seconds to create (`__init__`).
|
||||
|
||||
- create a bot <https://core.telegram.org/bots#6-botfather>
|
||||
- copy its `{token}`
|
||||
- add the bot to a chat and send it a message such as `/start`
|
||||
- go to <https://api.telegram.org/bot`{token}`/getUpdates> to find out
|
||||
the `{chat_id}`
|
||||
- paste the `{token}` & `{chat_id}` below
|
||||
|
||||
>>> from tqdm.contrib.telegram import tqdm, trange
|
||||
>>> for i in tqdm(iterable, token='{token}', chat_id='{chat_id}'):
|
||||
... ...
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
token : str, required. Telegram token
|
||||
[default: ${TQDM_TELEGRAM_TOKEN}].
|
||||
chat_id : str, required. Telegram chat ID
|
||||
[default: ${TQDM_TELEGRAM_CHAT_ID}].
|
||||
|
||||
See `tqdm.auto.tqdm.__init__` for other parameters.
|
||||
"""
|
||||
if not kwargs.get('disable'):
|
||||
kwargs = kwargs.copy()
|
||||
self.tgio = TelegramIO(
|
||||
kwargs.pop('token', getenv('TQDM_TELEGRAM_TOKEN')),
|
||||
kwargs.pop('chat_id', getenv('TQDM_TELEGRAM_CHAT_ID')))
|
||||
super(tqdm_telegram, self).__init__(*args, **kwargs)
|
||||
|
||||
def display(self, **kwargs):
|
||||
super(tqdm_telegram, self).display(**kwargs)
|
||||
fmt = self.format_dict
|
||||
if fmt.get('bar_format', None):
|
||||
fmt['bar_format'] = fmt['bar_format'].replace(
|
||||
'<bar/>', '{bar:10u}').replace('{bar}', '{bar:10u}')
|
||||
else:
|
||||
fmt['bar_format'] = '{l_bar}{bar:10u}{r_bar}'
|
||||
self.tgio.write(self.format_meter(**fmt))
|
||||
|
||||
def clear(self, *args, **kwargs):
|
||||
super(tqdm_telegram, self).clear(*args, **kwargs)
|
||||
if not self.disable:
|
||||
self.tgio.write("")
|
||||
|
||||
def close(self):
|
||||
if self.disable:
|
||||
return
|
||||
super(tqdm_telegram, self).close()
|
||||
if not (self.leave or (self.leave is None and self.pos == 0)):
|
||||
self.tgio.delete()
|
||||
|
||||
|
||||
def ttgrange(*args, **kwargs):
|
||||
"""
|
||||
A shortcut for `tqdm.contrib.telegram.tqdm(xrange(*args), **kwargs)`.
|
||||
On Python3+, `range` is used instead of `xrange`.
|
||||
"""
|
||||
return tqdm_telegram(_range(*args), **kwargs)
|
||||
|
||||
|
||||
# Aliases
|
||||
tqdm = tqdm_telegram
|
||||
trange = ttgrange
|
||||
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
IO/concurrency helpers for `tqdm.contrib`.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from collections import deque
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from ..auto import tqdm as tqdm_auto
|
||||
|
||||
__author__ = {"github.com/": ["casperdcl"]}
|
||||
__all__ = ['MonoWorker']
|
||||
|
||||
|
||||
class MonoWorker(object):
|
||||
"""
|
||||
Supports one running task and one waiting task.
|
||||
The waiting task is the most recent submitted (others are discarded).
|
||||
"""
|
||||
def __init__(self):
|
||||
self.pool = ThreadPoolExecutor(max_workers=1)
|
||||
self.futures = deque([], 2)
|
||||
|
||||
def submit(self, func, *args, **kwargs):
|
||||
"""`func(*args, **kwargs)` may replace currently waiting task."""
|
||||
futures = self.futures
|
||||
if len(futures) == futures.maxlen:
|
||||
running = futures.popleft()
|
||||
if not running.done():
|
||||
if len(futures): # clear waiting
|
||||
waiting = futures.pop()
|
||||
waiting.cancel()
|
||||
futures.appendleft(running) # re-insert running
|
||||
try:
|
||||
waiting = self.pool.submit(func, *args, **kwargs)
|
||||
except Exception as e:
|
||||
tqdm_auto.write(str(e))
|
||||
else:
|
||||
futures.append(waiting)
|
||||
return waiting
|
||||
Reference in New Issue
Block a user