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