DUNE-DAQ
DUNE Trigger and Data Acquisition software
Loading...
Searching...
No Matches
Configuration.py
Go to the documentation of this file.
1#!/usr/bin/env python
2# vim: set fileencoding=utf-8 :
3# DUNE DAQ modification notice:
4# This file has been modified from the original ATLAS config source for the DUNE DAQ project.
5# Fork baseline commit: 67a24e731 (2022-10-27).
6# Renamed since fork: yes (from python/config/Configuration.py to python/conffwk/Configuration.py).
7
8# Created by Andre Anjos <andre.dos.anjos@cern.ch>
9# Mon 22 Oct 2007 11:44:17 AM CEST
10
11"""A pythonic wrapper over the OKS Configuration wrapper.
12
13Necessary to give the user a more pythonic experience than dealing with
14std::vector objects and memory management.
15"""
16from . import schema
17from . import ConfigObject
18from ._daq_conffwk_py import _Configuration
19import logging
20from .proxy import _DelegateMetaFunction
21import re
22import os
23
25 metaclass=_DelegateMetaFunction):
26 memberclass = _Configuration
27
28
30 """Access OKS/RDB configuration databases from python.
31 """
32
33 def __core_init__(self):
34 self.__schema__ = schema.Cache(self, all=True)
35 self.__schema__.update_dal(self)
36
37 # initialize the inner set of configuration files available
38 self.databases = []
39 self.active_database = None
40 if self.get_impl_param():
41 self.databases.append(self.get_impl_param())
42 self.active_database = self.get_impl_param()
43
44 # we keep a cache of DAL'ed objects
45 self.__cache__ = {}
47
48 def __init__(self, connection='oksconflibs:'):
49 """Initializes a Configuration database.
50
51 Keyword arguments:
52
53 connection -- A connection string, in the form of <backend>:<database>
54 name, where <backend> may be set to be 'oksconflibs' or 'rdbconffwk' and
55 <database> is either the name of the database XML file (in the case of
56 'oksconflibs') or the name of a database associated with an RDB server
57 (in the case of 'rdbconffwk').
58
59 Warning: To use the RDB server, the IPC subsystem has to be initialized
60 beforehand and as this is not done by this package. If the parameter
61 'connection' is empty, the default is whatever is the default for the
62 conffwk::Configuration C++ class, which at this time boils down to look
63 if TDAQ_DB is set and take that default.
64
65 Raises RuntimeError, in case of problems.
66 """
67
68 try:
69 super(Configuration, self).__init__(connection)
70 except RuntimeError:
71 preamble = f"Unable to open database off of \"{connection}\""
72 if not re.search(r"^oksconflibs:", connection):
73 raise RuntimeError(f"""
74{preamble}; one reason is that it looks
75like the database type wasn't specified in the name (i.e. \"oksconflibs:<filename>\")
76""")
77 else:
78 dbfilename = connection[len("oksconflibs:"):]
79 if not os.path.exists(dbfilename):
80 raise RuntimeError(f"{preamble}; one reason is that it looks like \"{dbfilename}\" doesn't exist")
81 elif not re.search(r".xml$", dbfilename):
82 raise RuntimeError(f"{preamble}; one reason is that it looks like \"{dbfilename}\" isn't an XML file")
83 else:
84 raise RuntimeError(f"""
85{preamble}; try running
86\"oks_dump --files-only {dbfilename}\"
87to see if there's a problem with the input database""")
88
89 self.__core_init__()
90
91 def databases(self):
92 """Returns a list of associated databases which are opened"""
93 return self.databases
94
95 def set_active(self, name):
96 """Sets a database to become active when adding new objects.
97
98 This method raises NameError in case a database cannot be made active.
99 """
100
101 if name in self.databases:
102 self.active_database = name
103 else:
104 raise NameError('database "%s is not loaded in this object' % name)
105
107 """Initializes the internal DAL cache"""
108 for k in self.classes():
109 if k not in self.__cache__:
110 self.__cache__[k] = {}
111
112 def __update_cache__(self, objs):
113 """Updates the internal DAL cache"""
114 for obj in objs:
115 for oks_type in obj.oksTypes():
116 self.__cache__[oks_type][obj.id] = obj
117
118 def __delete_cache__(self, objs):
119 """Updates the internal DAL cache"""
120 for obj in objs:
121 for oks_type in obj.oksTypes():
122 if obj.id in self.__cache__[oks_type]:
123 del self.__cache__[oks_type][obj.id]
124
125 def __retrieve_cache__(self, class_name, id=None):
126 """Retrieves all objects that match a certain class_name/id"""
127 if id:
128 if id in self.__cache__[class_name]:
129 return self.__cache__[class_name][id]
130 else:
131 return iter(self.__cache__[class_name].values())
132
133 def get_objs(self, class_name, query=''):
134 """Returns a python list of ConfigObject's with the given class name.
135
136 Keyword arguments:
137
138 class_name -- This is the name of the OKS Class to be used for the
139 search. It has to be amongst one of the classes returned by the
140 "classes()" method.
141
142 query -- This is specific OKS query you may want to perform to reduce
143 the returned subset. By default it is empty, what makes me return all
144 objects for which the class (or base class) matches the 'class_name'
145 parameter you set.
146
147 Returns a (python) list with conffwk.ConfigObject's
148 """
149 objs = super(Configuration, self).get_objs(class_name, query)
150 return [ConfigObject.ConfigObject(k, self.__schema__, self)
151 for k in objs]
152
153 def attributes(self, class_name, all=False):
154 """Returns a list of attributes of the named class
155
156 This method will return a list of direct (not inherited) attributes of
157 a certain class as a python list. If the 'all' flag is set to True,
158 then direct and inherited attributes are returned.
159
160 Keyword arguments:
161
162 class_name -- This is the class of the object you want to inspect.
163
164 all -- If set to 'True', returns direct and inherited attributes,
165 otherwise, only direct attributes (the default).
166
167 Raises RuntimeError on problems
168 """
169 attribute_properties_as_strings = super(Configuration, self).attributes(class_name, all)
170 attribute_properties_to_return = {}
171 for attribute, properties in attribute_properties_as_strings.items():
172
173 for k, v in properties.items():
174 if v == "None":
175 properties[k] = None
176 elif v == "True":
177 properties[k] = True
178 elif v == "False":
179 properties[k] = False
180
181 attribute_properties_to_return[attribute] = properties
182
183 return attribute_properties_to_return
184
185 def relations(self, class_name, all=False):
186 """Returns a list of attributes of the named class
187
188 This method will return a list of direct (not inherited) relationships
189 of a certain class as a python list. If the 'all' flag is set to True,
190 then direct and inherited attributes are returned.
191
192 Keyword arguments:
193
194 class_name -- This is the class of the object you want to inspect.
195
196 all -- If set to 'True', returns direct and inherited relationships,
197 otherwise, only direct relationships (the default).
198
199 Raises RuntimeError on problems
200 """
201 relationship_properties_as_strings = super(Configuration, self).relations(class_name, all)
202 relationship_properties_to_return = {}
203
204 for relationship, properties in relationship_properties_as_strings.items():
205
206 for k, v in properties.items():
207 if v == "None":
208 properties[k] = None
209 elif v == "True":
210 properties[k] = True
211 elif v == "False":
212 properties[k] = False
213
214 relationship_properties_to_return[relationship] = properties
215
216 return relationship_properties_to_return
217
218
219 def superclasses(self, class_name, all=False):
220 """Returns a list of superclasses of the named class
221
222 This method will return a list of direct (not inherited) superclasses
223 of a certain class as a python list. If the 'all' flag is set to True,
224 then direct and inherited attributes are returned.
225
226 Keyword arguments:
227
228 class_name -- This is the class of the object you want to inspect.
229
230 all -- If set to 'True', returns direct and inherited superclasses,
231 otherwise, only direct superclasses (the default).
232
233 Raises RuntimeError on problems
234 """
235 return super(Configuration, self).superclasses(class_name, all)
236
237 def subclasses(self, class_name, all=False):
238 """Returns a list of subclasses of the named class
239
240 This method will return a list of direct (not inherited) subclasses of
241 a certain class as a python list. If the 'all' flag is set to True,
242 then direct and inherited attributes are returned.
243
244 Keyword arguments:
245
246 class_name -- This is the class of the object you want to inspect.
247
248 all -- If set to 'True', returns direct and inherited subclasses,
249 otherwise, only direct subclasses (the default).
250
251 Raises RuntimeError on problems
252 """
253 return super(Configuration, self).subclasses(class_name, all)
254
255 def classes(self):
256 """Returns a list of all classes loaded in this Configuration."""
257 return list(super(Configuration, self).classes())
258
259 def create_db(self, db_name, includes):
260 """Creates a new database on the specified server, sets it active.
261
262 This method creates a new database on the specified server. If the
263 server is not specified, what is returned by get_impl_name() is used.
264 After the creation, this database immediately becomes the "active"
265 database where new objects will be created at. You can reset that
266 using the "set_active()" call in objects of this class.
267
268 Keyword parameters:
269
270 db_name -- The name of the database to create.
271
272 includes -- A list of includes this database will have.
273 """
274 super(Configuration, self).create_db(db_name, includes)
275
276 # we take this opportunity to update the class cache we have
277 self.__schema__.update(self)
278 self.__schema__.update_dal(self)
280
281 # and to set the current available databases and active database
282 if db_name not in self.databases:
283 self.databases.append(db_name)
284 self.active_database = db_name
285
286 def get_includes(self, at=None):
287 """Returns a a list of all includes in a certain database.
288
289 Keyword arguments:
290
291 at -- This is the name of the database you want to get the includes
292 from. If set to 'None' (the default) we use whatever
293 self.active_database is set to hoping for the best.
294 """
295 if not at:
296 at = self.active_database
297 return super(Configuration, self).get_includes(at)
298
299 def remove_include(self, include, at=None):
300 """Removes a included file in a certain database.
301
302 This method will remove include files from the active database or from
303 any other include database if mentioned.
304
305 Keyword parameters:
306
307 include -- A single include to remove from the database.
308
309 at -- This is the name of database you want to remove the include(s)
310 from, If set to None (the default), I'll simply use the value of
311 'self.active_database', hoping for the best.
312 """
313 if not at:
314 at = self.active_database
315 super(Configuration, self).remove_include(at, include)
316
317 def add_include(self, include, at=None):
318 """Adds a new include to the database.
319
320 This method includes new files in the include section of your database.
321 You can specify the file to which you want to add the include file.
322 If you don't, it uses the last opened (active) file.
323
324 Keyword parameters:
325
326 include -- This is a single include to add into your database
327
328 at -- This is the name of database you want to add the include at,
329 If set to None (the default), I'll simply use the value of
330 'self.active_database', hoping for the best.
331 """
332 if not at:
333 at = self.active_database
334 if include not in self.get_includes(at):
335 super(Configuration, self).add_include(at, include)
336 # we take this opportunity to update the class cache we have
337 self.__schema__.update(self)
338 self.__schema__.update_dal(self)
340
341 def __str__(self):
342 return self.get_impl_spec() + \
343 ', %d classes loaded' % len(self.classes())
344
345 def __repr__(self):
346 return '<Configuration \'' + self.get_impl_spec() + '\'>'
347
348 def create_obj(self, class_name, uid, at=None):
349 """Creates a new ConfigObject, related with the database you specify.
350
351 Keyword arguments:
352
353 class_name -- This is the name of the OKS Class to be used for the
354 newly created object. It has to be amongst one of the classes returned
355 by the "classes()" method.
356
357 uid -- This is the UID of the object inside the OKS database.
358
359 at -- This is either the name of database you want to create the object
360 at, or another ConfigObject that will be used as a reference to
361 determine at which database to create the new object. If set to None
362 (the default), I'll simply use the value of 'self.active_database',
363 hoping for the best.
364 """
365 if not at:
366 at = self.active_database
367 obj = super(Configuration, self).create_obj(at, class_name, uid)
368 return ConfigObject.ConfigObject(obj, self.__schema__, self)
369
370 def get_obj(self, class_name, uid):
371 """Retrieves a ConfigObject you specified.
372
373 Keyword arguments:
374
375 class_name -- This is the name of the OKS Class to be used for the
376 search. It has to be amongst one of the classes returned by the
377 "classes()" method.
378
379 uid -- This is the UID of the object inside the OKS database.
380
381 """
382 obj = super(Configuration, self).get_obj(class_name, uid)
383 return ConfigObject.ConfigObject(obj, self.__schema__, self)
384
385 def add_dal(self, dal_obj, at=None, cache=None, recurse=True):
386 """Updates the related ConfigObject in the database using a DAL
387 reflection.
388
389 This method will take the properties of the DAL object passed as
390 parameter and will try to either create or update the relevant
391 ConfigObject in the database, at the file specified. It does this
392 recursively, in colaboration with the ConfigObject class.
393
394 Keyword arguments:
395
396 dal_obj -- This is the DAL object that will be used for the operation.
397 It should be a reflection of the ConfigObject you want to create.
398
399 at -- This is either the name of database you want to create the object
400 at, or another ConfigObject that will be used as a reference to
401 determine at which database to create the new object. If set to None
402 (the default), I'll simply use the value of 'self.active_database',
403 hoping for the best.
404
405 cache -- This is a cache that may be set by the Configuration object if
406 necessary. Users should *never* set this variable. This variable is
407 there to handle recursions gracefully.
408
409 recurse -- This is a boolean flag that indicates if you want to enable
410 recursion or not in the update. If set to 'True' (the default), I'll
411 recurse until all objects in the tree that do *not* exist yet in the
412 database are created (existing objects are gracefully ignored).
413 Otherwise, I'll not recurse at all and just make sure the attributes
414 and relationships of the object passed as parameter are set to what you
415 determine they should be. Please note that if you decide to update
416 relationships, that the objects to which you are pointing to should be
417 available in the database (directly or indirectly through includes) if
418 you choose to do this non-recursively.
419
420 Returns the ConfigObject you wanted to create or update, that you may
421 ignore for practical purposes.
422 """
423 if not cache:
424 cache = {}
425
426 if self.test_object(dal_obj.className(), dal_obj.id, 0, []):
427 obj = super(Configuration, self).get_obj(
428 dal_obj.className(), dal_obj.id)
429 co = ConfigObject.ConfigObject(obj, self.__schema__, self)
430 cache[dal_obj.fullName()] = co
431 self.__update_cache__([dal_obj])
432
433 else:
434 if not at:
435 at = self.active_database
436 obj = super(Configuration, self) \
437 .create_obj(at, dal_obj.className(),
438 dal_obj.id)
439 co = ConfigObject.ConfigObject(obj, self.__schema__, self)
440 cache[dal_obj.fullName()] = co
441 co.update_dal(dal_obj, self.add_dal, self.get_obj, cache=cache,
442 recurse=recurse)
443 self.__update_cache__([dal_obj])
444
445 return co
446
447 def update_dal(self, dal_obj, ignore_error=True, at=None, cache=None,
448 recurse=False):
449 """Updates the related ConfigObject in the database using a DAL
450 reflection.
451
452 This method will take the properties of the DAL object passed as
453 parameter and will try to either create or update the relevant
454 ConfigObject in the database, at the file specified. It does this
455 recursively, in colaboration with the ConfigObject class.
456
457 Keyword arguments:
458
459 dal_obj -- This is the DAL object that will be used for the operation.
460 It should be a reflection of the ConfigObject you want to create.
461
462 ignore_error -- This flag will make me ignore errors related to the
463 update of objects in this database. It is useful if you want to
464 overwrite as much as you can and leave the other objects which you
465 cannot write to untouched.
466 Otherwise, objects you cannot touch (write permissions or other
467 problems), when tried to be set, will raise a 'ValueError'.
468
469 at -- This is either the name of database you want to create the
470 object at, or another ConfigObject that will be used as a reference to
471 determine at which database to create the new object. If set to None
472 (the default), I'll simply use the value of 'self.active_database',
473 hoping for the best.
474
475 cache -- This is a cache that may be set by the Configuration object if
476 necessary. Users should *never* set this variable. This variable is
477 there to handle recursions gracefully.
478
479 recurse -- If this flag is set, the update will recurse until all
480 objects linked from the given object are updated. This encompasses
481 'include-file' objects. The default is a safe "False". Please,
482 understand the impact of what you are doing before setting this to
483 'True'.
484
485 Returns the ConfigObject you wanted to create or update, that you may
486 ignore for practical purposes.
487 """
488 if not cache:
489 cache = {}
490 co_func = self.update_dal_permissive
491 if not ignore_error:
492 co_func = self.update_dal_pedantic
493
494 if hasattr(dal_obj, '__old_id'):
495 old_id = getattr(dal_obj, '__old_id')
496 if old_id != dal_obj.id:
497 old = super(Configuration, self).get_obj(
498 dal_obj.className(), old_id)
499 if old:
500 old.rename(dal_obj.id)
501 delattr(dal_obj, '__old_id')
502
503 if self.test_object(dal_obj.className(), dal_obj.id, 0, []):
504 obj = super(Configuration, self).get_obj(
505 dal_obj.className(), dal_obj.id)
506 co = ConfigObject.ConfigObject(obj, self.__schema__, self)
507 cache[dal_obj.fullName()] = co
508 try:
509 co.update_dal(dal_obj, co_func, self.get_obj, cache=cache,
510 recurse=recurse)
511 except ValueError as e:
512 if not ignore_error:
513 raise
514 else:
515 logging.warning(
516 'Ignoring error in setting %s: %s'
517 % (repr(co), str(e)))
518 self.__update_cache__([dal_obj])
519
520 else:
521 if not at:
522 at = self.active_database
523 obj = super(Configuration, self) \
524 .create_obj(at, dal_obj.className(),
525 dal_obj.id)
526 co = ConfigObject.ConfigObject(obj, self.__schema__, self)
527 cache[dal_obj.fullName()] = co
528 co.update_dal(dal_obj, co_func, self.get_obj, cache=cache,
529 recurse=recurse)
530 self.__update_cache__([dal_obj])
531
532 return co
533
534 def update_dal_permissive(self, dal_obj, at=None, cache=None,
535 recurse=False):
536 """Alias to update_dal() with ignore_error=True"""
537 return self.update_dal(dal_obj, True, at, cache, recurse)
538
539 def update_dal_pedantic(self, dal_obj, at=None, cache=None, recurse=False):
540 """Alias to update_dal() with ignore_error=False"""
541 return self.update_dal(dal_obj, False, at, cache, recurse)
542
543 def get_dal(self, class_name, uid):
544 """Retrieves a DAL reflection of a ConfigObject in this OKS database.
545
546 This method acts recursively, until all objects deriving from the
547 object you are retrieving have been returned. It is possible to reach
548 the python limit using this. If that is the case, make sure to extend
549 this limit with the following technique:
550
551 import sys
552 sys.setrecursionlimit(10000) # for example
553
554 Keyword arguments:
555
556 class_name -- The name of the OKS class of the object you are trying to
557 retrieve
558
559 uid -- This is the object's UID.
560
561 Returns DAL representations of object in this Configuration database.
562 """
563 if uid not in self.__cache__[class_name]:
564 obj = self.get_obj(class_name, uid)
565 obj.as_dal(self.__cache__)
566 return self.__cache__[class_name][uid]
567
568 def get_dals(self, class_name):
569 """Retrieves (multiple) DAL reflections of ConfigObjects in this
570 database.
571
572 This method acts recursively, until all objects deriving from the
573 object you are retrieving have been returned.
574
575 Keyword arguments:
576
577 class_name -- The name of the OKS class of the objects you are trying
578 to retrieve
579
580 Returns DAL representations of objects in this Configuration database.
581 """
582
583 for k in self.get_objs(class_name):
584 if k.UID() not in self.__cache__[class_name]:
585 k.as_dal(self.__cache__)
586 return list(self.__cache__[class_name].values())
587
588 def get_all_dals(self):
589 """Retrieves (multiple) DAL reflections of ConfigObjects in this
590 database.
591
592 This method acts recursively, until all objects deriving from the
593 object you are retrieving have been returned.
594
595 Returns DAL representations of objects in this Configuration database.
596 """
597 from .ConfigObject import ConfigObject as CO
598
599 # get the unique values existing in the cache (probably few)
600 retval = {}
601 for v in self.__cache__.values():
602 for k in v.values():
603 if k.fullName() not in retval:
604 retval[k.fullName()] = k
605
606 # put all objects in the cache and update the return list
607 for class_name in self.classes():
608 for k in super(Configuration,
609 self).get_objs(class_name,
610 '(this (object-id \"\" !=))'):
611 if k.UID() not in self.__cache__[class_name]:
612 j = CO(k, self.__schema__, self).as_dal(self.__cache__)
613 retval[j.fullName()] = j
614 else:
615 j = self.__cache__[class_name][k.UID()]
616 retval[j.fullName()] = j
617
618 return retval
619
620 def destroy_dal(self, dal_obj):
621 """Destroyes the Database counterpart of the DAL object given.
622
623 This method will destroy the equivalent ConfigObject reflection from
624 this Configuration object.
625
626 Keyword parameters:
627
628 dal_obj -- This is the DAL reflection of the object you want to delete.
629
630 """
631 if self.test_object(dal_obj.className(), dal_obj.id, 0, []):
632 obj = self.get_obj(dal_obj.className(), dal_obj.id)
633 self.destroy_obj(obj)
634 self.__delete_cache__((dal_obj,))
635
636 def destroy_obj(self, obj):
637 """Destroyes the given database object.
638
639 This method will destroy the given ConfigObject object.
640
641 """
642
643 # the C++ implementation of destroy_obj wants
644 # a libpyconffwk.ConfigObject instance. So
645 # we have to extract it from our proxy
646 return super(Configuration, self).destroy_obj(obj._obj)
__init__(self, connection='oksconflibs:')
update_dal(self, dal_obj, ignore_error=True, at=None, cache=None, recurse=False)
superclasses(self, class_name, all=False)
attributes(self, class_name, all=False)
create_obj(self, class_name, uid, at=None)
update_dal_permissive(self, dal_obj, at=None, cache=None, recurse=False)
create_db(self, db_name, includes)
add_include(self, include, at=None)
remove_include(self, include, at=None)
relations(self, class_name, all=False)
update_dal_pedantic(self, dal_obj, at=None, cache=None, recurse=False)
subclasses(self, class_name, all=False)
get_objs(self, class_name, query='')
__retrieve_cache__(self, class_name, id=None)