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.
- {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/PKG-INFO +1 -1
- fiftyone_devicedetection_cloud-4.5.82/src/fiftyone_devicedetection_cloud/hardwareprofile_cloud.py +117 -0
- {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/src/fiftyone_devicedetection_cloud.egg-info/PKG-INFO +1 -1
- {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/src/fiftyone_devicedetection_cloud.egg-info/SOURCES.txt +1 -0
- {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/tests/test_cloud.py +36 -17
- fiftyone_devicedetection_cloud-4.5.82/tests/test_hardwareprofile_cloud.py +176 -0
- {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/tests/test_properties.py +35 -5
- fiftyone_devicedetection_cloud-4.5.82/version.txt +1 -0
- fiftyone_devicedetection_cloud-4.5.80/src/fiftyone_devicedetection_cloud/hardwareprofile_cloud.py +0 -61
- fiftyone_devicedetection_cloud-4.5.80/version.txt +0 -1
- {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/MANIFEST.in +0 -0
- {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/pyproject.toml +0 -0
- {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/readme.md +0 -0
- {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/setup.cfg +0 -0
- {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/setup.py +0 -0
- {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/src/fiftyone_devicedetection_cloud/__init__.py +0 -0
- {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/src/fiftyone_devicedetection_cloud/devicedetection_cloud.py +0 -0
- {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/src/fiftyone_devicedetection_cloud/devicedetection_cloud_pipelinebuilder.py +0 -0
- {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/src/fiftyone_devicedetection_cloud.egg-info/dependency_links.txt +0 -0
- {fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/src/fiftyone_devicedetection_cloud.egg-info/requires.txt +0 -0
- {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.
|
|
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
|
fiftyone_devicedetection_cloud-4.5.82/src/fiftyone_devicedetection_cloud/hardwareprofile_cloud.py
ADDED
|
@@ -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.
|
|
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
|
{fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/tests/test_cloud.py
RENAMED
|
@@ -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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
fiftyone_devicedetection_cloud-4.5.80/src/fiftyone_devicedetection_cloud/hardwareprofile_cloud.py
DELETED
|
@@ -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
|
|
File without changes
|
{fiftyone_devicedetection_cloud-4.5.80 → fiftyone_devicedetection_cloud-4.5.82}/pyproject.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|