The Gaudi Framework  master (1304469f)
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 elif isinstance(value, str):
128 return '"{}"'.format(value.replace('"', '\\"'))
129 return repr(value)
130
131
132class 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
245 return f"{self.__cpp_type__}/{self.name}"
246
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
305def 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
315def 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
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)