Skip to content

FIX: Run cloud examples without the on-premise engine, report why a property has no value, and make a broken example go red - #344

Merged
Automation51D merged 1 commit into
mainfrom
fix/cloud-example-failures
Sep 5, 2026
Merged

FIX: Run cloud examples without the on-premise engine, report why a property has no value, and make a broken example go red#344
Automation51D merged 1 commit into
mainfrom
fix/cloud-example-failures

Conversation

@jwrosewell

Copy link
Copy Markdown
Contributor

Why

Three faults, found by installing only the cloud packages and then running
the cloud examples against a resource key that has the hardware aspect but
is not entitled to the hardware properties themselves.

1. Half the cloud examples could not run at all. Four of the eight
imported fiftyone_devicedetection.devicedetection_pipelinebuilder, which
imports fiftyone_devicedetection_onpremise unconditionally, so a
cloud only example needed the on-premise native engine. PyPI publishes
that package as an sdist with no wheel, so anyone following a cloud example
had to build C++ from source first:

$ python -c "import json,urllib.request; d=json.load(urllib.request.urlopen('https://pypi.org/pypi/fiftyone-devicedetection-onpremise/json')); v=d['info']['version']; print(v, [f['packagetype'] for f in d['releases'][v]])"
4.5.81 ['sdist']

2. A missing entitlement was handled badly. The cloud service answers
correctly and says why it has no value, for example
"hardwarevendornullreason": "HardwareVendor is a paid feature. You need a licence key to retrieve data.", but HardwareProfileCloud read only
cloud_data["hardware"]["profiles"] and threw the reasons away. Each
profile therefore had no hardwarevendor at all, so
ExampleUtils.get_human_readable called no_value_message() on None.

3. The tests could not report either fault. The cloud example tests ran
the examples but asserted nothing about what they printed, and the cloud
package's own tests were not in the unit job at all. Tests that could not
find a resource key returned quietly and passed, so a run that proved
nothing looked the same on the dashboard as a run that proved something.

What changed

The examples run with only the cloud packages

  • configurator_console.py, metadata_console.py and
    gettingstarted_web/app.py use DeviceDetectionCloudPipelineBuilder,
    which does exactly the same work on the cloud path.
    DeviceDetectionPipelineBuilder differs only in that it adds the
    on-premise engine and usage sharing when data_file_path is given, which
    a cloud example never gives.
  • gettingstarted_console.py imported the builder and never used it,
    because it builds from the configuration file. The import is gone.
  • New test tests/test_cloudexampleimports.py imports every cloud example
    in a separate process with fiftyone_devicedetection_onpremise and
    fiftyone_devicedetection blocked, so the dependency cannot come back
    unnoticed. It needs no resource key and no network connection.

This is a change in the examples, not in the fiftyone_devicedetection
package. That package is a deliberate wrapper that offers both engines
through one builder, so making its import of the on-premise engine lazy
would be a separate decision about the package's contract. The examples
should not have been reaching for the wrapper in the first place.

The reason a property has no value is reported

  • hardwareprofile_cloud.py builds the aspect level values from the cloud
    response, pairing each null value with its companion <name>nullreason,
    and adds them to every profile that does not already carry that property.
    A response with no hardware section no longer raises.
  • ExampleUtils.get_human_readable distinguishes three cases, being a
    value, no value with a reason, and a property that is not in the results
    at all, and names the property in the last case.
  • ExampleUtils.get_profiles and get_no_profiles_message let the TAC and
    native model examples say plainly that no profiles came back.
  • The configurator example reads ismobile through the helper, so a
    resource key without that property reports the reason rather than
    stopping part way through.

The tests can now fail

  • New unit test fiftyone_devicedetection_cloud/tests/test_hardwareprofile_cloud.py
    runs the engine against fixed cloud responses, so it needs no resource
    key and no network connection.
  • ci/run-unit-tests.ps1 runs the cloud package as well as the on-premise
    one, so that test runs on every build rather than only when the
    integration job has a key.
  • tests/test_cloudexamples.py captures what each example writes and
    asserts the headings it must print, that nothing in the output reads as a
    programming fault, and that each lookup says something about the devices
    it found.
  • fiftyone_devicedetection_cloud/tests/test_cloud.py skips with a message
    naming the variable it wanted, rather than returning quietly.
    test_properties.py skips at module level instead of raising, and also
    skips when the 51Degrees.csv asset is absent, so a missing asset is not
    reported as a failure.
  • Resource key variables move onto the _51DEGREES_RESOURCE_KEY
    convention, in example_utils.py, the cloud tests, both tox files and
    ci/run-integration-tests.ps1. The previous name is still read as a
    fallback so an existing setup keeps working.

Before

Installed packages were fiftyone_devicedetection_cloud,
fiftyone_devicedetection_shared and fiftyone_devicedetection_examples,
with no on-premise package, on Python 3.14.5:

$ python -c "import fiftyone_devicedetection_examples.cloud.configurator_console"
ModuleNotFoundError: No module named 'fiftyone_devicedetection_onpremise'
$ python -c "import fiftyone_devicedetection_examples.cloud.gettingstarted_console"
ModuleNotFoundError: No module named 'fiftyone_devicedetection_onpremise'
$ python -c "import fiftyone_devicedetection_examples.cloud.metadata_console"
ModuleNotFoundError: No module named 'fiftyone_devicedetection_onpremise'
$ python -c "import fiftyone_devicedetection_examples.cloud.gettingstarted_web.app"
ModuleNotFoundError: No module named 'fiftyone_devicedetection_onpremise'

With the on-premise package in place, and a resource key that has the
hardware aspect but no entitlement to the hardware properties:

$ python -m fiftyone_devicedetection_examples.cloud.taclookup_console
This example shows the details of devices associated with a given 'Type Allocation Code' or 'TAC'.
...
Which devices are associated with the TAC '35925406'?
Traceback (most recent call last):
  ...
  File ".../example_utils.py", line 82, in get_human_readable
    return f"Unknown ({value.no_value_message()})"
                       ^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'no_value_message'
exit 1

After

Same three packages, still no on-premise package:

configurator_console         imports OK
gettingstarted_console       imports OK
metadata_console             imports OK
taclookup_console            imports OK
nativemodellookup_console    imports OK
failuretomatch               imports OK
gettingstarted_web.app       imports OK
useragentclienthints_web.app imports OK

Same key, same command:

$ python -m fiftyone_devicedetection_examples.cloud.taclookup_console
This example shows the details of devices associated with a given 'Type Allocation Code' or 'TAC'.
More background information on TACs can be found through various online sources such as Wikipedia: https://en.wikipedia.org/wiki/Type_Allocation_Code
----------------------------------------
Which devices are associated with the TAC '35925406'?
	Unknown (HardwareVendor is a paid feature. You need a licence key to retrieve data. Visit https://51degrees.com/pricing for details) Unknown (HardwareName is a paid feature. You need a licence key to retrieve data. Visit https://51degrees.com/pricing for details) (Unknown (HardwareModel is a paid feature. You need a licence key to retrieve data. Visit https://51degrees.com/pricing for details))
Which devices are associated with the TAC '86386802'?
	[seven profiles, same shape]
exit 0

The tests now go red

The new import test, against the example code as it was before this change:

$ pytest tests/test_cloudexampleimports.py -q
E  ImportError: 'fiftyone_devicedetection' must not be needed by a cloud example
   [four times, one per example]
4 failed, 1 passed, 4 subtests passed
exit 1

The new unit test, against the engine as it was before this change:

$ pytest tests/test_hardwareprofile_cloud.py -q
E  AssertionError: 'hardwarevendor' not found in {'devicetype': ..., 'ismobile': ...} : 'hardwarevendor' should be present on the profile so the reason it has no value can be reported
4 failed, 1 passed
exit 1

The rewritten example tests, against the engine and helper as they were
before this change, with the not entitled key:

$ pytest tests/test_cloudexamples.py -q
E  AttributeError: 'NoneType' object has no attribute 'no_value_message'
FAILED tests/test_cloudexamples.py::DeviceDetectionExampleTests::test_cloud_nativemodellookup_console
FAILED tests/test_cloudexamples.py::DeviceDetectionExampleTests::test_cloud_taclookup_console
2 failed, 3 passed
exit 1

Against this branch, same key:

$ pytest tests/test_cloudexamples.py tests/test_cloudexampleimports.py -q
6 passed, 8 subtests passed in 21.42s

$ cd fiftyone_devicedetection_cloud && pytest tests -q
12 passed, 1 skipped, 3 subtests passed in 15.74s

And with no resource key set at all, which is what the unit job will do:

$ cd fiftyone_devicedetection_cloud && pytest tests -q
5 passed, 8 skipped in 0.38s

Each skip names the variable it wanted:

SKIPPED [1] tests/test_cloud.py:90: No resource key found, so the tests that
call the cloud service cannot run. Set the environment variable
'_51DEGREES_RESOURCE_KEY' (the older name 'resource_key' is still read).
Create a resource key for free at https://configure.51degrees.com...

One thing for a reviewer to decide

The organisation secret still has its old name. The workflows now read
secrets._51DEGREES_RESOURCE_KEY_SUPER and fall back to
secrets.SUPER_RESOURCE_KEY, so nothing breaks either way. Renaming the
organisation secret would let common-ci's central
steps/set-resource-keys.ps1
export it, after which both fallbacks can go. That rename needs
organisation access and is not done here.


Produced with AI assistance. Every claim above was checked by running the
commands shown, and the output is real rather than illustrative. It needs
human review before merging.

… a property has no value, and make a broken example go red

Four of the eight cloud examples imported
fiftyone_devicedetection.devicedetection_pipelinebuilder, which
unconditionally imports the on-premise engine, so a cloud only example
could not be run without building the on-premise native library from
source. PyPI publishes that package as an sdist with no wheel, so anyone
following a cloud example needed a C++ toolchain first. The four now use
DeviceDetectionCloudPipelineBuilder, which does exactly the same work on
the cloud path, and one of them imported the builder without ever using
it. A new test imports every cloud example in a separate process with the
on-premise packages blocked, so the dependency cannot come back.

The hardware profile cloud engine threw away the reason the cloud service
gives when a resource key is not entitled to a property, so the TAC and
native model examples stopped with AttributeError: 'NoneType' object has
no attribute 'no_value_message'. The engine now carries each aspect level
value and its companion nullreason into every profile, and
get_human_readable reports the reason instead of calling a method on
None.

A new unit test runs the engine against fixed cloud responses, so it
needs no resource key and no network connection, and the cloud package is
added to the unit job so it runs on every build.

The cloud example tests asserted nothing about what the examples printed.
They now capture the output and assert the headings each example must
print, that nothing in it reads as a programming fault, and that each
lookup says something about the devices it found.

Tests that cannot run without a resource key now skip with a message
naming the variable they wanted, rather than returning quietly and
passing. The property coverage tests skip when the CSV asset is absent
rather than failing to import.

Resource key environment variables move onto the '_51DEGREES_RESOURCE_KEY'
convention, with the previous names still read as a fallback so an
existing setup keeps working.
@Automation51D
Automation51D merged commit 8337ca3 into main Sep 5, 2026
1 check passed
@Automation51D
Automation51D deleted the fix/cloud-example-failures branch September 5, 2026 02:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants