Package mobi :: Package mtld :: Package da :: Package device :: Module device_api
[hide private]
[frames] | no frames]

Source Code for Module mobi.mtld.da.device.device_api

  1  #!/usr/bin/env python 
  2   
  3  """ 
  4  @copyright: 
  5   Copyright (c) 2014 by mTLD Top Level Domain Limited. All rights reserved.\n 
  6   Portions copyright (c) 2008 by Argo Interactive Limited.\n 
  7   Portions copyright (c) 2008 by Nokia Inc.\n 
  8   Portions copyright (c) 2008 by Telecom Italia Mobile S.p.A.\n 
  9   Portions copyright (c) 2008 by Volantis Systems Limited.\n 
 10   Portions copyright (c) 2002-2008 by Andreas Staeding.\n 
 11   Portions copyright (c) 2008 by Zandan.\n 
 12  @author: dotMobi 
 13  """ 
 14   
 15  import os.path 
 16   
 17  from mobi.mtld.da.exception.data_file_exception import DataFileException 
 18  from mobi.mtld.da.device.tree import Tree 
 19  from mobi.mtld.da.device.config import Config 
 20  from mobi.mtld.da.property_name import PropertyName 
 21  from mobi.mtld.da.data_type import DataType 
 22  from mobi.mtld.da.property import Property 
 23  from mobi.mtld.da.properties import Properties 
 24   
