DUNE-DAQ
DUNE Trigger and Data Acquisition software
Loading...
Searching...
No Matches
schema.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# Wed 24 Oct 2007 05:22:49 PM CEST
5
6"""A set of utilities to simplify OKS instrospection.
7"""
8import sys
9import re
10import logging
11from . import ConfigObject
12
13# all supported OKS types are described here
14oks_types = {}
15oks_types['bool'] = ['bool']
16oks_types['integer'] = ['s8', 'u8', 's16', 'u16', 's32']
17oks_types['long'] = ['u32', 's64', 'u64']
18oks_types['float'] = ['float', 'double']
19oks_types['int-number'] = oks_types['integer'] + oks_types['long']
20oks_types['number'] = \
21 oks_types['long'] + oks_types['integer'] + oks_types['float']
22oks_types['time'] = ['date', 'time']
23oks_types['string'] = oks_types['time'] + ['string', 'uid', 'enum', 'class']
24
25range_regexp = re.compile(
26 r'(?P<s1>-?0?x?[\da-fA-F]+(\.\d+)?)-(?P<s2>-?0?x?[\da-fA-F]+(\.\d+)?)')
27
28
30 """Decodes a range string representation, returns a tuple with 2 values.
31
32 This is the supported format in regexp representation:
33 '([-0x]*\\d+)\\D+-?\\d+'
34 """
35 if s.find('..') != -1:
36 # print 'range: %s => %s' % (s, s.split('..'))
37 return s.split('..')
38 k = range_regexp.match(s)
39 if k:
40 # print 'range: %s => %s' % (s, (k.group('s1'), k.group('s2')))
41 return (k.group('s1'), k.group('s2'))
42 # print 'value: %s' % s
43 return s
44
45
46def str2integer(v, t, max):
47 """Converts a value v to integer, irrespectively of its formatting.
48
49 If the number starts with a '0', we convert it using an octal
50 representation. Else, we try a decimal conversion. If any of these fail,
51 we try an hexa conversion before throwing a ValueError.
52
53 Keyword arguments:
54
55 v -- the value to be converted
56 t -- the python type (int or float) to use in the conversion
57 """
58 if isinstance(v, t):
59 return v
60 if not v:
61 return v
62 if isinstance(v, tuple) or isinstance(v, list):
63 return [str2integer(k, t) for k in v]
64 if isinstance(v, str):
65 if v[0] == '*':
66 return max
67 elif v[0] == '0':
68 try:
69 return t(v, 8)
70 except ValueError:
71 return t(v, 16)
72 else:
73 try:
74 return t(v)
75 except ValueError:
76 return t(v, 16)
77 else:
78 return t(v)
79
80
81def to_int(v): return str2integer(v, int, sys.maxsize)
82
83
84def to_long(v): return str2integer(v, int, 0xffffffffffffffff)
85
86
87def check_range(v, range, range_re, pytype):
88 """Checks the range of the value 'v' to make sure it is inside."""
89 if isinstance(v, list):
90 for k in v:
91 check_range(k, range, range_re, pytype)
92 return
93
94 in_range = False
95 if type(v) == str and range_re is not None:
96 if range_re.match(v):
97 in_range = True
98 else:
99 for k in range:
100 if isinstance(k, tuple):
101 if v >= k[0] and v <= k[1]:
102 in_range = True
103 else:
104 if v == k:
105 in_range = True
106
107 if not in_range:
108 raise ValueError('Value %s is not in range %s' % (v, range))
109
110
111def check_relation(v, rel):
112 """Checks the value v against the relationship parameters in 'rel'."""
113 from conffwk.dal import DalBase
114
115 if not isinstance(v, DalBase):
116 raise ValueError('Relationships should be DAL objects, but %s is not' %
117 repr(v))
118
119 # check type
120 if rel['type'] not in v.oksTypes():
121 raise ValueError('Object %s is not of type or subtype %s' %
122 (repr(v), rel['type']))
123
124
125def check_cardinality(v, prop):
126 """Checks the cardinality of a certain attribute or relationship."""
127
128 # check cardinality
129 if prop['multivalue'] and not isinstance(v, list):
130 raise ValueError('Multivalued properties must be python lists')
131 elif not prop['multivalue'] and isinstance(v, list):
132 raise ValueError(
133 'Single valued properties cannot be set with python lists')
134
135
136def coerce(v, attr):
137 """Coerces the input value 'v' in the way the attribute expects."""
138
139 # coerce if necessary
140 if type(v) != attr['python_class']:
141 # coerce in this case
142 if attr['python_class'] in [int, int]:
143
144 if isinstance(v, str):
145 if len(v) == 0:
146 raise ValueError(
147 'Integer number cannot be assigned an empty string')
148 if v[0] == '0':
149 try:
150 return attr['python_class'](v, 8)
151 except ValueError:
152 return attr['python_class'](v, 16)
153 else:
154 try:
155 return attr['python_class'](v)
156 except ValueError:
157 return attr['python_class'](v, 16)
158 else:
159 v = attr['python_class'](v)
160
161 elif attr['python_class'] is bool:
162 if isinstance(v, str):
163 if v in ['0', 'false']:
164 v = False
165 else:
166 v = True
167 else:
168 v = attr['python_class'](v)
169
170 else:
171 v = attr['python_class'](v)
172
173 # check the range of each item, if a range was set
174 if attr['range']:
175 check_range(v, attr['range'], attr['range_re'], attr['python_class'])
176
177 # for special types, do a special check
178 if attr['type'] == 'class':
179 # class references must exist in the DAL the time I set it
180 from .dal import __dal__
181 check_range(v, list(__dal__.keys()),
182 attr['range_re'], attr['python_class'])
183
184 elif attr['type'] == 'date':
185 import time
186 try:
187 time.strptime(v, '%Y-%m-%d')
188 except ValueError as e:
189 try:
190 time.strptime(v, '%d/%m/%y')
191 except ValueError as e:
192 try:
193 time.strptime(v, '%Y-%b-%d')
194 except ValueError as e:
195 raise ValueError(
196 'Date types should have the format '
197 'dd/mm/yy or yyyy-mm-dd or yyyy-mon-dd: %s' % v)
198
199 elif attr['type'] == 'time':
200 import time
201 try:
202 time.strptime(v, '%Y-%m-%d %H:%M:%S')
203 except ValueError as e:
204 try:
205 time.strptime(v, '%d/%m/%y %H:%M:%S')
206 except ValueError as e:
207 try:
208 time.strptime(v, '%Y-%b-%d %H:%M:%S')
209 except ValueError as e:
210 raise ValueError(
211 'Time types should have the format dd/mm/yy HH:MM:SS '
212 'or yyyy-mm-dd HH:MM:SS or yyyy-mon-dd HH:MM:SS:'
213 + str(e))
214
215 return v
216
217
218def map_coercion(class_name, schema):
219 """Given a schema of a class, maps coercion functions from libpyconffwk."""
220
222
223 schema['mapping'] = {}
224
225 for k, v in list(schema['attribute'].items()):
226 typename = v['type']
227 getname = v['type']
228 if getname in oks_types['string']:
229 getname = 'string'
230 if v['multivalue']:
231 typename += '_vec'
232 getname += '_vec'
233 v['co_get_method'] = getattr(cls, 'get_' + getname)
234 v['co_set_method'] = getattr(cls, 'set_' + typename)
235
236 else:
237 v['co_get_method'] = getattr(cls, 'get_' + getname)
238 v['co_set_method'] = getattr(cls, 'set_' + typename)
239
240 if v['type'] in oks_types['string']:
241 v['python_class'] = str
242 elif v['type'] in oks_types['bool']:
243 v['python_class'] = bool
244 elif v['type'] in oks_types['integer']:
245 v['python_class'] = to_int
246 elif v['type'] in oks_types['long']:
247 v['python_class'] = to_long
248 elif v['type'] in oks_types['float']:
249 v['python_class'] = float
250
251 # split and coerce ranges
252 v['range_re'] = None
253 if v['range']:
254 if v['type'] == 'string':
255 v['range_re'] = re.compile(v['range'])
256 else:
257 v['range'] = [decode_range(j) for j in v['range'].split(',')]
258 for j in range(len(v['range'])):
259 if isinstance(v['range'][j], str):
260 v['range'][j] = v['python_class'](v['range'][j])
261 elif len(v['range'][j]) == 1:
262 v['range'][j] = v['python_class'](v['range'][j][0])
263 else: # len(v['range'][j]) == 2 (the only other case)
264 v['range'][j] = (v['python_class'](v['range'][j][0]),
265 v['python_class'](v['range'][j][1]))
266
267 # integer numbers have implicit ranges and it is better to check
268 elif v['type'] in oks_types['int-number']:
269 if v['type'] == 's8':
270 v['range'] = [(-2**7, (2**7)-1)]
271 if v['type'] == 'u8':
272 v['range'] = [(0, (2**8)-1)]
273 if v['type'] == 's16':
274 v['range'] = [(-2**15, (2**15)-1)]
275 if v['type'] == 'u16':
276 v['range'] = [(0, (2**16)-1)]
277 if v['type'] == 's32':
278 v['range'] = [(-2**31, (2**31)-1)]
279 if v['type'] == 'u32':
280 v['range'] = [(0, (2**32)-1)]
281 if v['type'] == 's64':
282 v['range'] = [(-2**63, (2**63)-1)]
283 if v['type'] == 'u64':
284 v['range'] = [(0, (2**64)-1)]
285
286 # coerce initial values
287 if v['init-value']:
288 try:
289 if v['type'] in oks_types['string']:
290 pass
291 elif v['multivalue']:
292 v['init-value'] = [coerce(j, v)
293 for j in v['init-value'].split(',')]
294 else:
295 v['init-value'] = coerce(v['init-value'], v)
296 except ValueError as e:
297 logging.warning('Initial value of "%s.%s" could not be '
298 'coerced: %s' %
299 (class_name, k, e))
300
301 # if the type is a date or time type, and there is not default,
302 # set "now"
303 if not v['init-value'] and v['type'] == 'date':
304 import datetime
305 v['init-value'] == datetime.date.today().isoformat()
306
307 # if the type is a date or time type, and there is not default,
308 # set "now"
309 if not v['init-value'] and v['type'] == 'time':
310 import datetime
311 now = datetime.datetime.today()
312 now.replace(microsecond=0)
313 v['init-value'] == now.isoformat(sep=' ')
314
315 for v in list(schema['relation'].values()):
316 if v['multivalue']:
317 v['co_get_method'] = getattr(cls, 'get_objs')
318 v['co_set_method'] = getattr(cls, 'set_objs')
319
320 else:
321 v['co_get_method'] = getattr(cls, 'get_obj')
322 v['co_set_method'] = getattr(cls, 'set_obj')
323
324 return schema
325
326
327class Cache(object):
328 """Defines a cache for all known schemas at a certain time.
329 """
330
331 def __init__(self, conffwk, all=True):
332 """Initializes the cache with information from the Configuration
333 object.
334
335 This method will browse for all declared classes in the Configuration
336 object given as input and will setup the schema for all known classes.
337 After this you can still update the cache using the update() method.
338
339 Keyword parameters:
340
341 conffwk -- The conffwk.Configuration object to use as base for the
342 current cache.
343
344 all -- A boolean indicating if I should store all the attributes and
345 relations from a certain class or just the ones directly associated
346 with a class.
347 """
348 self.data = {}
349 self.all = all
350 self.update(conffwk)
351
352 def update(self, conffwk):
353 """Updates this cache with information from the Configuration object.
354
355 This method will add new classes not yet know to this cache. Classes
356 with existing names will not be added. No warning is generated (this
357 should be done by the OKS layer in any case.
358 """
359 for k in conffwk.classes():
360 if k in list(self.data.keys()):
361 continue
362 self.data[k] = {}
363 self.data[k]['attribute'] = conffwk.attributes(k, self.all)
364 self.data[k]['relation'] = conffwk.relations(k, self.all)
365 self.data[k]['superclass'] = conffwk.superclasses(k, self.all)
366 self.data[k]['subclass'] = conffwk.subclasses(k, self.all)
367 map_coercion(k, self.data[k])
368
369 def update_dal(self, conffwk):
370 """Updates this cache with information for DAL.
371
372 This method will add new DAL classes not yet know to this cache.
373 Classes with existing DAL representations will not be touched.
374 """
375 from .dal import generate
376
377 # generate
378 klasses = generate(conffwk, [self.data[k]['dal'] for k
379 in list(self.data.keys())
380 if 'dal' in self.data[k]])
381 # associate
382 for k in klasses:
383 self.data[k.pyclassName()]['dal'] = k
384
385 def __getitem__(self, key):
386 """Gets the description of a certain class."""
387 return self.data[key]
388
389 def __str__(self):
390 """Prints a nice display of myself"""
391 return '%s: %d classes loaded' % \
392 (self.__class__.__name__, len(self.data)) + '\n' + \
393 str(list(self.data.keys()))
update(self, conffwk)
Definition schema.py:352
__init__(self, conffwk, all=True)
Definition schema.py:331
__getitem__(self, key)
Definition schema.py:385
update_dal(self, conffwk)
Definition schema.py:369
str2integer(v, t, max)
Definition schema.py:46
decode_range(s)
Definition schema.py:29
coerce(v, attr)
Definition schema.py:136
check_relation(v, rel)
Definition schema.py:111
check_range(v, range, range_re, pytype)
Definition schema.py:87
check_cardinality(v, prop)
Definition schema.py:125
map_coercion(class_name, schema)
Definition schema.py:218