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-2008 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-2008, Armand Lynch"
     36  __license__ = "LGPL"
     37  __url__ = "http://celljam.net/"
     38  __version__ = "6.4.0b"
     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: string
     59      @param devua: The user agent for the returned class.
     60      @type devua: string
     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("User Agent: %s\n" % self.devua)
     78                  s.append("WURFL ID: %s\n" % self.devid)
     79                  s.append("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("%s\n" % fbs)
     86                  s.append("Actual Device Root: %s\n\n" % self.actual_device_root)
     87                  for group in sorted(self.groups.keys()):
     88                      s.append("%s\n" % group.upper())
     89                      for cap in sorted(self.groups[group]):
     90                          s.append("%s: %s\n" % (cap, getattr(self, cap)))
     91                      s.append("\n")
     92                  return ''.join(s)
     93
     94          Device.fall_back = 'root'
     95          Device.groups = {}
     96      else:
     97          class Device(parent):
     98              """pywurfl Device"""
     99              pass
    100          parent.children.add(Device)
    101          Device.fall_back = parent.devid
    102
    103      if new_caps is not None:
    104          for name, value in new_caps.iteritems():
    105              setattr(Device, name, value)
    106
    107      Device.devid = devid
    108      Device.devua = devua
    109      Device.children = set()
    110      Device.actual_device_root = actual_device_root
    111
    112      return Device
    113
    114
    115 -class Devices(object):
    116      """
    117      Main pywurfl API class.
    118      """
    119
    120 -    def __init__(self):
    121          self.devids = {}
    122          self.devuas = {}
    123
    124          # Regexes to remove noise from user agents
    125          self._noise = (("/SN\d{15}", "/SNXXXXXXXXXXXXXXX"),
    126                         ("UP.Link\/.*?(\s|$)", ""),
    127                         ("\(via IBM WBI \d+\.\d+\)", ""),
    128                         ("GMCC\/\d\.\d", ""))
    129          self._noise_re = [(re.compile(exp), repl) for exp, repl in self._noise]
    130          self._name_test_re = re.compile(r'^(_|[a-z])(_|[a-z]|[0-9])+$')
    131
    132 -    def find_actual_root(self, device=RootDevice):
    133          """
    134          Find an actual device root.
    135
    136          @param device: A Device class.
    137          @type device: Device class
    138          @raise ActualDeviceNotFound:
    139          """
    140          while device is not RootDevice:
    141              if device.actual_device_root:
    142                  return device
    143              device = device.__bases__[0]
    144          raise ActualDeviceRootNotFound
    145
    146 -    def select_ua(self, devua, actual_device_root=False, filter_noise=True,
    147                    search=None, instance=True):
    148          """
    149          Return a Device object based on the user agent.
    150
    151          @param devua: The device user agent to search for.
    152          @type devua: string
    153          @param actual_device_root: Return a device that is an actual device
    154                                     root
    155          @type actual_device_root: boolean
    156          @param filter_noise: Remove noise words from devua.
    157          @type filter_noise: boolean
    158          @param search: The algorithm to use for searching. If 'search' is None,
    159                         a search will not be performed.
    160          @type search: pywurfl.Algorithm
    161          @param instance: Used to select that you want an instance instead of a
    162                           class object.
    163          @type instance: boolean
    164          @raise DeviceNotFound:
    165          """
    166          devua = devua.strip()
    167          device = None
    168          if devua in self.devuas:
    169              device = self.devuas.get(devua)
    170              if actual_device_root:
    171                  device = self.find_actual_root(device)
    172
    173          if device is None:
    174              if filter_noise:
    175                  for exp, repl in self._noise_re:
    176                      devua = exp.sub(repl, devua)
    177                  devua = devua.strip()
    178
    179                  if devua in self.devuas:
    180                      device = self.devuas.get(devua)
    181                      if actual_device_root:
    182                          device = self.find_actual_root(device)
    183
    184              if device is None and search is not None:
    185                  device = search(devua, self)
    186                  if actual_device_root:
    187                      device = self.find_actual_root(device)
    188
    189          if device is not None:
    190              if instance:
    191                  return device()
    192              else:
    193                  return device
    194          else:
    195              raise DeviceNotFound(devua)
    196
    197 -    def select_id(self, devid, actual_device_root=False, instance=True):
    198          """
    199          Return a Device object based on the WURFL ID.
    200
    201          @param devid: The WURFL id to search for.
    202          @type devid: string
    203          @param actual_device_root: Return a device that is an actual device
    204                                     root.
    205          @param instance: Used to select that you want an instance instead of a
    206                           class.
    207          @type instance: boolean
    208          @raise DeviceNotFound:
    209          """
    210          if devid in self.devids:
    211              device = self.devids.get(devid)
    212              if actual_device_root:
    213                  device = self.find_actual_root(device)
    214              if instance:
    215                  return device()
    216              else:
    217                  return device
    218          else:
    219              raise DeviceNotFound(devid)
    220
    221 -    def add_group(self, group):
    222          """
    223          Add a group to the WURFL class hierarchy
    224          @param group: The group's name. The group name should match this regexp
    225                        ^(_|[a-z])(_|[a-z]|[0-9])+$
    226          @type group: string
    227          """
    228          self._name_test('group', group)
    229          if group not in self.devids['generic'].groups:
    230              self.devids['generic'].groups[group] = []
    231          else:
    232              raise ExistsException("'%s' group exists" % group)
    233
    234 -    def remove_group(self, group):
    235          """
    236          Remove a group and all its capabilities from the WURFL class hierarchy
    237          @param group: The group name. The group name should match this
    238                        regex '^[a-z]+(_|[a-z])+$' and be unique.
    239          @type group: string
    240          """
    241          if group not in self.devids['generic'].groups:
    242              raise WURFLException("'%s' group not found" % group)
    243          caps = self.devids['generic'].groups[group]
    244          generic = self.devids['generic']
    245          for cap in caps:
    246              self._remove_capability(generic, cap)
    247          del self.devids['generic'].groups[group]
    248
    249 -    def _remove_capability(self, device, capability):
    250          if capability in device.__dict__:
    251              delattr(device, capability)
    252          for child in device.children:
    253              self._remove_capability(child, capability)
    254
    255 -    def _remove_tree(self, devid):
    256          device = self.devids[devid]
    257          for child in copy(device.children):
    258              self._remove_tree(child.devid)
    259          del self.devids[device.devid]
    260          del self.devuas[device.devua]
    261
    262 -    def add_capability(self, group, capability, default):
    263          """
    264          Add a capability to the WURFL class hierarchy
    265          @param group: The group name. The group name should match this
    266                        regex ^(_|[a-z])(_|[a-z]|[0-9])+$
    267          @type group: string
    268          @param capability: The capability name. The capability name should match
    269                             this regex ^(_|[a-z])(_|[a-z]|[0-9])+$' and be
    270                             unique amongst all capabilities.
    271          @type capability: string
    272          """
    273          try:
    274              self.add_group(group)
    275          except ExistsException:
    276              # If the group already exists, pass
    277              pass
    278
    279          self._name_test('capability', capability)
    280
    281          for grp, caps in self.devids['generic'].groups.iteritems():
    282              if capability in caps:
    283                  raise ExistsException("'%s' capability exists in group '%s'" %
    284                                        (capability, grp))
    285          else:
    286              self.devids['generic'].groups[group].append(capability)
    287              setattr(self.devids['generic'], capability, default)
    288
    289 -    def remove_capability(self, capability):
    290          """
    291          Remove a capability from the WURFL class hierarchy
    292          @param capability: The capability name.
    293          @type capability: string
    294          """
    295          for group in self.devids['generic'].groups:
    296              if capability in self.devids['generic'].groups[group]:
    297                  break
    298          else:
    299              raise WURFLException("'%s' capability not found" % capability)
    300          generic = self.devids['generic']
    301          self._remove_capability(generic, capability)
    302          self.devids['generic'].groups[group].remove(capability)
    303
    304 -    def add(self, parent, devid, devua, actual_device_root=False,
    305              capabilities=None):
    306          """
    307          Add a device to the WURFL class hierarchy
    308
    309          @param parent: A WURFL ID.
    310          @type parent: string
    311          @param devid: The device id for the new device.
    312          @type devid: string
    313          @param devua: The user agent for the new device.
    314          @type devua: string
    315          @param actual_device_root: Whether or not the new device is an
    316                                     actual device.
    317          @type actual_device_root: boolean
    318          @param capabilities: The new capabilities for the new device class.
    319          @type capabilities: dict
    320          """
    321          if parent not in self.devids:
    322              raise DeviceNotFound(parent)
    323          if devid in self.devids:
    324              raise ExistsException("'%s' device already exists" % devid)
    325          elif devua in self.devuas:
    326              dup_devid = self.devuas[devua].devid
    327              raise ExistsException("'%s' duplicate user agent with '%s'" %
    328                                    (devua, dup_devid))
    329
    330          self.devids[devid] = devclass(self.devids[parent], devid, devua,
    331                                        actual_device_root, capabilities)
    332          self.devuas[devua] = self.devids[devid]
    333
    334 -    def insert_before(self, child, devid, devua, actual_device_root=False,
    335                        capabilities=None):
    336          """
    337          Create and insert a device before another. The parent of the inserted
    338          device becomes the parent of the child device. The child device's
    339          parent is changed to the inserted device.
    340
    341          @param child: A WURFL ID. The child device cannot be the generic
    342                        device.
    343          @type child: string
    344          @param devid: The device id for the new device.
    345          @type devid: string
    346          @param devua: The user agent for the new device.
    347          @type devua: string
    348          @param actual_device_root: Whether or not the new device is an
    349                                     actual device.
    350          @type actual_device_root: boolean
    351          @param capabilities: The new capabilities for the new device class.
    352          @type capabilities: dict
    353          """
    354          if child == 'generic':
    355              raise WURFLException("cannot insert device before generic device")
    356          if child not in self.devids:
    357              raise DeviceNotFound(child)
    358          if devid in self.devids:
    359              raise ExistsException("'%s' device already exists" % devid)
    360          elif devua in self.devuas:
    361              dup_devid = self.devuas[devua].devid
    362              raise ExistsException("'%s' duplicate user agent with '%s'" %
    363                                    (devua, dup_devid))
    364
    365          child_device = self.devids[child]
    366          parent_device = child_device.__bases__[0]
    367          new_device = devclass(parent_device, devid, devua, actual_device_root,
    368                                capabilities)
    369          parent_device.children.remove(child_device)
    370          new_device.children.add(child_device)
    371          child_device.__bases__ = (new_device,)
    372          child_device.fall_back = devid
    373          self.devids[devid] = new_device
    374          self.devuas[devua] = self.devids[devid]
    375
    376 -    def insert_after(self, parent, devid, devua, actual_device_root=False,
    377                       capabilities=None):
    378          """
    379          Create and insert a device after another. The parent of the inserted
    380          device becomes the parent argument. The children of the parent device
    381          become the children of the inserted device then the parent device's
    382          children attribute is to the inserted device.
    383
    384          @param parent: A WURFL ID.
    385          @type parent: string
    386          @param devid: The device id for the new device.
    387          @type devid: string
    388          @param devua: The user agent for the new device.
    389          @type devua: string
    390          @param actual_device_root: Whether or not the new device is an
    391                                     actual device.
    392          @type actual_device_root: boolean
    393          @param capabilities: The new capabilities for the new device class.
    394          @type capabilities: dict
    395          """
    396          if parent not in self.devids:
    397              raise DeviceNotFound(parent)
    398          if devid in self.devids:
    399              raise ExistsException("'%s' device already exists" % devid)
    400          elif devua in self.devuas:
    401              dup_devid = self.devuas[devua].devid
    402              raise ExistsException("'%s' duplicate user agent with '%s'" %
    403                                    (devua, dup_devid))
    404
    405          parent_device = self.devids[parent]
    406          new_device = devclass(parent_device, devid, devua, actual_device_root,
    407                                capabilities)
    408          new_device.children = parent_device.children
    409          new_device.children.remove(new_device)
    410          parent_device.children = set([new_device])
    411
    412          for child_device in new_device.children:
    413              child_device.__bases__ = (new_device,)
    414              child_device.fall_back = devid
    415          self.devids[devid] = new_device
    416          self.devuas[devua] = self.devids[devid]
    417
    418 -    def remove(self, devid):
    419          """
    420          Remove a device from the WURFL class hierarchy
    421
    422          @param devid: A WURFL ID. The generic device cannot be removed.
    423          @type devid: string
    424          """
    425          if devid not in self.devids:
    426              raise DeviceNotFound(devid)
    427          if devid == 'generic':
    428              raise WURFLException("cannot remove generic device")
    429
    430          device = self.devids[devid]
    431          parent_device = device.__bases__[0]
    432          for cls in device.children:
    433              # set the base class of children devices to the base of this device
    434              cls.__bases__ = device.__bases__
    435              parent_device.children.add(cls)
    436              cls.fall_back = parent_device.devid
    437          parent_device.children.remove(device)
    438
    439          del self.devids[device.devid]
    440          del self.devuas[device.devua]
    441
    442 -    def remove_tree(self, devid):
    443          """
    444          Remove a device and all of its children from the WURFL class hierarchy
    445
    446          @param devid: A WURFL ID. The generic device cannot be removed.
    447          @type devid: string
    448          """
    449          if devid not in self.devids:
    450              raise DeviceNotFound(devid)
    451          if devid == 'generic':
    452              raise WURFLException("cannot remove generic device")
    453
    454          device = self.devids[devid]
    455          self._remove_tree(devid)
    456          parent_device = device.__bases__[0]
    457          parent_device.children.remove(device)
    458
    459      @property
    460 -    def groups(self):
    461          """
    462          Yields all group names
    463          """
    464          return self.devids['generic'].groups.iterkeys()
    465
    466 -    def _capability_generator(self, return_groups=False):
    467          for group in self.devids['generic'].groups:
    468              for capability in self.devids['generic'].groups[group]:
    469                  if return_groups:
    470                      yield (group, capability)
    471                  else:
    472                      yield capability
    473
    474      @property
    475 -    def capabilities(self):
    476          """
    477          Yields all capability names
    478          """
    479          for capability in self._capability_generator():
    480              yield capability
    481
    482      @property
    483 -    def grouped_capabilities(self):
    484          """
    485          Yields the tuple (group, capability) for all capabilities
    486          """
    487          for grp_cap in self._capability_generator(return_groups=True):
    488              yield grp_cap
    489
    490      @property
    491 -    def ids(self):
    492          """
    493          Return an iterator of all WURFL device ids
    494          """
    495          return self.devids.iterkeys()
    496
    497      @property
    498 -    def uas(self):
    499          """
    500          Return an iterator of all device user agents
    501          """
    502          return self.devuas.iterkeys()
    503
    504      @property
    505 -    def md5_hexdigest(self):
    506          """
    507          Return MD5 hex digest for all WURFL data
    508          """
    509          data = []
    510          [data.append(str(self.devids[x]())) for x in sorted(self.devids)]
    511          return md5.new(''.join(data)).hexdigest()
    512
    513 -    def __iter__(self):
    514          return self.devids.__iter__()
    515
    516 -    def __len__(self):
    517          return len(self.devids)
    518
    519 -    def _normalize_types(self):
    520          type_set = set()
    521          common_caps = ['actual_device_root', 'children', 'devid', 'devua',
    522                         'groups', 'fall_back']
    523
    524          for device in self.devids.itervalues():
    525              for cap in (c for c in device.__dict__ if c not in common_caps and
    526                          not c.startswith('_')):
    527                  if isinstance(getattr(device, cap), str):
    528                      type_set.add((cap, str))
    529                      try:
    530                          type_set.remove((cap, float))
    531                      except KeyError:
    532                          pass
    533                      try:
    534                          type_set.remove((cap, int))
    535                      except KeyError:
    536                          pass
    537                      try:
    538                          type_set.remove((cap, bool))
    539                      except KeyError:
    540                          pass
    541                  elif isinstance(getattr(device, cap), float):
    542                      if (cap, str) not in type_set:
    543                          type_set.add((cap, float))
    544                          try:
    545                              type_set.remove((cap, int))
    546                          except KeyError:
    547                              pass
    548                          try:
    549                              type_set.remove((cap, bool))
    550                          except KeyError:
    551                              pass
    552                  elif isinstance(getattr(device, cap), int):
    553                      if ((cap, str) not in type_set and
    554                          (cap, float) not in type_set):
    555                          if isinstance(getattr(device, cap), bool):
    556                              if (cap, int) not in type_set:
    557                                  type_set.add((cap, bool))
    558                          else:
    559                              type_set.add((cap, int))
    560                              try:
    561                                  type_set.remove((cap, bool))
    562                              except KeyError:
    563                                  pass
    564
    565          conv_dict = {}
    566          for cap, cap_type in type_set:
    567              conv_dict[cap] = cap_type
    568
    569          for device in self.devids.itervalues():
    570              for cap in conv_dict:
    571                  if cap in device.__dict__:
    572                      setattr(device, cap, conv_dict[cap](device.__dict__[cap]))
    573
    574 -    def _name_test(self, name, value):
    575          if not self._name_test_re.match(value):
    576              msg = "%s '%s' does not conform to regexp "
    577              msg += "r'^(_|[a-z])(_|[a-z]|[0-9])+$'"
    578              raise WURFLException(msg % (name, value))
    579