25 -class DeviceApi(object):
26 """ 27 The DeviceAtlas Device Detection API provides a way to detect devices based on 28 the HTTP headers. Using the headers, the API returns device information such 29 as screen width, screen height, is mobile, vendor, model etc. 30 31 DeviceApi.get_properties(user_agent_or_headers, client_side_properties) 32 33 To get the most accurate results: 34 1- Pass the whole HTTP headers. 35 2- Use the DeviceAtlas client-side-component and pass the result. 36 37 Example usage: 38 39 >>> device_api = DeviceApi() 40 >>> device_api.load_data_from_file("/path/to/datafile.json") 41 >>> 42 >>> # get all properties from the headers 43 >>> properties = device_api.get_properties(headers) 44 >>> 45 >>> # .... use the properties .... 46 >>> 47 >>> if properties.contains("isMobilePhone", True): 48 >>> # it is a mobile phone 49 >>> 50 >>> if "model" in properties: 51 >>> device_model = str(properties.get("model")) 52 """ 53 54 api_version = '2.0' 55 56 __config = None 57 __tree = None 58 __cached_client_side_properties = None 59 __cached_user_agent = None 60 __cached_headers = None 61 __properties = None 62
63 - def __init__(self, config = None):
64 """ 65 Constructs a DeviceApi instance with default configs. You can see the 66 default configs in the class "Configuration". 67 68 @param config: Instance of Configuration. You can change the DeviceAtlas API 69 configs by creating an instance or Configuration and setting your custom 70 config values then passing the instance to the DeviceApi constructor. 71 """ 72 if config is None: 73 self.__config = Config() 74 else: 75 self.__config = config
76
77 - def load_data_from_file(self, json_data_file_path):
78 """ 79 Load the DeviceAtlas device detection data into the API from a JSON file. 80 The JSON data file is provided from the DeviceAtlas web-site. 81 @param json_data_file_path: Path to the JSON data file. 82 """ 83 84 if not os.path.isfile(json_data_file_path): 85 raise DataFileException("File not found: " + json_data_file_path) 86 87 json = open(json_data_file_path, 'r').read() 88 89 self.load_data_from_string(json)
90
91 - def load_data_from_string(self, json_data_string):
92 """ 93 Load the DeviceAtlas device detection data into the API from a string. 94 @param json_data_string: JSON data string. 95 """ 96 self.__tree = None 97 self.__tree = Tree(json_data_string, self.__config)
98
99 - def get_property_names(self):
100 """ 101 Get a set of available device property names. 102 It returns a list of PropertyName objects. 103 """ 104 property_names = [] 105 property_type_names = self.__tree.property_names() 106 for property_type_name in property_type_names: 107 property_names.append( 108 PropertyName( 109 property_type_name[1:len(property_type_name)], 110 self.__get_property_as_byte( 111 property_type_name[0]))) 112 return property_names
113
114 - def get_data_version(self):
115 """ 116 Get the device data (JSON file) version. 117 """ 118 return self.__tree.data_version()
119
120 - def get_data_revision(self):
121 """ 122 Get the device data (JSON file) revision. 123 """ 124 return self.__tree.data_revision
125
127 """ 128 Get the device data (JSON file) creation timestamp. 129 """ 130 return self.__tree.data_creation_timestamp()
131
132 - def get_properties(self, user_agent_or_headers, client_side_properties = None):
133 """ 134 Get known properties from a User-Agent or HTTP headers optionally 135 merged with properties from the client side component. 136 The client side component (JS) sets a cookie with collected properties. 137 The client properties will over-ride any properties discovered from the main 138 JSON data file. 139 140 @param user_agent_or_headers: User-Agent string or array of HTTP headers. 141 @param client_side_properties: String of client side properties with the format 142 the client side component provides. 143 It returns a list of Property objects 144 """ 145 new_cached_client_side_properties = False 146 147 if (client_side_properties is None or 148 self.__cached_client_side_properties != client_side_properties): 149 150 self.__cached_client_side_properties = client_side_properties 151 new_cached_client_side_properties = True 152 153 # Just a UA 154 if (isinstance(user_agent_or_headers, str) or 155 isinstance(user_agent_or_headers, unicode)): 156 157 if (new_cached_client_side_properties or 158 self.__cached_user_agent is None or 159 self.__cached_user_agent != user_agent_or_headers): 160 161 self.__cached_user_agent = user_agent_or_headers 162 self.__cached_headers = None 163 self.__tree.put_properties(self.__cached_user_agent, self.__cached_headers, 164 self.__cached_client_side_properties) 165 self.__properties = self.__tree.properties 166 167 # Headers 168 else: 169 170 if (user_agent_or_headers is not None and 171 len(user_agent_or_headers) > 0 and 172 (self.__cached_headers is None or 173 self.__cached_headers != user_agent_or_headers)): 174 175 self.__cached_user_agent = None 176 self.__cached_headers = user_agent_or_headers 177 self.__properties = self.__get_properties_from_headers( 178 user_agent_or_headers, 179 self.__cached_client_side_properties) 180 181 # add language and locale properties 182 language_header = "accept-language" 183 if (self.__config.include_lang_props and 184 language_header in self.__cached_headers): 185 186 accept_language = self.__cached_headers[language_header] 187 self.__add_language_properties(accept_language) 188 189 if (self.__properties == {} and self.__config.return_none_when_no_properties): 190 self.__properties = None 191 192 elif (self.__properties is None and 193 not self.__config.return_none_when_no_properties): 194 self.__properties = {} 195 196 return self.__properties
197 198 # Private 199
200 - def __add_language_properties(self, accept_language):
201 """ 202 Get the Accept-Language header and add language properties to the property 203 list. 204 @param accept_language: Accept-Language header. 205 """ 206 207 if accept_language is None or accept_language.replace(' ', '') == '': 208 return 209 210 langs = accept_language.split(',') 211 212 best = '' 213 q_best = 0 214 215 # go through the header parts 216 for langs_i in langs: 217 218 lang = langs_i.split(';') 219 220 # get q 221 q = 1 222 223 if len(lang) > 1: 224 s = lang[1].replace(' ', '') 225 if s[0:2] == "q=": 226 try: 227 q = float(s[2:]) 228 except: 229 q = 0 230 else: 231 continue # Invalid data 232 233 # compare last best with current item, update if current item is better 234 locale = lang[0].replace(' ', '') # lang or locale string 235 length = len(locale) 236 237 if (q > q_best or (q == q_best and length > 2 and length > len(best) and 238 locale[0:2] == best[0:2])): 239 best = locale 240 q_best = q 241 # if best item is found don't search more 242 if length == 4 and q == 1: 243 break 244 245 # end for 246 247 # set lang properties 248 if best != "*": 249 lang_locale = best.replace('_', '-').split('-') 250 lang = lang_locale[0].lower() 251 if len(lang_locale) == 2: 252 locale = lang + '-' + lang_locale[1].upper() 253 else: 254 locale = None 255 256 if lang != '': 257 property_name_language = 'language' 258 self.__tree.properties[property_name_language] = Property(lang, 's') 259 if locale is not None and len(locale) == 5: 260 property_name_language_locale = 'languageLocale' 261 self.__tree.properties[property_name_language_locale] = Property(locale, 262 's')
263
264 - def __get_property_as_byte(self, type_char):
265 if type_char == 's': 266 return DataType.STRING 267 if type_char == 'b': 268 return DataType.BOOLEAN 269 if type_char == 'i': 270 return DataType.INTEGER 271 if type_char == 'd': 272 return DataType.DOUBLE 273 return DataType.UNKNOWN
274 275
276 - def __get_properties_from_headers(self, headers, client_side_properties):
277 278 # make header keys lower-cased with no underlines 279 self.__normalise_keys(headers) 280 281 # get user-agent-header-name list from the tree object 282 # collect the stock-ua headers if any exists 283 stock_ua_headers = [] 284 tree_stock_ua_headers = self.__tree.stock_ua_headers 285 286 for stock_ua_header in tree_stock_ua_headers: 287 if stock_ua_header in headers: 288 stock_ua_headers.append(headers[stock_ua_header]) 289 290 # get the user-agent header 291 if "user-agent" in headers: 292 ua = headers["user-agent"] 293 stock_ua_headers.append(ua) 294 else: 295 ua = '' 296 297 # ua is used for ua-props 298 # stock_ua_headers is used for device detection, ua is added to the end of 299 # this list 300 self.__tree.put_properties(ua, stock_ua_headers, client_side_properties) 301 302 return self.__tree.properties
303 304
305 - def __normalise_keys(self, headers):
306 307 normalised_keys = {} 308 original_keys = list(headers.keys()) 309 310 # Get normalised keys 311 for key, value in headers.items(): 312 normalised_key = key.lower().replace('_','-').replace('http-','') 313 normalised_keys[normalised_key] = value 314 315 # Add normalised keys to headers 316 for key, value in normalised_keys.items(): 317 headers[key] = value 318 319 # Remove non-normalised headers if there are any 320 for key in original_keys: 321 if key not in normalised_keys: 322 headers.pop(key)
323