Package gavo :: Package votable :: Module enc_binary
[frames] | no frames]

Source Code for Module gavo.votable.enc_binary

  1  """ 
  2  Binary VOTable encoding. 
  3  """ 
  4   
  5  #c Copyright 2008-2019, the GAVO project 
  6  #c 
  7  #c This program is free software, covered by the GNU GPL.  See the 
  8  #c COPYING file in the source distribution. 
  9   
 10   
 11  import datetime #noflake: used in generated code 
 12  import struct 
 13   
 14  from gavo import utils           #noflake: used by generated code 
 15  from gavo.utils import pgsphere  #noflake: used by generated code 
 16  from gavo.votable import coding  #noflake: used by generated code 
 17  from gavo.votable import common 
 18   
 19   
 20  floatNaN = struct.pack("!f", common.NaN) 
 21  doubleNaN = struct.pack("!d", common.NaN) 
 22   
 23   
24 -def _getArrayShapingCode(field, padder):
25 """returns common code for almost all array serialization. 26 27 Field must describe an array (as opposed to a single value). 28 29 padder must be python-source for whatever is used to pad 30 arrays that are too short. 31 """ 32 base = [ 33 "if val is None: val = []"] 34 if field.isMultiDim(): 35 # it's an nD array (n>1): flatten out all values 36 base.append("val = coding.ravel(val)") 37 if field.hasVarLength(): 38 return base+["tokens.append(struct.pack('!i', len(val)))"] 39 else: 40 return base+["val = coding.trim(val, %s, %s)"%( 41 field.getLength(), padder)]
42 43
44 -def _addNullvalueCode(field, nullvalue, src):
45 """adds code to let null values kick in a necessary. 46 47 nullvalue here has to be a ready-made *python* literal. Take care 48 when passing in user supplied values here. 49 """ 50 if nullvalue is None: 51 action = (" raise common.BadVOTableData('None passed for field" 52 " that has no NULL value', None, '%s', hint='Integers in VOTable" 53 " have no natural serializations for missing values. You need to" 54 " define one using values null to allow for NULL in integer columns')" 55 )%field.getDesignation() 56 else: 57 action = " tokens.append(%s)"%nullvalue 58 return [ 59 "if val is None:", 60 action, 61 "else:" 62 ]+common.indentList(src, " ")
63 64
65 -def _makeBooleanEncoder(field):
66 return [ 67 "if val is None:", 68 " tokens.append('?')", 69 "elif val:", 70 " tokens.append('1')", 71 "else:", 72 " tokens.append('0')", 73 ]
74 75
76 -def _makeBitEncoder(field, allowNULL=False):
77 # bits and bit arrays are just (possibly long) integers 78 # length may be None for var length. 79 length = field.getLength() 80 if allowNULL: 81 src = [ 82 "if val is None:" 83 " tokens.append('\\0\\0\\0\\0')",] 84 else: 85 src = [ 86 "if val is None:", 87 " raise common.BadVOTableData('Bits have no NULL value', None,", 88 " '%s')"%field.getDesignation(),] 89 90 src.extend([ 91 "else:", 92 " tmp = []", 93 " curByte, rest = val%256, val//256", 94 " while curByte:", 95 " tmp.append(chr(curByte))", 96 " curByte, rest = rest%256, rest//256", 97 " if not tmp:", # make sure we leave somthing even for 0 98 " tmp.append(chr(0))", 99 " tmp.reverse()",]) 100 101 if length!=1: # this not just a single bit 102 if length is None: # variable length: dump number of bits 103 src.extend([ 104 " tokens.append(struct.pack('!i', len(tmp)*8))"]) 105 else: # crop/expand as necesary 106 numBytes = int(length)//8+(not not int(length)%8) 107 src.extend([ 108 " if len(tmp)<%d: tmp = [chr(0)]*(%d-len(tmp))+tmp"%( 109 numBytes, numBytes), 110 " if len(tmp)>%d: tmp = tmp[-%d:]"%(numBytes, numBytes)]) 111 112 src.extend([ 113 " tokens.append(struct.pack('%ds'%len(tmp), ''.join(tmp)))"]) 114 return src
115 116
117 -def _generateFloatEncoderMaker(fmtCode, nullName):
118 def makeFloatEncoder(field): 119 return [ 120 "if val is None:", 121 " tokens.append(%s)"%nullName, 122 "else:", 123 " tokens.append(struct.pack('%s', val))"%fmtCode]
124 return makeFloatEncoder 125 126
127 -def _generateComplexEncoderMaker(fmtCode, singleNull):
128 def makeComplexEncoder(field): 129 return [ 130 "if val is None:", 131 " tokens.append(%s+%s)"%(singleNull, singleNull), 132 "else:", 133 " tokens.append(struct.pack('%s', val.real, val.imag))"%fmtCode]
134 return makeComplexEncoder 135 136
137 -def _generateIntEncoderMaker(fmtCode):
138 def makeIntEncoder(field): 139 nullvalue = coding.getNullvalue(field, int) 140 if nullvalue is not None: 141 nullvalue = repr(struct.pack(fmtCode, int(nullvalue))) 142 return _addNullvalueCode(field, nullvalue,[ 143 "tokens.append(struct.pack('%s', val))"%fmtCode])
144 return makeIntEncoder 145 146
147 -def _makeUnsignedByteEncoder(field):
148 # allow these to come from strings, too (db type bytea) 149 nullvalue = coding.getNullvalue(field, int) 150 if nullvalue is not None: 151 nullvalue = repr(struct.pack("B", int(nullvalue))) 152 return _addNullvalueCode(field, nullvalue,[ 153 "if isinstance(val, int):", 154 " tokens.append(struct.pack('B', val))", 155 "else:", 156 " tokens.append(struct.pack('c', val[:1]))"])
157 158
159 -def _makeCharEncoder(field):
160 nullvalue = coding.getNullvalue(field, lambda _: True) 161 if nullvalue is not None: 162 nullvalue = repr(struct.pack("c", str(nullvalue))) 163 164 return _addNullvalueCode(field, nullvalue, [ 165 "tokens.append(struct.pack('c', str(val)))"])
166
167 -def _makeUnicodeCharEncoder(field):
168 nullvalue = coding.getNullvalue(field, lambda _: True) 169 if nullvalue is not None: 170 coded = nullvalue.encode("utf-16be") 171 nullvalue = repr(struct.pack("%ds"%len(coded), coded)) 172 return _addNullvalueCode(field, nullvalue, [ 173 "coded = val.encode('utf-16be')", 174 "tokens.append(struct.pack('%ds'%len(coded), coded))"])
175 176
177 -def _makeCharArrayEncoder(field):
178 # special handling for character arrays, since we don't want to treat 179 # those as character arrays in python. 180 nullvalue = coding.getNullvalue(field, lambda _: True, default="") 181 src = [] 182 183 src.extend(common.getXtypeEncoderCode(field)) 184 src.append("val = coding.trimString(val, %s)"%repr(field.arraysize)) 185 186 if field.hasVarLength(): 187 src.append("tokens.append(struct.pack('!i', len(val)))") 188 if nullvalue is None: 189 nullvalue = repr('\0\0\0\0') 190 else: 191 # The str in the next line allows nullvalue to be unicode (containing 192 # ascii, of course) 193 nullvalue = repr(struct.pack("!i%ds"%len(nullvalue), 194 len(nullvalue), str(nullvalue))) 195 else: 196 if nullvalue is not None: 197 nullvalue = repr(struct.pack("%ds"%field.getLength(), 198 str(coding.trimString(nullvalue, field.arraysize)))) 199 # no predefined nullvalue for constant-length strings 200 201 if field.datatype=="unicodeChar": 202 src.append("val = val.encode('utf-16be')") 203 elif field.datatype=="char": 204 src.extend([ 205 'if isinstance(val, unicode):', 206 ' val = val.encode("ascii", "replace")']) 207 208 src.append("tokens.append(struct.pack('%ds'%len(val), val))") 209 return _addNullvalueCode(field, nullvalue, src)
210 211 212 _encoders = { 213 "boolean": _makeBooleanEncoder, 214 "bit": _makeBitEncoder, 215 "unsignedByte": _makeUnsignedByteEncoder, 216 "short": _generateIntEncoderMaker('!h'), 217 "int": _generateIntEncoderMaker('!i'), 218 "long": _generateIntEncoderMaker('!q'), 219 "char": _makeCharEncoder, 220 "unicodeChar": _makeUnicodeCharEncoder, 221 "double": _generateFloatEncoderMaker("!d", "doubleNaN"), 222 "float": _generateFloatEncoderMaker("!f", "floatNaN"), 223 "doubleComplex": _generateComplexEncoderMaker("!dd", "doubleNaN"), 224 "floatComplex": _generateComplexEncoderMaker("!ff", "floatNaN"), 225 } 226
227 -def _getArrayEncoderLines(field):
228 """returns python lines to encode array values of field. 229 """ 230 type = field.datatype 231 232 # bit array literals are integers, same as bits 233 if type=="bit": 234 return _makeBitEncoder(field) 235 236 if type=="char" or type=="unicodeChar": 237 return _makeCharArrayEncoder(field) 238 239 240 # Everything else can use some common array shaping code since value comes in 241 # some kind of sequence. 242 padder = '[None]' 243 src = [ # Painful name juggling to avoid having to call functions. 244 "fullTokens = tokens", 245 "tokens = []", 246 "if val is None:", 247 " arr = []", 248 "else:", 249 " arr = val", 250 "for val in arr:" 251 ]+common.indentList(_encoders[field.datatype](field), " ") 252 253 src.extend([ 254 "fullTokens.append(''.join(tokens))", 255 "tokens = fullTokens"]) 256 257 return (common.getXtypeEncoderCode(field) 258 + _getArrayShapingCode(field, padder) 259 + src)
260 261
262 -def getLinesFor(field):
263 """returns a sequence of python source lines to encode values described 264 by field into tabledata. 265 """ 266 if field.isScalar(): 267 return _encoders[field.datatype](field) 268 else: 269 return _getArrayEncoderLines(field)
270 271
272 -def getPostamble(tableDefinition):
273 return [ 274 "return ''.join(tokens)"]
275 276
277 -def getGlobals(tableDefinition):
278 return globals()
279