diff --git a/pipeline/fiftyone_pipeline_cloudrequestengine/src/fiftyone_pipeline_cloudrequestengine/cloudengine.py b/pipeline/fiftyone_pipeline_cloudrequestengine/src/fiftyone_pipeline_cloudrequestengine/cloudengine.py index 79bb5007..18a40a91 100644 --- a/pipeline/fiftyone_pipeline_cloudrequestengine/src/fiftyone_pipeline_cloudrequestengine/cloudengine.py +++ b/pipeline/fiftyone_pipeline_cloudrequestengine/src/fiftyone_pipeline_cloudrequestengine/cloudengine.py @@ -24,6 +24,7 @@ BasicListEvidenceKeyFilter from fiftyone_pipeline_engines.engine import Engine from fiftyone_pipeline_core.aspectproperty_value import AspectPropertyValue +from fiftyone_pipeline_core.jsonbundler import WKT_TYPES from .clouddata import CloudData @@ -133,8 +134,19 @@ def process_internal(self, flowdata): result = {} + # A shape in well-known text arrives as an object holding the text. + # The property list is keyed by the names the cloud service gives, + # such as Areas, and the response by the same names in lower case. + wkt_keys = { + name.lower() for name, meta in self.properties.items() + if str(meta.get("type", "")).lower() in WKT_TYPES} + for key, value in engineData.items(): + if (key.lower() in wkt_keys and isinstance(value, dict) + and list(value) == ["value"]): + value = value["value"] + if key + "nullreason" in engineData: result[key] = AspectPropertyValue(no_value_message=engineData[key + "nullreason"]) else: diff --git a/pipeline/fiftyone_pipeline_cloudrequestengine/tests/test_cloudengine_wkt.py b/pipeline/fiftyone_pipeline_cloudrequestengine/tests/test_cloudengine_wkt.py new file mode 100644 index 00000000..6e938d08 --- /dev/null +++ b/pipeline/fiftyone_pipeline_cloudrequestengine/tests/test_cloudengine_wkt.py @@ -0,0 +1,60 @@ + +""" +A WKT value in a response from the remote server is an object with one member, +value, holding the WKT text, and the engine gives the text. +""" + +import json +import unittest + +from fiftyone_pipeline_cloudrequestengine.cloudengine import CloudEngine +from fiftyone_pipeline_core.elementdata_dictionary import ElementDataDictionary +from fiftyone_pipeline_core.flowelement import FlowElement +from fiftyone_pipeline_core.pipelinebuilder import PipelineBuilder + +AREA = "POLYGON((1 2, 3 4, 5 6, 1 2))" + + +class FakeCloudRequestEngine(FlowElement): + + def __init__(self, answer): + super(FakeCloudRequestEngine, self).__init__() + self.datakey = "cloud" + self.answer = answer + # Keyed by the names the cloud service gives, as + # CloudRequestEngine.get_engine_properties keys them. + self.flow_element_properties = { + "ip": {"Areas": {"name": "Areas", "type": "WktString"}, + "Name": {"name": "Name", "type": "String"}}} + + def process_internal(self, flowdata): + flowdata.set_element_data(ElementDataDictionary( + self, {"cloud": json.dumps(self.answer)})) + + +def read(answer): + engine = CloudEngine() + engine.datakey = "ip" + pipeline = PipelineBuilder({ + "add_javascript_builder": False, "use_setheader_properties": False}) \ + .add(FakeCloudRequestEngine(answer)).add(engine).build() + flowdata = pipeline.create_flowdata() + flowdata.process() + pipeline.close() + return flowdata.get("ip") + + +class CloudEngineWktTests(unittest.TestCase): + + def test_the_text_is_taken_out_of_the_object(self): + data = read({"ip": {"areas": {"value": AREA}, "name": "a place"}}) + self.assertEqual(AREA, data.get("areas").value()) + self.assertEqual("a place", data.get("name").value()) + + def test_text_that_is_already_plain_is_kept(self): + data = read({"ip": {"areas": AREA, "name": "a place"}}) + self.assertEqual(AREA, data.get("areas").value()) + + +if __name__ == "__main__": + unittest.main() 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 575508ba..a8e733a0 100644 --- a/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/jsonbundler.py +++ b/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/jsonbundler.py @@ -24,6 +24,10 @@ from .flowelement import FlowElement from .elementdata_dictionary import ElementDataDictionary +# The property types that hold a shape in well-known text. The JSON holds such +# a value as an object with one member, value, holding the text. +WKT_TYPES = ("wkt", "wktstring", "wkb", "wkbr") + class JSONBundlerElement(FlowElement): """! @@ -184,6 +188,10 @@ def process_internal(self, flowdata): 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 diff --git a/pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_wkt.py b/pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_wkt.py new file mode 100644 index 00000000..04f0b18f --- /dev/null +++ b/pipeline/fiftyone_pipeline_core/tests/test_jsonbundler_wkt.py @@ -0,0 +1,50 @@ + +""" +A WKT value is written to JSON as an object with one member, value, holding +the WKT text, as the value types page of the 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 + +AREA = "POLYGON((1 2, 3 4, 5 6, 1 2))" + + +class AreasElement(FlowElement): + + def __init__(self, type_name): + super(AreasElement, self).__init__() + self.datakey = "areas" + self.properties = { + "areas": {"name": "Areas", "type": type_name}, + "name": {"name": "Name", "type": "string"}} + + def process_internal(self, flowdata): + flowdata.set_element_data(ElementDataDictionary( + self, {"Areas": AREA, "Name": "a place"})) + + +def json_of(type_name): + pipeline = PipelineBuilder({}).add(AreasElement(type_name)).build() + flowdata = pipeline.create_flowdata() + flowdata.process() + pipeline.close() + return flowdata.jsonbundler.json["areas"] + + +class JsonBuilderWktTests(unittest.TestCase): + + def test_a_wkt_value_is_an_object_holding_the_text(self): + for type_name in ("WktString", "wkb", "WKBR", "wkt"): + self.assertEqual( + {"value": AREA}, json_of(type_name)["areas"], type_name) + + def test_other_values_are_written_as_they_are(self): + self.assertEqual("a place", json_of("WktString")["name"]) + + +if __name__ == "__main__": + unittest.main()