From cf9683bd7cced9fcd263b7af16e664828a8c266a Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Mon, 9 Nov 2026 21:03:30 +0000 Subject: [PATCH 1/5] Test that the JSON Builder writes keys in lower case --- .../tests/test_jsonbundler_lower_case.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_lower_case.py diff --git a/pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_lower_case.py b/pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_lower_case.py new file mode 100644 index 00000000..5ab1f18a --- /dev/null +++ b/pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_lower_case.py @@ -0,0 +1,63 @@ + +""" +The JSON Builder writes the key of each top-level entry and of each sub-entry, +including the meta-Property suffixes, in lower case, apart from +javascriptProperties, as the JSON Builder specification says. +""" + +import unittest + +from fiftyone_pipeline_core.elementdata_dictionary import ElementDataDictionary +from fiftyone_pipeline_core.flowelement import FlowElement +from fiftyone_pipeline_core.pipelinebuilder import PipelineBuilder + + +class MixedCaseElement(FlowElement): + + def __init__(self): + super(MixedCaseElement, self).__init__() + self.datakey = "MixedCase" + self.properties = { + "DelayedProp": { + "name": "DelayedProp", "type": "string", + "delayexecution": True}, + "OtherProp": { + "name": "OtherProp", "type": "string", + "evidenceproperties": ["delayedprop"]}, + "EmptyProp": {"name": "EmptyProp", "type": "string"}} + + def process_internal(self, flowdata): + flowdata.set_element_data(ElementDataDictionary( + self, {"DelayedProp": "a", "OtherProp": "b", "EmptyProp": None})) + + +class JsonBuilderLowerCaseTests(unittest.TestCase): + + def process(self): + pipeline = PipelineBuilder({}).add(MixedCaseElement()).build() + self.addCleanup(pipeline.close) + flowdata = pipeline.create_flowdata() + flowdata.process() + return flowdata.jsonbundler.json + + def test_every_key_is_lower_case_but_javascript_properties(self): + json = self.process() + self.assertIn("javascriptProperties", json) + self.assertIn("mixedcase", json) + for key, value in json.items(): + if key == "javascriptProperties": + continue + self.assertEqual(key.lower(), key) + for sub_key in value: + self.assertEqual(sub_key.lower(), sub_key, sub_key) + + def test_the_meta_property_keys_are_lower_case(self): + section = self.process()["mixedcase"] + self.assertTrue(section["delayedpropdelayexecution"]) + self.assertIn("otherpropevidenceproperties", section) + self.assertIn("emptypropnullreason", section) + self.assertIsNone(section["emptyprop"]) + + +if __name__ == "__main__": + unittest.main() From f77d4f99e13dd0ee6b64cc4658dfcd0d493554af Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Mon, 9 Nov 2026 21:04:25 +0000 Subject: [PATCH 2/5] Write the JSON Builder's element and meta-property keys in lower case --- .../src/fiftyone_pipeline_core/jsonbundler.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/jsonbundler.py b/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/jsonbundler.py index 9f2a8ca7..4262d740 100644 --- a/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/jsonbundler.py +++ b/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/jsonbundler.py @@ -103,7 +103,7 @@ def process_internal(self, flowdata): if len(delayed_evidence_properties_list): delayed_evidence_properties[propertykey] = list(map(lambda x: - flow_element.datakey + '.' + x, delayed_evidence_properties_list)) + flow_element.datakey.lower() + '.' + x, delayed_evidence_properties_list)) self.property_cache[flow_element.datakey] = { @@ -115,7 +115,7 @@ def process_internal(self, flowdata): # Create empty area for FlowElement properties to go - output[flow_element.datakey] = {} + output[flow_element.datakey.lower()] = {} for propertykey, propertymeta in properties.items(): value = None @@ -125,13 +125,13 @@ def process_internal(self, flowdata): if propertykey in property_cache["delayExecutionList"]: - output[flow_element.datakey][propertykey.lower() + "delayexecution"] = True + output[flow_element.datakey.lower()][propertykey.lower() + "delayexecution"] = True # Check if property has any delayed execution evidence properties and set in JSON if yes if propertykey in property_cache["evidenceProperties"]: - output[flow_element.datakey][propertykey.lower() + 'evidenceproperties'] = property_cache["evidenceProperties"][propertykey] + output[flow_element.datakey.lower()][propertykey.lower() + 'evidenceproperties'] = property_cache["evidenceProperties"][propertykey] try: From e4baa1860b848847ff2604894c4080bf00d4041e Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Mon, 9 Nov 2026 21:43:27 +0000 Subject: [PATCH 3/5] Store the lower case test element's data under the lower case key --- .../tests/test_jsonbundler_lower_case.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_lower_case.py b/pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_lower_case.py index 5ab1f18a..b7150b08 100644 --- a/pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_lower_case.py +++ b/pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_lower_case.py @@ -27,8 +27,9 @@ def __init__(self): "EmptyProp": {"name": "EmptyProp", "type": "string"}} def process_internal(self, flowdata): - flowdata.set_element_data(ElementDataDictionary( - self, {"DelayedProp": "a", "OtherProp": "b", "EmptyProp": None})) + # FlowData.get reads the data under the lower case key. + flowdata.data["mixedcase"] = ElementDataDictionary( + self, {"DelayedProp": "a", "OtherProp": "b", "EmptyProp": None}) class JsonBuilderLowerCaseTests(unittest.TestCase): From 3653b1f26e1add19fb5108fc8633f0e3c9b1c638 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Tue, 10 Nov 2026 10:41:06 +0000 Subject: [PATCH 4/5] Set the mixed case test element's data through set_element_data, as a real element does --- .../tests/test_jsonbundler_lower_case.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_lower_case.py b/pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_lower_case.py index b7150b08..028eac4a 100644 --- a/pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_lower_case.py +++ b/pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_lower_case.py @@ -7,6 +7,7 @@ import unittest +from fiftyone_pipeline_core.aspectproperty_value import AspectPropertyValue from fiftyone_pipeline_core.elementdata_dictionary import ElementDataDictionary from fiftyone_pipeline_core.flowelement import FlowElement from fiftyone_pipeline_core.pipelinebuilder import PipelineBuilder @@ -27,9 +28,11 @@ def __init__(self): "EmptyProp": {"name": "EmptyProp", "type": "string"}} def process_internal(self, flowdata): - # FlowData.get reads the data under the lower case key. - flowdata.data["mixedcase"] = ElementDataDictionary( - self, {"DelayedProp": "a", "OtherProp": "b", "EmptyProp": None}) + # Set as a real element sets it, under its mixed case data key. + flowdata.set_element_data(ElementDataDictionary( + self, {"DelayedProp": "a", "OtherProp": "b", + "EmptyProp": AspectPropertyValue( + no_value_message="No value")})) class JsonBuilderLowerCaseTests(unittest.TestCase): @@ -56,8 +59,10 @@ def test_the_meta_property_keys_are_lower_case(self): section = self.process()["mixedcase"] self.assertTrue(section["delayedpropdelayexecution"]) self.assertIn("otherpropevidenceproperties", section) - self.assertIn("emptypropnullreason", section) + self.assertEqual("No value", section["emptypropnullreason"]) self.assertIsNone(section["emptyprop"]) + self.assertEqual("a", section["delayedprop"]) + self.assertEqual("b", section["otherprop"]) if __name__ == "__main__": From 78753823415a271eafdbd93adce3c59ad7100255 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Tue, 10 Nov 2026 10:41:07 +0000 Subject: [PATCH 5/5] Keep Element Data under the data key in lower case, which is how FlowData.get reads it --- .../src/fiftyone_pipeline_core/flowdata.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/flowdata.py b/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/flowdata.py index c6f83c7c..914d7596 100644 --- a/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/flowdata.py +++ b/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/flowdata.py @@ -203,7 +203,8 @@ def set_element_data(self, element_data): """ - self.data[element_data.flow_element.datakey] = element_data + # Kept under the data key in lower case, which is how get reads it. + self.data[element_data.flow_element.datakey.lower()] = element_data def set_error(self, flow_error):