File indexing completed on 2026-04-10 08:39:16
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010 """
0011 The implementation of base data structure to host various settings collected
0012 from external source with built-in validation and schema translation support.
0013
0014 The main reasons for such incapsulation are to
0015 - apply in one place all data validation actions (for attributes and values)
0016 - introduce internal information schema (names of attribues) to remove dependency
0017 with data structrure, formats, names from external sources (e.g. AGIS/CRIC)
0018
0019
0020 :author: Alexey Anisenkov
0021 :contact: anisyonk@cern.ch
0022 :date: January 2018
0023 """
0024
0025 import ast
0026 import copy
0027 import logging
0028 logger = logging.getLogger(__name__)
0029
0030
0031 class BaseData(object):
0032 """
0033 High-level object to host structured data collected from external source
0034 It's considered to be like a bridge (connector) in order to remove direct dependency to
0035 external schema (format) implementation
0036 """
0037
0038 _keys = {}
0039
0040 def _load_data(self, data, kmap={}, validators=None):
0041 """
0042 Construct and initialize data from ext source.
0043
0044 :param data: input dictionary of raw data settings
0045 :param kmap: the translation map of data attributes from external format to internal schema
0046 :param validators: map of validation handlers to be applied
0047 """
0048
0049
0050
0051
0052
0053
0054
0055
0056
0057
0058
0059
0060
0061
0062 if validators is None:
0063
0064 validators = {int: self.clean_numeric,
0065 str: self.clean_string,
0066 bool: self.clean_boolean,
0067 dict: self.clean_dictdata,
0068
0069 None: self.clean_string,
0070 }
0071
0072 try:
0073 _items = list(self._keys.items())
0074 except Exception:
0075 _items = self._keys.iteritems()
0076 for ktype, knames in _items:
0077
0078 for kname in knames:
0079 raw, value = None, None
0080
0081 ext_names = kmap.get(kname) or kname
0082 try:
0083 if isinstance(ext_names, basestring):
0084 ext_names = [ext_names]
0085 except Exception:
0086 if isinstance(ext_names, str):
0087 ext_names = [ext_names]
0088
0089 for name in ext_names:
0090 raw = data.get(name)
0091 if raw is not None:
0092 break
0093
0094
0095 hvalidator = validators.get(ktype, validators.get(None))
0096 if callable(hvalidator):
0097 value = hvalidator(raw, ktype, kname, defval=copy.deepcopy(getattr(self, kname, None)))
0098
0099 hvalidator = getattr(self, 'clean__%s' % kname, None)
0100 if callable(hvalidator):
0101 value = hvalidator(raw, value)
0102
0103 setattr(self, kname, value)
0104
0105 self.clean()
0106
0107 def clean(self):
0108 """
0109 Validate and finally clean up required data values (required object properties) if need
0110 Executed once all fields have already passed field-specific validation checks
0111 Could be customized by child object
0112 :return: None
0113 """
0114 pass
0115
0116
0117
0118
0119 def clean_numeric(self, raw, ktype, kname=None, defval=0):
0120 """
0121 Clean and convert input value to requested numeric type
0122 :param raw: raw input data
0123 :param ktype: variable type to which result should be casted
0124 :param defval: default value to be used in case of cast error
0125 """
0126
0127 if isinstance(raw, ktype):
0128 return raw
0129
0130 try:
0131 if isinstance(raw, basestring):
0132 raw = raw.strip()
0133 except Exception:
0134 if isinstance(raw, str):
0135 raw = raw.strip()
0136
0137 try:
0138 return ktype(raw)
0139 except Exception:
0140 if raw is not None:
0141 logger.warning('failed to convert data for key=%s, raw=%s to type=%s, defval=%s' % (kname, raw, ktype, defval))
0142 return defval
0143
0144 def clean_string(self, raw, ktype, kname=None, defval=""):
0145 """
0146 Clean and convert input value to requested string type
0147 :param raw: raw input data
0148 :param ktype: variable type to which result should be casted
0149 :param defval: default value to be used in case of cast error
0150 """
0151
0152 if isinstance(raw, ktype):
0153 return raw
0154
0155 if raw is None:
0156 return defval
0157 else:
0158 try:
0159 if isinstance(raw, basestring):
0160 raw = raw.strip()
0161 except Exception:
0162 if isinstance(raw, str):
0163 raw = raw.strip()
0164 try:
0165 return ktype(raw)
0166 except Exception:
0167 logger.warning('failed to convert data for key=%s, raw=%s to type=%s' % (kname, raw, ktype))
0168 return defval
0169
0170 def clean_boolean(self, raw, ktype, kname=None, defval=None):
0171 """
0172 Clean and convert input value to requested boolean type
0173 :param raw: raw input data
0174 :param ktype: variable type to which result should be casted
0175 :param defval: default value to be used in case of cast error
0176 """
0177
0178 if isinstance(raw, ktype):
0179 return raw
0180
0181 if raw is None:
0182 return defval
0183
0184 val = str(raw).strip().lower()
0185 allowed_values = ['', 'none', 'true', 'false', 'yes', 'no', '1', '0']
0186
0187 if val not in allowed_values:
0188 logger.warning('failed to convert data for key=%s, raw=%s to type=%s' % (kname, raw, ktype))
0189 return defval
0190
0191 return val.lower() in ['1', 'true', 'yes']
0192
0193 def clean_dictdata(self, raw, ktype, kname=None, defval=None):
0194 """
0195 Clean and convert input value to requested dict type
0196 :param raw: raw input data
0197 :param ktype: variable type to which result should be casted
0198 :param defval: default value to be used in case of cast error
0199 """
0200
0201 if isinstance(raw, str):
0202 raw = ast.literal_eval(raw)
0203
0204 if isinstance(raw, ktype):
0205 return raw
0206
0207 elif raw is None:
0208 return defval
0209 try:
0210 return ktype(raw)
0211 except Exception:
0212 logger.warning('failed to convert data for key=%s, raw=%s to type=%s' % (kname, raw, ktype))
0213 return defval
0214
0215 def clean_listdata(self, raw, ktype, kname=None, defval=None):
0216 """
0217 Clean and convert input value to requested list type
0218 :param raw: raw input data
0219 :param ktype: variable type to which result should be casted
0220 :param defval: default value to be used in case of cast error
0221 """
0222
0223 if isinstance(raw, ktype):
0224 return raw
0225
0226 elif raw is None:
0227 return defval
0228 else:
0229 try:
0230 if isinstance(raw, basestring):
0231 raw = raw.split(',')
0232 except Exception:
0233 if isinstance(raw, str):
0234 raw = raw.split(',')
0235 try:
0236 return ktype(raw)
0237 except Exception:
0238 logger.warning('failed to convert data for key=%s, raw=%s to type=%s' % (kname, raw, ktype))
0239 return defval
0240
0241
0242
0243
0244
0245
0246
0247
0248 def __repr__(self):
0249 """
0250 Default representation of an object
0251 """
0252
0253 ret = []
0254 attrs = [key for key in dir(self) if not callable(getattr(self, key)) and not key.startswith('_')]
0255 for key in sorted(attrs):
0256 ret.append(" %s=%s" % (key, getattr(self, key)))
0257 ret.append('')
0258 return '\n'.join(ret)