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