1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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']
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
120 """
121 Main pywurfl API class.
122 """
123
125 self.devids = {}
126 self.devuas = {}
127
128
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
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
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
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
258 if capability in device.__dict__:
259 delattr(device, capability)
260 for child in device.children:
261 self._remove_capability(child, capability)
262
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
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
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
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
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
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
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
484 """
485 Yields all group names
486 """
487 return self.devids[u'generic'].groups.iterkeys()
488
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
499 """
500 Yields all capability names
501 """
502 for capability in self._capability_generator():
503 yield capability
504
505 @property
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
515 """
516 Return an iterator of all WURFL device ids
517 """
518 return self.devids.iterkeys()
519
520 @property
522 """
523 Return an iterator of all device user agents
524 """
525 return self.devuas.iterkeys()
526
527 @property
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
538
540 return len(self.devids)
541
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
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
605 if isinstance(val, basestring):
606 if isinstance(val, str):
607 raise UnicodeError(u"argument '%s' must be a unicode string" % name)
608