Package SpecClient :: Module SpecMessage
[hide private]
[frames] | no frames]

Source Code for Module SpecClient.SpecMessage

  1  """SpecMessage module 
  2   
  3  This module defines classes and functions for creating messages 
  4  from data received from Spec, and for generating messages to be 
  5  sent to Spec. 
  6   
  7  It handles the different message versions (headers 2, 3 and 4). 
  8  """ 
  9   
 10  __author__ = 'Matias Guijarro' 
 11  __version__ = '1.0' 
 12   
 13  import struct 
 14  import time 
 15  import types 
 16   
 17  import SpecArray 
 18  import SpecReply 
 19   
 20  (DOUBLE, STRING, ERROR, ASSOC) = (1,2,3,4) 
 21   
 22  MAGIC_NUMBER=4277009102 
 23  NATIVE_HEADER_VERSION=4 
 24  NULL='\000' 
 25   
 26  # cmd 
 27  (CLOSE, ABORT, CMD, CMD_WITH_RETURN, REGISTER, UNREGISTER, EVENT,  \ 
 28   FUNC, FUNC_WITH_RETURN, CHAN_READ, CHAN_SEND, REPLY, HELLO, HELLO_REPLY) = \ 
 29                                           (1,2,3,4,6,7,8,9,10,11,12,13,14,15) 
 30   
 31  # flags 
 32  DELETED = 0x0001 
 33   
 34   
