#!/usr/bin/python3 """ ===================================================================================== attributeproxy.py: provide defaults and optional warnings for missings attributes in a wrapped object; used by frigcal [1.2] for configs module settings; separate module, as it may be a useful elsewhere; see end of this file for expected usage patterns; ===================================================================================== """ class AttributeProxy: """ provide a default for any object's attribute that is missing; catches and delegates all attribute fetches/sets to wrapped object; """ __trace = False # becomes _AttributeProxy__trace def __init__(self, wrappedobject, defaultvalue=None, warnof=False): """ skip my own __setattr__ here """ object.__setattr__(self, '_wrappedobject', wrappedobject) object.__setattr__(self, '_defaultvalue', defaultvalue) object.__setattr__(self, '_warnof', warnof) def __getattr__(self, name): """ on self.name for undefined name doesn't recur for self._X or __X: defined """ if self.__trace: print('getattr', name) if self._warnof and not hasattr(self._wrappedobject, name): self.__warning(name) return getattr(self._wrappedobject, name, self._defaultvalue) def __setattr__(self, name, value): """ on self.name=value for all names pass all other sets to wrapped object """ if self.__trace: print('setattr', name, value) setattr(self._wrappedobject, name, value) def __warning(self, name): """ issue warning no missing attribute if enabled; include type and object type names if present [1.2.3] """ wrapped = self._wrappedobject print('Warning: %s %s missing attribute "%s"' % (getattr(type(wrapped), '__name__', 'unknown'), getattr(wrapped, '__name__', 'object'), name), end='; ') print('using default: %r' % self._defaultvalue) if __name__ == '__main__': # self-test if run: no output before 'Finished' means tests passed import sys, io sys.stdout = io.StringIO() # capture prints class C: a=1 i = C() pC = AttributeProxy(C, warnof=True) # wrap class pi = AttributeProxy(i, 'dflt', True) # wrap instance import attributeproxy as m # import, not run pm = AttributeProxy(m, warnof=True) # wrap module assert pC.a == 1 and pC.x == None assert pi.a == 1 and pi.x == 'dflt' assert pm.x == None assert sys.stdout.getvalue() == ( "Warning: type C missing attribute \"x\"; using default: None\n" "Warning: C object missing attribute \"x\"; using default: 'dflt'\n" "Warning: module attributeproxy missing attribute \"x\"; using default: None\n") sys.stderr.write('Finished\n') if sys.platform.startswith('win'): sys.stderr.write('Press Enter\n'); input() # keep open if clicked """ ===================================================================================== Usage patterns: # module norm >>> from attributeproxy import AttributeProxy >>> import frigcal_configs as m >>> m.clickmode 'mouse' >>> m.x Traceback (most recent call last): File "", line 1, in AttributeError: 'module' object has no attribute 'x' # module wrapped >>> p = AttributeProxy(m) # default None, no warning >>> p.clickmode 'mouse' >>> p.x >>> p = AttributeProxy(m, defaultvalue=True) >>> p.x True >>> p = AttributeProxy(m, defaultvalue='missing', warnof=True) >>> p.x Warning: module frigcal_configs missing attribute "x"; using default: 'missing' 'missing' # built-ins >>> q = AttributeProxy([1], 99, True) >>> q.sort >>> q.nonesuch Warning: list object missing attribute "nonesuch"; using default: 99 99 >>> q = AttributeProxy(99, 1, True) >>> q.nonesuch Warning: int object missing attribute "nonesuch"; using default: 1 1 # class, instance >>> class C: a=1 ... >>> q = AttributeProxy(C, 'absent', True) >>> q.a 1 >>> q.x Warning: type C missing attribute "x"; using default: 'absent' 'absent' >>> q = AttributeProxy(C(), 'absent', True) >>> q.a 1 >>> q.x Warning: C object missing attribute "x"; using default: 'absent' 'absent' ===================================================================================== """