Package pyamf :: Package util |
[hide private]
[frames] | no frames]

# Source Code for Package pyamf.util

       1  # -*- coding: utf-8 -*-
       2  #
       3  # Copyright (c) 2007-2009 The PyAMF Project.
       4  # See LICENSE.txt for details.
       5
       6  """
       7  AMF Utilities.
       8
       9  @since: 0.1.0
      10  """
      11
      12  import struct
      13  import calendar
      14  import datetime
      15  import types
      16  import inspect
      17
      18  import pyamf
      19
      20  try:
      21      from cStringIO import StringIO
      22  except ImportError:
      23      from StringIO import StringIO
      24
      25  try:
      26      set
      27  except NameError:
      28      from sets import Set as set
      29
      30
      31  #: XML types.
      32  xml_types = None
      33  ET = None
      34  #: On some Python versions retrieving a negative timestamp, like
      35  #: C{datetime.datetime.utcfromtimestamp(-31536000.0)} is broken.
      36  negative_timestamp_broken = False
      37
      38  int_types = [int]
      39  str_types = [str]
      40
      41  # py3k support
      42  try:
      43      int_types.append(long)
      44  except NameError:
      45      pass
      46
      47  try:
      48      str_types.append(unicode)
      49  except NameError:
      50      pass
      51
      52  #: Numeric types.
      53  int_types = tuple(int_types)
      54  #: String types.
      55  str_types = tuple(str_types)
      56
      57  PosInf = 1e300000
      58  NegInf = -1e300000
      59  # we do this instead of float('nan') because windows throws a wobbler.
      60  NaN = PosInf / PosInf
      61
      62
      63 -def find_xml_lib():
      64      """
      65      Run through a predefined order looking through the various C{ElementTree}
      66      implementations so that any type can be encoded but PyAMF will return
      67      elements as the first implementation found.
      68
      69      We work through the C implementations first - then the pure Python
      70      versions. The downside to this is that a possible of three libraries will
      71      be loaded into memory that are not used but the libs are small
      72      (relatively) and the flexibility that this gives seems to outweigh the
      73      cost. Time will tell.
      74
      75      @since: 0.4
      76      """
      77      global xml_types, ET
      78
      79      xml_types = []
      80
      81      try:
      82          import xml.etree.cElementTree as cET
      83
      84          ET = cET
      85          xml_types.append(type(cET.Element('foo')))
      86      except ImportError:
      87          pass
      88
      89      try:
      90          import cElementTree as cET
      91
      92          if ET is None:
      93              ET = cET
      94
      95          xml_types.append(type(cET.Element('foo')))
      96      except ImportError:
      97          pass
      98
      99      try:
     100          import xml.etree.ElementTree as pET
     101
     102          if ET is None:
     103              ET = pET
     104
     105          xml_types.append(pET._ElementInterface)
     106      except ImportError:
     107          pass
     108
     109      try:
     110          import elementtree.ElementTree as pET
     111
     112          if ET is None:
     113              ET = pET
     114
     115          xml_types.append(pET._ElementInterface)
     116      except ImportError:
     117          pass
     118
     119      for x in xml_types[:]:
     120          # hack for jython
     121          if x.__name__ == 'instance':
     122              xml_types.remove(x)
     123
     124      xml_types = tuple(xml_types)
     125
     126      return xml_types
     127
     128
     129 -class StringIOProxy(object):
     130      """
     131      I am a C{StringIO} type object containing byte data from the AMF stream.
     132
     133      @see: U{ByteArray on OSFlash (external)
     134      <http://osflash.org/documentation/amf3#x0c_-_bytearray>}
     135      @see: U{Parsing ByteArrays on OSFlash (external)
     136      <http://osflash.org/documentation/amf3/parsing_byte_arrays>}
     137      """
     138
     139      _wrapped_class = StringIO
     140
     141 -    def __init__(self, buf=None):
     142          """
     143          @raise TypeError: Unable to coerce C{buf} to C{StringIO}.
     144          """
     145          self._buffer = StringIOProxy._wrapped_class()
     146
     147          if isinstance(buf, (str, unicode)):
     148              self._buffer.write(buf)
     149          elif hasattr(buf, 'getvalue'):
     150              self._buffer.write(buf.getvalue())
     151          elif hasattr(buf, 'read') and hasattr(buf, 'seek') and hasattr(buf, 'tell'):
     152              old_pos = buf.tell()
     153              buf.seek(0)
     154              self._buffer.write(buf.read())
     155              buf.seek(old_pos)
     156          elif buf is None:
     157              pass
     158          else:
     159              raise TypeError("Unable to coerce buf->StringIO")
     160
     161          self._get_len()
     162          self._len_changed = False
     163          self._buffer.seek(0, 0)
     164
     165 -    def getvalue(self):
     166          """
     167          Get raw data from buffer.
     168          """
     169          return self._buffer.getvalue()
     170
     171 -    def read(self, n=-1):
     172          """
     173          Reads C{n} bytes from the stream.
     174          """
     175          bytes = self._buffer.read(n)
     176
     177          return bytes
     178
     179 -    def seek(self, pos, mode=0):
     180          """
     181          Sets the file-pointer offset, measured from the beginning of this stream,
     182          at which the next write operation will occur.
     183
     184          @param pos:
     185          @type pos: C{int}
     186          @param mode:
     187          @type mode: C{int}
     188          """
     189          return self._buffer.seek(pos, mode)
     190
     191 -    def tell(self):
     192          """
     193          Returns the position of the stream pointer.
     194          """
     195          return self._buffer.tell()
     196
     197 -    def truncate(self, size=0):
     198          """
     199          Truncates the stream to the specified length.
     200
     201          @param size: The length of the stream, in bytes.
     202          @type size: C{int}
     203          """
     204          if size == 0:
     205              self._buffer = StringIOProxy._wrapped_class()
     206              self._len_changed = True
     207
     208              return
     209
     210          cur_pos = self.tell()
     211          self.seek(0)
     212          buf = self.read(size)
     213          self._buffer = StringIOProxy._wrapped_class()
     214
     215          self._buffer.write(buf)
     216          self.seek(cur_pos)
     217          self._len_changed = True
     218
     219 -    def write(self, s):
     220          """
     221          Writes the content of the specified C{s} into this buffer.
     222
     223          @param s:
     224          @type s:
     225          """
     226          self._buffer.write(s)
     227          self._len_changed = True
     228
     229 -    def _get_len(self):
     230          """
     231          Return total number of bytes in buffer.
     232          """
     233          if hasattr(self._buffer, 'len'):
     234              self._len = self._buffer.len
     235
     236              return
     237
     238          old_pos = self._buffer.tell()
     239          self._buffer.seek(0, 2)
     240
     241          self._len = self._buffer.tell()
     242          self._buffer.seek(old_pos)
     243
     244 -    def __len__(self):
     245          if not self._len_changed:
     246              return self._len
     247
     248          self._get_len()
     249          self._len_changed = False
     250
     251          return self._len
     252
     253 -    def consume(self):
     254          """
     255          Chops the tail off the stream starting at 0 and ending at C{tell()}.
     256          The stream pointer is set to 0 at the end of this function.
     257
     258          @since: 0.4
     259          """
     260          try:
     261              bytes = self.read()
     262          except IOError:
     263              bytes = ''
     264
     265          self.truncate()
     266
     267          if len(bytes) > 0:
     268              self.write(bytes)
     269              self.seek(0)
     270
     271
     272 -class DataTypeMixIn(object):
     273      """
     274      Provides methods for reading and writing basic data types for file-like
     275      objects.
     276
     277      @ivar endian: Byte ordering used to represent the data. Default byte order
     278          is L{ENDIAN_NETWORK}.
     279      @type endian: C{str}
     280      """
     281
     282      #: Network byte order
     283      ENDIAN_NETWORK = "!"
     284      #: Native byte order
     285      ENDIAN_NATIVE = "@"
     286      #: Little endian
     287      ENDIAN_LITTLE = "<"
     288      #: Big endian
     289      ENDIAN_BIG = ">"
     290
     291      endian = ENDIAN_NETWORK
     292
     293 -    def _read(self, length):
     294          """
     295          Reads C{length} bytes from the stream. If an attempt to read past the
     296          end of the buffer is made, L{IOError} is raised.
     297          """
     298          bytes = self.read(length)
     299
     300          if len(bytes) != length:
     301              self.seek(0 - len(bytes), 1)
     302
     303              raise IOError("Tried to read %d byte(s) from the stream" % length)
     304
     305          return bytes
     306
     307 -    def _is_big_endian(self):
     308          """
     309          Whether this system is big endian or not.
     310
     311          @rtype: C{bool}
     312          """
     313          if self.endian == DataTypeMixIn.ENDIAN_NATIVE:
     314              return DataTypeMixIn._system_endian == DataTypeMixIn.ENDIAN_BIG
     315
     316          return self.endian in (DataTypeMixIn.ENDIAN_BIG, DataTypeMixIn.ENDIAN_NETWORK)
     317
     318 -    def read_uchar(self):
     319          """
     320          Reads an C{unsigned char} from the stream.
     321          """
     322          return struct.unpack("B", self._read(1))[0]
     323
     324 -    def write_uchar(self, c):
     325          """
     326          Writes an C{unsigned char} to the stream.
     327
     328          @param c: Unsigned char
     329          @type c: C{int}
     330          @raise TypeError: Unexpected type for int C{c}.
     331          @raise OverflowError: Not in range.
     332          """
     333          if type(c) not in int_types:
     334              raise TypeError('expected an int (got:%r)' % (type(c),))
     335
     336          if not 0 <= c <= 255:
     337              raise OverflowError("Not in range, %d" % c)
     338
     339          self.write(struct.pack("B", c))
     340
     341 -    def read_char(self):
     342          """
     343          Reads a C{char} from the stream.
     344          """
     345          return struct.unpack("b", self._read(1))[0]
     346
     347 -    def write_char(self, c):
     348          """
     349          Write a C{char} to the stream.
     350
     351          @param c: char
     352          @type c: C{int}
     353          @raise TypeError: Unexpected type for int C{c}.
     354          @raise OverflowError: Not in range.
     355          """
     356          if type(c) not in int_types:
     357              raise TypeError('expected an int (got:%r)' % (type(c),))
     358
     359          if not -128 <= c <= 127:
     360              raise OverflowError("Not in range, %d" % c)
     361
     362          self.write(struct.pack("b", c))
     363
     364 -    def read_ushort(self):
     365          """
     366          Reads a 2 byte unsigned integer from the stream.
     367          """
     368          return struct.unpack("%sH" % self.endian, self._read(2))[0]
     369
     370 -    def write_ushort(self, s):
     371          """
     372          Writes a 2 byte unsigned integer to the stream.
     373
     374          @param s: 2 byte unsigned integer
     375          @type s: C{int}
     376          @raise TypeError: Unexpected type for int C{s}.
     377          @raise OverflowError: Not in range.
     378          """
     379          if type(s) not in int_types:
     380              raise TypeError('expected an int (got:%r)' % (type(s),))
     381
     382          if not 0 <= s <= 65535:
     383              raise OverflowError("Not in range, %d" % s)
     384
     385          self.write(struct.pack("%sH" % self.endian, s))
     386
     387 -    def read_short(self):
     388          """
     389          Reads a 2 byte integer from the stream.
     390          """
     391          return struct.unpack("%sh" % self.endian, self._read(2))[0]
     392
     393 -    def write_short(self, s):
     394          """
     395          Writes a 2 byte integer to the stream.
     396
     397          @param s: 2 byte integer
     398          @type s: C{int}
     399          @raise TypeError: Unexpected type for int C{s}.
     400          @raise OverflowError: Not in range.
     401          """
     402          if type(s) not in int_types:
     403              raise TypeError('expected an int (got:%r)' % (type(s),))
     404
     405          if not -32768 <= s <= 32767:
     406              raise OverflowError("Not in range, %d" % s)
     407
     408          self.write(struct.pack("%sh" % self.endian, s))
     409
     410 -    def read_ulong(self):
     411          """
     412          Reads a 4 byte unsigned integer from the stream.
     413          """
     414          return struct.unpack("%sL" % self.endian, self._read(4))[0]
     415
     416 -    def write_ulong(self, l):
     417          """
     418          Writes a 4 byte unsigned integer to the stream.
     419
     420          @param l: 4 byte unsigned integer
     421          @type l: C{int}
     422          @raise TypeError: Unexpected type for int C{l}.
     423          @raise OverflowError: Not in range.
     424          """
     425          if type(l) not in int_types:
     426              raise TypeError('expected an int (got:%r)' % (type(l),))
     427
     428          if not 0 <= l <= 4294967295:
     429              raise OverflowError("Not in range, %d" % l)
     430
     431          self.write(struct.pack("%sL" % self.endian, l))
     432
     433 -    def read_long(self):
     434          """
     435          Reads a 4 byte integer from the stream.
     436          """
     437          return struct.unpack("%sl" % self.endian, self._read(4))[0]
     438
     439 -    def write_long(self, l):
     440          """
     441          Writes a 4 byte integer to the stream.
     442
     443          @param l: 4 byte integer
     444          @type l: C{int}
     445          @raise TypeError: Unexpected type for int C{l}.
     446          @raise OverflowError: Not in range.
     447          """
     448          if type(l) not in int_types:
     449              raise TypeError('expected an int (got:%r)' % (type(l),))
     450
     451          if not -2147483648 <= l <= 2147483647:
     452              raise OverflowError("Not in range, %d" % l)
     453
     454          self.write(struct.pack("%sl" % self.endian, l))
     455
     456 -    def read_24bit_uint(self):
     457          """
     458          Reads a 24 bit unsigned integer from the stream.
     459
     460          @since: 0.4
     461          """
     462          order = None
     463
     464          if not self._is_big_endian():
     465              order = [0, 8, 16]
     466          else:
     467              order = [16, 8, 0]
     468
     469          n = 0
     470
     471          for x in order:
     472              n += (self.read_uchar() << x)
     473
     474          return n
     475
     476 -    def write_24bit_uint(self, n):
     477          """
     478          Writes a 24 bit unsigned integer to the stream.
     479
     480          @since: 0.4
     481          @param n: 24 bit unsigned integer
     482          @type n: C{int}
     483          @raise TypeError: Unexpected type for int C{n}.
     484          @raise OverflowError: Not in range.
     485          """
     486          if type(n) not in int_types:
     487              raise TypeError('expected an int (got:%r)' % (type(n),))
     488
     489          if not 0 <= n <= 0xffffff:
     490              raise OverflowError("n is out of range")
     491
     492          order = None
     493
     494          if not self._is_big_endian():
     495              order = [0, 8, 16]
     496          else:
     497              order = [16, 8, 0]
     498
     499          for x in order:
     500              self.write_uchar((n >> x) & 0xff)
     501
     502 -    def read_24bit_int(self):
     503          """
     504          Reads a 24 bit integer from the stream.
     505
     506          @since: 0.4
     507          """
     508          n = self.read_24bit_uint()
     509
     510          if n & 0x800000 != 0:
     511              # the int is signed
     512              n -= 0x1000000
     513
     514          return n
     515
     516 -    def write_24bit_int(self, n):
     517          """
     518          Writes a 24 bit integer to the stream.
     519
     520          @since: 0.4
     521          @param n: 24 bit integer
     522          @type n: C{int}
     523          @raise TypeError: Unexpected type for int C{n}.
     524          @raise OverflowError: Not in range.
     525          """
     526          if type(n) not in int_types:
     527              raise TypeError('expected an int (got:%r)' % (type(n),))
     528
     529          if not -8388608 <= n <= 8388607:
     530              raise OverflowError("n is out of range")
     531
     532          order = None
     533
     534          if not self._is_big_endian():
     535              order = [0, 8, 16]
     536          else:
     537              order = [16, 8, 0]
     538
     539          if n < 0:
     540              n += 0x1000000
     541
     542          for x in order:
     543              self.write_uchar((n >> x) & 0xff)
     544
     545 -    def read_double(self):
     546          """
     547          Reads an 8 byte float from the stream.
     548          """
     549          return struct.unpack("%sd" % self.endian, self._read(8))[0]
     550
     551 -    def write_double(self, d):
     552          """
     553          Writes an 8 byte float to the stream.
     554
     555          @param d: 8 byte float
     556          @type d: C{float}
     557          @raise TypeError: Unexpected type for float C{d}.
     558          """
     559          if not type(d) is float:
     560              raise TypeError('expected a float (got:%r)' % (type(d),))
     561
     562          self.write(struct.pack("%sd" % self.endian, d))
     563
     564 -    def read_float(self):
     565          """
     566          Reads a 4 byte float from the stream.
     567          """
     568          return struct.unpack("%sf" % self.endian, self._read(4))[0]
     569
     570 -    def write_float(self, f):
     571          """
     572          Writes a 4 byte float to the stream.
     573
     574          @param f: 4 byte float
     575          @type f: C{float}
     576          @raise TypeError: Unexpected type for float C{f}.
     577          """
     578          if type(f) is not float:
     579              raise TypeError('expected a float (got:%r)' % (type(f),))
     580
     581          self.write(struct.pack("%sf" % self.endian, f))
     582
     583 -    def read_utf8_string(self, length):
     584          """
     585          Reads a UTF-8 string from the stream.
     586
     587          @rtype: C{unicode}
     588          """
     589          str = struct.unpack("%s%ds" % (self.endian, length), self.read(length))[0]
     590
     591          return unicode(str, "utf8")
     592
     593 -    def write_utf8_string(self, u):
     594          """
     595          Writes a unicode object to the stream in UTF-8.
     596
     597          @param u: unicode object
     598          @raise TypeError: Unexpected type for str C{u}.
     599          """
     600          if type(u) not in str_types:
     601              raise TypeError('expected a str (got:%r)' % (type(u),))
     602
     603          bytes = u.encode("utf8")
     604
     605          self.write(struct.pack("%s%ds" % (self.endian, len(bytes)), bytes))
     606
     607
     608  if struct.pack('@H', 1)[0] == '\x01':
     609      DataTypeMixIn._system_endian = DataTypeMixIn.ENDIAN_LITTLE
     610  else:
     611      DataTypeMixIn._system_endian = DataTypeMixIn.ENDIAN_BIG
     612
     613
     614 -class BufferedByteStream(StringIOProxy, DataTypeMixIn):
     615      """
     616      An extension of C{StringIO}.
     617
     618      Features:
     619       - Raises L{IOError} if reading past end.
     620       - Allows you to C{peek()} at the next byte.
     621
     622      @see: L{cBufferedByteStream<cpyamf.util.cBufferedByteStream>}
     623      """
     624
     625 -    def __init__(self, buf=None):
     626          """
     627          @param buf: Initial byte stream.
     628          @type buf: C{str} or C{StringIO} instance
     629          """
     630          StringIOProxy.__init__(self, buf=buf)
     631
     632          self.seek(0)
     633
     634 -    def read(self, length=-1):
     635          """
     636          Reads up to the specified number of bytes from the stream into
     637          the specified byte array of specified length.
     638
     639          @raise IOError: Attempted to read past the end of the buffer.
     640          """
     641          if length == -1 and self.at_eof():
     642              raise IOError('Attempted to read from the buffer but already at '
     643                  'the end')
     644          elif length > 0 and self.tell() + length > len(self):
     645              raise IOError('Attempted to read %d bytes from the buffer but '
     646                  'only %d remain' % (length, len(self) - self.tell()))
     647
     648          return StringIOProxy.read(self, length)
     649
     650 -    def peek(self, size=1):
     651          """
     652          Looks C{size} bytes ahead in the stream, returning what it finds,
     653          returning the stream pointer to its initial position.
     654
     655          @param size: Default is 1.
     656          @type size: C{int}
     657          @raise ValueError: Trying to peek backwards.
     658
     659          @rtype:
     660          @return: Bytes.
     661          """
     662          if size == -1:
     663              return self.peek(len(self) - self.tell())
     664
     665          if size < -1:
     666              raise ValueError("Cannot peek backwards")
     667
     668          bytes = ''
     669          pos = self.tell()
     670
     671          while not self.at_eof() and len(bytes) != size:
     672              bytes += self.read(1)
     673
     674          self.seek(pos)
     675
     676          return bytes
     677
     678 -    def remaining(self):
     679          """
     680          Returns number of remaining bytes.
     681
     682          @rtype: C{number}
     683          @return: Number of remaining bytes.
     684          """
     685          return len(self) - self.tell()
     686
     687 -    def at_eof(self):
     688          """
     689          Returns C{True} if the internal pointer is at the end of the stream.
     690
     691          @rtype: C{bool}
     692          """
     693          return self.tell() == len(self)
     694
     695 -    def append(self, data):
     696          """
     697          Append data to the end of the stream. The pointer will not move if
     698          this operation is successful.
     699
     700          @param data: The data to append to the stream.
     701          @type data: C{str} or C{unicode}
     702          @raise TypeError: data is not C{str} or C{unicode}
     703          """
     704          t = self.tell()
     705
     706          # seek to the end of the stream
     707          self.seek(0, 2)
     708
     709          if hasattr(data, 'getvalue'):
     710              self.write_utf8_string(data.getvalue())
     711          else:
     712              self.write_utf8_string(data)
     713
     714          self.seek(t)
     715
     716 -    def __add__(self, other):
     717          old_pos = self.tell()
     718          old_other_pos = other.tell()
     719
     720          new = BufferedByteStream(self)
     721
     722          other.seek(0)
     723          new.seek(0, 2)
     724          new.write(other.read())
     725
     726          self.seek(old_pos)
     727          other.seek(old_other_pos)
     728          new.seek(0)
     729
     730          return new
     731
     732
     733 -def hexdump(data):
     734      """
     735      Get hexadecimal representation of C{StringIO} data.
     736
     737      @type data:
     738      @param data:
     739      @rtype: C{str}
     740      @return: Hexadecimal string.
     741      """
     742      import string
     743
     744      hex = ascii = buf = ""
     745      index = 0
     746
     747      for c in data:
     748          hex += "%02x " % ord(c)
     749          if c in string.printable and c not in string.whitespace:
     750              ascii += c
     751          else:
     752              ascii += "."
     753
     754          if len(ascii) == 16:
     755              buf += "%04x:  %s %s %s\n" % (index, hex[:24], hex[24:], ascii)
     756              hex = ascii = ""
     757              index += 16
     758
     759      if len(ascii):
     760          buf += "%04x:  %-24s %-24s %s\n" % (index, hex[:24], hex[24:], ascii)
     761
     762      return buf
     763
     764
     765 -def get_timestamp(d):
     766      """
     767      Returns a UTC timestamp for a C{datetime.datetime} object.
     768
     769      @type d: C{datetime.datetime}
     770      @param d: The date object.
     771      @return: UTC timestamp.
     772      @rtype: C{str}
     773
     774      @note: Inspiration taken from the U{Intertwingly blog
     775      <http://intertwingly.net/blog/2007/09/02/Dealing-With-Dates>}.
     776      """
     777      if isinstance(d, datetime.date) and not isinstance(d, datetime.datetime):
     778          d = datetime.datetime.combine(d, datetime.time(0, 0, 0, 0))
     779
     780      msec = str(d.microsecond).rjust(6).replace(' ', '0')
     781
     782      return float('%s.%s' % (calendar.timegm(d.utctimetuple()), msec))
     783
     784
     785 -def get_datetime(secs):
     786      """
     787      Return a UTC date from a timestamp.
     788
     789      @type secs: C{long}
     790      @param secs: Seconds since 1970.
     791      @return: UTC timestamp.
     792      @rtype: C{datetime.datetime}
     793      """
     794      if secs < 0 and negative_timestamp_broken:
     795          return datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=secs)
     796
     797      return datetime.datetime.utcfromtimestamp(secs)
     798
     799
     800 -def get_properties(obj):
     801      """
     802      @since: 0.5
     803      """
     804      if hasattr(obj, 'keys'):
     805          return set(obj.keys())
     806      elif hasattr(obj, '__dict__'):
     807          return obj.__dict__.keys()
     808
     809      return []
     810
     811
     812 -def get_attrs(obj):
     813      """
     814      Gets a C{dict} of the attrs of an object in a predefined resolution order.
     815
     816      @raise AttributeError: A duplicate attribute was already found in this
     817          collection, are you mixing different key types?
     818      """
     819      if hasattr(obj, 'iteritems'):
     820          attrs = {}
     821
     822          for k, v in obj.iteritems():
     823              sk = str(k)
     824
     825              if sk in attrs.keys():
     826                  raise AttributeError('A duplicate attribute (%s) was '
     827                      'already found in this collection, are you mixing '
     828                      'different key types?' % (sk,))
     829
     830              attrs[sk] = v
     831
     832          return attrs
     833      elif hasattr(obj, '__dict__'):
     834          return obj.__dict__.copy()
     835      elif hasattr(obj, '__slots__'):
     836          attrs = {}
     837
     838          for k in obj.__slots__:
     839              attrs[k] = getattr(obj, k)
     840
     841          return attrs
     842
     843      return None
     844
     845
     846 -def set_attrs(obj, attrs):
     847      """
     848      A generic function which applies a collection of attributes C{attrs} to
     849      object C{obj}.
     850
     851      @param obj: An instance implementing the C{__setattr__} function
     852      @param attrs: A collection implementing the C{iteritems} function
     853      @type attrs: Usually a dict
     854      """
     855      if isinstance(obj, (list, dict)):
     856          for k, v in attrs.iteritems():
     857              obj[k] = v
     858
     859          return
     860
     861      for k, v in attrs.iteritems():
     862          setattr(obj, k, v)
     863
     864
     865 -def get_class_alias(klass):
     866      """
     867      Returns a alias class suitable for klass. Defaults to L{pyamf.ClassAlias}
     868      """
     869      for k, v in pyamf.ALIAS_TYPES.iteritems():
     870          for kl in v:
     871              if isinstance(kl, types.FunctionType):
     872                  if kl(klass) is True:
     873                      return k
     874              elif isinstance(kl, (type, (types.ClassType, types.ObjectType))):
     875                  if issubclass(klass, kl):
     876                      return k
     877
     878      return pyamf.ClassAlias
     879
     880
     881 -def is_class_sealed(klass):
     882      """
     883      Returns a C{boolean} whether or not the supplied class can accept dynamic
     884      properties.
     885
     886      @rtype: C{bool}
     887      @since: 0.5
     888      """
     889      mro = inspect.getmro(klass)
     890      new = False
     891
     892      if mro[-1] is object:
     893          mro = mro[:-1]
     894          new = True
     895
     896      for kls in mro:
     897          if new and '__dict__' in kls.__dict__:
     898              return False
     899
     900          if not hasattr(kls, '__slots__'):
     901              return False
     902
     903      return True
     904
     905
     906 -def get_class_meta(klass):
     907      """
     908      Returns a C{dict} containing meta data based on the supplied class, useful
     909      for class aliasing.
     910
     911      @rtype: C{dict}
     912      @since: 0.5
     913      """
     914      if not isinstance(klass, (type, types.ClassType)) or klass is object:
     915          raise TypeError('klass must be a class object, got %r' % type(klass))
     916
     917      meta = {
     918          'static_attrs': None,
     919          'exclude_attrs': None,
     920          'readonly_attrs': None,
     921          'amf3': None,
     922          'dynamic': None,
     923          'alias': None,
     924          'external': None
     925      }
     926
     927      if not hasattr(klass, '__amf__'):
     928          return meta
     929
     930      a = klass.__amf__
     931
     932      if type(a) is dict:
     933          in_func = lambda x: x in a
     934          get_func = a.__getitem__
     935      else:
     936          in_func = lambda x: hasattr(a, x)
     937          get_func = lambda x: getattr(a, x)
     938
     939      for prop in ['alias', 'amf3', 'dynamic', 'external']:
     940          if in_func(prop):
     941              meta[prop] = get_func(prop)
     942
     943      for prop in ['static', 'exclude', 'readonly']:
     944          if in_func(prop):
     945              meta[prop + '_attrs'] = list(get_func(prop))
     946
     947      return meta
     948
     949
     950 -class IndexedCollection(object):
     951      """
     952      A class that provides a quick and clean way to store references and
     953      referenced objects.
     954
     955      @note: All attributes on the instance are private.
     956      @ivar exceptions: If C{True} then L{ReferenceError<pyamf.ReferenceError>}
     957          will be raised, otherwise C{None} will be returned.
     958      """
     959
     960 -    def __init__(self, use_hash=False, exceptions=True):
     961          if use_hash is True:
     962              self.func = hash
     963          else:
     964              self.func = id
     965
     966          self.exceptions = exceptions
     967
     968          self.clear()
     969
     970 -    def clear(self):
     971          """
     972          Clears the index.
     973          """
     974          self.list = []
     975          self.dict = {}
     976
     977 -    def getByReference(self, ref):
     978          """
     979          Returns an object based on the reference.
     980
     981          @raise TypeError: Bad reference type.
     982          @raise pyamf.ReferenceError: Reference not found.
     983          """
     984          if not isinstance(ref, (int, long)):
     985              raise TypeError("Bad reference type")
     986
     987          try:
     988              return self.list[ref]
     989          except IndexError:
     990              if self.exceptions is False:
     991                  return None
     992
     993              raise pyamf.ReferenceError("Reference %r not found" % (ref,))
     994
     995 -    def getReferenceTo(self, obj):
     996          """
     997          Returns a reference to C{obj} if it is contained within this index.
     998
     999          @raise pyamf.ReferenceError: Value not found.
    1000          """
    1001          try:
    1002              return self.dict[self.func(obj)]
    1003          except KeyError:
    1004              if self.exceptions is False:
    1005                  return None
    1006
    1007              raise pyamf.ReferenceError("Value %r not found" % (obj,))
    1008
    1009 -    def append(self, obj):
    1010          """
    1011          Appends C{obj} to this index.
    1012
    1013          @note: Uniqueness is not checked
    1014          @return: The reference to C{obj} in this index.
    1015          """
    1016          h = self.func(obj)
    1017
    1018          self.list.append(obj)
    1019          idx = len(self.list) - 1
    1020          self.dict[h] = idx
    1021
    1022          return idx
    1023
    1024 -    def __eq__(self, other):
    1025          if isinstance(other, list):
    1026              return self.list == other
    1027          elif isinstance(other, dict):
    1028              return self.dict == other
    1029
    1030          return False
    1031
    1032 -    def __len__(self):
    1033          return len(self.list)
    1034
    1035 -    def __getitem__(self, idx):
    1036          return self.getByReference(idx)
    1037
    1038 -    def __contains__(self, obj):
    1039          try:
    1040              r = self.getReferenceTo(obj)
    1041          except pyamf.ReferenceError:
    1042              r = None
    1043
    1044          return r is not None
    1045
    1046 -    def __repr__(self):
    1047          return '<%s list=%r dict=%r>' % (self.__class__.__name__, self.list, self.dict)
    1048
    1049 -    def __iter__(self):
    1050          return iter(self.list)
    1051
    1052
    1053 -class IndexedMap(IndexedCollection):
    1054      """
    1055      Like L{IndexedCollection}, but also maps to another object.
    1056
    1057      @since: 0.4
    1058      """
    1059
    1060 -    def __init__(self, use_hash=False, exceptions=True):
    1061          IndexedCollection.__init__(self, use_hash, exceptions)
    1062
    1063 -    def clear(self):
    1064          """
    1065          Clears the index and mapping.
    1066          """
    1067          IndexedCollection.clear(self)
    1068
    1069          self.mapped = []
    1070
    1071 -    def getMappedByReference(self, ref):
    1072          """
    1073          Returns the mapped object by reference.
    1074
    1075          @raise TypeError: Bad reference type.
    1076          @raise pyamf.ReferenceError: Reference not found.
    1077          """
    1078          if not isinstance(ref, (int, long)):
    1079              raise TypeError("Bad reference type.")
    1080
    1081          try:
    1082              return self.mapped[ref]
    1083          except IndexError:
    1084              if self.exceptions is False:
    1085                  return None
    1086
    1087              raise pyamf.ReferenceError("Reference %r not found" % ref)
    1088
    1089 -    def append(self, obj):
    1090          """
    1091          Appends C{obj} to this index.
    1092
    1093          @return: The reference to C{obj} in this index.
    1094          """
    1095          idx = IndexedCollection.append(self, obj)
    1096          diff = (idx + 1) - len(self.mapped)
    1097
    1098          for i in range(0, diff):
    1099              self.mapped.append(None)
    1100
    1101          return idx
    1102
    1103 -    def map(self, obj, mapped_obj):
    1104          """
    1105          Maps an object.
    1106          """
    1107          idx = self.append(obj)
    1108          self.mapped[idx] = mapped_obj
    1109
    1110          return idx
    1111
    1112
    1113 -def is_ET_element(obj):
    1114      """
    1115      Determines if the supplied C{obj} param is a valid ElementTree element.
    1116      """
    1117      return isinstance(obj, xml_types)
    1118
    1119
    1120 -def is_float_broken():
    1121      """
    1122      Older versions of Python (<=2.5) and the Windows platform are renowned for
    1123      mixing up 'special' floats. This function determines whether this is the
    1124      case.
    1125
    1126      @since: 0.4
    1127      @rtype: C{bool}
    1128      """
    1129      global NaN
    1130
    1131      return str(NaN) != str(struct.unpack("!d", '\xff\xf8\x00\x00\x00\x00\x00\x00')[0])
    1132
    1133
    1134 -def isNaN(val):
    1135      """
    1136      @since: 0.5
    1137      """
    1138      return str(float(val)) == str(NaN)
    1139
    1140
    1141 -def isPosInf(val):
    1142      """
    1143      @since: 0.5
    1144      """
    1145      return str(float(val)) == str(PosInf)
    1146
    1147
    1148 -def isNegInf(val):
    1149      """
    1150      @since: 0.5
    1151      """
    1152      return str(float(val)) == str(NegInf)
    1153
    1154
    1155  # init the module from here ..
    1156
    1157  find_xml_lib()
    1158
    1159  try:
    1160      datetime.datetime.utcfromtimestamp(-31536000.0)
    1161  except ValueError:
    1162      negative_timestamp_broken = True
    1163
    1164  if is_float_broken():
    1165 -    def read_double_workaround(self):
    1166          global PosInf, NegInf, NaN
    1167
    1168          """
    1169          Override the L{DataTypeMixIn.read_double} method to fix problems
    1170          with doubles by using the third-party C{fpconst} library.
    1171          """
    1172          bytes = self.read(8)
    1173
    1174          if self._is_big_endian():
    1175              if bytes == '\xff\xf8\x00\x00\x00\x00\x00\x00':
    1176                  return NaN
    1177
    1178              if bytes == '\xff\xf0\x00\x00\x00\x00\x00\x00':
    1179                  return NegInf
    1180
    1181              if bytes == '\x7f\xf0\x00\x00\x00\x00\x00\x00':
    1182                  return PosInf
    1183          else:
    1184              if bytes == '\x00\x00\x00\x00\x00\x00\xf8\xff':
    1185                  return NaN
    1186
    1187              if bytes == '\x00\x00\x00\x00\x00\x00\xf0\xff':
    1188                  return NegInf
    1189
    1190              if bytes == '\x00\x00\x00\x00\x00\x00\xf0\x7f':
    1191                  return PosInf
    1192
    1193          return struct.unpack("%sd" % self.endian, bytes)[0]
    1194
    1195      DataTypeMixIn.read_double = read_double_workaround
    1196
    1197 -    def write_double_workaround(self, d):
    1198          """
    1199          Override the L{DataTypeMixIn.write_double} method to fix problems
    1200          with doubles by using the third-party C{fpconst} library.
    1201          """
    1202          if type(d) is not float:
    1203              raise TypeError('expected a float (got:%r)' % (type(d),))
    1204
    1205          if isNaN(d):
    1206              if self._is_big_endian():
    1207                  self.write('\xff\xf8\x00\x00\x00\x00\x00\x00')
    1208              else:
    1209                  self.write('\x00\x00\x00\x00\x00\x00\xf8\xff')
    1210          elif isNegInf(d):
    1211              if self._is_big_endian():
    1212                  self.write('\xff\xf0\x00\x00\x00\x00\x00\x00')
    1213              else:
    1214                  self.write('\x00\x00\x00\x00\x00\x00\xf0\xff')
    1215          elif isPosInf(d):
    1216              if self._is_big_endian():
    1217                  self.write('\x7f\xf0\x00\x00\x00\x00\x00\x00')
    1218              else:
    1219                  self.write('\x00\x00\x00\x00\x00\x00\xf0\x7f')
    1220          else:
    1221              write_double_workaround.old_func(self, d)
    1222
    1223      x = DataTypeMixIn.write_double
    1224      DataTypeMixIn.write_double = write_double_workaround
    1225      write_double_workaround.old_func = x
    1226
    1227
    1228  try:
    1229      from cpyamf.util import BufferedByteStream, IndexedCollection, IndexedMap
    1230
    1231 -    class StringIOProxy(BufferedByteStream):
    1232          _wrapped_class = None
    1233
    1234 -        def __init__(self, *args, **kwargs):
    1235              BufferedByteStream.__init__(self, *args, **kwargs)
    1236              self._buffer = self
    1237
    1238 -    class DataTypeMixIn(BufferedByteStream):
    1239          #: Network byte order
    1240          ENDIAN_NETWORK = "!"
    1241          #: Native byte order
    1242          ENDIAN_NATIVE = "@"
    1243          #: Little endian
    1244          ENDIAN_LITTLE = "<"
    1245          #: Big endian
    1246          ENDIAN_BIG = ">"
    1247  except ImportError:
    1248      pass
    1249