The Gaudi Framework  master (d98a2936)
_configurables.py
Go to the documentation of this file.
1 
12 _GLOBAL_INSTANCES = False
13 
14 
15 def useGlobalInstances(enable):
16  """
17  Enable or disable the global instances database.
18 
19  By default global instances are enabled.
20  """
21  global _GLOBAL_INSTANCES
22  if enable == _GLOBAL_INSTANCES:
23  return
24  if not enable:
25  assert (
26  not Configurable.instances
27  ), "Configurable instances DB not empty, cannot be disabled"
28  _GLOBAL_INSTANCES = enable
29 
30 
31 class Property(object):
32  """
33  Descriptor class to implement validation of Configurable properties.
34  """
35 
36  def __init__(self, cpp_type, default, doc="undocumented", semantics=None):
37  from .semantics import getSemanticsFor
38 
39  if semantics is None:
40  self.semantics = getSemanticsFor(cpp_type)
41  else:
42  self.semantics = getSemanticsFor(semantics, strict=True)
43 
44  self.default = default
45  self.__doc__ = doc
46 
47  @property
48  def cpp_type(self):
49  return self.semantics.cpp_type
50 
51  @property
52  def name(self):
53  return self.semantics.name
54 
55  def __get__(self, instance, owner):
56  if self.name not in instance._properties and hasattr(self.semantics, "default"):
57  instance._properties[self.name] = self.semantics.default(self.default)
58  return self.semantics.load(instance._properties.get(self.name, self.default))
59 
60  def __set__(self, instance, value):
61  instance._properties[self.name] = self.semantics.store(value)
62 
63  def __delete__(self, instance):
64  del instance._properties[self.name]
65 
66  def __set_name__(self, owner, name):
67  self.semantics.name = name
68 
69  def __is_set__(self, instance, owner):
70  try:
71  value = instance._properties[self.name]
72  return self.semantics.is_set(value)
73  except KeyError:
74  return False
75 
76  def __opt_value__(self, instance, owner):
77  return self.semantics.opt_value(
78  instance._properties.get(self.name, self.default)
79  )
80 
81  def __merge__(self, instance, owner, value):
82  """
83  Return "merge" (according to the semantic) of the value
84  in this property and the incoming value.
85  """
86  if not self.__is_set__(instance, owner):
87  return value
88  return self.semantics.merge(self.__get__(instance, owner), value)
89 
90 
92  """
93  Metaclass for Configurables.
94  """
95 
96  def __new__(cls, name, bases, namespace, **kwds):
97  props = {
98  key: namespace[key]
99  for key in namespace
100  if isinstance(namespace[key], Property)
101  }
102  if props:
103  doc = namespace.get("__doc__", "").rstrip()
104  doc += "\n\nProperties\n----------\n"
105  doc += "\n".join(
106  [
107  f"- {name}: {p.cpp_type} ({p.default!r})\n {p.__doc__}\n"
108  for name, p in props.items()
109  ]
110  )
111  namespace["__doc__"] = doc
112  namespace["_descriptors"] = props
113  slots = set(namespace.get("__slots__", []))
114  slots.update(["_properties", "_name"])
115  namespace["__slots__"] = tuple(slots)
116  result = type.__new__(cls, name, bases, namespace)
117  return result
118 
119 
120 def opt_repr(value):
121  """
122  String representation of the value, such that it can be consumed be the
123  Gaudi option parsers.
124  """
125  if hasattr(value, "__opt_repr__"):
126  return value.__opt_repr__()
127  elif isinstance(value, str):
128  return '"{}"'.format(value.replace('"', '\\"'))
129  return repr(value)
130 
131 
132 class Configurable(metaclass=ConfigurableMeta):
133  """
134  Base class for all configurable instances.
135  """
136 
137  instances = {}
138 
139  def __init__(self, name=None, **kwargs):
140  self._name = None
141  self._properties = {}
142  if "parent" in kwargs:
143  parent = kwargs.pop("parent")
144  if isinstance(parent, str):
145  parent = self.instances[parent]
146  if not name:
147  raise TypeError("name is needed when a parent is specified")
148  name = f"{parent.name}.{name}"
149  if name:
150  self.name = name
151  elif not _GLOBAL_INSTANCES:
152  self.name = self.__cpp_type__
153  for key, value in kwargs.items():
154  setattr(self, key, value)
155 
156  @classmethod
157  def getInstance(cls, name):
158  return cls.instances.get(name) or cls(name)
159 
160  @property
161  def name(self):
162  if not self._name:
163  raise AttributeError(
164  f"{repr(type(self).__name__)} instance was not named yet"
165  )
166  return self._name
167 
168  @name.setter
169  def name(self, value):
170  if value == self._name:
171  return # it's already the name of the instance, nothing to do
172  if not isinstance(value, str) or not value:
173  raise TypeError(f"expected string, got {type(value).__name__} instead")
174  if _GLOBAL_INSTANCES:
175  if value in self.instances:
176  raise ValueError(f"name {repr(value)} already used")
177  if self._name in self.instances:
178  del self.instances[self._name]
179  self._name = value
180  self.instances[value] = self
181  else:
182  self._name = value
183 
184  @name.deleter
185  def name(self):
186  if _GLOBAL_INSTANCES:
187  # check if it was set
188  del self.instances[self.name]
189  self._name = None
190  else:
191  raise TypeError("name attribute cannot be deleted")
192 
193  def __repr__(self):
194  args = []
195  try:
196  args.append(repr(self.name))
197  except AttributeError:
198  pass # no name
199  args.extend(f"{k}={repr(v)}" for k, v in self._properties.items())
200  return "{}({})".format(type(self).__name__, ", ".join(args))
201 
202  def __getstate__(self):
203  state = {"properties": self._properties}
204  try:
205  state["name"] = self.name
206  except AttributeError:
207  pass # no name
208  return state
209 
210  def __setstate__(self, state):
211  self._name = None
212  self.name = state.get("name")
213  self._properties = state["properties"]
214 
215  def __opt_value__(self):
216  if self.__cpp_type__ == self.name:
217  return self.__cpp_type__
218  return f"{self.__cpp_type__}/{self.name}"
219 
220  def __opt_properties__(self, explicit_defaults=False):
221  name = self.name
222  out = {}
223  for p in self._descriptors.values():
224  if explicit_defaults or p.__is_set__(self, type(self)):
225  out[".".join([name, p.name])] = opt_repr(
226  p.__opt_value__(self, type(self))
227  )
228  return out
229 
230  def is_property_set(self, propname):
231  return self._descriptors[propname].__is_set__(self, type(self))
232 
233  @classmethod
234  def getGaudiType(cls):
235  return cls.__component_type__
236 
237  @classmethod
238  def getType(cls):
239  return cls.__cpp_type__
240 
241  def getName(self):
242  return self.name
243 
244  def getFullJobOptName(self):
245  return f"{self.__cpp_type__}/{self.name}"
246 
247  def toStringProperty(self):
248  return f"{self.__cpp_type__}/{self.name}"
249 
250  @classmethod
252  return {k: v.default for k, v in cls._descriptors.items()}
253 
254  @classmethod
255  def getDefaultProperty(cls, name):
256  return cls._descriptors[name].default
257 
258  def clone(self, newname=None):
259  """Clone instance with all its properties."""
260  return self.__class__(newname, **self._properties)
261 
262  def merge(self, other):
263  """
264  Merge the properties of the other instance into the current one.
265 
266  The two instances have to be of the same type, have the same name
267  (or both unnamed) and the settings must be mergable (according to
268  their semantics).
269  """
270  if self is other:
271  return self
272  if type(self) is not type(other):
273  raise TypeError(
274  f"cannot merge instance of {type(other).__name__} into an instance of { type(self).__name__}"
275  )
276  if hasattr(self, "name") != hasattr(other, "name"):
277  raise ValueError("cannot merge a named configurable with an unnamed one")
278  if hasattr(self, "name") and (self.name != other.name):
279  raise ValueError(
280  f"cannot merge configurables with different names ({self.name} and {other.name})"
281  )
282 
283  for name in other._properties:
284  if (
285  name in self._properties
286  and self._properties[name] == other._properties[name]
287  ):
288  continue
289  try:
290  self._properties[name] = self._descriptors[name].__merge__(
291  self, type(self), getattr(other, name)
292  )
293  except ValueError as err:
294  raise ValueError(
295  "conflicting settings for property {} of {}: {}".format(
296  name,
297  self.name if hasattr(self, "name") else type(self).__name__,
298  str(err),
299  )
300  )
301 
302  return self
303 
304 
305 def makeConfigurableClass(name, **namespace):
306  """
307  Create a Configurable specialization.
308  """
309  properties = namespace.pop("properties", {})
310  namespace.update({pname: Property(*pargs) for pname, pargs in properties.items()})
311 
312  return type(name, (Configurable,), namespace)
313 
314 
315 def all_options(explicit_defaults=False):
316  """
317  Return a dictionary with all explicitly set options, or with also the
318  defaults if explicit_defaults is set to True.
319  """
320  opts = {}
321  for c in Configurable.instances.values():
322  opts.update(c.__opt_properties__(explicit_defaults))
323  return opts
GaudiConfig2._configurables.Configurable.getName
def getName(self)
Definition: _configurables.py:241
GaudiConfig2._configurables.Property.__doc__
__doc__
Definition: _configurables.py:45
GaudiConfig2._configurables.Property.default
default
Definition: _configurables.py:44
GaudiConfig2._configurables.ConfigurableMeta
Definition: _configurables.py:91
GaudiConfig2._configurables.Configurable
Definition: _configurables.py:132
GaudiConfig2._configurables.Property.__init__
def __init__(self, cpp_type, default, doc="undocumented", semantics=None)
Definition: _configurables.py:36
GaudiPartProp.decorators.get
get
decorate the vector of properties
Definition: decorators.py:283
GaudiConfig2._configurables.Configurable.is_property_set
def is_property_set(self, propname)
Definition: _configurables.py:230
GaudiConfig2._configurables.Configurable.__opt_properties__
def __opt_properties__(self, explicit_defaults=False)
Definition: _configurables.py:220
GaudiConfig2._configurables.Configurable.__cpp_type__
__cpp_type__
Definition: _configurables.py:216
GaudiConfig2._configurables.Configurable.getDefaultProperty
def getDefaultProperty(cls, name)
Definition: _configurables.py:255
GaudiConfig2._configurables.Configurable.instances
instances
Definition: _configurables.py:137
GaudiConfig2._configurables.Property.cpp_type
def cpp_type(self)
Definition: _configurables.py:48
GaudiConfig2._configurables.Property.__is_set__
def __is_set__(self, instance, owner)
Definition: _configurables.py:69
GaudiConfig2._configurables.Configurable.getGaudiType
def getGaudiType(cls)
Definition: _configurables.py:234
GaudiConfig2._configurables.Configurable.clone
def clone(self, newname=None)
Definition: _configurables.py:258
GaudiConfig2._configurables.Property.__merge__
def __merge__(self, instance, owner, value)
Definition: _configurables.py:81
GaudiConfig2._configurables.Configurable.__init__
def __init__(self, name=None, **kwargs)
Definition: _configurables.py:139
GaudiConfig2._configurables.Configurable.__repr__
def __repr__(self)
Definition: _configurables.py:193
GaudiConfig2.semantics.getSemanticsFor
def getSemanticsFor(cpp_type, strict=False)
Definition: semantics.py:719
GaudiConfig2._configurables.Property.name
def name(self)
Definition: _configurables.py:52
GaudiConfig2._configurables.Property.__set__
def __set__(self, instance, value)
Definition: _configurables.py:60
GaudiConfig2._configurables.Configurable.getFullJobOptName
def getFullJobOptName(self)
Definition: _configurables.py:244
GaudiConfig2._configurables.Configurable.merge
def merge(self, other)
Definition: _configurables.py:262
GaudiConfig2._configurables.Configurable.name
name
Definition: _configurables.py:150
GaudiConfig2._configurables.opt_repr
def opt_repr(value)
Definition: _configurables.py:120
GaudiConfig2._configurables.all_options
def all_options(explicit_defaults=False)
Definition: _configurables.py:315
GaudiConfig2._configurables.Configurable.__getstate__
def __getstate__(self)
Definition: _configurables.py:202
format
GAUDI_API std::string format(const char *,...)
MsgStream format utility "a la sprintf(...)".
Definition: MsgStream.cpp:93
GaudiConfig2._configurables.Configurable.toStringProperty
def toStringProperty(self)
Definition: _configurables.py:247
GaudiConfig2._configurables.Configurable.getDefaultProperties
def getDefaultProperties(cls)
Definition: _configurables.py:251
gaudirun.type
type
Definition: gaudirun.py:160
merge
int merge(const char *target, const char *source, bool fixup=false, bool dbg=true)
Definition: merge.C:417
GaudiConfig2._configurables.Configurable._properties
_properties
Definition: _configurables.py:141
GaudiConfig2._configurables.Property.__delete__
def __delete__(self, instance)
Definition: _configurables.py:63
GaudiConfig2._configurables.Configurable.getType
def getType(cls)
Definition: _configurables.py:238
GaudiConfig2._configurables.Property.__set_name__
def __set_name__(self, owner, name)
Definition: _configurables.py:66
GaudiConfig2._configurables.ConfigurableMeta.__new__
def __new__(cls, name, bases, namespace, **kwds)
Definition: _configurables.py:96
GaudiConfig2._configurables.Property.semantics
semantics
Definition: _configurables.py:40
GaudiConfig2._configurables.Property
Definition: _configurables.py:31
GaudiConfig2._configurables.Property.__opt_value__
def __opt_value__(self, instance, owner)
Definition: _configurables.py:76
Gaudi.CommonGaudiConfigurables.cls
cls
Definition: CommonGaudiConfigurables.py:43
GaudiConfig2._configurables.makeConfigurableClass
def makeConfigurableClass(name, **namespace)
Definition: _configurables.py:305
GaudiConfig2._configurables.Configurable.__opt_value__
def __opt_value__(self)
Definition: _configurables.py:215
GaudiConfig2._configurables.useGlobalInstances
def useGlobalInstances(enable)
Definition: _configurables.py:15
GaudiConfig2._configurables.Property.__get__
def __get__(self, instance, owner)
Definition: _configurables.py:55
GaudiConfig2._configurables.Configurable._name
_name
Definition: _configurables.py:140
GaudiConfig2._configurables.Configurable.getInstance
def getInstance(cls, name)
Definition: _configurables.py:157
GaudiConfig2._configurables.Configurable.__setstate__
def __setstate__(self, state)
Definition: _configurables.py:210