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