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