The Gaudi Framework  master (76fc3702)
Loading...
Searching...
No Matches
_db.py
Go to the documentation of this file.
12import logging
13import os
14import sys
15
16
17class ConfDB2(object):
18 def __init__(self):
19 import shelve
20 from pathlib import Path
21
22 from GaudiPluginService.cpluginsvc import GAUDI_DEFAULT_PLUGIN_PATH
23
24 self._dbs = {}
25 ignored_files = set(os.environ.get("CONFIGURABLE_DB_IGNORE", "").split(","))
26 for path in GAUDI_DEFAULT_PLUGIN_PATH:
27 if not path or not os.path.isdir(path):
28 continue
29 dbfiles = [
30 f.absolute().as_posix()
31 for f in Path(path).glob("*.confdb2")
32 if f.absolute().as_posix() not in ignored_files
33 ]
34 dbfiles.sort()
35 for db in [shelve.open(f, "r") for f in dbfiles]:
36 for key in db:
37 self._dbs.setdefault(key, db)
38
39 def __getitem__(self, key):
40 return self._dbs[key][key]
41
42 def __contains__(self, key):
43 return key in self._dbs
44
45 def __iter__(self):
46 return iter(self._dbs)
47
48
49# allow overriding the low level DB access for testing
50if "GAUDICONFIG2_DB" in os.environ:
51 exec(
52 "from {} import {} as _DB".format(*os.environ["GAUDICONFIG2_DB"].rsplit(".", 1))
53 )
54else: # pragma no cover
55 _DB = ConfDB2()
56
57
58_TRANS_TABLE = str.maketrans("<>&*,: ().", "__rp__s___")
59
60
62 """
63 Translate a C++ type name (with templates etc.) to Python identifier.
64 """
65 return name.replace(", ", ",").translate(_TRANS_TABLE)
66
67
68def split_namespace(typename):
69 """
70 Split a C++ qualified namespace in the tuple (top_namespace, rest) starting
71 searching the separator from pos.
72
73 >>> split_namespace('std::chrono::time_point')
74 ('std', 'chrono::time_point')
75 """
76 # find next namespace separator skipping template arguments
77 tpos = typename.find("<")
78 pos = typename.find("::")
79 # '<' can appear only in a class name, if we find a '::'
80 # earlier than a '<', then we got a namespace, else a class
81 if pos > 0 and (tpos < 0 or pos < tpos):
82 head = typename[:pos]
83 tail = typename[pos + 2 :]
84 else:
85 head = None
86 tail = typename
87 return (head, tail)
88
89
90class ConfigurablesDB(object):
91 """
92 Helper to expose Configurables classes (from Configurables database) as
93 a tree of subpackages, each mapped to a namespace.
94 """
95
96 def __init__(self, modulename, root=None):
97 """
98 @param modulename: name of the module
99 @param root: name of the root modules (that pointing to the root C++
100 namespace), None is ewuivalent to pass modulename as root
101 """
102 self._log = logging.getLogger(modulename)
103 self._log.debug("initializing module %r (with root %r)", modulename, root)
104
105 self.__name__ = modulename
106 self.__loader__ = None
107 self._root = root or modulename
108 assert (not root) or modulename.startswith(
109 root + "."
110 ), "modulename should be (indirect submodule of root)"
111
112 self._namespace = modulename[len(root) + 1 :].replace(".", "::") if root else ""
113 self._log.debug("%r mapping namespace %r", modulename, self._namespace or "::")
114
115 self._namespaces, self._classes = self._getEntries()
116 self._alt_names = {}
117 for cname in self._classes:
118 alt_name = _normalize_cpp_type_name(cname)
119 if alt_name != cname:
120 self._alt_names[alt_name] = cname
121 if " " in cname: # allow matching of 'T<A, B>' a well as 'T<A,B>'
122 self._alt_names[cname.replace(" ", "")] = cname
123 self.__all__ = list(
124 self._namespaces.union(self._classes).union(self._alt_names)
125 )
126
127 sys.modules[modulename] = self
128
129 for submodule in self._namespaces:
130 setattr(
131 self,
132 submodule,
133 ConfigurablesDB(".".join([self.__name__, submodule]), self._root),
134 )
135
136 def _getEntries(self):
137 """
138 Extract from the Configurables DB the namespaces and classes names in
139 the namespace this instance represents.
140 """
141 self._log.debug("getting list of entries under %r", self._namespace)
142 prefix = self._namespace + "::" if self._namespace else ""
143 prefix_len = len(prefix)
144
145 namespaces = set()
146 classes = set()
147 for name in _DB:
148 if name.startswith(prefix):
149 head, tail = split_namespace(name[prefix_len:])
150 if head:
151 namespaces.add(head)
152 else:
153 classes.add(tail)
154
155 self._log.debug(
156 "found %d namespaces and %d classes", len(namespaces), len(classes)
157 )
158 return (namespaces, classes)
159
160 def __getattr__(self, name):
161 """
162 Helper to instantiate on demand Configurable classes.
163 """
164 if name in self._classes:
165 fullname = "::".join([self._namespace, name]) if self._namespace else name
166 from ._configurables import makeConfigurableClass
167
168 self._log.debug("generating %r (%s)", name, fullname)
169 entry = makeConfigurableClass(
170 name,
171 __module__=self.__name__,
172 __qualname__=name,
173 __cpp_type__=fullname,
174 **_DB[fullname],
175 )
176 elif name.replace(" ", "") in self._alt_names:
177 entry = getattr(self, self._alt_names[name.replace(" ", "")])
178 elif name == "__spec__": # pragma no cover
179 import importlib
180
181 entry = importlib.machinery.ModuleSpec(
182 name=self.__package__,
183 loader=self.__loader__,
184 )
185 elif name == "__package__": # pragma no cover
186 entry = self.__name__
187 else:
188 raise AttributeError(
189 "module {!r} has no attribute {!r}".format(self.__name__, name)
190 )
191 setattr(self, name, entry)
192 return entry
193
194 def getByType(self, typename):
195 """
196 Return a configurable from the fully qualified type name (relative to
197 the current namespace).any
198 """
199 head, tail = split_namespace(typename)
200 if head:
201 return getattr(self, head).getByType(tail)
202 else:
203 return getattr(self, tail)
GAUDI_API std::string format(const char *,...)
MsgStream format utility "a la sprintf(...)".
Definition MsgStream.cpp:93
__contains__(self, key)
Definition _db.py:42
__getitem__(self, key)
Definition _db.py:39
getByType(self, typename)
Definition _db.py:194
__init__(self, modulename, root=None)
Definition _db.py:96
_normalize_cpp_type_name(name)
Definition _db.py:61
split_namespace(typename)
Definition _db.py:68