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 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']
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
122 """
123 Main pywurfl API class.
124 """
125
127 self.devids = {}
128 self.devuas = {}
129 self._name_test_re = re.compile(ur'^(_|[a-z])(_|[a-z]|[0-9])+$')
130
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
176
177
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
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
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
259 if capability in device.__dict__:
260 delattr(device, capability)
261 for child in device.children:
262 self._remove_capability(child, capability)
263
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
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
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
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
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
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
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
485 """
486 Yields all group names
487 """
488 return self.devids[u'generic'].groups.iterkeys()
489
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
500 """
501 Yields all capability names
502 """
503 for capability in self._capability_generator():
504 yield capability
505
506 @property
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
516 """
517 Return an iterator of all WURFL device ids
518 """
519 return self.devids.iterkeys()
520
521 @property
523 """
524 Return an iterator of all device user agents
525 """
526 return self.devuas.iterkeys()
527
528 @property
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
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