Source code for visidata.extensible

from functools import wraps, lru_cache


__all__ = ['Extensible', 'cache', 'drawcache', 'drawcache_property']

[docs]class Extensible: _cache_clearers = [] # list of func() to call in clearCaches() @classmethod def init(cls, membername, initfunc=lambda: None, copy=False): 'Prepend equivalent of ``self.<membername> = initfunc()`` to ``<cls>.__init__``. If *copy* is True, <membername> will be copied when object is copied.' def thisclass_hasattr(cls, k): return getattr(cls, k, None) is not getattr(cls.__bases__[0], k, None) # must check hasattr first or else this might be parent's __init__ oldinit = thisclass_hasattr(cls, '__init__') and getattr(cls, '__init__') def newinit(self, *args, **kwargs): if not hasattr(self, membername): # can be overridden by a subclass setattr(self, membername, initfunc()) if oldinit: oldinit(self, *args, **kwargs) else: super(cls, self).__init__(*args, **kwargs) cls.__init__ = wraps(oldinit)(newinit) if oldinit else newinit oldcopy = thisclass_hasattr(cls, '__copy__') and getattr(cls, '__copy__') def newcopy(self, *args, **kwargs): if oldcopy: ret = oldcopy(self, *args, **kwargs) else: ret = super(cls, self).__copy__(*args, **kwargs) setattr(ret, membername, getattr(self, membername) if copy and hasattr(self, membername) else initfunc()) return ret cls.__copy__ = wraps(oldcopy)(newcopy) if oldcopy else newcopy @classmethod def superclasses(cls): yield cls yield from cls.__bases__ for b in cls.__bases__: if hasattr(b, 'superclasses'): yield from b.superclasses() @classmethod def api(cls, func): oldfunc = getattr(cls, func.__name__, None) if oldfunc: func = wraps(oldfunc)(func) from visidata import vd func.importingModule = vd.importingModule setattr(cls, func.__name__, func) return func @classmethod def before(cls, beforefunc): funcname = beforefunc.__name__ oldfunc = getattr(cls, funcname, None) if not oldfunc: setattr(cls, funcname, beforefunc) @wraps(oldfunc) def wrappedfunc(*args, **kwargs): beforefunc(*args, **kwargs) return oldfunc(*args, **kwargs) setattr(cls, funcname, wrappedfunc) return wrappedfunc @classmethod def after(cls, afterfunc): funcname = afterfunc.__name__ oldfunc = getattr(cls, funcname, None) if not oldfunc: setattr(cls, funcname, afterfunc) @wraps(oldfunc) def wrappedfunc(*args, **kwargs): r = oldfunc(*args, **kwargs) afterfunc(*args, **kwargs) return r setattr(cls, funcname, wrappedfunc) return wrappedfunc @classmethod def class_api(cls, func): '''`@Class.class_api` works much like `@Class.api`, but for class methods. This method is used internally but may not be all that useful for plugin and module authors. Note that `@classmethod` must still be provided, and **the order of multiple decorators is crucial**, in that `@<class>.class_api` must come before `@classmethod`: :: @Sheet.class_api @classmethod def addCommand(cls, ...): ''' name = func.__get__(None, dict).__func__.__name__ oldfunc = getattr(cls, name, None) if oldfunc: func = wraps(oldfunc)(func) setattr(cls, name, func) return func @classmethod def property(cls, func): @property @wraps(func) def dofunc(self): return func(self) setattr(cls, func.__name__, dofunc) return dofunc @classmethod def lazy_property(cls, func): 'Return ``func()`` on first access and cache result; return cached result thereafter.' name = '_' + func.__name__ cls.init(name, lambda: None, copy=False) @property @wraps(func) def get_if_not(self): if getattr(self, name, None) is None: setattr(self, name, func(self)) return getattr(self, name) setattr(cls, func.__name__, get_if_not) return get_if_not @classmethod def cached_property(cls, func): 'Return ``func()`` on first access, and cache result; return cached result until ``clearCaches()``.' @property @wraps(func) @lru_cache(maxsize=None) def get_if_not(self): return func(self) setattr(cls, func.__name__, get_if_not) Extensible._cache_clearers.append(get_if_not.fget.cache_clear) return get_if_not @classmethod def clear_all_caches(cls): for func in Extensible._cache_clearers: func()
def cache(func): 'Return func(...) on first access, and cache result; return cached result until clearCaches().' @wraps(func) @lru_cache(maxsize=None) def call_if_not(self, *args, **kwargs): return func(self, *args, **kwargs) Extensible._cache_clearers.append(call_if_not.cache_clear) return call_if_not # @drawcache is vd alias for @cache, since vd clears it every frame drawcache = cache def drawcache_property(func): return property(drawcache(func))