14 from __future__
import absolute_import
21 from inspect
import isclass
25 VERBOSE, DEBUG, INFO, WARNING, ERROR, FATAL
31 InvertNode, ControlFlowLeaf,
32 ControlFlowNode, CFTrue, CFFalse)
36 'Configurable',
'ConfigurableAlgorithm',
'ConfigurableAlgTool',
37 'ConfigurableAuditor',
'ConfigurableService',
'ConfigurableUser',
38 'VERBOSE',
'DEBUG',
'INFO',
'WARNING',
'ERROR',
'FATAL',
39 'appendPostConfigAction',
'removePostConfigAction'
44 log = logging.getLogger(
'Configurable')
49 Expand environment variables "data".
50 Data can be string, list, tuple and dictionary. For collection, all the
51 contained strings will be manipulated (recursively).
56 return os.path.expandvars(data)
57 elif typ
in [list, tuple]:
72 Error occurred in the configuration process.
80 class PropertyReference(object):
85 return "@%s" % self.
name
90 refname, refprop = self.
name.rsplit(
'.', 1)
91 if refname
in Configurable.allConfigurables:
92 conf = Configurable.allConfigurables[refname]
93 retval = getattr(conf, refprop)
94 if hasattr(retval,
"getFullName"):
95 retval = retval.getFullName()
98 "name '%s' not found resolving '%s'" % (refname, self))
102 """This function allow transparent integration with
103 Configurable.getValuedProperties.
110 except AttributeError:
120 """Base class for Gaudi components that implement the IProperty interface.
121 Provides most of the boilerplate code, but the actual useful classes
122 are its derived ConfigurableAlgorithm, ConfigurableService, and
123 ConfigurableAlgTool."""
129 propertyNoValue =
'<no value>'
131 printHeaderWidth = 100
146 configurableServices = {
150 _configurationLocked =
False
153 """To Gaudi, any object with the same type/name is the same object. Hence,
154 this is mimicked in the configuration: instantiating a new Configurable
155 of a type with the same name will return the same instance."""
158 func_code = six.get_function_code(cls.
__init__)
159 func_defaults = six.get_function_defaults(cls.
__init__)
163 name = kwargs[
'name']
164 elif 'name' in func_code.co_varnames:
166 index = list(func_code.co_varnames).index(
'name')
169 name = args[index - 1]
172 name = func_defaults[index - (len(args) + 1)]
177 except (IndexError, TypeError):
178 raise TypeError(
'no "name" argument while instantiating "%s"' %
183 if hasattr(cls,
'DefaultedName'):
184 name = cls.DefaultedName
187 elif not name
or type(name) != str:
190 'could not retrieve name from %s.__init__ arguments' %
195 if issubclass(cls, ConfigurableAlgTool)
and '.' not in name:
196 name =
'ToolSvc.' + name
211 for n, v
in kwargs.items():
215 conf, ConfigurableUser):
218 setattr(conf,
"_enabled",
True)
222 spos = name.find(
'/')
225 ti_name =
"%s/%s" % (name, name)
233 if i_name == name[spos + 1:]
and i_name
in cls.
configurables:
240 (spos > 0
and i_name == name[spos + 1:]
243 if conf.__class__
is ConfigurableGeneric:
247 newconf = object.__new__(cls)
248 cls.
__init__(newconf, *args, **kwargs)
253 for n
in newconf.__slots__:
255 for n
in conf._properties:
256 if names[n.lower()] != n:
258 "Option '%s' was used for %s, but the correct spelling is '%s'"
259 % (n, name, names[n.lower()]))
260 setattr(newconf, names[n.lower()], getattr(conf, n))
261 for n, v
in kwargs.items():
262 setattr(newconf, n, v)
269 'attempt to redefine type of "%s" (was: %s, new: %s)%s',
270 name, conf.__class__.__name__, cls.__name__,
276 for n, v
in kwargs.items():
281 conf = object.__new__(cls)
287 for base
in cls.__bases__:
288 if base.__name__ ==
'ConfigurableService':
300 klass = self.__class__
303 if klass == Configurable:
305 "%s is an ABC and can not be instantiated" % str(Configurable))
316 for meth, nArgs
in meths.items():
318 f = six.get_unbound_function(getattr(klass, meth))
319 except AttributeError:
320 raise NotImplementedError(
321 "%s is missing in class %s" % (meth, str(klass)))
324 nargcount = six.get_function_code(f).co_argcount
325 fdefaults = six.get_function_defaults(f)
326 ndefaults = fdefaults
and len(fdefaults)
or 0
327 if not nargcount - ndefaults <= nArgs <= nargcount:
328 raise TypeError(
"%s.%s requires exactly %d arguments" %
329 (klass, meth, nArgs))
337 if hasattr(self.__class__,
'DefaultedName'):
338 self.
_name = self.__class__.DefaultedName
361 dict[name] = proxy.__get__(self)
362 except AttributeError:
365 dict[
'_Configurable__children'] = self.
__children
366 dict[
'_Configurable__tools'] = self.
__tools
367 dict[
'_name'] = self.
_name
371 return (self.
_name, )
375 from contextlib
import contextmanager
386 for n, v
in dict.items():
398 newconf = object.__new__(self.__class__)
403 proxy.__set__(newconf, proxy.__get__(self))
404 except AttributeError:
414 if not type(configs)
in (list, tuple):
415 configs = (configs, )
421 if not isinstance(cfg, Configurable):
422 raise TypeError(
"'%s' is not a Configurable" % str(cfg))
427 ccjo = cc.getJobOptName()
429 if c.getJobOptName() == ccjo:
430 log.error(
'attempt to add a duplicate ... dupe ignored%s',
438 descr.__set__(self, cc)
440 setattr(self, cc.getName(), cc)
441 except AttributeError:
452 if isinstance(self.
_properties[attr].__get__(self), DataHandle):
456 if c.getName() == attr:
459 raise AttributeError(
460 "'%s' object has no attribute '%s'" % (self.__class__, attr))
465 "%s: Configuration cannot be modified after the ApplicationMgr has been started."
469 except AttributeError:
470 raise AttributeError(
471 "Configurable '%s' does not have property '%s'." %
472 (self.__class__.__name__, name))
479 prop.__delete__(self)
480 prop.__set__(self, prop.default)
490 if c.getName() == attr:
495 del self.__dict__[attr]
496 except (AttributeError, KeyError):
503 __nonzero__ = __bool__
506 if type(items) != list
and type(items) != tuple:
516 return copy.deepcopy(child)
530 if hasattr(cc,
'setParent')
and parent:
533 except RuntimeError
as e:
535 log.error(str(e) +
'%s', error_explanation)
536 ccbd = cc.configurables[cc.getJobOptName()]
540 if cc
in proxy.history:
541 proxy.__set__(ccbd, proxy.__get__(cc))
555 "children() is deprecated, use getChildren() instead for consistency"
558 "getChildren() returns a copy; to add a child, use 'parent += child'%s",
563 """Get all (private) configurable children, both explicit ones (added with +=)
564 and the ones in the private GaudiHandle properties"""
569 c = proxy.__get__(self)
570 except AttributeError:
573 if isinstance(c, Configurable)
and not c.isPublic():
575 elif isinstance(c, GaudiHandle):
577 conf = c.configurable
578 except AttributeError:
581 if not conf.isPublic():
583 elif isinstance(c, GaudiHandleArray):
587 if isinstance(ci, Configurable):
591 conf = ci.configurable
592 except AttributeError:
604 elems.append(c.getFullName())
609 if not hasattr(self,
'_initok')
or not self.
_initok:
611 raise TypeError(
"Configurable.__init__ not called in %s override" %
612 self.__class__.__name__)
626 handle = self.getHandle()
628 log.debug(
'no handle for %s: not transporting properties',
636 setattr(handle, name, getattr(self, name))
645 props[name] = proxy.__get__(self)
646 except AttributeError:
647 props[name] = Configurable.propertyNoValue
652 """Get all properties with their description string as { name : (value, desc) }."""
656 props[name] = (proxy.__get__(self), proxy.__doc__)
657 except AttributeError:
658 props[name] = (Configurable.propertyNoValue, proxy.__doc__)
665 value = proxy.__get__(self)
666 if hasattr(value,
'getFullName'):
667 value = value.getFullName()
668 elif type(value)
in [list, tuple]:
671 if hasattr(i,
'getFullName'):
672 new_value.append(i.getFullName())
675 value =
type(value)(new_value)
676 elif type(value)
is dict:
679 if hasattr(value[i],
'getFullName'):
682 new_value[i] = value[i]
701 for k, v
in cls._properties.
items():
702 if not k
in c.__dict__
and hasattr(v,
'default'):
703 c.__dict__[k] = v.default
716 if name
in c.__dict__:
717 return c.__dict__[name]
721 v = cls._properties[name]
722 if hasattr(v,
'default'):
730 """Returns the value of the given property.
732 if hasattr(self, name):
733 return getattr(self, name)
738 """Set the value of a given property
740 return setattr(self, name, value)
743 """Tell if the property 'name' has been set or not.
745 Because of a problem with list and dictionary properties, in those cases
746 if the value is equal to the default, the property is considered as not
749 if not hasattr(self, name):
752 if isinstance(default, (list, dict, DataHandle)):
753 value = getattr(self, name)
754 return value != default
775 "jobOptName() is deprecated, use getJobOptName() instead for consistency%s",
790 if log.isEnabledFor(logging.DEBUG):
798 def clone(self, name=None, **kwargs):
800 if hasattr(self,
'DefaultedName'):
801 name = self.DefaultedName
803 name = self.getType()
805 newconf = Configurable.__new__(self.__class__, name)
806 self.__class__.
__init__(newconf, name)
808 for proxy
in self._properties.values():
810 value = proxy.__get__(self)
811 if type(value)
in [str, list, dict, tuple]:
813 value =
type(value)(value)
814 proxy.__set__(newconf, value)
815 except AttributeError:
818 for c
in self.__children:
821 for n, t
in self.__tools.
items():
822 newconf.addTool(t, n)
824 for name, value
in kwargs.items():
825 setattr(newconf, name, value)
831 dot = fullname.find(
'.')
833 parentname = fullname[:dot]
834 longname = fullname[dot + 1:]
838 dot = longname.find(
'.')
840 name = longname[:dot]
843 return parentname, name, longname
846 if isclass(tool)
and issubclass(tool, ConfigurableAlgTool):
850 elif isinstance(tool, ConfigurableAlgTool):
852 name = tool.splitName()[1]
853 priv_tool = tool.clone(self.
getName() +
'.' + name)
856 classname = tool.__name__
858 classname =
type(tool).__name__
860 "addTool requires AlgTool configurable. Got %s type" %
865 setattr(self, name, self.
__tools[name])
880 handle = __main__.Service(svc)
885 if hasattr(self,
'configure' + svc):
886 eval(
'self.configure' + svc +
'( handle )')
889 dlls = self.getDlls()
892 elif type(dlls) == types.StringType:
895 from __main__
import theApp
896 dlls = filter(
lambda d: d
not in theApp.Dlls, dlls)
909 preLen = Configurable.printHeaderPre
910 postLen = Configurable.printHeaderWidth - \
911 preLen - 3 - len(title)
912 postLen =
max(preLen, postLen)
913 return indentStr +
'/%s %s %s' % (preLen *
'*', title, postLen *
'*')
917 preLen = Configurable.printHeaderPre
918 postLen = Configurable.printHeaderWidth - \
919 preLen - 12 - len(title)
920 postLen =
max(preLen, postLen)
921 return indentStr +
'\\%s (End of %s) %s' % (preLen *
'-', title,
925 return '{0}({1!r})'.
format(self.__class__.__name__, self.
name())
927 def __str__(self, indent=0, headerLastIndentUnit=indentUnit):
929 indentStr = indent * Configurable.indentUnit
934 headerIndent = (indent - 1) * \
935 Configurable.indentUnit + headerLastIndentUnit
938 rep = Configurable._printHeader(headerIndent, title)
944 rep += indentStr +
'|-<no properties>' + os.linesep
948 for p
in props.keys():
949 nameWidth =
max(nameWidth, len(p))
950 for p, v
in props.items():
952 prefix = indentStr +
'|-%-*s' % (nameWidth, p)
954 if log.isEnabledFor(logging.DEBUG):
955 if v != Configurable.propertyNoValue:
956 address =
' @%11s' %
hex(id(v))
961 default = defs.get(p)
962 if v == Configurable.propertyNoValue:
964 strVal = repr(default)
968 if hasattr(v,
"getGaudiHandle"):
969 vv = v.getGaudiHandle()
973 (GaudiHandle, GaudiHandleArray, DataHandle)):
976 if hasattr(default,
"toStringProperty"):
977 strDef = repr(default.toStringProperty())
979 strDef = repr(default)
980 if strDef == repr(vv.toStringProperty()):
984 strDef = repr(default)
986 line = prefix +
' = ' + strVal
988 if strDef
is not None:
990 if len(line) + len(strDef) > Configurable.printHeaderWidth:
991 line += os.linesep + indentStr +
'| ' + \
992 (len(prefix) - len(indentStr) - 3) *
' '
993 line +=
' (default: %s)' % (strDef, )
995 rep += line + os.linesep
1007 rep += cfg.__str__(indent + 1,
'|=') + os.linesep
1010 rep += Configurable._printFooter(indentStr, title)
1015 Return True is the instance can be "applied".
1016 Always False for plain Configurable instances
1017 (i.e. not ConfigurableUser).
1033 object.__setattr__(obj, self.
__name__, value)
1040 Configurable.__init__(self, name)
1049 return 'GenericComponent'
1060 super(ConfigurableGeneric, self).
__setattr__(name, value)
1064 if isinstance(value, Configurable):
1065 self.__dict__[name] = value
1069 if not name
in self._properties:
1071 self._properties[name].__set__(self, value)
1085 'AuditAlgorithms': 0,
1086 'AuditInitialize': 0,
1087 'AuditReinitialize': 0,
1093 super(ConfigurableAlgorithm, self).
__init__(name)
1114 elif rhs
is CFFalse:
1140 return (repr(self) == repr(other))
1143 """Return a unique identifier for this object.
1145 As we use the `repr` of this object to check for equality, we use it
1146 here to define uniqueness.
1149 return hash((repr(self), ))
1156 'AuditInitialize': 0,
1167 return iService(self.
_name)
1186 'AuditInitialize': 0,
1191 super(ConfigurableAlgTool, self).
__init__(name)
1192 if '.' not in self.
_name:
1196 name = name[name.find(
'/') + 1:]
1218 return pop + Configurable.getPrintTitle(self)
1227 if isinstance(c, ConfigurableAlgTool):
1228 c.setParent(parentName)
1232 name = name[name.rfind(
'.') + 1:]
1253 return parent ==
'ToolSvc'
1264 name = name[name.rfind(
'.') + 1:]
1265 return str(self.
getType() +
'/' + name)
1272 __slots__ = {
'_jobOptName': 0,
'OutputLevel': 0,
'Enable': 1}
1275 super(ConfigurableAuditor, self).
__init__(name)
1277 name = name[name.find(
'/') + 1:]
1299 "__used_instances__": [],
1311 __used_configurables__ = []
1314 __queried_configurables__ = []
1316 def __init__(self, name=Configurable.DefaultName, _enabled=True, **kwargs):
1317 super(ConfigurableUser, self).
__init__(name)
1318 for n, v
in kwargs.items():
1336 if type(used)
is tuple:
1337 used, used_name = used
1341 if type(used)
is str:
1342 used_class = confDbGetConfigurable(used)
1347 inst = used_class(name=used_name, _enabled=
False)
1348 except AttributeError:
1352 inst = used_class(name=used_name)
1356 if type(queried)
is str:
1357 queried = confDbGetConfigurable(used)
1358 inst = queried(_enabled=
False)
1359 except AttributeError:
1365 Declare that we are going to modify the Configurable 'other' in our
1366 __apply_configuration__.
1369 if hasattr(other,
"__users__"):
1370 other.__users__.append(self)
1374 Declare that we are going to retrieve property values from the
1375 ConfigurableUser 'other' in our __apply_configuration__.
1377 if not isinstance(other, ConfigurableUser):
1379 "'%s': Cannot make passive use of '%s', it is not a ConfigurableUser"
1380 % (self.
name(), other.name()))
1381 other.__addActiveUseOf(self)
1395 Remove this ConfigurableUser instance from the users list of the used
1401 used.__users__.remove(self)
1405 Propagate the property 'name' (if set) to other configurables (if possible).
1408 propagate to all the entries in __used_configurables__
1409 a configurable instance:
1410 propagate only to it
1411 list of configurable instances:
1412 propagate to all of them.
1416 - if the local property is set, the other property will be overwritten
1417 - local property not set and other set => keep other
1418 - local property not set and other not set => overwrite the default for
1419 ConfigurableUser instances and set the property for Configurables
1424 elif type(others)
not in [list, tuple]:
1430 for other
in [o
for o
in others
if name
in o.__slots__]:
1433 if other.isPropertySet(name):
1435 "Property '%(prop)s' is set in both '%(self)s' and '%(other)s', using '%(self)s.%(prop)s'"
1437 "self": self.
name(),
1438 "other": other.name(),
1441 other.setProp(name, value)
1443 elif not other.isPropertySet(name):
1444 if isinstance(other, ConfigurableUser):
1445 otherType =
type(other._properties[name].getDefault())
1446 other._properties[name].setDefault(value)
1447 if otherType
in [list, dict]:
1450 other.setProp(name, otherType(value))
1452 other.setProp(name, value)
1457 Call propagateProperty for each property listed in 'names'.
1458 If 'names' is None, all the properties are propagated.
1462 names = [p
for p
in self.
__slots__ if not p.startswith(
"_")]
1468 Function to be overridden to convert the high level configuration into a
1470 The default implementation calls applyConf, which is the method defined
1471 in some ConfigurableUser implementations.
1477 Function to be overridden to convert the high level configuration into a
1484 Function used to define the name of the private instance of a given class
1486 This method is used when the __used_configurables_property__ declares the
1487 need of a private used configurable without specifying the name.
1489 if type(cls)
is str:
1492 clName = cls.__name__
1493 return "%s_%s" % (self.name(), clName)
1497 Return the used instance with a given name.
1500 if i.name() == name:
1501 if hasattr(i,
"_enabled"):
1506 raise KeyError(name)
1510 Return True is the instance can be "applied".
1516 postConfigActions = []
1521 Add a new callable ('function') to the list of post-configuration actions.
1522 If the callable is already in the list, it is moved to the end of the list.
1523 The list is directly accessible as 'GaudiKernel.Configurable.postConfigActions'.
1526 postConfigActions.remove(function)
1529 postConfigActions.append(function)
1534 Remove a callable from the list of post-config actions.
1535 The list is directly accessible as 'GaudiKernel.Configurable.postConfigActions'.
1537 postConfigActions.remove(function)
1540 _appliedConfigurableUsers_ =
False
1545 Call the apply method of all the ConfigurableUser instances respecting the
1546 dependencies. First the C.U.s that are not used by anybody, then the used
1547 ones, when they are not used anymore.
1550 global _appliedConfigurableUsers_, postConfigActions
1551 if _appliedConfigurableUsers_:
1553 _appliedConfigurableUsers_ =
True
1555 def applicableConfUsers():
1557 Generator returning all the configurables that can be applied in the
1558 order in which they can be applied.
1571 yield next(c
for c
in Configurable.allConfigurables.values()
1572 if c.isApplicable())
1573 except StopIteration:
1576 debugApplyOrder =
'GAUDI_DUBUG_CONF_USER' in os.environ
1577 for c
in applicableConfUsers():
1579 log.info(
"applying configuration of %s", c.name())
1581 sys.stderr.write(
'applying %r' % c)
1582 c.__apply_configuration__()
1585 log.info(
"skipping configuration of %s", c.name())
1587 if hasattr(c,
"__detach_used__"):
1593 c
for c
in Configurable.allConfigurables.values()
if
1594 hasattr(c,
'__apply_configuration__')
and c._enabled
and not c._applied
1598 raise Error(
"Detected loop in the ConfigurableUser"
1599 " dependencies: %r" % [c.name()
for c
in leftConfUsers])
1602 unknown = set(Configurable.allConfigurables)
1606 log.debug(
'new configurable created automatically: %s', k)
1608 Configurable.allConfigurables[k].
properties()
1612 for action
in postConfigActions:
1618 Obsolete (buggy) implementation of applyConfigurableUsers(), kept to provide
1619 backward compatibility for configurations that where relying (implicitly) on
1620 bug #103803, or on a specific (non guaranteed) order of execution.
1622 @see applyConfigurableUsers()
1625 global _appliedConfigurableUsers_, postConfigActions
1626 if _appliedConfigurableUsers_:
1628 _appliedConfigurableUsers_ =
True
1630 debugApplyOrder =
'GAUDI_DUBUG_CONF_USER' in os.environ
1632 c
for c
in Configurable.allConfigurables.values()
1633 if hasattr(c,
"__apply_configuration__")
1636 while applied
and confUsers:
1640 if hasattr(c,
"__users__")
and c.__users__:
1641 newConfUsers.append(c)
1646 enabled = (
not hasattr(c,
"_enabled"))
or c._enabled
1648 log.info(
"applying configuration of %s", c.name())
1650 sys.stderr.write(
'applying %r' % c)
1651 c.__apply_configuration__()
1654 log.info(
"skipping configuration of %s", c.name())
1655 if hasattr(c,
"__detach_used__"):
1658 confUsers = newConfUsers
1661 raise Error(
"Detected loop in the ConfigurableUser "
1662 " dependencies: %r" % [c.name()
for c
in confUsers])
1665 unknown = set(Configurable.allConfigurables)
1669 log.debug(
'new configurable created automatically: %s', k)
1671 Configurable.allConfigurables[k].
properties()
1675 for action
in postConfigActions:
1681 Function to select all and only the configurables that have to be used in
1682 GaudiPython.AppMgr constructor.
1683 This is needed because in Athena the implementation have to be different (the
1684 configuration is used in a different moment).
1687 k
for k, v
in Configurable.allConfigurables.items()
1688 if v.getGaudiType() !=
"User")
1693 Clean up all configurations and configurables.
1695 for c
in Configurable.allConfigurables.values():
1696 c.__class__.configurables.clear()
1697 Configurable.allConfigurables.clear()
1700 ConfigurableGeneric.configurables.clear()
1701 from .ProcessJobOptions
import _included_files
1704 for file
in _included_files:
1705 dirname, basname = os.path.split(file)
1706 basname, ext = os.path.splitext(basname)
1707 if basname
in sys.modules:
1708 del sys.modules[basname]
1709 _included_files.clear()
1718 return self.
stack[-1]
1726 name = prefix + str(cnt)
1727 while name
in allConfigurables:
1729 name = prefix + str(cnt)
1733 from Configurables
import GaudiSequencer
1738 if visitee
in (CFTrue, CFFalse):
1739 stack.append(self.
_newSeq(Invert=visitee
is CFFalse))
1740 elif isinstance(visitee, (ControlFlowLeaf, ConfigurableAlgorithm)):
1741 stack.append(visitee)
1742 elif isinstance(visitee, (OrNode, AndNode, OrderedNode)):
1747 ModeOR=isinstance(visitee, OrNode),
1748 ShortCircuit=
not isinstance(visitee, OrderedNode),
1751 elif isinstance(visitee, ignore):
1752 if hasattr(stack[-1],
'IgnoreFilterPassed'):
1753 stack[-1].IgnoreFilterPassed =
True
1757 Members=[stack.pop()], IgnoreFilterPassed=
True))
1758 elif isinstance(visitee, InvertNode):
1759 if hasattr(stack[-1],
'Invert'):
1760 stack[-1].Invert =
True
1762 stack.append(self.
_newSeq(Members=[stack.pop()], Invert=
True))
1767 Convert a control flow expression to nested GaudiSequencers.
1769 if not isinstance(expression, ControlFlowNode):
1770 raise ValueError(
'ControlFlowNode instance expected, got %s' %
1771 type(expression).__name__)
1773 expression.visitNode(visitor)
1774 return visitor.sequence
1779 Helper class to use a ControlFlowNode as an algorithm configurable
1786 if name
in Configurable.allConfigurables:
1787 instance = Configurable.allConfigurables[name]
1788 assert type(instance)
is cls, \
1789 (
'trying to reuse {0!r} as name of a {1} instance while it''s '
1790 'already used for an instance of {2}').
format(
1793 type(instance).__name__)
1796 instance = super(SuperAlgorithm, cls).
__new__(cls)
1797 Configurable.allConfigurables[name] = instance
1804 setattr(self, key, kwargs[key])
1829 Instantiate and algorithm of type 'typ' with a name suitable for use
1830 inside a SuperAlgorithm.
1832 name =
'{0}_{1}'.
format(self.
name, kwargs.pop(
'name', typ.getType()))
1833 return typ(name, **kwargs)
1836 raise NotImplementedError()
1846 super(SuperAlgorithm, self).
__setattr__(name, value)
1847 if name
in (
'_name',
'graph'):
1851 class PropSetter(object):
1852 def enter(self, node):
1854 setattr(node, name, value)
1855 except (ValueError, AttributeError):
1859 def leave(self, node):
1862 self._visitSubNodes(PropSetter())