class Callback: """ Base class and interface for callback mechanism This class can be used directly for monitoring file transfers by providing ``callback=Callback(hooks=...)`` (see the ``hooks`` argument, below), or subclassed for more specialised behaviour. Parameters ---------- size: int (optional) Nominal quantity for the value that corresponds to a complete transfer, e.g., total number of tiles or total number of bytes value: int (0) Starting internal counter value hooks: dict or None A dict of named functions to be called on each update. The signature of these must be ``f(size, value, **kwargs)`` """ def __init__(self, size=None, value=0, hooks=None, **kwargs): self.size = size self.value = value self.hooks = hooks or {} self.kw = kwargs def set_size(self, size): """ Set the internal maximum size attribute Usually called if not initially set at instantiation. Note that this triggers a ``call()``. Parameters ---------- size: int """ self.size = size self.call() def absolute_update(self, value): """ Set the internal value state Triggers ``call()`` Parameters ---------- value: int """ self.value = value self.call() def relative_update(self, inc=1): """ Delta increment the internal counter Triggers ``call()`` Parameters ---------- inc: int """ self.value += inc self.call() def call(self, hook_name=None, **kwargs): """ Execute hook(s) with current state Each function is passed the internal size and current value Parameters ---------- hook_name: str or None If given, execute on this hook kwargs: passed on to (all) hook(s) """ if not self.hooks: return kw = self.kw.copy() kw.update(kwargs) if hook_name: if hook_name not in self.hooks: return return self.hooks[hook_name](self.size, self.value, **kw) for hook in self.hooks.values() or []: hook(self.size, self.value, **kw) def wrap(self, iterable): """ Wrap an iterable to call ``relative_update`` on each iterations Parameters ---------- iterable: Iterable The iterable that is being wrapped """ for item in iterable: self.relative_update() yield item def branch(self, path_1, path_2, kwargs): """ Set callbacks for child transfers If this callback is operating at a higher level, e.g., put, which may trigger transfers that can also be monitored. The passed kwargs are to be *mutated* to add ``callback=``, if this class supports branching to children. Parameters ---------- path_1: str Child's source path path_2: str Child's destination path kwargs: dict arguments passed to child method, e.g., put_file. Returns ------- """ return None def no_op(self, *_, **__): pass def __getattr__(self, item): """ If undefined methods are called on this class, nothing happens """ return self.no_op @classmethod def as_callback(cls, maybe_callback=None): """Transform callback=... into Callback instance For the special value of ``None``, return the global instance of ``NoOpCallback``. This is an alternative to including ``callback=_DEFAULT_CALLBACK`` directly in a method signature. """ if maybe_callback is None: return _DEFAULT_CALLBACK return maybe_callback class NoOpCallback(Callback): """ This implementation of Callback does exactly nothing """ def call(self, *args, **kwargs): return None class DotPrinterCallback(Callback): """ Simple example Callback implementation Almost identical to Callback with a hook that prints a char; here we demonstrate how the outer layer may print "#" and the inner layer "." """ def __init__(self, chr_to_print="#", **kwargs): self.chr = chr_to_print super().__init__(**kwargs) def branch(self, path_1, path_2, kwargs): """Mutate kwargs to add new instance with different print char""" kwargs["callback"] = DotPrinterCallback(".") def call(self, **kwargs): """Just outputs a character""" print(self.chr, end="") class TqdmCallback(Callback): """ A callback to display a progress bar using tqdm Parameters ---------- tqdm_kwargs : dict, (optional) Any argument accepted by the tqdm constructor. See the `tqdm doc `_. Will be forwarded to tqdm. Examples -------- >>> import fsspec >>> from fsspec.callbacks import TqdmCallback >>> fs = fsspec.filesystem("memory") >>> path2distant_data = "/your-path" >>> fs.upload( ".", path2distant_data, recursive=True, callback=TqdmCallback(), ) You can forward args to tqdm using the ``tqdm_kwargs`` parameter. >>> fs.upload( ".", path2distant_data, recursive=True, callback=TqdmCallback(tqdm_kwargs={"desc": "Your tqdm description"}), ) """ def __init__(self, tqdm_kwargs=None, *args, **kwargs): try: import tqdm self._tqdm = tqdm except ImportError as exce: raise ImportError( "Using TqdmCallback requires tqdm to be installed" ) from exce self._tqdm_kwargs = tqdm_kwargs or {} super().__init__(*args, **kwargs) def set_size(self, size): self.tqdm = self._tqdm.tqdm(total=size, **self._tqdm_kwargs) def relative_update(self, inc=1): self.tqdm.update(inc) def __del__(self): self.tqdm.close() self.tqdm = None _DEFAULT_CALLBACK = NoOpCallback()