pywurfl

Package pywurfl |
[hide private]
[frames] | no frames]

# Source Code for Package pywurfl

      1  # pywurfl - Wireless Universal Resource File Tools in Python
      2  # Copyright (C) 2006-2010 Armand Lynch
      3  #
      4  # This library is free software; you can redistribute it and/or modify it
      5  # under the terms of the GNU Lesser General Public License as published by the
      6  # Free Software Foundation; either version 2.1 of the License, or (at your
      7  # option) any later version.
      8  #
      9  # This library is distributed in the hope that it will be useful, but WITHOUT
     10  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
     11  # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
     12  # details.
     13  #
     14  # You should have received a copy of the GNU Lesser General Public License
     15  # along with this library; if not, write to the Free Software Foundation, Inc.,
     16  # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
     17  #
     18  # Armand Lynch <lyncha@users.sourceforge.net>
     19
     20  __doc__ = \
     21  """
     22  pywurfl - Python tools for processing and querying the Wireless Universal Resource File (WURFL)
     23  """
     24
     25  import re
     26  import md5
     27  from copy import copy
     28
     29  from pywurfl.exceptions import (WURFLException, ActualDeviceRootNotFound,
     30                                  DeviceNotFound, ExistsException)
     31
     32
     33  __author__ = "Armand Lynch <lyncha@users.sourceforge.net>"
     34  __contributors__ = "Pau Aliagas <pau@newtral.org>"
     35  __copyright__ = "Copyright 2006-2010, Armand Lynch"
     36  __license__ = "LGPL"
     37  __url__ = "http://celljam.net/"
     38  __version__ = "7.0.0"
     39  __all__ = ['devclass', 'Devices']
     40
     41
     42 -class RootDevice(object):
     43      """
     44      pywurfl Root Device base class.
     45
     46      All classes created by pywurfl are at root a subclass of this class.
     47      """
     48      pass
     49
     50
     51 -def devclass(parent, devid, devua, actual_device_root, new_caps=None):
     52      """
     53      Return a pywurfl.Device class.
     54
     55      @param parent: A Device class or None.
     56      @type parent: Device
     57      @param devid: The device id for the returned class.
     58      @type devid: unicode
     59      @param devua: The user agent for the returned class.
     60      @type devua: unicode
     61      @param actual_device_root: Whether or not the returned class is an actual
     62                                 device.
     63      @type actual_device_root: boolean
     64      @param new_caps: The new capabilities for the returned class.
     65      @type new_caps: dict
     66      """
     67      if parent is None:
     68          class Device(RootDevice):
     69              """pywurfl Generic Device"""
     70              def __iter__(self):
     71                  for group in sorted(self.groups.keys()):
     72                      for capability in sorted(self.groups[group]):
     73                          yield (group, capability, getattr(self, capability))
     74
     75              def __str__(self):
     76                  s = []
     77                  s.append(u"User Agent: %s\n" % self.devua)
     78                  s.append(u"WURFL ID: %s\n" % self.devid)
     79                  s.append(u"Fallbacks: ")
     80                  fbs = []
     81                  base_class = self.__class__.__bases__[0]
     82                  while base_class is not RootDevice:
     83                      fbs.append(base_class.devid)
     84                      base_class = base_class.__bases__[0]
     85                  s.append(u"%s\n" % fbs)
     86                  s.append(u"Actual Device Root: %s\n\n" %
     87                           self.actual_device_root)
     88                  for group in sorted(self.groups.keys()):
     89                      s.append(u"%s\n" % group.upper())
     90                      for cap in sorted(self.groups[group]):
     91                          attr = getattr(self, cap)
     92                          if isinstance(attr, basestring):
     93                              attr = attr.encode('ascii', 'xmlcharrefreplace')
     94                          s.append(u"%s: %s\n" % (cap, attr))
     95                      s.append(u"\n")
     96                  return u''.join(s)
     97
     98          Device.fall_back = u'root'
     99          Device.groups = {}
    100      else:
    101          class Device(parent):
    102              """pywurfl Device"""
    103              pass
    104          parent.children.add(Device)
    105          Device.fall_back = parent.devid
    106
    107      if new_caps is not None:
    108          for name, value in new_caps.iteritems():
    109              setattr(Device, name, value)
    110
    111      Device.devid = devid
    112      Device.devua = devua
    113      Device.children = set()
    114      Device.actual_device_root = actual_device_root
    115
    116      return Device
    117
    118
    119 -class Devices(object):
    120      """
    121      Main pywurfl API class.
    122      """
    123
    124 -    def __init__(self):
    125          self.devids = {}
    126          self.devuas = {}
    127
    128          # Regexes to remove noise from user agents
    129          self._noise = ((u"/SN\d{15}", u"/SNXXXXXXXXXXXXXXX"),
    130                         (u"UP.Link\/.*?(\s|$)", u""),
    131                         (u"\(via IBM WBI \d+\.\d+\)", u""),
    132                         (u"GMCC\/\d\.\d", u""))
    133          self._noise_re = [(re.compile(exp), repl) for exp, repl in self._noise]
    134          self._name_test_re = re.compile(ur'^(_|[a-z])(_|[a-z]|[0-9])+$')
    135
    136 -    def find_actual_root(self, device=RootDevice):
    137          """
    138          Find an actual device root.
    139
    140          @param device: A Device class.
    141          @type device: Device class
    142          @raise ActualDeviceNotFound:
    143          """
    144          while device is not RootDevice:
    145              if device.actual_device_root:
    146                  return device
    147              device = device.__bases__[0]
    148          raise ActualDeviceRootNotFound
    149
    150 -    def select_ua(self, devua, actual_device_root=False, filter_noise=True,
    151                    search=None, instance=True):
    152          """
    153          Return a Device object based on the user agent.
    154
    155          @param devua: The device user agent to search for.
    156          @type devua: unicode
    157          @param actual_device_root: Return a device that is an actual device
    158                                     root
    159          @type actual_device_root: boolean
    160          @param filter_noise: Remove noise words from devua.
    161          @type filter_noise: boolean
    162          @param search: The algorithm to use for searching. If 'search' is None,
    163                         a search will not be performed.
    164          @type search: pywurfl.Algorithm
    165          @param instance: Used to select that you want an instance instead of a
    166                           class object.
    167          @type instance: boolean
    168          @raise DeviceNotFound:
    169          """
    170          _unicode_check(u'devua', devua)
    171          devua = devua.strip()
    172          device = None
    173          if devua in self.devuas:
    174              device = self.devuas.get(devua)
    175              if actual_device_root:
    176                  device = self.find_actual_root(device)
    177
    178          if device is None:
    179              if filter_noise:
    180                  for exp, repl in self._noise_re:
    181                      devua = exp.sub(repl, devua)
    182                  devua = devua.strip()
    183
    184                  if devua in self.devuas:
    185                      device = self.devuas.get(devua)
    186                      if actual_device_root:
    187                          device = self.find_actual_root(device)
    188
    189              if device is None and search is not None:
    190                  device = search(devua, self)
    191                  if actual_device_root:
    192                      device = self.find_actual_root(device)
    193
    194          if device is not None:
    195              if instance:
    196                  return device()
    197              else:
    198                  return device
    199          else:
    200              raise DeviceNotFound(devua)
    201
    202 -    def select_id(self, devid, actual_device_root=False, instance=True):
    203          """
    204          Return a Device object based on the WURFL ID.
    205
    206          @param devid: The WURFL id to search for.
    207          @type devid: unicode
    208          @param actual_device_root: Return a device that is an actual device
    209                                     root.
    210          @param instance: Used to select that you want an instance instead of a
    211                           class.
    212          @type instance: boolean
    213          @raise DeviceNotFound:
    214          """
    215          _unicode_check(u'devid', devid)
    216          if devid in self.devids:
    217              device = self.devids.get(devid)
    218              if actual_device_root:
    219                  device = self.find_actual_root(device)
    220              if instance:
    221                  return device()
    222              else:
    223                  return device
    224          else:
    225              raise DeviceNotFound(devid)
    226
    227 -    def add_group(self, group):
    228          """
    229          Add a group to the WURFL class hierarchy
    230          @param group: The group's name. The group name should match this regexp
    231                        ^(_|[a-z])(_|[a-z]|[0-9])+$
    232          @type group: unicode
    233          """
    234          _unicode_check(u'group', group)
    235          self._name_test(u'group', group)
    236          if group not in self.devids[u'generic'].groups:
    237              self.devids[u'generic'].groups[group] = []
    238          else:
    239              raise ExistsException(u"'%s' group exists" % group)
    240
    241 -    def remove_group(self, group):
    242          """
    243          Remove a group and all its capabilities from the WURFL class hierarchy
    244          @param group: The group name. The group name should match this
    245                        regex '^[a-z]+(_|[a-z])+$' and be unique.
    246          @type group: unicode
    247          """
    248          _unicode_check(u'group', group)
    249          if group not in self.devids[u'generic'].groups:
    250              raise WURFLException(u"'%s' group not found" % group)
    251          caps = self.devids[u'generic'].groups[group]
    252          generic = self.devids[u'generic']
    253          for cap in caps:
    254              self._remove_capability(generic, cap)
    255          del self.devids[u'generic'].groups[group]
    256
    257 -    def _remove_capability(self, device, capability):
    258          if capability in device.__dict__:
    259              delattr(device, capability)
    260          for child in device.children:
    261              self._remove_capability(child, capability)
    262
    263 -    def _remove_tree(self, devid):
    264          device = self.devids[devid]
    265          for child in copy(device.children):
    266              self._remove_tree(child.devid)
    267          del self.devids[device.devid]
    268          del self.devuas[device.devua]
    269
    270 -    def add_capability(self, group, capability, default):
    271          """
    272          Add a capability to the WURFL class hierarchy
    273          @param group: The group name. The group name should match this
    274                        regex ^(_|[a-z])(_|[a-z]|[0-9])+$
    275          @type group: unicode
    276          @param capability: The capability name. The capability name should match
    277                             this regex ^(_|[a-z])(_|[a-z]|[0-9])+$' and be
    278                             unique amongst all capabilities.
    279          @type capability: unicode
    280          """
    281          _unicode_check(u'group', group)
    282          _unicode_check(u'capability', capability)
    283          _unicode_check(u'default', default)
    284          try:
    285              self.add_group(group)
    286          except ExistsException:
    287              # If the group already exists, pass
    288              pass
    289
    290          self._name_test(u'capability', capability)
    291
    292          for grp, caps in self.devids[u'generic'].groups.iteritems():
    293              if capability in caps:
    294                  raise ExistsException(u"'%s' capability exists in group '%s'" %
    295                                        (capability, grp))
    296          else:
    297              self.devids[u'generic'].groups[group].append(capability)
    298              setattr(self.devids[u'generic'], capability, default)
    299
    300 -    def remove_capability(self, capability):
    301          """
    302          Remove a capability from the WURFL class hierarchy
    303          @param capability: The capability name.
    304          @type capability: unicode
    305          """
    306          _unicode_check(u'capability', capability)
    307          for group in self.devids[u'generic'].groups:
    308              if capability in self.devids[u'generic'].groups[group]:
    309                  break
    310          else:
    311              raise WURFLException(u"'%s' capability not found" % capability)
    312          generic = self.devids[u'generic']
    313          self._remove_capability(generic, capability)
    314          self.devids[u'generic'].groups[group].remove(capability)
    315
    316 -    def add(self, parent, devid, devua, actual_device_root=False,
    317              capabilities=None):
    318          """
    319          Add a device to the WURFL class hierarchy
    320
    321          @param parent: A WURFL ID.
    322          @type parent: unicode
    323          @param devid: The device id for the new device.
    324          @type devid: unicode
    325          @param devua: The user agent for the new device.
    326          @type devua: unicode
    327          @param actual_device_root: Whether or not the new device is an
    328                                     actual device.
    329          @type actual_device_root: boolean
    330          @param capabilities: The new capabilities for the new device class.
    331          @type capabilities: dict
    332          """
    333          _unicode_check(u'parent', parent)
    334          _unicode_check(u'devid', devid)
    335          _unicode_check(u'devua', devua)
    336          if parent not in self.devids:
    337              raise DeviceNotFound(parent)
    338          if devid in self.devids:
    339              raise ExistsException(u"'%s' device already exists" % devid)
    340          elif devua in self.devuas:
    341              dup_devid = self.devuas[devua].devid
    342              raise ExistsException(u"'%s' duplicate user agent with '%s'" %
    343                                    (devua, dup_devid))
    344
    345          self.devids[devid] = devclass(self.devids[parent], devid, devua,
    346                                        actual_device_root, capabilities)
    347          self.devuas[devua] = self.devids[devid]
    348
    349 -    def insert_before(self, child, devid, devua, actual_device_root=False,
    350                        capabilities=None):
    351          """
    352          Create and insert a device before another. The parent of the inserted
    353          device becomes the parent of the child device. The child device's
    354          parent is changed to the inserted device.
    355
    356          @param child: A WURFL ID. The child device cannot be the generic
    357                        device.
    358          @type child: unicode
    359          @param devid: The device id for the new device.
    360          @type devid: unicode
    361          @param devua: The user agent for the new device.
    362          @type devua: unicode
    363          @param actual_device_root: Whether or not the new device is an
    364                                     actual device.
    365          @type actual_device_root: boolean
    366          @param capabilities: The new capabilities for the new device class.
    367          @type capabilities: dict
    368          """
    369          _unicode_check(u'child', child)
    370          _unicode_check(u'devid', devid)
    371          _unicode_check(u'devua', devua)
    372          if child == u'generic':
    373              raise WURFLException(u"cannot insert device before generic device")
    374          if child not in self.devids:
    375              raise DeviceNotFound(child)
    376          if devid in self.devids:
    377              raise ExistsException(u"'%s' device already exists" % devid)
    378          elif devua in self.devuas:
    379              dup_devid = self.devuas[devua].devid
    380              raise ExistsException(u"'%s' duplicate user agent with '%s'" %
    381                                    (devua, dup_devid))
    382
    383          child_device = self.devids[child]
    384          parent_device = child_device.__bases__[0]
    385          new_device = devclass(parent_device, devid, devua, actual_device_root,
    386                                capabilities)
    387          parent_device.children.remove(child_device)
    388          new_device.children.add(child_device)
    389          child_device.__bases__ = (new_device,)
    390          child_device.fall_back = devid
    391          self.devids[devid] = new_device
    392          self.devuas[devua] = self.devids[devid]
    393
    394 -    def insert_after(self, parent, devid, devua, actual_device_root=False,
    395                       capabilities=None):
    396          """
    397          Create and insert a device after another. The parent of the inserted
    398          device becomes the parent argument. The children of the parent device
    399          become the children of the inserted device then the parent device's
    400          children attribute is to the inserted device.
    401
    402          @param parent: A WURFL ID.
    403          @type parent: unicode
    404          @param devid: The device id for the new device.
    405          @type devid: unicode
    406          @param devua: The user agent for the new device.
    407          @type devua: unicode
    408          @param actual_device_root: Whether or not the new device is an
    409                                     actual device.
    410          @type actual_device_root: boolean
    411          @param capabilities: The new capabilities for the new device class.
    412          @type capabilities: dict
    413          """
    414          _unicode_check(u'parent', parent)
    415          _unicode_check(u'devid', devid)
    416          _unicode_check(u'devua', devua)
    417          if parent not in self.devids:
    418              raise DeviceNotFound(parent)
    419          if devid in self.devids:
    420              raise ExistsException(u"'%s' device already exists" % devid)
    421          elif devua in self.devuas:
    422              dup_devid = self.devuas[devua].devid
    423              raise ExistsException(u"'%s' duplicate user agent with '%s'" %
    424                                    (devua, dup_devid))
    425
    426          parent_device = self.devids[parent]
    427          new_device = devclass(parent_device, devid, devua, actual_device_root,
    428                                capabilities)
    429          new_device.children = parent_device.children
    430          new_device.children.remove(new_device)
    431          parent_device.children = set([new_device])
    432
    433          for child_device in new_device.children:
    434              child_device.__bases__ = (new_device,)
    435              child_device.fall_back = devid
    436          self.devids[devid] = new_device
    437          self.devuas[devua] = self.devids[devid]
    438
    439 -    def remove(self, devid):
    440          """
    441          Remove a device from the WURFL class hierarchy
    442
    443          @param devid: A WURFL ID. The generic device cannot be removed.
    444          @type devid: unicode
    445          """
    446          _unicode_check(u'devid', devid)
    447          if devid not in self.devids:
    448              raise DeviceNotFound(devid)
    449          if devid == u'generic':
    450              raise WURFLException(u"cannot remove generic device")
    451
    452          device = self.devids[devid]
    453          parent_device = device.__bases__[0]
    454          for cls in device.children:
    455              # set the base class of children devices to the base of this device
    456              cls.__bases__ = device.__bases__
    457              parent_device.children.add(cls)
    458              cls.fall_back = parent_device.devid
    459          parent_device.children.remove(device)
    460
    461          del self.devids[device.devid]
    462          del self.devuas[device.devua]
    463
    464 -    def remove_tree(self, devid):
    465          """
    466          Remove a device and all of its children from the WURFL class hierarchy
    467
    468          @param devid: A WURFL ID. The generic device cannot be removed.
    469          @type devid: unicode
    470          """
    471          _unicode_check(u'devid', devid)
    472          if devid not in self.devids:
    473              raise DeviceNotFound(devid)
    474          if devid == u'generic':
    475              raise WURFLException(u"cannot remove generic device")
    476
    477          device = self.devids[devid]
    478          self._remove_tree(devid)
    479          parent_device = device.__bases__[0]
    480          parent_device.children.remove(device)
    481
    482      @property
    483 -    def groups(self):
    484          """
    485          Yields all group names
    486          """
    487          return self.devids[u'generic'].groups.iterkeys()
    488
    489 -    def _capability_generator(self, return_groups=False):
    490          for group in self.devids[u'generic'].groups:
    491              for capability in self.devids[u'generic'].groups[group]:
    492                  if return_groups:
    493                      yield (group, capability)
    494                  else:
    495                      yield capability
    496
    497      @property
    498 -    def capabilities(self):
    499          """
    500          Yields all capability names
    501          """
    502          for capability in self._capability_generator():
    503              yield capability
    504
    505      @property
    506 -    def grouped_capabilities(self):
    507          """
    508          Yields the tuple (group, capability) for all capabilities
    509          """
    510          for grp_cap in self._capability_generator(return_groups=True):
    511              yield grp_cap
    512
    513      @property
    514 -    def ids(self):
    515          """
    516          Return an iterator of all WURFL device ids
    517          """
    518          return self.devids.iterkeys()
    519
    520      @property
    521 -    def uas(self):
    522          """
    523          Return an iterator of all device user agents
    524          """
    525          return self.devuas.iterkeys()
    526
    527      @property
    528 -    def md5_hexdigest(self):
    529          """
    530          Return MD5 hex digest for all WURFL data
    531          """
    532          data = []
    533          [data.append(str(self.devids[x]())) for x in sorted(self.devids)]
    534          return md5.new(u''.join(data)).hexdigest()
    535
    536 -    def __iter__(self):
    537          return self.devids.__iter__()
    538
    539 -    def __len__(self):
    540          return len(self.devids)
    541
    542 -    def _normalize_types(self):
    543          type_set = set()
    544          common_caps = [u'actual_device_root', u'children', u'devid', u'devua',
    545                         u'groups', u'fall_back']
    546
    547          for device in self.devids.itervalues():
    548              for cap in (c for c in device.__dict__ if c not in common_caps and
    549                          not c.startswith(u'_')):
    550                  if isinstance(getattr(device, cap), unicode):
    551                      type_set.add((cap, unicode))
    552                      try:
    553                          type_set.remove((cap, float))
    554                      except KeyError:
    555                          pass
    556                      try:
    557                          type_set.remove((cap, int))
    558                      except KeyError:
    559                          pass
    560                      try:
    561                          type_set.remove((cap, bool))
    562                      except KeyError:
    563                          pass
    564                  elif isinstance(getattr(device, cap), float):
    565                      if (cap, unicode) not in type_set:
    566                          type_set.add((cap, float))
    567                          try:
    568                              type_set.remove((cap, int))
    569                          except KeyError:
    570                              pass
    571                          try:
    572                              type_set.remove((cap, bool))
    573                          except KeyError:
    574                              pass
    575                  elif isinstance(getattr(device, cap), int):
    576                      if ((cap, unicode) not in type_set and
    577                          (cap, float) not in type_set):
    578                          if isinstance(getattr(device, cap), bool):
    579                              if (cap, int) not in type_set:
    580                                  type_set.add((cap, bool))
    581                          else:
    582                              type_set.add((cap, int))
    583                              try:
    584                                  type_set.remove((cap, bool))
    585                              except KeyError:
    586                                  pass
    587
    588          conv_dict = {}
    589          for cap, cap_type in type_set:
    590              conv_dict[cap] = cap_type
    591
    592          for device in self.devids.itervalues():
    593              for cap in conv_dict:
    594                  if cap in device.__dict__:
    595                      setattr(device, cap, conv_dict[cap](device.__dict__[cap]))
    596
    597 -    def _name_test(self, name, value):
    598          if not self._name_test_re.match(value):
    599              msg = u"%s '%s' does not conform to regexp "
    600              msg += u"r'^(_|[a-z])(_|[a-z]|[0-9])+$'"
    601              raise WURFLException(msg % (name, value))
    602
    603
    604 -def _unicode_check(name, val):
    605      if isinstance(val, basestring):
    606          if isinstance(val, str):
    607              raise UnicodeError(u"argument '%s' must be a unicode string" % name)
    608