The Gaudi Framework  master (adcf1ca6)
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 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
91class ConfigurableMeta(type):
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
120def 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 return repr(value)
128
129
130class Configurable(metaclass=ConfigurableMeta):
131 """
132 Base class for all configurable instances.
133 """
134
135 instances = {}
136
137 def __init__(self, name=None, **kwargs):
138 self._name = None
139 self._properties = {}
140 if "parent" in kwargs:
141 parent = kwargs.pop("parent")
142 if isinstance(parent, str):
143 parent = self.instances[parent]
144 if not name:
145 raise TypeError("name is needed when a parent is specified")
146 name = f"{parent.name}.{name}"
147 if name:
148 self.name = name
149 elif not _GLOBAL_INSTANCES:
150 self.name = self.__cpp_type__
151 for key, value in kwargs.items():
152 setattr(self, key, value)
153
154 @classmethod
155 def getInstance(cls, name):
156 return cls.instances.get(name) or cls(name)
157
158 @property
159 def name(self):
160 if not self._name:
161 raise AttributeError(
162 f"{repr(type(self).__name__)} instance was not named yet"
163 )
164 return self._name
165
166 @name.setter
167 def name(self, value):
168 if value == self._name:
169 return # it's already the name of the instance, nothing to do
170 if not isinstance(value, str) or not value:
171 raise TypeError(f"expected string, got {type(value).__name__} instead")
172 if _GLOBAL_INSTANCES:
173 if value in self.instances:
174 raise ValueError(f"name {repr(value)} already used")
175 if self._name in self.instances:
176 del self.instances[self._name]
177 self._name = value
178 self.instances[value] = self
179 else:
180 self._name = value
181
182 @name.deleter
183 def name(self):
184 if _GLOBAL_INSTANCES:
185 # check if it was set
186 del self.instances[self.name]
187 self._name = None
188 else:
189 raise TypeError("name attribute cannot be deleted")
190
191 def __repr__(self):
192 args = []
193 try:
194 args.append(repr(self.name))
195 except AttributeError:
196 pass # no name
197 args.extend(f"{k}={repr(v)}" for k, v in self._properties.items())
198 return "{}({})".format(type(self).__name__, ", ".join(args))
199
200 def __getstate__(self):
201 state = {"properties": self._properties}
202 try:
203 state["name"] = self.name
204 except AttributeError:
205 pass # no name
206 return state
207
208 def __setstate__(self, state):
209 self._name = None
210 self.name = state.get("name")
211 self._properties = state["properties"]
212
213 def __opt_value__(self):
214 if self.__cpp_type__ == self.name:
215 return self.__cpp_type__
216 return f"{self.__cpp_type__}/{self.name}"
217
218 def __opt_properties__(self, explicit_defaults=False):
219 name = self.name
220 out = {}
221 for p in self._descriptors.values():
222 if explicit_defaults or p.__is_set__(self, type(self)):
223 out[".".join([name, p.name])] = opt_repr(
224 p.__opt_value__(self, type(self))
225 )
226 return out
227
228 def is_property_set(self, propname):
229 return self._descriptors[propname].__is_set__(self, type(self))
230
231 @classmethod
232 def getGaudiType(cls):
233 return cls.__component_type__
234
235 @classmethod
236 def getType(cls):
237 return cls.__cpp_type__
238
239 def getName(self):
240 return self.name
241
243 return f"{self.__cpp_type__}/{self.name}"
244
246 return f"{self.__cpp_type__}/{self.name}"
247
248 @classmethod
250 return {k: v.default for k, v in cls._descriptors.items()}
251
252 @classmethod
253 def getDefaultProperty(cls, name):
254 return cls._descriptors[name].default
255
256 def clone(self, newname=None):
257 """Clone instance with all its properties."""
258 return self.__class__(newname, **self._properties)
259
260 def merge(self, other):
261 """
262 Merge the properties of the other instance into the current one.
263
264 The two instances have to be of the same type, have the same name
265 (or both unnamed) and the settings must be mergable (according to
266 their semantics).
267 """
268 if self is other:
269 return self
270 if type(self) is not type(other):
271 raise TypeError(
272 f"cannot merge instance of {type(other).__name__} into an instance of { type(self).__name__}"
273 )
274 if hasattr(self, "name") != hasattr(other, "name"):
275 raise ValueError("cannot merge a named configurable with an unnamed one")
276 if hasattr(self, "name") and (self.name != other.name):
277 raise ValueError(
278 f"cannot merge configurables with different names ({self.name} and {other.name})"
279 )
280
281 for name in other._properties:
282 if (
283 name in self._properties
284 and self._properties[name] == other._properties[name]
285 ):
286 continue
287 try:
288 self._properties[name] = self._descriptors[name].__merge__(
289 self, type(self), getattr(other, name)
290 )
291 except ValueError as err:
292 raise ValueError(
293 "conflicting settings for property {} of {}: {}".format(
294 name,
295 self.name if hasattr(self, "name") else type(self).__name__,
296 str(err),
297 )
298 )
299
300 return self
301
302
303def makeConfigurableClass(name, **namespace):
304 """
305 Create a Configurable specialization.
306 """
307 properties = namespace.pop("properties", {})
308 namespace.update({pname: Property(*pargs) for pname, pargs in properties.items()})
309
310 return type(name, (Configurable,), namespace)
311
312
313def all_options(explicit_defaults=False):
314 """
315 Return a dictionary with all explicitly set options, or with also the
316 defaults if explicit_defaults is set to True.
317 """
318 opts = {}
319 for c in Configurable.instances.values():
320 opts.update(c.__opt_properties__(explicit_defaults))
321 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)