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-2008, Armand Lynch"
36 __license__ = "LGPL"
37 __url__ = "http://celljam.net/"
38 __version__ = "6.4.0b"
39 __all__ = ['devclass', 'Devices']
40
41
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: string
59 @param devua: The user agent for the returned class.
60 @type devua: string
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("User Agent: %s\n" % self.devua)
78 s.append("WURFL ID: %s\n" % self.devid)
79 s.append("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("%s\n" % fbs)
86 s.append("Actual Device Root: %s\n\n" % self.actual_device_root)
87 for group in sorted(self.groups.keys()):
88 s.append("%s\n" % group.upper())
89 for cap in sorted(self.groups[group]):
90 s.append("%s: %s\n" % (cap, getattr(self, cap)))
91 s.append("\n")
92 return ''.join(s)
93
94 Device.fall_back = 'root'
95 Device.groups = {}
96 else:
97 class Device(parent):
98 """pywurfl Device"""
99 pass
100 parent.children.add(Device)
101 Device.fall_back = parent.devid
102
103 if new_caps is not None:
104 for name, value in new_caps.iteritems():
105 setattr(Device, name, value)
106
107 Device.devid = devid
108 Device.devua = devua
109 Device.children = set()
110 Device.actual_device_root = actual_device_root
111
112 return Device
113
114
116 """
117 Main pywurfl API class.
118 """
119
121 self.devids = {}
122 self.devuas = {}
123
124
125 self._noise = (("/SN\d{15}", "/SNXXXXXXXXXXXXXXX"),
126 ("UP.Link\/.*?(\s|$)", ""),
127 ("\(via IBM WBI \d+\.\d+\)", ""),
128 ("GMCC\/\d\.\d", ""))
129 self._noise_re = [(re.compile(exp), repl) for exp, repl in self._noise]
130 self._name_test_re = re.compile(r'^(_|[a-z])(_|[a-z]|[0-9])+$')
131
133 """
134 Find an actual device root.
135
136 @param device: A Device class.
137 @type device: Device class
138 @raise ActualDeviceNotFound:
139 """
140 while device is not RootDevice:
141 if device.actual_device_root:
142 return device
143 device = device.__bases__[0]
144 raise ActualDeviceRootNotFound
145
146 - def select_ua(self, devua, actual_device_root=False, filter_noise=True,
147 search=None, instance=True):
148 """
149 Return a Device object based on the user agent.
150
151 @param devua: The device user agent to search for.
152 @type devua: string
153 @param actual_device_root: Return a device that is an actual device
154 root
155 @type actual_device_root: boolean
156 @param filter_noise: Remove noise words from devua.
157 @type filter_noise: boolean
158 @param search: The algorithm to use for searching. If 'search' is None,
159 a search will not be performed.
160 @type search: pywurfl.Algorithm
161 @param instance: Used to select that you want an instance instead of a
162 class object.
163 @type instance: boolean
164 @raise DeviceNotFound:
165 """
166 devua = devua.strip()
167 device = None
168 if devua in self.devuas:
169 device = self.devuas.get(devua)
170 if actual_device_root:
171 device = self.find_actual_root(device)
172
173 if device is None:
174 if filter_noise:
175 for exp, repl in self._noise_re:
176 devua = exp.sub(repl, devua)
177 devua = devua.strip()
178
179 if devua in self.devuas:
180 device = self.devuas.get(devua)
181 if actual_device_root:
182 device = self.find_actual_root(device)
183
184 if device is None and search is not None:
185 device = search(devua, self)
186 if actual_device_root:
187 device = self.find_actual_root(device)
188
189 if device is not None:
190 if instance:
191 return device()
192 else:
193 return device
194 else:
195 raise DeviceNotFound(devua)
196
197 - def select_id(self, devid, actual_device_root=False, instance=True):
198 """
199 Return a Device object based on the WURFL ID.
200
201 @param devid: The WURFL id to search for.
202 @type devid: string
203 @param actual_device_root: Return a device that is an actual device
204 root.
205 @param instance: Used to select that you want an instance instead of a
206 class.
207 @type instance: boolean
208 @raise DeviceNotFound:
209 """
210 if devid in self.devids:
211 device = self.devids.get(devid)
212 if actual_device_root:
213 device = self.find_actual_root(device)
214 if instance:
215 return device()
216 else:
217 return device
218 else:
219 raise DeviceNotFound(devid)
220
222 """
223 Add a group to the WURFL class hierarchy
224 @param group: The group's name. The group name should match this regexp
225 ^(_|[a-z])(_|[a-z]|[0-9])+$
226 @type group: string
227 """
228 self._name_test('group', group)
229 if group not in self.devids['generic'].groups:
230 self.devids['generic'].groups[group] = []
231 else:
232 raise ExistsException("'%s' group exists" % group)
233
235 """
236 Remove a group and all its capabilities from the WURFL class hierarchy
237 @param group: The group name. The group name should match this
238 regex '^[a-z]+(_|[a-z])+$' and be unique.
239 @type group: string
240 """
241 if group not in self.devids['generic'].groups:
242 raise WURFLException("'%s' group not found" % group)
243 caps = self.devids['generic'].groups[group]
244 generic = self.devids['generic']
245 for cap in caps:
246 self._remove_capability(generic, cap)
247 del self.devids['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: string
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: string
272 """
273 try:
274 self.add_group(group)
275 except ExistsException:
276
277 pass
278
279 self._name_test('capability', capability)
280
281 for grp, caps in self.devids['generic'].groups.iteritems():
282 if capability in caps:
283 raise ExistsException("'%s' capability exists in group '%s'" %
284 (capability, grp))
285 else:
286 self.devids['generic'].groups[group].append(capability)
287 setattr(self.devids['generic'], capability, default)
288
290 """
291 Remove a capability from the WURFL class hierarchy
292 @param capability: The capability name.
293 @type capability: string
294 """
295 for group in self.devids['generic'].groups:
296 if capability in self.devids['generic'].groups[group]:
297 break
298 else:
299 raise WURFLException("'%s' capability not found" % capability)
300 generic = self.devids['generic']
301 self._remove_capability(generic, capability)
302 self.devids['generic'].groups[group].remove(capability)
303
304 - def add(self, parent, devid, devua, actual_device_root=False,
305 capabilities=None):
306 """
307 Add a device to the WURFL class hierarchy
308
309 @param parent: A WURFL ID.
310 @type parent: string
311 @param devid: The device id for the new device.
312 @type devid: string
313 @param devua: The user agent for the new device.
314 @type devua: string
315 @param actual_device_root: Whether or not the new device is an
316 actual device.
317 @type actual_device_root: boolean
318 @param capabilities: The new capabilities for the new device class.
319 @type capabilities: dict
320 """
321 if parent not in self.devids:
322 raise DeviceNotFound(parent)
323 if devid in self.devids:
324 raise ExistsException("'%s' device already exists" % devid)
325 elif devua in self.devuas:
326 dup_devid = self.devuas[devua].devid
327 raise ExistsException("'%s' duplicate user agent with '%s'" %
328 (devua, dup_devid))
329
330 self.devids[devid] = devclass(self.devids[parent], devid, devua,
331 actual_device_root, capabilities)
332 self.devuas[devua] = self.devids[devid]
333
334 - def insert_before(self, child, devid, devua, actual_device_root=False,
335 capabilities=None):
336 """
337 Create and insert a device before another. The parent of the inserted
338 device becomes the parent of the child device. The child device's
339 parent is changed to the inserted device.
340
341 @param child: A WURFL ID. The child device cannot be the generic
342 device.
343 @type child: string
344 @param devid: The device id for the new device.
345 @type devid: string
346 @param devua: The user agent for the new device.
347 @type devua: string
348 @param actual_device_root: Whether or not the new device is an
349 actual device.
350 @type actual_device_root: boolean
351 @param capabilities: The new capabilities for the new device class.
352 @type capabilities: dict
353 """
354 if child == 'generic':
355 raise WURFLException("cannot insert device before generic device")
356 if child not in self.devids:
357 raise DeviceNotFound(child)
358 if devid in self.devids:
359 raise ExistsException("'%s' device already exists" % devid)
360 elif devua in self.devuas:
361 dup_devid = self.devuas[devua].devid
362 raise ExistsException("'%s' duplicate user agent with '%s'" %
363 (devua, dup_devid))
364
365 child_device = self.devids[child]
366 parent_device = child_device.__bases__[0]
367 new_device = devclass(parent_device, devid, devua, actual_device_root,
368 capabilities)
369 parent_device.children.remove(child_device)
370 new_device.children.add(child_device)
371 child_device.__bases__ = (new_device,)
372 child_device.fall_back = devid
373 self.devids[devid] = new_device
374 self.devuas[devua] = self.devids[devid]
375
376 - def insert_after(self, parent, devid, devua, actual_device_root=False,
377 capabilities=None):
378 """
379 Create and insert a device after another. The parent of the inserted
380 device becomes the parent argument. The children of the parent device
381 become the children of the inserted device then the parent device's
382 children attribute is to the inserted device.
383
384 @param parent: A WURFL ID.
385 @type parent: string
386 @param devid: The device id for the new device.
387 @type devid: string
388 @param devua: The user agent for the new device.
389 @type devua: string
390 @param actual_device_root: Whether or not the new device is an
391 actual device.
392 @type actual_device_root: boolean
393 @param capabilities: The new capabilities for the new device class.
394 @type capabilities: dict
395 """
396 if parent not in self.devids:
397 raise DeviceNotFound(parent)
398 if devid in self.devids:
399 raise ExistsException("'%s' device already exists" % devid)
400 elif devua in self.devuas:
401 dup_devid = self.devuas[devua].devid
402 raise ExistsException("'%s' duplicate user agent with '%s'" %
403 (devua, dup_devid))
404
405 parent_device = self.devids[parent]
406 new_device = devclass(parent_device, devid, devua, actual_device_root,
407 capabilities)
408 new_device.children = parent_device.children
409 new_device.children.remove(new_device)
410 parent_device.children = set([new_device])
411
412 for child_device in new_device.children:
413 child_device.__bases__ = (new_device,)
414 child_device.fall_back = devid
415 self.devids[devid] = new_device
416 self.devuas[devua] = self.devids[devid]
417
419 """
420 Remove a device from the WURFL class hierarchy
421
422 @param devid: A WURFL ID. The generic device cannot be removed.
423 @type devid: string
424 """
425 if devid not in self.devids:
426 raise DeviceNotFound(devid)
427 if devid == 'generic':
428 raise WURFLException("cannot remove generic device")
429
430 device = self.devids[devid]
431 parent_device = device.__bases__[0]
432 for cls in device.children:
433
434 cls.__bases__ = device.__bases__
435 parent_device.children.add(cls)
436 cls.fall_back = parent_device.devid
437 parent_device.children.remove(device)
438
439 del self.devids[device.devid]
440 del self.devuas[device.devua]
441
443 """
444 Remove a device and all of its children from the WURFL class hierarchy
445
446 @param devid: A WURFL ID. The generic device cannot be removed.
447 @type devid: string
448 """
449 if devid not in self.devids:
450 raise DeviceNotFound(devid)
451 if devid == 'generic':
452 raise WURFLException("cannot remove generic device")
453
454 device = self.devids[devid]
455 self._remove_tree(devid)
456 parent_device = device.__bases__[0]
457 parent_device.children.remove(device)
458
459 @property
461 """
462 Yields all group names
463 """
464 return self.devids['generic'].groups.iterkeys()
465
467 for group in self.devids['generic'].groups:
468 for capability in self.devids['generic'].groups[group]:
469 if return_groups:
470 yield (group, capability)
471 else:
472 yield capability
473
474 @property
476 """
477 Yields all capability names
478 """
479 for capability in self._capability_generator():
480 yield capability
481
482 @property
484 """
485 Yields the tuple (group, capability) for all capabilities
486 """
487 for grp_cap in self._capability_generator(return_groups=True):
488 yield grp_cap
489
490 @property
492 """
493 Return an iterator of all WURFL device ids
494 """
495 return self.devids.iterkeys()
496
497 @property
499 """
500 Return an iterator of all device user agents
501 """
502 return self.devuas.iterkeys()
503
504 @property
506 """
507 Return MD5 hex digest for all WURFL data
508 """
509 data = []
510 [data.append(str(self.devids[x]())) for x in sorted(self.devids)]
511 return md5.new(''.join(data)).hexdigest()
512
515
517 return len(self.devids)
518
520 type_set = set()
521 common_caps = ['actual_device_root', 'children', 'devid', 'devua',
522 'groups', 'fall_back']
523
524 for device in self.devids.itervalues():
525 for cap in (c for c in device.__dict__ if c not in common_caps and
526 not c.startswith('_')):
527 if isinstance(getattr(device, cap), str):
528 type_set.add((cap, str))
529 try:
530 type_set.remove((cap, float))
531 except KeyError:
532 pass
533 try:
534 type_set.remove((cap, int))
535 except KeyError:
536 pass
537 try:
538 type_set.remove((cap, bool))
539 except KeyError:
540 pass
541 elif isinstance(getattr(device, cap), float):
542 if (cap, str) not in type_set:
543 type_set.add((cap, float))
544 try:
545 type_set.remove((cap, int))
546 except KeyError:
547 pass
548 try:
549 type_set.remove((cap, bool))
550 except KeyError:
551 pass
552 elif isinstance(getattr(device, cap), int):
553 if ((cap, str) not in type_set and
554 (cap, float) not in type_set):
555 if isinstance(getattr(device, cap), bool):
556 if (cap, int) not in type_set:
557 type_set.add((cap, bool))
558 else:
559 type_set.add((cap, int))
560 try:
561 type_set.remove((cap, bool))
562 except KeyError:
563 pass
564
565 conv_dict = {}
566 for cap, cap_type in type_set:
567 conv_dict[cap] = cap_type
568
569 for device in self.devids.itervalues():
570 for cap in conv_dict:
571 if cap in device.__dict__:
572 setattr(device, cap, conv_dict[cap](device.__dict__[cap]))
573
575 if not self._name_test_re.match(value):
576 msg = "%s '%s' does not conform to regexp "
577 msg += "r'^(_|[a-z])(_|[a-z]|[0-9])+$'"
578 raise WURFLException(msg % (name, value))
579