Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,37 @@ def get_evidence_key_filter(self):

return BasicListEvidenceKeyFilter([])

@staticmethod
def record_json(record):

"""!
A record, being the Properties of one Element Data in a list, as an
object. A Property that has been assigned no value is null with its
nullreason beside it, and a Property that is null without one has no
entry.
@type record: dict
@param record: Property name to value or aspect property value
@rtype: dict
@return: The record as it is written to JSON
"""

written = {}

for key, value in record.items():
key = str(key).lower()

if hasattr(value, "has_value"):
if value.has_value():
written[key] = value.value()
else:
written[key] = None
written[key + "nullreason"] = (
value.no_value_message() or "Unknown")
elif value is not None:
written[key] = value

return written

def process_internal(self, flowdata):

"""!
Expand Down Expand Up @@ -134,6 +165,7 @@ def process_internal(self, flowdata):
for propertykey, propertymeta in properties.items():
value = None
null_reason = "Unknown"
no_value = False

# Check if property has delayed execution and set in JSON if yes

Expand Down Expand Up @@ -162,21 +194,28 @@ def process_internal(self, flowdata):
value = value_container.value()
else:
value = None
null_reason = value_container.no_value_message()
no_value = True
null_reason = (
value_container.no_value_message()
or null_reason)

# Check if list of aspect property values
# A list of records, such as the profiles of a hardware
# profile lookup, or of aspect property values

elif isinstance(value_container, list) and isinstance(value_container[0], object):
elif isinstance(value_container, list):

output = []
items = []

for item in value_container:
if item.has_value():
output.append(item.value())
if isinstance(item, dict):
items.append(self.record_json(item))
elif hasattr(item, "has_value"):
if item.has_value():
items.append(item.value())
else:
null_reason = item.no_value_message
items.append(item)

value = output
value = items

else:

Expand All @@ -187,14 +226,18 @@ def process_internal(self, flowdata):
# Catching missing property exceptions and other errors

continue

if (isinstance(value, str)
and str(propertymeta.get("type", "")).lower() in WKT_TYPES):
value = {"value": value}

output[flow_element.datakey.lower()][propertykey.lower()] = value
if value is None:
output[flow_element.datakey.lower()][propertykey.lower() + "nullreason"] = null_reason
# A null that was not assigned 'no value', which an element
# that is not an engine can give, has no entry of its own. A
# JavaScript Property is still listed below.
if value is not None or no_value:
output[flow_element.datakey.lower()][propertykey.lower()] = value
if value is None:
output[flow_element.datakey.lower()][propertykey.lower() + "nullreason"] = null_reason

sequence = flowdata.evidence.get("query.sequence")

Expand Down
95 changes: 95 additions & 0 deletions pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_records.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@

"""
A Property whose value is a list of Element Data is written as a list of
objects, each following the null rules of the JSON Builder specification, and
the other Properties are written beside it.
"""

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


class ProfilesElement(FlowElement):

def __init__(self):
super(ProfilesElement, self).__init__()
self.datakey = "hardware"
self.properties = {
"profiles": {"name": "Profiles", "type": "array"},
"vendor": {"name": "Vendor", "type": "string"},
"model": {"name": "Model", "type": "string"},
"colour": {"name": "Colour", "type": "string"},
"script": {"name": "Script", "type": "javascript"},
"size": {"name": "Size", "type": "string"}}

def process_internal(self, flowdata):
records = [
{"hardwarename": AspectPropertyValue(value="Phone"),
"hardwarevendor": AspectPropertyValue(no_value_message="No data"),
"hardwaremodel": None},
{"hardwarename": AspectPropertyValue(value="Tablet")},
{"hardwarename": AspectPropertyValue()}]
flowdata.set_element_data(ElementDataDictionary(
self, {"profiles": records,
"vendor": AspectPropertyValue(value="Acme"),
"model": None,
"colour": AspectPropertyValue(
no_value_message="No colour"),
"script": None,
"size": AspectPropertyValue()}))


class JsonBuilderRecordTests(unittest.TestCase):

def process_json(self):
pipeline = PipelineBuilder({}).add(ProfilesElement()).build()
self.addCleanup(pipeline.close)
flowdata = pipeline.create_flowdata()
flowdata.process()
return flowdata.jsonbundler.json

def process(self):
return self.process_json()["hardware"]

def test_each_record_is_an_object(self):
self.assertEqual(
[{"hardwarename": "Phone",
"hardwarevendor": None,
"hardwarevendornullreason": "No data"},
{"hardwarename": "Tablet"},
{"hardwarename": None, "hardwarenamenullreason": "Unknown"}],
self.process()["profiles"])

def test_a_null_that_was_not_assigned_no_value_has_no_entry(self):
self.assertNotIn("hardwaremodel", self.process()["profiles"][0])

def test_the_other_properties_are_still_written(self):
self.assertEqual("Acme", self.process()["vendor"])

def test_a_top_level_null_not_assigned_no_value_has_no_entry(self):
hardware = self.process()
self.assertNotIn("model", hardware)
self.assertNotIn("modelnullreason", hardware)

def test_a_null_javascript_property_is_still_listed(self):
json = self.process_json()
self.assertNotIn("script", json["hardware"])
self.assertIn("hardware.script", json["javascriptProperties"])

def test_no_value_with_no_message_has_a_text_reason(self):
hardware = self.process()
self.assertIsNone(hardware["size"])
self.assertEqual("Unknown", hardware["sizenullreason"])

def test_a_top_level_property_assigned_no_value_is_null(self):
hardware = self.process()
self.assertIsNone(hardware["colour"])
self.assertEqual("No colour", hardware["colournullreason"])


if __name__ == "__main__":
unittest.main()
Loading