1class Callback: 2 """ 3 Base class and interface for callback mechanism 4 5 This class can be used directly for monitoring file transfers by 6 providing ``callback=Callback(hooks=...)`` (see the ``hooks`` argument, 7 below), or subclassed for more specialised behaviour. 8 9 Parameters 10 ---------- 11 size: int (optional) 12 Nominal quantity for the value that corresponds to a complete 13 transfer, e.g., total number of tiles or total number of 14 bytes 15 value: int (0) 16 Starting internal counter value 17 hooks: dict or None 18 A dict of named functions to be called on each update. The signature 19 of these must be ``f(size, value, **kwargs)`` 20 """ 21 22 def __init__(self, size=None, value=0, hooks=None, **kwargs): 23 self.size = size 24 self.value = value 25 self.hooks = hooks or {} 26 self.kw = kwargs 27 28 def set_size(self, size): 29 """ 30 Set the internal maximum size attribute 31 32 Usually called if not initially set at instantiation. Note that this 33 triggers a ``call()``. 34 35 Parameters 36 ---------- 37 size: int 38 """ 39 self.size = size 40 self.call() 41 42 def absolute_update(self, value): 43 """ 44 Set the internal value state 45 46 Triggers ``call()`` 47 48 Parameters 49 ---------- 50 value: int 51 """ 52 self.value = value 53 self.call() 54 55 def relative_update(self, inc=1): 56 """ 57 Delta increment the internal cuonter 58 59 Triggers ``call()`` 60 61 Parameters 62 ---------- 63 inc: int 64 """ 65 self.value += inc 66 self.call() 67 68 def call(self, hook_name=None, **kwargs): 69 """ 70 Execute hook(s) with current state 71 72 Each function is passed the internal size and current value 73 74 Parameters 75 ---------- 76 hook_name: str or None 77 If given, execute on this hook 78 kwargs: passed on to (all) hook(s) 79 """ 80 if not self.hooks: 81 return 82 kw = self.kw.copy() 83 kw.update(kwargs) 84 if hook_name: 85 if hook_name not in self.hooks: 86 return 87 return self.hooks[hook_name](self.size, self.value, **kw) 88 for hook in self.hooks.values() or []: 89 hook(self.size, self.value, **kw) 90 91 def wrap(self, iterable): 92 """ 93 Wrap an iterable to call ``relative_update`` on each iterations 94 95 Parameters 96 ---------- 97 iterable: Iterable 98 The iterable that is being wrapped 99 """ 100 for item in iterable: 101 self.relative_update() 102 yield item 103 104 def branch(self, path_1, path_2, kwargs): 105 """ 106 Set callbacks for child transfers 107 108 If this callback is operating at a higher level, e.g., put, which may 109 trigger transfers that can also be monitored. The passed kwargs are 110 to be *mutated* to add ``callback=``, if this class supports branching 111 to children. 112 113 Parameters 114 ---------- 115 path_1: str 116 Child's source path 117 path_2: str 118 Child's destination path 119 kwargs: dict 120 arguments passed to child method, e.g., put_file. 121 122 Returns 123 ------- 124 125 """ 126 return None 127 128 def no_op(self, *_, **__): 129 pass 130 131 def __getattr__(self, item): 132 """ 133 If undefined methods are called on this class, nothing happens 134 """ 135 return self.no_op 136 137 @classmethod 138 def as_callback(cls, maybe_callback=None): 139 """Transform callback=... into Callback instance 140 141 For the special value of ``None``, return the global instance of 142 ``NoOpCallback``. This is an alternative to including 143 ``callback=_DEFAULT_CALLBACK`` directly in a method signature. 144 """ 145 if maybe_callback is None: 146 return _DEFAULT_CALLBACK 147 return maybe_callback 148 149 150class NoOpCallback(Callback): 151 """ 152 This implementation of Callback does exactly nothing 153 """ 154 155 def call(self, *args, **kwargs): 156 return None 157 158 159class DotPrinterCallback(Callback): 160 """ 161 Simple example Callback implementation 162 163 Almost identical to Callback with a hook that prints a char; here we 164 demonstrate how the outer layer may print "#" and the inner layer "." 165 """ 166 167 def __init__(self, chr_to_print="#", **kwargs): 168 self.chr = chr_to_print 169 super().__init__(**kwargs) 170 171 def branch(self, path_1, path_2, kwargs): 172 """Mutate kwargs to add new instance with different print char""" 173 kwargs["callback"] = DotPrinterCallback(".") 174 175 def call(self, **kwargs): 176 """Just outputs a character""" 177 print(self.chr, end="") 178 179 180_DEFAULT_CALLBACK = NoOpCallback() 181