The Gaudi Framework  v33r2 (a6f0ec87)
semantics.py
Go to the documentation of this file.
1 
11 from __future__ import absolute_import
12 import sys
13 import re
14 import logging
15 try:
16  from collections.abc import MutableSequence, MutableMapping
17 except ImportError: # pragma no cover
18  # Python 2 version
19  from collections import MutableSequence, MutableMapping
20 
21 if sys.version_info >= (3, ): # pragma no cover
22  basestring = str
23 
24 _log = logging.getLogger(__name__)
25 is_64bits = sys.maxsize > 2**32
26 
27 
28 class PropertySemantics(object):
29  '''
30  Basic property semantics implementation, with no validation/transformation.
31  '''
32  __handled_types__ = (re.compile(r'.*'), )
33 
34  def __init__(self, cpp_type, name=None):
35  self.name = None
36  self.cpp_type = cpp_type
37 
38  @property
39  def cpp_type(self):
40  return self._cpp_type
41 
42  @cpp_type.setter
43  def cpp_type(self, value):
44  if not any(
45  h.match(value) if hasattr(h, 'match') else h == value
46  for h in self.__handled_types__):
47  raise TypeError('C++ type {!r} not supported'.format(value))
48  self._cpp_type = value
49 
50  def load(self, value):
51  '''
52  Transformation for data when reading the property.
53  '''
54  return value
55 
56  def store(self, value):
57  '''
58  Validation/transformation of the data to be stored.
59  '''
60  return value
61 
62  def is_set(self, value):
63  '''
64  Allow overriding the definition of "is set" if we need helper types.
65  '''
66  return True
67 
68  def opt_value(self, value):
69  '''
70  Option string version of value.
71  '''
72  if hasattr(value, '__opt_value__'):
73  return value.__opt_value__()
74  return value
75 
76  def merge(self, a, b):
77  '''
78  "Merge" two values.
79 
80  Used when merging two Configurable instances, by default just ensure
81  the two values do not conflict, but it can be overridden in
82  derived semantics to, for example, append to the two lists.
83  '''
84  if self.store(a) != self.store(b):
85  raise ValueError('cannot merge values %r and %r' % (a, b))
86  return a
87 
88 
89 DefaultSemantics = PropertySemantics
90 
91 
93  __handled_types__ = ('std::string', )
94 
95  def store(self, value):
96  if not isinstance(value, basestring):
97  raise ValueError('cannot set property {} to {!r}'.format(
98  self.name, value))
99  return value
100 
101 
103  __handled_types__ = ('bool', )
104 
105  def store(self, value):
106  return bool(value)
107 
108 
110  __handled_types__ = ('float', 'double')
111 
112  def store(self, value):
113  from numbers import Number
114  if not isinstance(value, Number):
115  raise TypeError(
116  'number expected, got {!r} in assignemnt to {}'.format(
117  value, self.name))
118  return float(value)
119 
120 
122  # dictionary generated with tools/print_limits.cpp
123  INT_RANGES = {
124  'signed char': (-128, 127),
125  'short': (-32768, 32767),
126  'int': (-2147483648, 2147483647),
127  'long': ((-9223372036854775808, 9223372036854775807) if is_64bits else
128  (-2147483648, 2147483647)),
129  'long long': (-9223372036854775808, 9223372036854775807),
130  'unsigned char': (0, 255),
131  'unsigned short': (0, 65535),
132  'unsigned int': (0, 4294967295),
133  'unsigned long': (0,
134  18446744073709551615 if is_64bits else 4294967295),
135  'unsigned long long': (0, 18446744073709551615),
136  }
137 
138  __handled_types__ = tuple(INT_RANGES)
139 
140  def store(self, value):
141  from numbers import Number
142  if not isinstance(value, Number):
143  raise TypeError(
144  'number expected, got {!r} in assignemnt to {}'.format(
145  value, self.name))
146  v = int(value)
147  if v != value:
148  _log.warning('converted %s to %d in assignment to %s', value, v,
149  self.name)
150  min_value, max_value = self.INT_RANGES[self.cpp_type]
151  if v < min_value or v > max_value:
152  raise ValueError('value {} outside limits for {!r} {}'.format(
153  v, self.cpp_type, self.INT_RANGES[self.cpp_type]))
154  return v
155 
156 
158  __handled_types__ = ('Algorithm', 'Auditor',
159  re.compile(r'AlgTool(:I[a-zA-Z0-9_]*)*$'),
160  re.compile(r'Service(:I[a-zA-Z0-9_]*)*$'))
161 
162  def __init__(self, cpp_type, name=None):
163  super(ComponentSemantics, self).__init__(cpp_type, name)
164  self.interfaces = cpp_type.split(':')
165  self.cpp_type = self.interfaces.pop(0)
166  self.interfaces = set(self.interfaces)
167 
168  def store(self, value):
169  from . import Configurable, Configurables
170  if isinstance(value, Configurable):
171  value.name # make sure the configurable has a name
172  elif isinstance(value, basestring):
173  # try to map the sring to an existing Configurable
174  if value in Configurable.instances:
175  value = Configurable.instances[value]
176  else:
177  # or create one from type and name
178  if '/' in value:
179  t, n = value.split('/')
180  else:
181  t = n = value
182  value = Configurables.getByType(t).getInstance(n)
183  else:
184  raise TypeError(
185  'cannot assign {!r} to {!r}, requested string or {!r}'.format(
186  value, self.name, self.cpp_type))
187  if value.__component_type__ != self.cpp_type:
188  raise TypeError(
189  'wrong type for {!r}: expected {!r}, got {!r}'.format(
190  self.name, self.cpp_type, value.__component_type__))
191  try:
192  # if no interface is declared we cannot check
193  if value.__interfaces__:
194  if not self.interfaces.issubset(value.__interfaces__):
195  raise TypeError(
196  'wrong interfaces for {!r}: required {}'.format(
197  self.name, list(self.interfaces)))
198  except AttributeError:
199  pass # no interfaces declared by the configrable, cannot check
200  return value
201 
202  def default(self, value):
203  return self.store(value)
204 
205 
206 def extract_template_args(cpp_type):
207  '''
208  Return an iterator over the list of template arguments in a C++ type
209  string.
210 
211  >>> t = 'map<string, vector<int, allocator<int> >, allocator<v<i>, a<i>> >'
212  >>> list(extract_template_args(t))
213  ['string', 'vector<int, allocator<int> >', 'allocator<v<i>, a<i>>']
214  >>> list(extract_template_args('int'))
215  []
216  '''
217  template_level = 0
218  arg_start = -1
219  for p, c in enumerate(cpp_type):
220  if c == ',':
221  if template_level == 1:
222  yield cpp_type[arg_start:p].strip()
223  arg_start = p + 1
224  elif c == '<':
225  template_level += 1
226  if template_level == 1:
227  arg_start = p + 1
228  elif c == '>':
229  template_level -= 1
230  if template_level == 0:
231  yield cpp_type[arg_start:p].strip()
232 
233 
234 class _ListHelper(MutableSequence):
235  def __init__(self, semantics):
236  self.value_semantics = semantics
237  self.default = None
238  self._data = []
239  self.is_dirty = False
240 
241  @property
242  def data(self):
243  return self._data if self.is_dirty else self.default
244 
245  def __len__(self):
246  return len(self.data)
247 
248  def __getitem__(self, key):
249  return self.value_semantics.load(self.data.__getitem__(key))
250 
251  def __setitem__(self, key, value):
252  self.is_dirty = True
253  self.data.__setitem__(key, self.value_semantics.store(value))
254 
255  def __delitem__(self, key):
256  if not self.is_dirty:
257  raise RuntimeError('cannot remove elements from the default value')
258  self.data.__delitem__(key)
259 
260  def __eq__(self, other):
261  return self.data == other.data
262 
263  def __ne__(self, other):
264  return self.data != other.data
265 
266  def insert(self, key, value):
267  self.is_dirty = True
268  self.data.insert(key, self.value_semantics.store(value))
269 
270  def append(self, value):
271  self.is_dirty = True
272  self.data.append(self.value_semantics.store(value))
273 
274  def extend(self, iterable):
275  self.is_dirty = True
276  self.data.extend(
277  self.value_semantics.store(value) for value in iterable)
278 
279  def opt_value(self):
280  return [self.value_semantics.opt_value(item) for item in self.data]
281 
282  def __repr__(self):
283  return repr(self.data)
284 
285 
287  __handled_types__ = (re.compile(r'(std::)?(vector|list)<.*>$'), )
288 
289  def __init__(self, cpp_type, name=None, valueSem=None):
290  super(SequenceSemantics, self).__init__(cpp_type, name)
291  self.value_semantics = valueSem or getSemanticsFor(
292  list(extract_template_args(cpp_type))[0])
293 
294  def store(self, value):
295  new_value = _ListHelper(self.value_semantics)
296  new_value.extend(value)
297  return new_value
298 
299  def default(self, value):
300  new_value = _ListHelper(self.value_semantics)
301  new_value.default = value
302  return new_value
303 
304  def opt_value(self, value):
305  '''
306  Option string version of value.
307  '''
308  if not isinstance(value, _ListHelper):
309  value = self.default(value)
310  return value.opt_value()
311 
312 
314  '''
315  Extend the sequence-semantics with a merge-method to behave like a
316  OrderedSet: Values are unique but the order is maintained.
317  Use 'OrderedSet<T>' as fifth parameter of the Gaudi::Property<T> constructor
318  to invoke this merging method.
319  '''
320  __handled_types__ = (re.compile(r"^OrderedSet<.*>$"), )
321 
322  def __init__(self, cpp_type, name=None):
323  super(OrderedSetSemantics, self).__init__(cpp_type, name)
324 
325  def merge(self, bb, aa):
326  for b in bb:
327  if b not in aa:
328  aa.append(b)
329  return aa
330 
331 
332 class _DictHelper(MutableMapping):
333  def __init__(self, key_semantics, value_semantics):
334  self.key_semantics = key_semantics
335  self.value_semantics = value_semantics
336  self.default = None
337  self._data = {}
338  self.is_dirty = False
339 
340  @property
341  def data(self):
342  return self._data if self.is_dirty else self.default
343 
344  def __len__(self):
345  return len(self.data)
346 
347  def __getitem__(self, key):
348  return self.value_semantics.load(
349  self.data.__getitem__(self.key_semantics.store(key)))
350 
351  def __setitem__(self, key, value):
352  self.is_dirty = True
353  self.data.__setitem__(
354  self.key_semantics.store(key), self.value_semantics.store(value))
355 
356  def __delitem__(self, key):
357  if not self.is_dirty:
358  raise RuntimeError('cannot remove elements from the default value')
359  self.data.__delitem__(self.key_semantics.store(key))
360 
361  def __iter__(self):
362  for key in self.data:
363  yield self.key_semantics.load(key)
364 
365  def keys(self):
366  return list(self)
367 
368  def items(self):
369  for key, value in self.data.items():
370  yield (self.key_semantics.load(key),
371  self.value_semantics.load(value))
372 
373  def values(self):
374  for value in self.data.values():
375  yield self.value_semantics.load(value)
376 
377  def __contains__(self, key):
378  return self.key_semantics.store(key) in self.data
379 
380  def get(self, key, default=None):
381  key = self.key_semantics.store(key)
382  if key in self.data:
383  return self.value_semantics.load(self.data[key])
384  return default
385 
386  # __contains__, , get, __eq__, __ne__
387  # popitem, clear, setdefault
388 
389  def update(self, otherMap):
390  self.is_dirty = True
391  for key, value in otherMap.items():
392  self.data[self.key_semantics.store(
393  key)] = self.value_semantics.store(value)
394 
395  def opt_value(self):
396  return {
397  self.key_semantics.opt_value(key):
398  self.value_semantics.opt_value(value)
399  for key, value in self.data.items()
400  }
401 
402  def __repr__(self):
403  return repr(self.data)
404 
405 
407  __handled_types__ = (re.compile(r'(std::)?(unordered_)?map<.*>$'), )
408 
409  def __init__(self, cpp_type, name=None):
410  super(MappingSemantics, self).__init__(cpp_type, name)
411  template_args = list(extract_template_args(cpp_type))
412  self.key_semantics = getSemanticsFor(template_args[0])
413  self.value_semantics = getSemanticsFor(template_args[1])
414 
415  def store(self, value):
416  new_value = _DictHelper(self.key_semantics, self.value_semantics)
417  new_value.update(value)
418  return new_value
419 
420  def default(self, value):
421  new_value = _DictHelper(self.key_semantics, self.value_semantics)
422  new_value.default = value
423  return new_value
424 
425  def opt_value(self, value):
426  '''
427  Option string version of value.
428  '''
429  if not isinstance(value, _DictHelper):
430  value = self.default(value)
431  return value.opt_value()
432 
433 
434 SEMANTICS = [
435  c for c in globals().values() if isinstance(c, type)
436  and issubclass(c, PropertySemantics) and c is not PropertySemantics
437 ]
438 
439 
440 def getSemanticsFor(cpp_type, name=None):
441  for semantics in SEMANTICS:
442  try:
443  return semantics(cpp_type, name)
444  except TypeError:
445  pass
446  return DefaultSemantics(cpp_type, name)
def __init__(self, cpp_type, name=None)
Definition: semantics.py:34
GAUDI_API std::string format(const char *,...)
MsgStream format utility "a la sprintf(...)".
Definition: MsgStream.cpp:119
def getSemanticsFor(cpp_type, name=None)
Definition: semantics.py:440
def get(self, key, default=None)
Definition: semantics.py:380
def __setitem__(self, key, value)
Definition: semantics.py:351
def __init__(self, cpp_type, name=None)
Definition: semantics.py:162
def insert(self, key, value)
Definition: semantics.py:266
def update(self, otherMap)
Definition: semantics.py:389
def __init__(self, semantics)
Definition: semantics.py:235
def extend(self, iterable)
Definition: semantics.py:274
def extract_template_args(cpp_type)
Definition: semantics.py:206
def __setitem__(self, key, value)
Definition: semantics.py:251
def __init__(self, cpp_type, name=None)
Definition: semantics.py:409
def __init__(self, cpp_type, name=None)
Definition: semantics.py:322
def __init__(self, key_semantics, value_semantics)
Definition: semantics.py:333
def __init__(self, cpp_type, name=None, valueSem=None)
Definition: semantics.py:289