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