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