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