From 32b0ff32899dc14178c357dec553b1ab8c4785ab Mon Sep 17 00:00:00 2001 From: Massimo DiPierro Date: Fri, 2 Dec 2011 16:16:30 -0600 Subject: [PATCH] new user agent, thananks Angelo --- VERSION | 2 +- gluon/contrib/user_agent_parser.py | 316 +++++++++++++++++++---------- 2 files changed, 210 insertions(+), 108 deletions(-) diff --git a/VERSION b/VERSION index c174dfc2..a28755db 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 1.99.3 (2011-11-30 22:36:21) dev +Version 1.99.3 (2011-12-02 16:16:28) dev diff --git a/gluon/contrib/user_agent_parser.py b/gluon/contrib/user_agent_parser.py index fd134758..8fa7ee10 100644 --- a/gluon/contrib/user_agent_parser.py +++ b/gluon/contrib/user_agent_parser.py @@ -9,6 +9,9 @@ Aim is Taken from http://pypi.python.org/pypi/httpagentparser (MIT license) Modified my Ross Peoples for web2py to better support iPhone and iPad. +Modified by Angelo Compagnucci to better support a wide ringe of mobile devices. +Now it supports: tablet device (is_tablet), BlackBerry, BlackBerry PlayBook, Android Tablets, Windows Mobile, +Symbian. """ import sys @@ -52,13 +55,14 @@ class DetectorsHub(dict): class DetectorBase(object): name = "" # "to perform match in DetectorsHub object" - info_type = "override me" - result_key = "override me" + info_type = '' #override me + result_key = '' #override me order = 10 # 0 is highest - look_for = "string to look for" + look_for = [] # list of words to look for skip_if_found = [] # strings if present stop processin can_register = False is_mobile = False + is_tablet = False prefs = dict() # dict(info_type = [name1, name2], ..) version_splitters = ["/", " "] _suggested_detectors = None @@ -71,9 +75,16 @@ class DetectorBase(object): def detect(self, agent, result): if agent and self.checkWords(agent): result[self.info_type] = dict(name=self.name) - result[self.info_type]['is_mobile'] = self.is_mobile - if not result.get('is_mobile',None): - result['is_mobile'] = result[self.info_type]['is_mobile'] + is_mobile=self.is_mobile + is_tablet=self.is_tablet + if result.get('is_mobile') is None: + result['is_mobile'] = False + if is_mobile: + result['is_mobile'] = is_mobile + if result.get('is_tablet') is None: + result['is_tablet'] = False + if is_tablet: + result['is_tablet'] = is_tablet version = self.getVersion(agent) if version: @@ -86,14 +97,18 @@ class DetectorBase(object): for w in self.skip_if_found: if w in agent: return False - if self.look_for in agent: - return True - return False + for w in self.look_for: + if not w in agent: + return False + return True + # It get the version correctly only if the words list + # contains only one element, alternatively, you have to + # override this method def getVersion(self, agent): # -> version string /None vs = self.version_splitters - return agent.split(self.look_for + vs[0])[-1].split(vs[1])[0].strip() + return agent.split(self.look_for[0] + vs[0])[1].split(vs[1])[0].strip() class OS(DetectorBase): @@ -118,85 +133,128 @@ class Browser(DetectorBase): class Macintosh(OS): - look_for = 'Macintosh' + look_for = ['Macintosh'] prefs = dict(dist=None) def getVersion(self, agent): pass class Firefox(Browser): - look_for = "Firefox" + look_for = ["Firefox"] class Konqueror(Browser): - look_for = "Konqueror" + look_for = ["Konqueror"] version_splitters = ["/", ";"] class Opera(Browser): - look_for = "Opera" + look_for = ["Opera"] def getVersion(self, agent): - return agent.split(self.look_for)[1][1:].split(' ')[0] + return agent.split(self.look_for[0])[1][1:].split(' ')[0] class Netscape(Browser): - look_for = "Netscape" + look_for = ["Netscape"] class MSIE(Browser): - look_for = "MSIE" + look_for = ["MSIE"] skip_if_found = ["Opera"] name = "Microsoft Internet Explorer" version_splitters = [" ", ";"] class Galeon(Browser): - look_for = "Galeon" + look_for = ["Galeon"] class Safari(Browser): - look_for = "Safari" - - def checkWords(self, agent): - unless_list = ["Chrome", "OmniWeb"] - if self.look_for in agent: - for word in unless_list: - if word in agent: - return False - return True + look_for = ["Safari"] + skip_if_found = ["Chrome", "OmniWeb", "Mobile", "iPad", 'Android'] def getVersion(self, agent): if "Version/" in agent: - return agent.split('Version/')[-1].split(' ')[0].strip() - else: - # Mobile Safari - return agent.split('Safari ')[-1].split(' ')[0].strip() + return agent.split('Version/')[1].split(' ')[0].strip() + +class SafariTablet(Browser): + name = "Safari" + look_for = ['Safari', 'Android'] + skip_if_found = ["Chrome", "OmniWeb", "Mobile", "iPad"] + is_mobile = True + is_tablet = True + + def getVersion(self, agent): + if "Version/" in agent: + return agent.split('Version/')[1].split(' ')[0].strip() + +class SafariMobile(Browser): + name = "Safari" + look_for = ["Safari", "Mobile"] + is_mobile = True + + def getVersion(self, agent): + if "Version/" in agent: + return agent.split('Version/')[1].split(' ')[0].strip() + +class SafariNokia(Browser): + name = "Safari" + look_for = ["Safari", "SymbianOS"] + is_mobile = True + + def getVersion(self, agent): + pass + +class SafariiPad(Browser): + name = "Safari" + look_for = ["Safari", "iPad"] + skip_if_found = ["Chrome", "OmniWeb"] + is_mobile = True + is_tablet = True + + def getVersion(self, agent): + if "Version/" in agent: + return agent.split('Version/')[1].split(' ')[0].strip() class Linux(OS): - look_for = 'Linux' - prefs = dict(browser=["Firefox"], - dist=["Ubuntu", "Android"], flavor=None) + look_for = ["Linux"] + prefs = dict(dist=["Ubuntu", "Android", "Debian"], flavor=None) def getVersion(self, agent): pass -class RIM(OS): - look_for = 'BlackBerry' - prefs = dict(dist=[None], flavor=None) - +class BlackBerry(OS): + look_for = ['BlackBerry'] + prefs = dict( flavor=['PlayBook']) + is_mobile = True + + # Manual check for tablet + def checkWords(self, agent): + if 'BlackBerry' in agent or 'PlayBook' in agent: + return True + return False + def getVersion(self, agent): pass +class PlayBook(Flavor): + look_for = ['PlayBook'] + is_mobile=True + is_tablet=True + + def getVersion(self, agent): + return agent.partition('Tablet OS')[2].partition(';')[0].strip() class Macintosh(OS): - look_for = 'Macintosh' + look_for = ['Macintosh'] prefs = dict(dist=None, flavor=['MacOS']) + def getVersion(self, agent): pass class MacOS(Flavor): - look_for = 'Mac OS' - prefs = dict(browser=['Firefox', 'Opera', "Microsoft Internet Explorer"]) + look_for = ['Mac OS'] + prefs = dict(browser=['Safari','SafariMobile', 'SafariIpad', 'Firefox', 'Opera', "Microsoft Internet Explorer"]) def getVersion(self, agent): version_end_chars = [';', ')'] @@ -209,63 +267,81 @@ class MacOS(Flavor): class Windows(OS): - look_for = 'Windows' + look_for = ['Windows', 'NT'] prefs = dict(browser=["Microsoft Internet Explorer", 'Firefox'], dist=['WindowsMobile'], flavor=None) def getVersion(self, agent): - v = agent.split('Windows')[-1].split(';')[0].strip() - if ')' in v: - v = v.split(')')[0] - return v + v = agent.partition('NT') + return v[1]+' '+v[2].replace(')',';').partition(';')[0].strip() +class WindowsMobile(Dist): + name = 'Phone' + look_for = ['Windows', 'Phone'] + is_mobile = True + + def getVersion(self, agent): + return agent.partition('Windows Phone')[2].replace(')','').partition(';')[0].strip() class Ubuntu(Dist): - look_for = 'Ubuntu' + look_for = ['Ubuntu'] version_splitters = ["/", " "] prefs = dict(browser=['Firefox']) class Debian(Dist): - look_for = 'Debian' + look_for = ['Debian'] version_splitters = ["/", " "] prefs = dict(browser=['Firefox']) class Chrome(Browser): - look_for = "Chrome" + look_for = ['Chrome'] version_splitters = ["/", " "] class ChromeOS(OS): - look_for = "CrOS" + look_for = ['CrOS'] version_splitters = [" ", " "] prefs = dict(browser=['Chrome']) def getVersion(self, agent): vs = self.version_splitters - return agent.split(self.look_for+vs[0])[-1].split(vs[1])[1].strip()[:-1] - -class WindowsMobile(Dist): - look_for = 'Windows Phone' - is_mobile = True - - def getVersion(self, agent): - return agent.split('Windows Phone')[-1].split(';')[0].strip().split(' ')[-1] + return agent.split(self.look_for[0]+vs[0])[1].split(vs[1])[1].strip()[:-1] class Android(Dist): - look_for = 'Android' + look_for = ['Android'] + prefs = dict(browser=['SafariTablet', 'SafariMobile']) is_mobile = True def getVersion(self, agent): - return agent.split('Android')[-1].split(';')[0].strip() + return agent.split('Android')[1].split(';')[0].strip() +class SymbianOS(OS): + look_for = ['SymbianOS'] + prefs = dict(dist = ['Series'], browser = ['Safari', 'Opera']) + is_mobile = True + version_splitters = ['/', '; '] + +class Series(Flavor): + look_for = ['SymbianOS', 'Series'] + version_splitters = ['/', ';'] + + def getVersion(self, agent): + return agent.partition('Series')[2].partition(' ')[0].replace('/',' ') + +class BrowserNG(Browser): + look_for = ['BrowserNG'] + version_splitters = ['/', ';'] class iPhone(Dist): - look_for = 'iPhone' - is_mobile = True + look_for = ['iPhone'] + is_mobile = True + prefs = dict(browser=['SafariMobile']) def getVersion(self, agent): version_end_chars = ['like', ';', ')'] - part = agent.split('CPU OS')[-1].strip() + if (not 'CPU iPhone OS' in agent) and (not 'CPU OS' in agent): + return 'X' + part = agent.split('OS')[1].strip() for c in version_end_chars: if c in part: version = 'iOS ' + part.split(c)[0].strip() @@ -273,12 +349,15 @@ class iPhone(Dist): return version.replace('_', '.') class iPad(Dist): - look_for = 'iPad' + look_for = ['iPad'] is_mobile = True + is_tablet = True def getVersion(self, agent): version_end_chars = ['like', ';', ')'] - part = agent.split('CPU OS')[-1].strip() + if not 'OS' in agent: + return '' + part = agent.split('OS')[1].strip() for c in version_end_chars: if c in part: version = 'iOS ' + part.split(c)[0].strip() @@ -290,21 +369,13 @@ detectorshub = DetectorsHub() def detect(agent): result = dict() prefs = dict() - _suggested_detectors = [] for info_type in detectorshub: - if not _suggested_detectors: - detectors = detectorshub[info_type] - _d_prefs = prefs.get(info_type, []) - detectors = detectorshub.reorderByPrefs(detectors, _d_prefs) - if "detector" in locals(): - detector._suggested_detectors = detectors - else: - detectors = _suggested_detectors + detectors = detectorshub[info_type] + _d_prefs = prefs.get(info_type, []) + detectors = detectorshub.reorderByPrefs(detectors, _d_prefs) for detector in detectors: - # print "detector name: ", detector.name if detector.detect(agent, result): prefs = detector.prefs - _suggested_detectors = detector._suggested_detectors break return result @@ -327,8 +398,7 @@ def simple_detect(agent): os_version = os_list and ('flavor' in result and result['flavor'] and result['flavor'].get( 'version')) or ('dist' in result and result['dist'] and result['dist'].get('version')) \ or ('os' in result and result['os'] and result['os'].get('version')) or "" - browser = 'browser' in result and result['browser']['name'] \ - or 'Unknown Browser' + browser = 'browser' in result and result['browser']['name'] or 'Unknown Browser' browser_version = 'browser' in result \ and result['browser'].get('version') or "" if browser_version: @@ -336,7 +406,7 @@ def simple_detect(agent): if os_version: os = " ".join((os, os_version)) #is_mobile = ('dist' in result and result.dist.is_mobile) or ('os' in result and result.os.is_mobile) or False - return os, browser, result.is_mobile + return os, browser, result['is_mobile'] if __name__ == '__main__': @@ -344,38 +414,70 @@ if __name__ == '__main__': import unittest data = ( - ("Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-GB; rv:1.9.0.10) Gecko/2009042315 Firefox/3.0.10", - ('MacOS Macintosh X 10.5', 'Firefox 3.0.10'), - {'flavor': {'version': 'X 10.5', 'name': 'MacOS'}, 'os': {'name': 'Macintosh'}, 'browser': {'version': '3.0.10', 'name': 'Firefox'}},), - ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24,gzip(gfe)", - ('MacOS Macintosh X 10.6.6', 'Chrome 11.0.696.3'), - {'flavor': {'version': 'X 10.6.6', 'name': 'MacOS'}, 'os': {'name': 'Macintosh'}, 'browser': {'version': '11.0.696.3', 'name': 'Chrome'}},), - ("Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100308 Ubuntu/10.04 (lucid) Firefox/3.6 GTB7.1", - ('Ubuntu Linux 10.04', 'Firefox 3.6'), - {'dist': {'version': '10.04', 'name': 'Ubuntu'}, 'os': {'name': 'Linux'}, 'browser': {'version': '3.6', 'name': 'Firefox'}},), - ("Mozilla/5.0 (Linux; U; Android 2.2.1; fr-ch; A43 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", - ('Android Linux 2.2.1', 'Safari 4.0'), - {'dist': {'version': '2.2.1', 'name': 'Android'}, 'os': {'name': 'Linux'}, 'browser': {'version': '4.0', 'name': 'Safari'}},), - ("Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3", - ('MacOS IPhone X', 'Safari 3.0'), - {'flavor': {'version': 'X', 'name': 'MacOS'}, 'dist': {'version': 'X', 'name': 'IPhone'}, 'browser': {'version': '3.0', 'name': 'Safari'}},), - ("Mozilla/5.0 (X11; CrOS i686 0.0.0) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.27 Safari/534.24,gzip(gfe)", - ('ChromeOS 0.0.0', 'Chrome 11.0.696.27'), - {'os': {'name': 'ChromeOS', 'version': '0.0.0'}, 'browser': {'name': 'Chrome', 'version': '11.0.696.27'}},), - ("Mozilla/4.0 (compatible; MSIE 6.0; MSIE 5.5; Windows NT 5.1) Opera 7.02 [en]", - ('Windows NT 5.1', 'Opera 7.02'), - {'os': {'name': 'Windows', 'version': 'NT 5.1'}, 'browser': {'name': 'Opera', 'version': '7.02'}},), - ("Opera/9.80 (X11; Linux i686; U; en) Presto/2.9.168 Version/11.50", - ("Linux", "Opera 9.80"), - {"os": {"name": "Linux"}, "browser": {"name": "Opera", "version": "9.80"}},), - ("Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5) Gecko/20060127 Netscape/8.1", - ("Windows NT 5.1", "Netscape 8.1"), - {'os': {'name': 'Windows', 'version': 'NT 5.1'}, 'browser': {'name': 'Netscape', 'version': '8.1'}},), + + + ('Mozilla/5.0 (SymbianOS/9.2; U; Series60/3.1 Nokia6120c/3.83; Profile/MIDP-2.0 Configuration/CLDC-1.1) AppleWebKit/413 (KHTML, like Gecko) Safari/413', + ('Series SymbianOS 60 3.1', 'Safari', True), + {'is_mobile': True, 'is_tablet': False, 'flavor': {'name': 'Series', 'version': '60 3.1'}, 'os': {'name': 'SymbianOS', 'version': '9.2'}, 'browser': {'name': 'Safari'}},), + ('Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124', + ('Series SymbianOS 60 5.0', 'BrowserNG 7.1.18124', True), + {'is_mobile': True, 'is_tablet': False, 'flavor': {'name': 'Series', 'version': '60 5.0'}, 'os': {'name': 'SymbianOS', 'version': '9.4'}, 'browser': {'name': 'BrowserNG', 'version': '7.1.18124'}},), + ('Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Windows Phone 6.5.3.5)', + ('Phone Windows 6.5.3.5', 'Microsoft Internet Explorer 6.0', True), + {'is_mobile': True, 'is_tablet': False, 'dist': {'name': 'Phone', 'version': '6.5.3.5'}, 'os': {'name': 'Windows', 'version': 'NT 5.1'}, 'browser': {'name': 'Microsoft Internet Explorer', 'version': '6.0'}},), + ('Mozilla/5.0 (PlayBook; U; RIM Tablet OS 1.0.0; en-US) AppleWebKit/534.8+ (KHTML, like Gecko) Version/0.0.1 Safari/534.8+', + ('PlayBook BlackBerry 1.0.0', 'Safari 0.0.1', True), + {'is_mobile': True, 'is_tablet': True, 'flavor': {'name': 'PlayBook', 'version': '1.0.0'}, 'os': {'name': 'BlackBerry'}, 'browser': {'name': 'Safari', 'version': '0.0.1'}},), + ('Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en-US) AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.246 Mobile Safari/534.1+', + ('BlackBerry', 'Safari 6.0.0.246', True), + {'is_mobile': True, 'is_tablet': False, 'os': {'name': 'BlackBerry'}, 'browser': {'name': 'Safari', 'version': '6.0.0.246'}},), + ('Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en-US) AppleWebKit/534.8+ (KHTML, like Gecko) Version/6.0.0.600 Mobile Safari/534.8+', + ('BlackBerry', 'Safari 6.0.0.600', True), + {'is_mobile': True, 'is_tablet': False, 'os': {'name': 'BlackBerry'}, 'browser': {'name': 'Safari', 'version': '6.0.0.600'}},), + ('Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5', + ('MacOS iPad X', 'Safari 5.0.2', True), + {'is_mobile': True, 'is_tablet': True, 'flavor': {'version': 'X', 'name': 'MacOS'}, 'dist': {'version': 'iOS 4.2.1', 'name': 'iPad'}, 'browser': {'name': 'Safari', 'version': '5.0.2'}},), + ('Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5) Gecko/20060127 Netscape/8.1', + ('Windows NT 5.1', 'Netscape 8.1', False), + {'is_mobile': False, 'is_tablet': False, 'os': {'name': 'Windows', 'version': 'NT 5.1'}, 'browser': {'name': 'Netscape', 'version': '8.1'}},), + ('Mozilla/5.0 (Linux; U; Android 3.0.1; en-us; A500 Build/HRI66) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13', + ('Android Linux 3.0.1', 'Safari 4.0', True), + {'is_mobile': True, 'is_tablet': True, 'dist': {'version': '3.0.1', 'name': 'Android'}, 'os': {'name': 'Linux'}, 'browser': {'version': '4.0', 'name': 'Safari'}},), + ('Mozilla/5.0 (Linux; U; Android 2.3.7; it-it; Dream/Sapphire Build/FRG83) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1', + ('Android Linux 2.3.7', 'Safari 4.0', True), + {'is_mobile': True, 'is_tablet': False, 'dist': {'version': '2.3.7', 'name': 'Android'}, 'os': {'name': 'Linux'}, 'browser': {'version': '4.0', 'name': 'Safari'}},), + ('Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-GB; rv:1.9.0.10) Gecko/2009042315 Firefox/3.0.10', + ('MacOS Macintosh X 10.5', 'Firefox 3.0.10', False), + { 'is_mobile': False, 'is_tablet': False, 'flavor': {'version': 'X 10.5', 'name': 'MacOS'}, 'os': {'name': 'Macintosh'}, 'browser': {'version': '3.0.10', 'name': 'Firefox'}},), + ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24,gzip(gfe)', + ('MacOS Macintosh X 10.6.6', 'Chrome 11.0.696.3', False), + {'is_mobile': False, 'is_tablet': False, 'flavor': {'version': 'X 10.6.6', 'name': 'MacOS'}, 'os': {'name': 'Macintosh'}, 'browser': {'version': '11.0.696.3', 'name': 'Chrome'}},), + ('Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100308 Ubuntu/10.04 (lucid) Firefox/3.6 GTB7.1', + ('Ubuntu Linux 10.04', 'Firefox 3.6', False), + {'is_mobile': False, 'is_tablet': False, 'dist': {'version': '10.04', 'name': 'Ubuntu'}, 'os': {'name': 'Linux'}, 'browser': {'version': '3.6', 'name': 'Firefox'}},), + ('Mozilla/5.0 (Linux; U; Android 2.2.1; fr-ch; A43 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1', + ('Android Linux 2.2.1', 'Safari 4.0', True), + {'is_mobile': True, 'is_tablet': False, 'dist': {'version': '2.2.1', 'name': 'Android'}, 'os': {'name': 'Linux'}, 'browser': {'version': '4.0', 'name': 'Safari'}},), + ('Mozilla/5.0 (Linux; U; Android 2.3.4; it-it; LG-P990 Build/GRJ22) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 MMS/LG-Android-MMS-V1.0/1.2', + ('Android Linux 2.3.4', 'Safari 4.0', True), + {'is_mobile': True, 'is_tablet': False, 'dist': {'version': '2.3.4', 'name': 'Android'}, 'os': {'name': 'Linux'}, 'browser': {'version': '4.0', 'name': 'Safari'}},), + ('Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3', + ('MacOS iPhone X', 'Safari 3.0', True), + {'is_mobile': True, 'is_tablet': False, 'flavor': {'version': 'X', 'name': 'MacOS'}, 'dist': {'version': 'X', 'name': 'iPhone'}, 'browser': {'version': '3.0', 'name': 'Safari'}},), + ('Mozilla/5.0 (X11; CrOS i686 0.0.0) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.27 Safari/534.24,gzip(gfe)', + ('ChromeOS 0.0.0', 'Chrome 11.0.696.27', False), + {'is_mobile': False, 'is_tablet': False, 'os': {'name': 'ChromeOS', 'version': '0.0.0'}, 'browser': {'name': 'Chrome', 'version': '11.0.696.27'}},), + ('Mozilla/4.0 (compatible; MSIE 6.0; MSIE 5.5; Windows NT 5.1) Opera 7.02 [en]', + ('Windows NT 5.1', 'Opera 7.02', False), + {'is_mobile': False, 'is_tablet': False, 'os': {'name': 'Windows', 'version': 'NT 5.1'}, 'browser': {'name': 'Opera', 'version': '7.02'}},), + ('Opera/9.80 (X11; Linux i686; U; en) Presto/2.9.168 Version/11.50', + ('Linux', 'Opera 9.80', False), + {'is_mobile': False, 'is_tablet': False, 'os': {'name': 'Linux'}, 'browser': {'name': 'Opera', 'version': '9.80'}},), ) class TestHAP(unittest.TestCase): def setUp(self): - self.harass_repeat = 1000 + self.harass_repeat = 100 self.data = data def test_simple_detect(self):