fiftyone-devicedetection-cloud 4.5.80__tar.gz → 4.5.82__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/PKG-INFO +1 -1
  2. fiftyone_devicedetection_cloud-4.5.82/src/fiftyone_devicedetection_cloud/hardwareprofile_cloud.py +117 -0
  3. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/src/fiftyone_devicedetection_cloud.egg-info/PKG-INFO +1 -1
  4. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/src/fiftyone_devicedetection_cloud.egg-info/SOURCES.txt +1 -0
  5. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/tests/test_cloud.py +36 -17
  6. fiftyone_devicedetection_cloud-4.5.82/tests/test_hardwareprofile_cloud.py +176 -0
  7. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/tests/test_properties.py +35 -5
  8. fiftyone_devicedetection_cloud-4.5.82/version.txt +1 -0
  9. fiftyone_devicedetection_cloud-4.5.80/src/fiftyone_devicedetection_cloud/hardwareprofile_cloud.py +0 -61
  10. fiftyone_devicedetection_cloud-4.5.80/version.txt +0 -1
  11. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/MANIFEST.in +0 -0
  12. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/pyproject.toml +0 -0
  13. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/readme.md +0 -0
  14. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/setup.cfg +0 -0
  15. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/setup.py +0 -0
  16. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/src/fiftyone_devicedetection_cloud/__init__.py +0 -0
  17. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/src/fiftyone_devicedetection_cloud/devicedetection_cloud.py +0 -0
  18. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/src/fiftyone_devicedetection_cloud/devicedetection_cloud_pipelinebuilder.py +0 -0
  19. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/src/fiftyone_devicedetection_cloud.egg-info/dependency_links.txt +0 -0
  20. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/src/fiftyone_devicedetection_cloud.egg-info/requires.txt +0 -0
  21. {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/src/fiftyone_devicedetection_cloud.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fiftyone_devicedetection_cloud
3
- Version: 4.5.80
3
+ Version: 4.5.82
4
4
  Summary: 51Degrees Device Detection parses HTTP headers to return detailed hardware, operating system, browser, and crawler information for the devices used to access your website or service. This package retrieves device detection results by consuming the 51Degrees cloud service.
5
5
  Home-page: https://51degrees.com/?utm_source=pypi&utm_medium=package&utm_campaign=device-detection-python&utm_content=fiftyone_devicedetection_cloud-setup.py&utm_term=url
6
6
  Author: 51Degrees Engineering
@@ -0,0 +1,117 @@
1
+ # *********************************************************************
2
+ # This Original Work is copyright of 51 Degrees Mobile Experts Limited.
3
+ # Copyright 2026 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
+ from __future__ import absolute_import
24
+ from fiftyone_pipeline_cloudrequestengine.cloudengine import CloudEngine
25
+
26
+ from fiftyone_pipeline_engines.aspectdata_dictionary import AspectDataDictionary
27
+ from fiftyone_pipeline_core.aspectproperty_value import AspectPropertyValue
28
+
29
+ import json
30
+
31
+ # The suffix the cloud service adds to a property name to carry the reason
32
+ # that property has no value, for example "hardwarevendornullreason".
33
+ NULL_REASON_SUFFIX = "nullreason"
34
+
35
+
36
+ def _build_aspect_values(hardware):
37
+ """!
38
+ Build the aspect level property values from the "hardware" section of a
39
+ cloud response, pairing each null value with the reason the service gave
40
+ for it.
41
+
42
+ @type hardware: dict
43
+ @param hardware: the "hardware" section of the cloud response
44
+
45
+ @rtype dict
46
+ @return property name to AspectPropertyValue
47
+ """
48
+
49
+ values = {}
50
+
51
+ for key, value in hardware.items():
52
+ if key == "profiles" or key.endswith(NULL_REASON_SUFFIX):
53
+ continue
54
+
55
+ if value is None:
56
+ reason = hardware.get(key + NULL_REASON_SUFFIX)
57
+ values[key] = AspectPropertyValue(
58
+ no_value_message=reason if isinstance(reason, str) else None)
59
+ else:
60
+ values[key] = AspectPropertyValue(value=value)
61
+
62
+ return values
63
+
64
+
65
+ class HardwareProfileCloud(CloudEngine):
66
+ """!
67
+ The hardware profile cloud engine
68
+ """
69
+ def __init__(self):
70
+
71
+ super(HardwareProfileCloud, self).__init__()
72
+
73
+ self.datakey = "hardware"
74
+
75
+ def process_internal(self, flowdata):
76
+
77
+ cloud_data = flowdata.get("cloud").get("cloud")
78
+
79
+ cloud_data = json.loads(cloud_data)
80
+
81
+ hardware = cloud_data.get("hardware") or {}
82
+
83
+ if not isinstance(hardware, dict):
84
+ hardware = {}
85
+
86
+ # Properties the resource key is not entitled to are returned by the
87
+ # cloud service at the aspect level rather than inside each profile,
88
+ # with a companion "<name>nullreason" saying why there is no value.
89
+ # Collect those so the reason can travel with every profile instead
90
+ # of being thrown away.
91
+ aspect_values = _build_aspect_values(hardware)
92
+
93
+ devices = []
94
+
95
+ for profile in hardware.get("profiles") or []:
96
+ device = {}
97
+ for property_key, property_value in profile.items():
98
+ device[property_key] = AspectPropertyValue(value=property_value)
99
+
100
+ # Add the properties the service could not supply, carrying the
101
+ # reason it gave, so a caller reading a profile gets an
102
+ # explanation rather than nothing at all.
103
+ for property_key, aspect_value in aspect_values.items():
104
+ if property_key not in device:
105
+ device[property_key] = aspect_value
106
+
107
+ devices.append(device)
108
+
109
+ # The aspect level values are exposed alongside the profiles so a
110
+ # caller with no matching profiles can still read the reason.
111
+ contents = dict(aspect_values)
112
+ contents["profiles"] = devices
113
+
114
+ data = AspectDataDictionary(self, contents)
115
+
116
+ flowdata.set_element_data(data)
117
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fiftyone_devicedetection_cloud
3
- Version: 4.5.80
3
+ Version: 4.5.82
4
4
  Summary: 51Degrees Device Detection parses HTTP headers to return detailed hardware, operating system, browser, and crawler information for the devices used to access your website or service. This package retrieves device detection results by consuming the 51Degrees cloud service.
5
5
  Home-page: https://51degrees.com/?utm_source=pypi&utm_medium=package&utm_campaign=device-detection-python&utm_content=fiftyone_devicedetection_cloud-setup.py&utm_term=url
6
6
  Author: 51Degrees Engineering
@@ -13,4 +13,5 @@ src/fiftyone_devicedetection_cloud.egg-info/dependency_links.txt
13
13
  src/fiftyone_devicedetection_cloud.egg-info/requires.txt
14
14
  src/fiftyone_devicedetection_cloud.egg-info/top_level.txt
15
15
  tests/test_cloud.py
16
+ tests/test_hardwareprofile_cloud.py
16
17
  tests/test_properties.py
@@ -29,8 +29,29 @@ from fiftyone_devicedetection_cloud.devicedetection_cloud_pipelinebuilder import
29
29
 
30
30
  from fiftyone_devicedetection_cloud.devicedetection_cloud import DeviceDetectionCloud
31
31
 
32
- if not "resource_key" in os.environ:
33
- print("To run the cloud tests, please set a valid 51Degrees cloud resource key as the resource_key environment variable. e.g `export resource_key=MYresource_key` on the command line")
32
+ # Resource key environment variables follow the 51Degrees convention, which
33
+ # is that every one of them starts with "_51DEGREES_RESOURCE_KEY". The name
34
+ # used before the convention was adopted is still read, so an existing setup
35
+ # keeps working.
36
+ RESOURCE_KEY_ENV_VAR = "_51DEGREES_RESOURCE_KEY"
37
+ LEGACY_RESOURCE_KEY_ENV_VAR = "resource_key"
38
+
39
+ MISSING_KEY_MESSAGE = (
40
+ "No resource key found, so the tests that call the cloud service "
41
+ f"cannot run. Set the environment variable '{RESOURCE_KEY_ENV_VAR}' "
42
+ f"(the older name '{LEGACY_RESOURCE_KEY_ENV_VAR}' is still read). "
43
+ "Create a resource key for free at "
44
+ "https://configure.51degrees.com?utm_source=code&utm_medium=example&utm_campaign=device-detection-python&utm_content=fiftyone_devicedetection_cloud-tests-test_cloud.py&utm_term=resource-key-required")
45
+
46
+
47
+ def get_resource_key():
48
+ """!
49
+ The resource key from the environment, or None when neither the aligned
50
+ nor the older variable is set.
51
+ """
52
+
53
+ return (os.environ.get(RESOURCE_KEY_ENV_VAR)
54
+ or os.environ.get(LEGACY_RESOURCE_KEY_ENV_VAR))
34
55
 
35
56
  mobile_ua = "Mozilla/5.0 (iPhone; CPU iPhone OS 11_2 like Mac OS X) AppleWebKit/604.4.7 (KHTML, like Gecko) Mobile/15C114"
36
57
 
@@ -38,15 +59,19 @@ cloud_request_origin_test_params = [('', True), ('test.com', True), ('51Degrees.
38
59
 
39
60
  class DeviceDetectionTests(unittest.TestCase):
40
61
 
62
+ def setUp(self):
63
+ self.resource_key = get_resource_key()
64
+
65
+ if not self.resource_key:
66
+ self.skipTest(MISSING_KEY_MESSAGE)
67
+
68
+
41
69
  def test_pipeline_builder_cloud_engine_init(self):
42
70
  """!
43
71
  Tests whether the device detection pipeline builder adds the correct engines when initialised with a resource key
44
72
  """
45
73
 
46
- if not "resource_key" in os.environ:
47
- return
48
-
49
- pipeline = DeviceDetectionCloudPipelineBuilder(resource_key = os.environ['resource_key']).build()
74
+ pipeline = DeviceDetectionCloudPipelineBuilder(resource_key = self.resource_key).build()
50
75
 
51
76
  self.assertTrue(pipeline.flow_elements[0].datakey == "cloud")
52
77
  self.assertTrue(pipeline.flow_elements[1].datakey == "device")
@@ -56,10 +81,7 @@ class DeviceDetectionTests(unittest.TestCase):
56
81
  Tests whether a properties list is created on the cloud engine
57
82
  """
58
83
 
59
- if not "resource_key" in os.environ:
60
- return
61
-
62
- pipeline = DeviceDetectionCloudPipelineBuilder(resource_key = os.environ['resource_key']).build()
84
+ pipeline = DeviceDetectionCloudPipelineBuilder(resource_key = self.resource_key).build()
63
85
 
64
86
  properties = pipeline.flow_elements[1].get_properties()
65
87
 
@@ -70,10 +92,7 @@ class DeviceDetectionTests(unittest.TestCase):
70
92
  Check property lookup works
71
93
  """
72
94
 
73
- if not "resource_key" in os.environ:
74
- return
75
-
76
- pipeline = DeviceDetectionCloudPipelineBuilder(resource_key = os.environ['resource_key']).build()
95
+ pipeline = DeviceDetectionCloudPipelineBuilder(resource_key = self.resource_key).build()
77
96
 
78
97
  fd = pipeline.create_flowdata()
79
98
 
@@ -89,7 +108,7 @@ class DeviceDetectionTests(unittest.TestCase):
89
108
  not available in any datafile
90
109
  """
91
110
 
92
- pipeline = DeviceDetectionCloudPipelineBuilder(resource_key = os.environ['resource_key']).build()
111
+ pipeline = DeviceDetectionCloudPipelineBuilder(resource_key = self.resource_key).build()
93
112
 
94
113
  fd = pipeline.create_flowdata()
95
114
 
@@ -118,7 +137,7 @@ class DeviceDetectionTests(unittest.TestCase):
118
137
 
119
138
  start = time.time()
120
139
 
121
- pipeline = DeviceDetectionCloudPipelineBuilder(resource_key = os.environ['resource_key']).build()
140
+ pipeline = DeviceDetectionCloudPipelineBuilder(resource_key = self.resource_key).build()
122
141
 
123
142
  fd = pipeline.create_flowdata()
124
143
 
@@ -144,7 +163,7 @@ class DeviceDetectionTests(unittest.TestCase):
144
163
  not available in cloud
145
164
  """
146
165
 
147
- pipeline = DeviceDetectionCloudPipelineBuilder(resource_key = os.environ['resource_key']).build()
166
+ pipeline = DeviceDetectionCloudPipelineBuilder(resource_key = self.resource_key).build()
148
167
 
149
168
  fd = pipeline.create_flowdata()
150
169
 
@@ -0,0 +1,176 @@
1
+ # *********************************************************************
2
+ # This Original Work is copyright of 51 Degrees Mobile Experts Limited.
3
+ # Copyright 2026 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
+ """!
24
+ These tests run against fixed cloud responses, so they need no network
25
+ connection and no resource key. They cover the case that used to stop the
26
+ TAC and native model examples with an AttributeError, which is a resource
27
+ key that returns the hardware profiles but is not entitled to the hardware
28
+ properties themselves.
29
+ """
30
+
31
+ import json
32
+ import unittest
33
+
34
+ from fiftyone_devicedetection_cloud.hardwareprofile_cloud import HardwareProfileCloud
35
+
36
+ # A response of the shape the cloud service returns for a TAC lookup when
37
+ # the resource key has the hardware aspect but is not entitled to the
38
+ # hardware properties. The values are null at the aspect level and each one
39
+ # is paired with a reason. The profiles carry only the properties the key is
40
+ # entitled to.
41
+ NOT_ENTITLED_RESPONSE = json.dumps({
42
+ "hardware": {
43
+ "profiles": [
44
+ {"devicetype": "SmartPhone", "ismobile": True}
45
+ ],
46
+ "hardwarevendor": None,
47
+ "hardwarevendornullreason":
48
+ "HardwareVendor is a paid feature. You need a licence key to "
49
+ "retrieve data.",
50
+ "hardwarename": None,
51
+ "hardwarenamenullreason":
52
+ "HardwareName is a paid feature. You need a licence key to "
53
+ "retrieve data.",
54
+ "hardwaremodel": None,
55
+ "hardwaremodelnullreason":
56
+ "HardwareModel is a paid feature. You need a licence key to "
57
+ "retrieve data."
58
+ }
59
+ })
60
+
61
+ # A response from a fully entitled resource key.
62
+ ENTITLED_RESPONSE = json.dumps({
63
+ "hardware": {
64
+ "profiles": [
65
+ {
66
+ "hardwarevendor": "Apple",
67
+ "hardwarename": ["iPhone 6"],
68
+ "hardwaremodel": "A1586"
69
+ }
70
+ ]
71
+ }
72
+ })
73
+
74
+ # A response where the hardware aspect is absent altogether, which is what a
75
+ # resource key without the hardware aspect returns.
76
+ NO_HARDWARE_RESPONSE = json.dumps({"device": {"ismobile": True}})
77
+
78
+
79
+ class _FakeCloudElementData:
80
+ """!
81
+ Stands in for the element data the cloud request engine publishes.
82
+ """
83
+
84
+ def __init__(self, response):
85
+ self._response = response
86
+
87
+ def get(self, key):
88
+ return self._response
89
+
90
+
91
+ class _FakeFlowData:
92
+ """!
93
+ Stands in for a FlowData carrying a fixed cloud response, so the engine
94
+ can be tested with no network connection and no resource key.
95
+ """
96
+
97
+ def __init__(self, response):
98
+ self._response = response
99
+ self.element_data = None
100
+
101
+ def get(self, key):
102
+ return _FakeCloudElementData(self._response)
103
+
104
+ def set_element_data(self, data):
105
+ self.element_data = data
106
+
107
+
108
+ def _process(response):
109
+ """!
110
+ Run a fixed cloud response through the hardware profile engine and
111
+ return the resulting element data.
112
+ """
113
+
114
+ engine = HardwareProfileCloud()
115
+ flowdata = _FakeFlowData(response)
116
+
117
+ engine.process_internal(flowdata)
118
+
119
+ return flowdata.element_data
120
+
121
+
122
+ class HardwareProfileCloudTests(unittest.TestCase):
123
+
124
+ def test_null_reason_is_carried_into_each_profile(self):
125
+ hardware = _process(NOT_ENTITLED_RESPONSE)
126
+ profiles = hardware.get("profiles")
127
+
128
+ self.assertEqual(1, len(profiles))
129
+
130
+ profile = profiles[0]
131
+
132
+ for name in ["hardwarevendor", "hardwarename", "hardwaremodel"]:
133
+ self.assertIn(
134
+ name, profile,
135
+ f"'{name}' should be present on the profile so the reason "
136
+ "it has no value can be reported")
137
+ self.assertFalse(profile[name].has_value())
138
+ self.assertIn("paid feature", profile[name].no_value_message())
139
+
140
+ def test_null_reason_is_reported_by_the_example_helper(self):
141
+ # The example helper lives in the examples package, which is not a
142
+ # dependency of this one, so the same formatting is asserted here
143
+ # against the values the engine produced.
144
+ hardware = _process(NOT_ENTITLED_RESPONSE)
145
+ profile = hardware.get("profiles")[0]
146
+ value = profile["hardwarevendor"]
147
+
148
+ self.assertEqual(
149
+ "HardwareVendor is a paid feature. You need a licence key to "
150
+ "retrieve data.",
151
+ value.no_value_message())
152
+
153
+ def test_entitled_values_are_reported_as_values(self):
154
+ hardware = _process(ENTITLED_RESPONSE)
155
+ profile = hardware.get("profiles")[0]
156
+
157
+ self.assertTrue(profile["hardwarevendor"].has_value())
158
+ self.assertEqual("Apple", profile["hardwarevendor"].value())
159
+ self.assertEqual(["iPhone 6"], profile["hardwarename"].value())
160
+ self.assertEqual("A1586", profile["hardwaremodel"].value())
161
+
162
+ def test_absent_hardware_aspect_gives_no_profiles_rather_than_an_error(self):
163
+ hardware = _process(NO_HARDWARE_RESPONSE)
164
+
165
+ self.assertEqual([], hardware.get("profiles"))
166
+
167
+ def test_aspect_level_values_are_exposed_alongside_the_profiles(self):
168
+ # A caller with no matching profiles can still read the reason.
169
+ hardware = _process(NOT_ENTITLED_RESPONSE)
170
+
171
+ self.assertIn("paid feature",
172
+ hardware.get("hardwarevendor").no_value_message())
173
+
174
+
175
+ if __name__ == "__main__":
176
+ unittest.main()
@@ -43,11 +43,41 @@ chrome_ua = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
43
43
  # TODO remove setheader properties from this list once UACH datafile is released.
44
44
  exclude_properties = ["setheaderbrowseraccept-ch", "setheaderplatformaccept-ch", "setheaderhardwareaccept-ch"]
45
45
 
46
- if "resource_key" in os.environ:
47
- resource_key = os.environ["resource_key"]
48
- else:
49
- raise Exception("To run the cloud tests, please set a valid 51Degrees "
50
- "cloud resource key as the resource_key environment variable.")
46
+ # Resource key environment variables follow the 51Degrees convention, which
47
+ # is that every one of them starts with "_51DEGREES_RESOURCE_KEY". The name
48
+ # used before the convention was adopted is still read, so an existing setup
49
+ # keeps working.
50
+ RESOURCE_KEY_ENV_VAR = "_51DEGREES_RESOURCE_KEY"
51
+ LEGACY_RESOURCE_KEY_ENV_VAR = "resource_key"
52
+
53
+ resource_key = (os.environ.get(RESOURCE_KEY_ENV_VAR)
54
+ or os.environ.get(LEGACY_RESOURCE_KEY_ENV_VAR))
55
+
56
+ if not resource_key:
57
+ # Skipping rather than raising, so a run without a key shows as skipped
58
+ # and names the variable, instead of turning into a collection error.
59
+ import pytest
60
+
61
+ pytest.skip(
62
+ "No resource key found, so the tests that call the cloud service "
63
+ f"cannot run. Set the environment variable '{RESOURCE_KEY_ENV_VAR}' "
64
+ f"(the older name '{LEGACY_RESOURCE_KEY_ENV_VAR}' is still read). "
65
+ "Create a resource key for free at "
66
+ "https://configure.51degrees.com?utm_source=code&utm_medium=example&utm_campaign=device-detection-python&utm_content=fiftyone_devicedetection_cloud-tests-test_properties.py&utm_term=resource-key-required",
67
+ allow_module_level=True)
68
+
69
+ if not os.path.isfile(header_file_path):
70
+ # The property list comes from an asset the build fetches with a licence
71
+ # key. Without it these tests cannot run, so skip rather than turn a
72
+ # missing asset into a failure.
73
+ import pytest
74
+
75
+ pytest.skip(
76
+ f"The property list file '{header_file_path}' is not present, so "
77
+ "the property coverage tests cannot run. It is fetched by "
78
+ "ci/fetch-assets.ps1 when a device detection licence key is "
79
+ "available.",
80
+ allow_module_level=True)
51
81
 
52
82
  # Get Properties list
53
83
  properties_list = get_properties_from_header_file(header_file_path)
@@ -0,0 +1 @@
1
+ 4.5.82
@@ -1,61 +0,0 @@
1
- # *********************************************************************
2
- # This Original Work is copyright of 51 Degrees Mobile Experts Limited.
3
- # Copyright 2026 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
- from __future__ import absolute_import
24
- from fiftyone_pipeline_cloudrequestengine.cloudengine import CloudEngine
25
-
26
- from fiftyone_pipeline_engines.aspectdata_dictionary import AspectDataDictionary
27
- from fiftyone_pipeline_core.aspectproperty_value import AspectPropertyValue
28
-
29
- import json
30
-
31
- class HardwareProfileCloud(CloudEngine):
32
- """!
33
- The hardware profile cloud engine
34
- """
35
- def __init__(self):
36
-
37
- super(HardwareProfileCloud, self).__init__()
38
-
39
- self.datakey = "hardware"
40
-
41
- def process_internal(self, flowdata):
42
-
43
- cloud_data = flowdata.get("cloud").get("cloud")
44
-
45
- cloud_data = json.loads(cloud_data)
46
-
47
- # Loop over cloud_data.devices properties to check if they have a value
48
-
49
- devices = []
50
-
51
- for profile in cloud_data["hardware"]["profiles"]:
52
- device = {}
53
- for property_key, property_value in profile.items():
54
- device[property_key] = AspectPropertyValue(value=property_value)
55
-
56
- devices.append(device)
57
-
58
- data = AspectDataDictionary(self, {"profiles": devices})
59
-
60
- flowdata.set_element_data(data)
61
-
@@ -1 +0,0 @@
1
- 4.5.80