\r\n

51Degrees Device Detection Python  4.4

Device Detection services for 51Degrees Pipeline

onpremise/datafileupdate_console.py

This example illustrates various parameters that can be adjusted when using the on-premise device detection engine, and controls when a new data file is sought and when it is loaded by the device detection software. Three main aspects are demonstrated:

  • Update on Start-Up
  • Filesystem Watcher
  • Daily auto-update

License Key

In order to test this example you will need a 51Degrees Enterprise license which can be purchased from our pricing page. Look for our "Bigger" or "Biggest" options.

Data Files

You can find out more about data files, licenses etc. at our FAQ page

Enterprise Data File

Enterprise (fully-featured) data files are typically released by 51Degrees four days a week (Mon-Thu) and on-premise deployments can fetch and download those files automatically. Equally, customers may choose to download the files themselves and move them into place to be detected by the 51Degrees filesystem watcher.

Manual Download

If you prefer to download files yourself, you may do so here:

1 https://distributor.51degrees.com/api/v2/download?LicenseKeys=<your_license_key>&Type=27&Download=True&Product=22

Lite Data File

Lite data files (free-to-use, limited capabilities, no license key required) are created roughly once a month and cannot be updated using auto-update, they may be downloaded from Github and are included with source distributions of this software.

Update on Start-Up

You can configure the pipeline builder to download an Enterprise data file on start-up.

Pre-Requisites

  • a license key
  • a file location for the download
    • this may be an existing file - which will be overwritten
    • or if it does not exist must end in ".hash" and must be in an existing directory

      Configuration

  • the pipeline must be configured to use a temp file
    1 create_temp_copy = True,
  • a DataFileUpdateService must be supplied
    1  update_event = UpdateEvent()
    2  update_service = DataFileUpdateService()
    3  update_service.on_complete(lambda status, file: update_event.set(status))
    4 ...
    5  data_file_update_service = update_service,
  • update on start-up must be specified, which will cause pipeline creation to block until a file is downloaded
    1 update_on_start = True,

File System Watcher

You can configure the pipeline builder to watch for changes to the currently loaded device detection data file, and to replace the file currently in use with the new one. This is useful, for example, if you wish to download and update the device detection file "manually" - i.e. you would download it then drop it into place with the same path as the currently loaded file. That location is checked periodically (by default every 30 mins) and this frequency can be configured.

Pre-Requisites

  • a license key
  • the file location of the existing file

    Configuration

  • the pipeline must be configured to use a temp file
    1 create_temp_copy = True,
  • a DataFileUpdateService must be supplied
    1  update_event = UpdateEvent()
    2  update_service = DataFileUpdateService()
    3  update_service.on_complete(lambda status, file: update_event.set(status))
    4 ...
    5  data_file_update_service = update_service,
  • configure the frequency with which the location is checked, in seconds (10 mins as shown)
    1 polling_interval = (10*60),

Daily auto-update

Enterprise data files are usually created four times a week. Each data file contains a date for when the next data file is expected. You can configure the pipeline so that it starts looking for a newer data file after that time, by connecting to the 51Degrees distributor to see if an update is available. If one is, then it is downloaded and will replace the existing device detection file, which is currently in use.

Pre-Requisites

  • a license key
  • the file location of the existing file

    Configuration

  • the pipeline must be configured to use a temp file
    1 create_temp_copy = True,
  • a DataFileUpdateService must be supplied
    1  update_event = UpdateEvent()
    2  update_service = DataFileUpdateService()
    3  update_service.on_complete(lambda status, file: update_event.set(status))
    4 ...
    5  data_file_update_service = update_service,
  • Set the frequency in seconds that the pipeline should check for updates to data files. A recommended polling interval in a production environment is around 30 minutes.
    1 polling_interval = (10*60),
  • Set the max amount of time in seconds that should be added to the polling interval. This is useful in datacenter applications where multiple instances may be polling for updates at the same time. A recommended ammount in production environments is 600 seconds.
    1 update_time_maximum_randomisation = (10*60),

Location

This example is available in full on GitHub.

This example requires a subscription to 51Degrees Device Data, a subscription can be acquired from the 51Degrees pricing page.

Required PyPi Dependencies:

