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