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 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']
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
121 """
122 Main pywurfl API class.
123 """
124
126 self.devids = {}
127 self.devuas = {}
128 self._name_test_re = re.compile(ur'^(_|[a-z])(_|[a-z]|[0-9])+$')
129
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
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
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
250 if capability in device.__dict__:
251 delattr(device, capability)
252 for child in device.children:
253 self._remove_capability(child, capability)
254
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
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
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
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
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
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
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
476 """
477 Yields all group names
478 """
479 return self.devids[u'generic'].groups.iterkeys()
480
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
491 """
492 Yields all capability names
493 """
494 for capability in self._capability_generator():
495 yield capability
496
497 @property
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
507 """
508 Return an iterator of all WURFL device ids
509 """
510 return self.devids.iterkeys()
511
512 @property
514 """
515 Return an iterator of all device user agents
516 """
517 return self.devuas.iterkeys()
518
519 @property
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
530
532 return len(self.devids)
533
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
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
597 if isinstance(val, basestring):
598 if isinstance(val, str):
599 raise UnicodeError(u"argument '%s' must be a unicode string" % name)
600