1 # *********************************************************************
2 # This Original Work is copyright of 51 Degrees Mobile Experts Limited.
3 # Copyright 2025 51 Degrees Mobile Experts Limited, Davidson House,
4 # Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
5 #
6 # This Original Work is licensed under the European Union Public Licence
7 # (EUPL) v.1.2 and is subject to its terms as set out below.
8 #
9 # If a copy of the EUPL was not distributed with this file, You can obtain
10 # one at https://opensource.org/licenses/EUPL-1.2.
11 #
12 # The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
13 # amended by the European Commission) shall be deemed incompatible for
14 # the purposes of the Work and the provisions of the compatibility
15 # clause in Article 5 of the EUPL shall not apply.
16 #
17 # If using the Work as, or as part of, a network application, by
18 # including the attribution notice(s) required under Article 5 of the EUPL
19 # in the end user terms of the application under an appropriate heading,
20 # such notice(s) shall fulfill the requirements of that article.
21 # *********************************************************************
22 
23 
158 
159 import os
160 import shutil
161 import sys
162 import threading
163 from datetime import datetime
164 
165 from fiftyone_devicedetection.devicedetection_pipelinebuilder import DeviceDetectionPipelineBuilder
168 from fiftyone_pipeline_core.logger import Logger
169 from fiftyone_devicedetection_shared.example_constants import ENTERPRISE_DATAFILE_NAME
170 from fiftyone_pipeline_engines.datafile_update_service import DataFileUpdateService
171 from fiftyone_pipeline_engines.datafile_update_service import UpdateStatus
172 
173 UPDATE_EXAMPLE_LICENSE_KEY_NAME = "license_key"
174 DEFAULT_DATA_FILENAME = os.path.expanduser("~") + os.path.sep + ENTERPRISE_DATAFILE_NAME
175 
176 
177 class UpdateEvent(threading.Event):
178  def __init__(self):
179  self.status = None
180  super().__init__()
181 
182  def set(self, status):
183  super().set()
184  self.status = status
185 
186  def clear(self):
187  self.status = None
188  super().clear()
189 
190 
191 class DataFileUpdateConsole:
192  def run(self, data_file, license_key, interactive, logger, output):
193  logger.log("info", "Starting example")
194 
195  # try to find a license key
196  if not license_key:
197  license_key = KeyUtils.get_named_key(UPDATE_EXAMPLE_LICENSE_KEY_NAME)
198 
199  if not license_key:
200  logger.log("error",
201  "In order to test this example you will need a 51Degrees Enterprise "
202  "license which can be obtained on a trial basis or purchased from our\n"
203  "pricing page http://51degrees.com/pricing. You must supply the license "
204  "key as an argument to this program, or as an environment or system variable "
205  f"named '{UPDATE_EXAMPLE_LICENSE_KEY_NAME}'")
206  raise Exception("No license key available")
207 
208  # work out where the downloaded file will be put, directory must exist
209  if (data_file != None):
210  try:
211  data_file = ExampleUtils.find_file(data_file)
212  except:
213  if (os.path.exists(os.path.dirname(data_file)) == False):
214  logger.log("error",
215  "The directory must exist when specifying a location for a new "
216  f"file to be downloaded. Path specified was '{data_file}'")
217  raise Exception("Directory for new file must exist")
218  logger.log("warning",
219  f"File {data_file} not found, a file will be downloaded to that location on "
220  "start-up")
221 
222  # no filename specified use the default
223  if (data_file == None):
224  data_file = os.path.realpath(DEFAULT_DATA_FILENAME)
225  logger.log("warning",
226  f"No filename specified. Using default '{data_file}' which will be downloaded to "
227  "that location on start-up, if it does not exist already")
228 
229  copy_data_file_name = data_file + ".bak"
230  if (os.path.exists(data_file)):
231  # let's check this file out
232  pipeline = DeviceDetectionPipelineBuilder(
233  data_file_path = data_file,
234  performance_profile = "LowMemory",
235  usage_sharing = False,
236  auto_update = False,
237  licence_keys = "").add_logger(logger).build()
238 
239  # and output the results
240  ExampleUtils.check_data_file(pipeline, logger)
241  if (ExampleUtils.get_data_file_tier(pipeline.get_element("device")) == "Lite"):
242  logger.log("error",
243  "Will not download an 'Enterprise' data file over the top of "
244  "a 'Lite' data file, please supply another location.")
245  raise Exception("File supplied has wrong data tier")
246  logger.log("info", "Existing data file will be replaced with downloaded data file")
247  logger.log("info", f"Existing data file will be copied to {copy_data_file_name}")
248 
249  # do we really want to do this
250  if (interactive):
251  output("Please note - this example will use available downloads "
252  "in your licensed allocation.")
253  user_input = input("Do you wish to continue with this example (y)? ")
254  if (user_input == None or user_input == "" or user_input.startswith("y") == False):
255  logger.log("info", "Stopping example without download")
256  return
257 
258  logger.log("info", "Checking file exists")
259  if os.path.exists(data_file):
260  logger.log("info", f"Existing data file copied to {copy_data_file_name}")
261  shutil.copy(data_file, copy_data_file_name)
262 
263  logger.log("info",
264  "Creating pipeline and initiating update on start-up - please wait for that "
265  "to complete")
266 
267  update_event = UpdateEvent()
268  update_service = DataFileUpdateService()
269  update_service.on_complete(lambda status, file: update_event.set(status))
270 
271  # Build the device detection pipeline and pass in the desired settings to configure
272  # automatic updates.
273  pipeline = DeviceDetectionPipelineBuilder(
274  # specify the filename for the data file. When using update on start-up
275  # the file need not exist, but the directory it is in must exist.
276  # Any file that is present is overwritten. Because the file will be
277  # overwritten the pipeline must be configured to copy the supplied
278  # file to a temporary file (create_temp_copy parameter == True).
279  data_file_path = data_file,
280  create_temp_copy = True,
281  # pass in the update listener which has been configured
282  # to notify when update complete
283  data_file_update_service = update_service,
284  # For automatic updates to work you will need to provide a license key.
285  # A license key can be obtained with a subscription from https://51degrees.com/pricing
286  licence_keys = license_key,
287  # Enable update on startup, the auto update system
288  # will be used to check for an update before the
289  # device detection engine is created. This will block
290  # creation of the pipeline.
291  update_on_start = True,
292  # Enable automatic updates once the pipeline has started
293  auto_update = True,
294  # Watch the data file on disk and refresh the engine
295  # as soon as that file is updated.
296  file_system_watcher=True,
297  data_update_product_type="V4TAC",
298  ).add_logger(logger).build()
299 
300  # thread blocks till update checking is complete - or if there is an
301  # exception we don't get this far
302  update_event.wait()
303  output(f"Update on start-up complete - status - {update_event.status}")
304 
305  if update_event.status == UpdateStatus.AUTO_UPDATE_SUCCESS:
306 
307  output("Modifying downloaded file to trigger reload - please wait for that"
308  " to complete")
309 
310  # wait for the dataUpdateService to notify us that it has updated
311  update_event.clear()
312 
313  # it's the same file but changing the file metadata will trigger reload,
314  # demonstrating that if you download a new file and replace the
315  # existing one, then it will be loaded
316  now = datetime.now().timestamp()
317  try:
318  os.utime(data_file, (now, now))
319  except:
320  raise Exception("Could not modify file time, abandoning "
321  "example")
322 
323  if update_event.wait(120):
324  output(f"Update on file modification complete, status: {update_event.status}")
325  else:
326  output("Update on file modification timed out")
327  else:
328  logger.log("error", "Auto update was not successful, abandoning example")
329  error_message = f"Auto update failed: {update_event.status}"
330  if update_event.status == UpdateStatus.AUTO_UPDATE_ERR_429_TOO_MANY_ATTEMPTS:
331  output(error_message)
332  else:
333  raise Exception(error_message)
334 
335  output("Finished Example")
336 
337 
338 def main(argv):
339  # Use the supplied path for the data file or find the lite
340  # file that is included in the repository.
341  license_key = argv[0] if len(argv) > 0 else None
342  data_file = argv[1] if len(argv) > 1 else None
343 
344  # Configure a logger to output to the console.
345  logger = Logger(min_level="info")
346 
347  DataFileUpdateConsole().run(data_file, license_key, True, logger, print)
348 
349 
350 if __name__ == "__main__":
351  main(sys.argv[1:])