35 -def message(*args, **kwargs):
36 """Return a new SpecMessage object 37 38 The returned SpecMessage object can be of any of the available message 39 class. You can specify the desired message class with the 'version' keyword 40 argument. If not specified, defaults to NATIVE_HEADER_VERSION. 41 42 Arguments are passed to the appropriate message constructor. 43 44 Keyword arguments: 45 version -- message version, defaults to NATIVE_HEADER_VERSION. When 46 reading messages from stream, you can set it to None and it will try 47 to guess the suitable message class by reading the header version 48 from Spec. 49 """ 50 version = kwargs.get('version', NATIVE_HEADER_VERSION) 51 order = kwargs.get('order', '<') 52 if len(order) == 0: 53 order = "<" 54 55 if version == 4: 56 m = message4(*args) 57 elif version == 3: 58 m = message3(*args) 59 elif version == 2: 60 m = message2(*args) 61 else: 62 m = anymessage(*args) #BE CAREFUL, only for reading message from stream 63 64 m.packedHeaderDataFormat=order+m.packedHeaderDataFormat[1:] 65 66 return m
67 68
69 -def rawtodictonary(rawstring):
70 """Transform a list as coming from a SPEC associative array 71 to a dictonary - 2dim arrays are transformed top dict with dict 72 entries. In SPEC the key contains \x1c""" 73 raw = rawstring.split(NULL)[:-2] 74 75 data = {} 76 for i in range(0,len(raw) - 1,2): 77 key,val = raw[i], raw[i+1] 78 keyel = key.split("\x1c") 79 if len(keyel) == 1: 80 if key in data: 81 data[key][None] = val 82 else: 83 data[key]=val 84 else: 85 if keyel[0] in data and type(data[keyel[0]])!=types.DictType: 86 data[keyel[0]]={ None: data[keyel[0]] } 87 88 try: 89 data[keyel[0]][keyel[1]] = val 90 except TypeError: 91 data[keyel[0]] = {keyel[1] : val} 92 except KeyError: 93 data[keyel[0]] = {keyel[1] : val} 94 return data
95 96
97 -def dictionarytoraw(dict):
98 """Transform a Python dictionary object to the string format 99 expected by Spec""" 100 data = "" 101 for key, val in dict.items(): 102 if type(val) == types.DictType: 103 for kkey, vval in val.iteritems(): 104 if kkey is None: 105 data += str(key) + NULL + str(vval) + NULL 106 else: 107 data += ''.join([str(key), '\x1c', str(kkey), NULL, str(vval), NULL]) 108 else: 109 data += str(key) + NULL + str(val) + NULL 110 111 return (len(data) > 0 and data) or NULL
112 113
114 -class SpecMessage:
115 """Base class for messages."""
116 - def __init__(self, packedHeader):
117 """Constructor 118 119 Arguments: 120 packedHeader -- string representing the packed header format for the message, 121 use the same syntax as the 'struct' Python module 122 """ 123 self.packedHeaderDataFormat = packedHeader 124 self.headerLength = struct.calcsize(self.packedHeaderDataFormat) 125 self.bytesToRead = self.headerLength 126 self.readheader = True 127 self.data = '' 128 self.type = None 129 130 #message properties 131 self.magic = None 132 self.vers = None 133 self.size = None 134 self.sn = None 135 self.sec = None 136 self.usec = None 137 self.cmd = None 138 self.type = None 139 self.rows = 0 140 self.cols = 0 141 self.name = None 142 self.err = 0 143 self.flags = 0
144 145
146 - def isComplete(self):
147 """Return wether a message read from stream has been fully received or not.""" 148 return self.bytesToRead == 0
149 150
151 - def readFromStream(self, streamBuf):
152 """Read buffer from stream and try to create a message from it 153 154 Arguments: 155 streamBuf - string buffer of the last bytes received from Spec 156 157 Return value : 158 the number of consumed bytes 159 """ 160 consumedBytes = 0 161 162 while self.bytesToRead > 0 and len(streamBuf[consumedBytes:]) >= self.bytesToRead: 163 if self.readheader: 164 self.readheader = False 165 self.type, self.bytesToRead = self.readHeader(streamBuf[:self.headerLength]) 166 consumedBytes = self.headerLength 167 else: 168 rawdata = streamBuf[consumedBytes:consumedBytes+self.bytesToRead] 169 consumedBytes += self.bytesToRead 170 self.bytesToRead = 0 171 172 self.data = self.readData(rawdata, self.type) 173 174 return consumedBytes
175 176
177 - def readHeader(self, rawstring):
178 """Read the header of the message coming from stream 179 180 Arguments: 181 rawstring -- raw bytes of the header 182 183 Return value: 184 (message data type, message data len) tuple 185 """ 186 return (None, 0)
187 188
189 - def readData(self, rawstring, datatype):
190 """Read the data part of the message coming from stream 191 192 Arguments: 193 rawstring -- raw data bytes 194 datatype -- data type 195 196 Return value: 197 the data read 198 """ 199 data = rawstring[:-1] #remove last NULL byte 200 201 if datatype == ERROR: 202 return data 203 elif datatype == STRING or datatype == DOUBLE: 204 # try to convert data to a more appropriate type 205 try: 206 data = int(data) 207 except: 208 try: 209 data = float(data) 210 except: 211 pass 212 213 return data 214 elif datatype == ASSOC: 215 return rawtodictonary(rawstring) 216 elif SpecArray.isArrayType(datatype): 217 #Here we read cols and rows... which are *supposed* to be received in the header!!! 218 #better approach: data contains this information (since it is particular to that data type) 219 return SpecArray.SpecArray(rawstring, datatype, self.rows, self.cols) 220 else: 221 raise TypeError
222 223
224 - def dataType(self, data):
225 """Try to guess data type 226 227 Works for obvious cases only 228 - it is a hard job guessing ARRAY_* types, we ignore this case (user has to provide a suitable datatype) 229 - we cannot make a difference between ERROR type and STRING type 230 """ 231 if type(data) == types.StringType: 232 return STRING 233 elif type(data) == types.DictType: 234 return ASSOC 235 elif type(data) == types.IntType or type(data) == types.LongType or type(data) == types.FloatType: 236 return STRING 237 #DOUBLE 238 elif isinstance(data, SpecArray.SpecArrayData): 239 self.rows, self.cols = data.shape 240 return data.type
241 242
243 - def sendingDataString(self, data, datatype):
244 """Return the string representing the data part of the message.""" 245 rawstring = '' 246 247 if datatype in (ERROR, STRING, DOUBLE): 248 rawstring = str(data) 249 elif datatype == ASSOC: 250 rawstring = dictionarytoraw(data) 251 elif SpecArray.isArrayType(datatype): 252 rawstring = data.tostring() 253 254 if len(rawstring) > 0: 255 rawstring += NULL 256 257 return rawstring
258 259
260 - def sendingString(self):
261 """Create a string representing the message which can be send 262 over the socket.""" 263 return ''
264 265
266 -class message2(SpecMessage):
267 """Version 2 message class"""
268 - def __init__(self, *args, **kwargs):
269 """Constructor 270 271 If called without arguments, message is supposed to be read from stream. 272 Otherwise, the 'init' method is called with the specified arguments, for 273 creating a message from arguments. 274 """ 275 SpecMessage.__init__(self, '<IiiiIIiiIII80s') 276 277 if len(args) > 0: 278 self.init(*args, **kwargs)
279 280
281 - def init(self, ser, cmd, name, data, datatype = None, rows = 0, cols = 0):
282 """ Create a message from the arguments""" 283 self.vers = 2 #header version 284 self.size = self.headerLength 285 self.magic = MAGIC_NUMBER 286 self.rows = rows 287 self.cols = cols 288 self.data = data 289 self.type = datatype or self.dataType(self.data) 290 self.time = time.time() 291 self.sec = int(self.time) 292 self.usec = int((self.time-self.sec)*1E6) 293 self.sn, self.cmd, self.name = ser, cmd, str(name)
294 295
296 - def readHeader(self, rawstring):
297 self.magic, self.vers, self.size, self.sn, \ 298 self.sec, self.usec, self.cmd, \ 299 datatype, self.rows, self.cols, \ 300 datalen, name = struct.unpack(self.packedHeaderDataFormat, rawstring) 301 if self.magic != MAGIC_NUMBER: 302 self.packedHeaderDataFormat=">"+self.packedHeaderDataFormat[1:] 303 self.magic, self.vers, self.size, self.sn, \ 304 self.sec, self.usec, self.cmd, \ 305 datatype, self.rows, self.cols, \ 306 datalen, name = struct.unpack(self.packedHeaderDataFormat, rawstring) 307 #rint 'READ header', self.magic, 'vers=', self.vers, 'size=', self.size, 'cmd=', self.cmd, 'type=', datatype, 'datalen=', datalen, 'err=', self.err, 'name=', str(self.name) 308 self.time = self.sec + float(self.usec) / 1E6 309 self.name = name.replace(NULL, '') #remove padding null bytes 310 311 return (datatype, datalen)
312 313
314 - def sendingString(self):
315 if self.type is None: 316 # invalid message 317 return '' 318 319 data = self.sendingDataString(self.data, self.type) 320 datalen = len(data) 321 322 header = struct.pack(self.packedHeaderDataFormat, self.magic, self.vers, self.size, 323 self.sn, self.sec, self.usec, self.cmd, self.type, 324 self.rows, self.cols, datalen, str(self.name)) 325 #print 'WRITE header', self.magic, 'vers=', self.vers, 'size=', self.size, 'cmd=', self.cmd, 'type=', self.type, 'datalen=', datalen, 'err=', self.err, 'name=', str(self.name) 326 #print 'WRITE data', data 327 return header + data
328 329
330 -class message3(SpecMessage):
331 - def __init__(self, *args, **kwargs):
332 SpecMessage.__init__(self, '<IiiiIIiiIIIi80s') 333 334 if len(args) > 0: 335 self.init(*args, **kwargs)
336 337
338 - def init(self, ser, cmd, name, data, datatype = None, rows = 0, cols = 0):
339 """ Create a message from the arguments """ 340 self.vers = 3 #header version 341 self.size = self.headerLength 342 self.magic = MAGIC_NUMBER 343 self.rows = rows 344 self.cols = cols 345 self.data = data 346 self.type = datatype or self.dataType(self.data) 347 self.time = time.time() 348 self.sec = int(self.time) 349 self.usec = int((self.time-self.sec)*1E6) 350 self.sn, self.cmd, self.name = ser, cmd, str(name)
351 352
353 - def readHeader(self, rawstring):
354 self.magic, self.vers, self.size, self.sn, \ 355 self.sec, self.usec, self.cmd, \ 356 datatype, self.rows, self.cols, \ 357 datalen, self.err, name = struct.unpack(self.packedHeaderDataFormat, rawstring) 358 if self.magic != MAGIC_NUMBER: 359 self.packedHeaderDataFormat=">"+self.packedHeaderDataFormat[1:] 360 self.magic, self.vers, self.size, self.sn, \ 361 self.sec, self.usec, self.cmd, \ 362 datatype, self.rows, self.cols, \ 363 datalen, self.err, name = struct.unpack(self.packedHeaderDataFormat, rawstring) 364 #print 'READ header', self.magic, 'vers=', self.vers, 'size=', self.size, 'cmd=', self.cmd, 'type=', datatype, 'datalen=', datalen, 'err=', self.err, 'name=', str(self.name) 365 self.time = self.sec + float(self.usec) / 1E6 366 self.name = name.replace(NULL, '') #remove padding null bytes 367 368 if self.err > 0: 369 datatype = ERROR #change message type to 'ERROR' for further processing 370 371 return (datatype, datalen)
372 373
374 - def sendingString(self):
375 if self.type is None: 376 # invalid message 377 return '' 378 379 data = self.sendingDataString(self.data, self.type) 380 datalen = len(data) 381 382 header = struct.pack(self.packedHeaderDataFormat, self.magic, self.vers, self.size, 383 self.sn, self.sec, self.usec, self.cmd, self.type, 384 self.rows, self.cols, datalen, self.err, str(self.name)) 385 386 #print 'WRITE header', self.magic, 'vers=', self.vers, 'size=', self.size, 'cmd=', self.cmd, 'type=', self.type, 'datalen=', datalen, 'err=', self.err, 'name=', str(self.name) 387 #print 'WRITE data', data 388 return header + data
389 390
391 -class message4(SpecMessage):
392 - def __init__(self, *args, **kwargs):
393 SpecMessage.__init__(self, '<IiIIIIiiIIIii80s') 394 395 if len(args) > 0: 396 self.init(*args, **kwargs)
397 398
399 - def init(self, ser, cmd, name, data, datatype = None, rows = 0, cols = 0):
400 """ Create a message from the arguments """ 401 self.vers = 4 #header version 402 self.size = self.headerLength 403 self.magic = MAGIC_NUMBER 404 self.rows = rows 405 self.cols = cols 406 self.data = data 407 self.type = datatype or self.dataType(self.data) 408 self.time = time.time() 409 self.sec = int(self.time) 410 self.usec = int((self.time-self.sec)*1E6) 411 self.sn, self.cmd, self.name = ser, cmd, str(name)
412 413
414 - def readHeader(self, rawstring):
415 self.magic, self.vers, self.size, self.sn, \ 416 self.sec, self.usec, self.cmd, \ 417 datatype, self.rows, self.cols, \ 418 datalen, self.err, self.flags, name = struct.unpack(self.packedHeaderDataFormat, rawstring) 419 if self.magic != MAGIC_NUMBER: 420 self.packedHeaderDataFormat=">"+self.packedHeaderDataFormat[1:] 421 self.magic, self.vers, self.size, self.sn, \ 422 self.sec, self.usec, self.cmd, \ 423 datatype, self.rows, self.cols, \ 424 datalen, self.err, self.flags, name = struct.unpack(self.packedHeaderDataFormat, rawstring) 425 #print 'READ header', self.magic, 'vers=', self.vers, 'size=', self.size, 'cmd=', self.cmd, 'type=', datatype, 'datalen=', datalen, 'err=', self.err, 'flags=', self.flags, 'name=', str(self.name) 426 self.time = self.sec + float(self.usec) / 1E6 427 self.name = name.replace(NULL, '') #remove padding null bytes 428 429 if self.err > 0: 430 datatype = ERROR #change message type to 'ERROR' for further processing 431 432 return (datatype, datalen)
433 434
435 - def sendingString(self):
436 if self.type is None: 437 # invalid message 438 return '' 439 440 data = self.sendingDataString(self.data, self.type) 441 datalen = len(data) 442 443 #print 'WRITE header', self.magic, 'vers=', self.vers, 'size=', self.size, 'cmd=', self.cmd, 'type=', self.type, 'datalen=', datalen, 'err=', self.err, 'flags=', self.flags, 'name=', str(self.name) 444 #print 'WRITE data', data 445 header = struct.pack(self.packedHeaderDataFormat, self.magic, self.vers, self.size, 446 self.sn, self.sec, self.usec, self.cmd, self.type, 447 self.rows, self.cols, datalen, self.err, self.flags, str(self.name)) 448 449 return header + data
450 451
452 -class anymessage(SpecMessage):
453 - def __init__(self, *args, **kwargs):
454 SpecMessage.__init__(self, '<Ii')
455 456
457 - def readFromStream(self, streamBuf):
458 if len(streamBuf) >= self.bytesToRead: 459 magic, version = struct.unpack(self.packedHeaderDataFormat, streamBuf[:self.headerLength]) 460 461 if magic != MAGIC_NUMBER: 462 self.packedHeaderDataFormat=">"+self.packedHeaderDataFormat[1:] 463 magic, version = struct.unpack(self.packedHeaderDataFormat, streamBuf[:self.headerLength]) 464 465 # try to guess which message class suits best 466 if version == 2: 467 self.__class__ = message2 468 message2.__init__(self) 469 return self.readFromStream(streamBuf) 470 elif version == 3: 471 self.__class__ = message3 472 message3.__init__(self) 473 return self.readFromStream(streamBuf) 474 elif version >= 4: 475 self.__class__ = message4 476 message4.__init__(self) 477 return self.readFromStream(streamBuf) 478 479 return 0
480 481
482 -def commandListToCommandString(cmdlist):
483 """Convert a command list to a Spec command string.""" 484 if type(cmdlist) == types.ListType and len(cmdlist) > 0: 485 cmd = [str(cmdlist[0])] 486 487 for arg in cmdlist[1:]: 488 argstr = repr(arg) 489 490 if type(arg) == types.DictType: 491 argstr = argstr.replace('{', '[') 492 argstr = argstr.replace('}', ']') 493 494 cmd.append(argstr) 495 496 return NULL.join(cmd) 497 else: 498 return ''
499 500
501 -def msg_cmd_with_return(cmd, version = NATIVE_HEADER_VERSION, order="<"):
502 """Return a command with return message""" 503 return message_with_reply(CMD_WITH_RETURN, "", cmd, version, order)
504 505
506 -def msg_func_with_return(cmd, version = NATIVE_HEADER_VERSION, order="<"):
507 """Return a func with return message""" 508 cmd = commandListToCommandString(cmd) 509 return message_with_reply(FUNC_WITH_RETURN, "", cmd, version, order)
510 511
512 -def msg_cmd(cmd, version = NATIVE_HEADER_VERSION, order="<"):
513 """Return a command without reply message""" 514 return message_no_reply(CMD, "", cmd, version, order)
515 516
517 -def msg_func(cmd, version = NATIVE_HEADER_VERSION, order="<"):
518 """Return a func without reply message""" 519 cmd = commandListToCommandString(cmd) 520 return message_no_reply(FUNC, "", cmd, version, order)
521 522
523 -def msg_chan_read(channel, version = NATIVE_HEADER_VERSION, order="<"):
524 """Return a property-reading message""" 525 return message_with_reply(CHAN_READ, channel, "", version, order)
526 527
528 -def msg_chan_send(channel, value, version = NATIVE_HEADER_VERSION, order="<"):
529 """Return a property-setting message""" 530 return message_no_reply(CHAN_SEND, channel, value, version, order)
531 532
533 -def msg_event(channel, value, version = NATIVE_HEADER_VERSION, order="<"):
534 """Return an event message""" 535 return message_no_reply(EVENT, channel, value, version, order)
536 537
538 -def msg_register(channel, version = NATIVE_HEADER_VERSION, order="<"):
539 """Return a register message""" 540 return message_no_reply(REGISTER, channel, "", version, order)
541 542
543 -def msg_unregister(channel, version = NATIVE_HEADER_VERSION, order="<"):
544 """Return an unregister message""" 545 return message_no_reply(UNREGISTER, channel, "", version, order)
546 547
548 -def msg_close(version = NATIVE_HEADER_VERSION, order="<"):
549 """Return a close message""" 550 return message_no_reply(CLOSE, "", "", version, order)
551 552
553 -def msg_abort(version = NATIVE_HEADER_VERSION, order="<"):
554 """Return an abort message""" 555 return message_no_reply(ABORT, "", "", version, order)
556 557
558 -def msg_hello(version = NATIVE_HEADER_VERSION, order="<"):
559 """Return a hello message""" 560 return message_no_reply(HELLO, "python", "", version, order)
561 562
563 -def msg_hello_reply(replyID, serverName, version = NATIVE_HEADER_VERSION, order="<"):
564 return message(replyID, HELLO_REPLY, serverName, serverName, version = version, order=order)
565 566 567 # Methods to send any Messages
568 -def message_with_reply(cmd, name, data, version = NATIVE_HEADER_VERSION, order="<"):
569 """ Lower level call to send a message of a certain type """ 570 newReply = SpecReply.SpecReply() 571 replyID = newReply.id 572 573 m = message(replyID, cmd, name, data, version = version, order=order) 574 575 return (newReply, m)
576 577
578 -def message_no_reply(cmd, name, data, version = NATIVE_HEADER_VERSION, order="<"):
579 """ Send a message which will not result in a reply from the server. 580 If a reply is sent depends only on the cmd and not on the method 581 to send the message """ 582 return message(0, cmd, name, data, version = version, order=order)
583 584
585 -def reply_message(replyID, name, data, version = NATIVE_HEADER_VERSION, order="<"):
586 return message(replyID, REPLY, name, data, version = version, order=order)
587 588
589 -def error_message(replyID, name, data, version = NATIVE_HEADER_VERSION, order="<"):
590 return message(replyID, REPLY, name, data, ERROR, version = version, order=order)
591