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