attrmap.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. #Copyright ReportLab Europe Ltd. 2000-2017
  2. #see license.txt for license details
  3. #history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/lib/attrmap.py
  4. __version__='3.3.0'
  5. __doc__='''Framework for objects whose assignments are checked. Used by graphics.
  6. We developed reportlab/graphics prior to Python 2 and metaclasses. For the
  7. graphics, we wanted to be able to declare the attributes of a class, check
  8. them on assignment, and convert from string arguments. Examples of
  9. attrmap-based objects can be found in reportlab/graphics/shapes. It lets
  10. us defined structures like the one below, which are seen more modern form in
  11. Django models and other frameworks.
  12. We'll probably replace this one day soon, hopefully with no impact on client
  13. code.
  14. class Rect(SolidShape):
  15. """Rectangle, possibly with rounded corners."""
  16. _attrMap = AttrMap(BASE=SolidShape,
  17. x = AttrMapValue(isNumber),
  18. y = AttrMapValue(isNumber),
  19. width = AttrMapValue(isNumber),
  20. height = AttrMapValue(isNumber),
  21. rx = AttrMapValue(isNumber),
  22. ry = AttrMapValue(isNumber),
  23. )
  24. '''
  25. from reportlab.lib.validators import isAnything, DerivedValue
  26. from reportlab.lib.utils import isSeq
  27. from reportlab import rl_config
  28. class CallableValue:
  29. '''a class to allow callable initial values'''
  30. def __init__(self,func,*args,**kw):
  31. #assert iscallable(func)
  32. self.func = func
  33. self.args = args
  34. self.kw = kw
  35. def __call__(self):
  36. return self.func(*self.args,**self.kw)
  37. class AttrMapValue:
  38. '''Simple multi-value holder for attribute maps'''
  39. def __init__(self,validate=None,desc=None,initial=None, advancedUsage=0, **kw):
  40. self.validate = validate or isAnything
  41. self.desc = desc
  42. self._initial = initial
  43. self._advancedUsage = advancedUsage
  44. for k,v in kw.items():
  45. setattr(self,k,v)
  46. def __getattr__(self,name):
  47. #hack to allow callable initial values
  48. if name=='initial':
  49. if isinstance(self._initial,CallableValue): return self._initial()
  50. return self._initial
  51. elif name=='hidden':
  52. return 0
  53. raise AttributeError(name)
  54. def __repr__(self):
  55. return 'AttrMapValue(%s)' % ', '.join(['%s=%r' % i for i in self.__dict__.items()])
  56. class AttrMap(dict):
  57. def __init__(self,BASE=None,UNWANTED=[],**kw):
  58. data = {}
  59. if BASE:
  60. if isinstance(BASE,AttrMap):
  61. data = BASE
  62. else:
  63. if not isSeq(BASE): BASE = (BASE,)
  64. for B in BASE:
  65. am = getattr(B,'_attrMap',self)
  66. if am is not self:
  67. if am: data.update(am)
  68. else:
  69. raise ValueError('BASE=%s has wrong kind of value' % ascii(B))
  70. dict.__init__(self,data)
  71. self.remove(UNWANTED)
  72. self.update(kw)
  73. def remove(self,unwanted):
  74. for k in unwanted:
  75. try:
  76. del self[k]
  77. except KeyError:
  78. pass
  79. def clone(self,UNWANTED=[],**kw):
  80. c = AttrMap(BASE=self,UNWANTED=UNWANTED)
  81. c.update(kw)
  82. return c
  83. def validateSetattr(obj,name,value):
  84. '''validate setattr(obj,name,value)'''
  85. if rl_config.shapeChecking:
  86. map = obj._attrMap
  87. if map and name[0]!= '_':
  88. #we always allow the inherited values; they cannot
  89. #be checked until draw time.
  90. if isinstance(value, DerivedValue):
  91. #let it through
  92. pass
  93. else:
  94. try:
  95. validate = map[name].validate
  96. if not validate(value):
  97. raise AttributeError("Illegal assignment of '%s' to '%s' in class %s" % (value, name, obj.__class__.__name__))
  98. except KeyError:
  99. raise AttributeError("Illegal attribute '%s' in class %s" % (name, obj.__class__.__name__))
  100. prop = getattr(obj.__class__,name,None)
  101. if isinstance(prop,property):
  102. try:
  103. prop.__set__(obj,value)
  104. except AttributeError:
  105. pass
  106. else:
  107. obj.__dict__[name] = value
  108. def _privateAttrMap(obj,ret=0):
  109. '''clone obj._attrMap if required'''
  110. A = obj._attrMap
  111. oA = getattr(obj.__class__,'_attrMap',None)
  112. if ret:
  113. if oA is A:
  114. return A.clone(), oA
  115. else:
  116. return A, None
  117. else:
  118. if oA is A:
  119. obj._attrMap = A.clone()
  120. def _findObjectAndAttr(src, P):
  121. '''Locate the object src.P for P a string, return parent and name of attribute
  122. '''
  123. P = P.split('.')
  124. if len(P) == 0:
  125. return None, None
  126. else:
  127. for p in P[0:-1]:
  128. src = getattr(src, p)
  129. return src, P[-1]
  130. def hook__setattr__(obj):
  131. if not hasattr(obj,'__attrproxy__'):
  132. C = obj.__class__
  133. import new
  134. obj.__class__=new.classobj(C.__name__,(C,)+C.__bases__,
  135. {'__attrproxy__':[],
  136. '__setattr__':lambda self,k,v,osa=getattr(obj,'__setattr__',None),hook=hook: hook(self,k,v,osa)})
  137. def addProxyAttribute(src,name,validate=None,desc=None,initial=None,dst=None):
  138. '''
  139. Add a proxy attribute 'name' to src with targets dst
  140. '''
  141. #sanity
  142. assert hasattr(src,'_attrMap'), 'src object has no _attrMap'
  143. A, oA = _privateAttrMap(src,1)
  144. if not isSeq(dst): dst = dst,
  145. D = []
  146. DV = []
  147. for d in dst:
  148. if isSeq(d):
  149. d, e = d[0], d[1:]
  150. obj, attr = _findObjectAndAttr(src,d)
  151. if obj:
  152. dA = getattr(obj,'_attrMap',None)