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:]
1263 name = name[name.rfind(
'.') + 1:]
1264 return str(self.
getType() +
'/' + name)
1271 __slots__ = {
'_jobOptName': 0,
'OutputLevel': 0,
'Enable': 1}
1274 super(ConfigurableAuditor, self).
__init__(name)
1276 name = name[name.find(
'/') + 1:]
1298 "__used_instances__": [],
1310 __used_configurables__ = []
1313 __queried_configurables__ = []
1315 def __init__(self, name=Configurable.DefaultName, _enabled=True, **kwargs):
1316 super(ConfigurableUser, self).
__init__(name)
1317 for n, v
in kwargs.items():
1335 if type(used)
is tuple:
1336 used, used_name = used
1340 if type(used)
is str:
1341 used_class = confDbGetConfigurable(used)
1346 inst = used_class(name=used_name, _enabled=
False)
1347 except AttributeError:
1351 inst = used_class(name=used_name)
1355 if type(queried)
is str:
1356 queried = confDbGetConfigurable(used)
1357 inst = queried(_enabled=
False)
1358 except AttributeError:
1364 Declare that we are going to modify the Configurable 'other' in our
1365 __apply_configuration__.
1368 if hasattr(other,
"__users__"):
1369 other.__users__.append(self)
1373 Declare that we are going to retrieve property values from the
1374 ConfigurableUser 'other' in our __apply_configuration__.
1376 if not isinstance(other, ConfigurableUser):
1378 "'%s': Cannot make passive use of '%s', it is not a ConfigurableUser"
1379 % (self.
name(), other.name()))
1380 other.__addActiveUseOf(self)
1394 Remove this ConfigurableUser instance from the users list of the used
1400 used.__users__.remove(self)
1404 Propagate the property 'name' (if set) to other configurables (if possible).
1407 propagate to all the entries in __used_configurables__
1408 a configurable instance:
1409 propagate only to it
1410 list of configurable instances:
1411 propagate to all of them.
1415 - if the local property is set, the other property will be overwritten
1416 - local property not set and other set => keep other
1417 - local property not set and other not set => overwrite the default for
1418 ConfigurableUser instances and set the property for Configurables
1423 elif type(others)
not in [list, tuple]:
1429 for other
in [o
for o
in others
if name
in o.__slots__]:
1432 if other.isPropertySet(name):
1434 "Property '%(prop)s' is set in both '%(self)s' and '%(other)s', using '%(self)s.%(prop)s'"
1436 "self": self.
name(),
1437 "other": other.name(),
1440 other.setProp(name, value)
1442 elif not other.isPropertySet(name):
1443 if isinstance(other, ConfigurableUser):
1444 otherType =
type(other._properties[name].getDefault())
1445 other._properties[name].setDefault(value)
1446 if otherType
in [list, dict]:
1449 other.setProp(name, otherType(value))
1451 other.setProp(name, value)
1456 Call propagateProperty for each property listed in 'names'.
1457 If 'names' is None, all the properties are propagated.
1461 names = [p
for p
in self.
__slots__ if not p.startswith(
"_")]
1467 Function to be overridden to convert the high level configuration into a
1469 The default implementation calls applyConf, which is the method defined
1470 in some ConfigurableUser implementations.
1476 Function to be overridden to convert the high level configuration into a
1483 Function used to define the name of the private instance of a given class
1485 This method is used when the __used_configurables_property__ declares the
1486 need of a private used configurable without specifying the name.
1488 if type(cls)
is str:
1491 clName = cls.__name__
1492 return "%s_%s" % (self.name(), clName)
1496 Return the used instance with a given name.
1499 if i.name() == name:
1500 if hasattr(i,
"_enabled"):
1505 raise KeyError(name)
1509 Return True is the instance can be "applied".
1515 postConfigActions = []
1520 Add a new callable ('function') to the list of post-configuration actions.
1521 If the callable is already in the list, it is moved to the end of the list.
1522 The list is directly accessible as 'GaudiKernel.Configurable.postConfigActions'.
1525 postConfigActions.remove(function)
1528 postConfigActions.append(function)
1533 Remove a callable from the list of post-config actions.
1534 The list is directly accessible as 'GaudiKernel.Configurable.postConfigActions'.
1536 postConfigActions.remove(function)
1539 _appliedConfigurableUsers_ =
False
1544 Call the apply method of all the ConfigurableUser instances respecting the
1545 dependencies. First the C.U.s that are not used by anybody, then the used
1546 ones, when they are not used anymore.
1549 global _appliedConfigurableUsers_, postConfigActions
1550 if _appliedConfigurableUsers_:
1552 _appliedConfigurableUsers_ =
True
1554 def applicableConfUsers():
1556 Generator returning all the configurables that can be applied in the
1557 order in which they can be applied.
1570 yield next(c
for c
in Configurable.allConfigurables.values()
1571 if c.isApplicable())
1572 except StopIteration:
1575 debugApplyOrder =
'GAUDI_DUBUG_CONF_USER' in os.environ
1576 for c
in applicableConfUsers():
1578 log.info(
"applying configuration of %s", c.name())
1580 sys.stderr.write(
'applying %r' % c)
1581 c.__apply_configuration__()
1584 log.info(
"skipping configuration of %s", c.name())
1586 if hasattr(c,
"__detach_used__"):
1592 c
for c
in Configurable.allConfigurables.values()
if
1593 hasattr(c,
'__apply_configuration__')
and c._enabled
and not c._applied
1597 raise Error(
"Detected loop in the ConfigurableUser"
1598 " dependencies: %r" % [c.name()
for c
in leftConfUsers])
1601 unknown = set(Configurable.allConfigurables)
1605 log.debug(
'new configurable created automatically: %s', k)
1607 Configurable.allConfigurables[k].
properties()
1611 for action
in postConfigActions:
1617 Obsolete (buggy) implementation of applyConfigurableUsers(), kept to provide
1618 backward compatibility for configurations that where relying (implicitly) on
1619 bug #103803, or on a specific (non guaranteed) order of execution.
1621 @see applyConfigurableUsers()
1624 global _appliedConfigurableUsers_, postConfigActions
1625 if _appliedConfigurableUsers_:
1627 _appliedConfigurableUsers_ =
True
1629 debugApplyOrder =
'GAUDI_DUBUG_CONF_USER' in os.environ
1631 c
for c
in Configurable.allConfigurables.values()
1632 if hasattr(c,
"__apply_configuration__")
1635 while applied
and confUsers:
1639 if hasattr(c,
"__users__")
and c.__users__:
1640 newConfUsers.append(c)
1645 enabled = (
not hasattr(c,
"_enabled"))
or c._enabled
1647 log.info(
"applying configuration of %s", c.name())
1649 sys.stderr.write(
'applying %r' % c)
1650 c.__apply_configuration__()
1653 log.info(
"skipping configuration of %s", c.name())
1654 if hasattr(c,
"__detach_used__"):
1657 confUsers = newConfUsers
1660 raise Error(
"Detected loop in the ConfigurableUser "
1661 " dependencies: %r" % [c.name()
for c
in confUsers])
1664 unknown = set(Configurable.allConfigurables)
1668 log.debug(
'new configurable created automatically: %s', k)
1670 Configurable.allConfigurables[k].
properties()
1674 for action
in postConfigActions:
1680 Function to select all and only the configurables that have to be used in
1681 GaudiPython.AppMgr constructor.
1682 This is needed because in Athena the implementation have to be different (the
1683 configuration is used in a different moment).
1686 k
for k, v
in Configurable.allConfigurables.items()
1687 if v.getGaudiType() !=
"User")
1692 Clean up all configurations and configurables.
1694 for c
in Configurable.allConfigurables.values():
1695 c.__class__.configurables.clear()
1696 Configurable.allConfigurables.clear()
1699 ConfigurableGeneric.configurables.clear()
1700 from .ProcessJobOptions
import _included_files
1703 for file
in _included_files:
1704 dirname, basname = os.path.split(file)
1705 basname, ext = os.path.splitext(basname)
1706 if basname
in sys.modules:
1707 del sys.modules[basname]
1708 _included_files.clear()
1717 return self.
stack[-1]
1725 name = prefix + str(cnt)
1726 while name
in allConfigurables:
1728 name = prefix + str(cnt)
1732 from Configurables
import GaudiSequencer
1737 if visitee
in (CFTrue, CFFalse):
1738 stack.append(self.
_newSeq(Invert=visitee
is CFFalse))
1739 elif isinstance(visitee, (ControlFlowLeaf, ConfigurableAlgorithm)):
1740 stack.append(visitee)
1741 elif isinstance(visitee, (OrNode, AndNode, OrderedNode)):
1746 ModeOR=isinstance(visitee, OrNode),
1747 ShortCircuit=
not isinstance(visitee, OrderedNode),
1750 elif isinstance(visitee, ignore):
1751 if hasattr(stack[-1],
'IgnoreFilterPassed'):
1752 stack[-1].IgnoreFilterPassed =
True
1756 Members=[stack.pop()], IgnoreFilterPassed=
True))
1757 elif isinstance(visitee, InvertNode):
1758 if hasattr(stack[-1],
'Invert'):
1759 stack[-1].Invert =
True
1761 stack.append(self.
_newSeq(Members=[stack.pop()], Invert=
True))
1766 Convert a control flow expression to nested GaudiSequencers.
1768 if not isinstance(expression, ControlFlowNode):
1769 raise ValueError(
'ControlFlowNode instance expected, got %s' %
1770 type(expression).__name__)
1772 expression.visitNode(visitor)
1773 return visitor.sequence
1778 Helper class to use a ControlFlowNode as an algorithm configurable
1785 if name
in Configurable.allConfigurables:
1786 instance = Configurable.allConfigurables[name]
1787 assert type(instance)
is cls, \
1788 (
'trying to reuse {0!r} as name of a {1} instance while it''s '
1789 'already used for an instance of {2}').
format(
1792 type(instance).__name__)
1795 instance = super(SuperAlgorithm, cls).
__new__(cls)
1796 Configurable.allConfigurables[name] = instance
1803 setattr(self, key, kwargs[key])
1828 Instantiate and algorithm of type 'typ' with a name suitable for use
1829 inside a SuperAlgorithm.
1831 name =
'{0}_{1}'.
format(self.
name, kwargs.pop(
'name', typ.getType()))
1832 return typ(name, **kwargs)
1835 raise NotImplementedError()
1845 super(SuperAlgorithm, self).
__setattr__(name, value)
1846 if name
in (
'_name',
'graph'):
1850 class PropSetter(object):
1851 def enter(self, node):
1853 setattr(node, name, value)
1854 except (ValueError, AttributeError):
1858 def leave(self, node):
1861 self._visitSubNodes(PropSetter())