diff --git a/fiftyone_pipeline_core/src/fiftyone_pipeline_core/pipelinebuilder.py b/fiftyone_pipeline_core/src/fiftyone_pipeline_core/pipelinebuilder.py index c95d3ff..72f8975 100644 --- a/fiftyone_pipeline_core/src/fiftyone_pipeline_core/pipelinebuilder.py +++ b/fiftyone_pipeline_core/src/fiftyone_pipeline_core/pipelinebuilder.py @@ -48,6 +48,10 @@ def __init__(self, settings= {}): @rtype: PipelineBuilder `use_setheader_properties (Bool)` - Whether to add the SetHeaderElement to the pipeline (default true) + `suppress_process_exceptions (Bool)` - If False (the default) the first error thrown + while processing is re-raised from flow_data.process(); if True, errors are stored on + flow_data.errors and process() returns normally. Recommended True for web apps so a + processing failure degrades gracefully instead of failing the request. @return: Returns a Pipeline Builder """ @@ -73,6 +77,11 @@ def __init__(self, settings= {}): else: self.data_file_update_service = None + if "suppress_process_exceptions" in settings: + self.suppress_process_exceptions = settings["suppress_process_exceptions"] + else: + self.suppress_process_exceptions = False + def get_javascript_elements(self): """ Adds the JavaScriptBuilder, JSONBundler and SequenceElement to the pipeline if @@ -138,8 +147,8 @@ def build(self): self.flow_elements.extend(self.get_javascript_elements()) self.flow_elements.extend(self.get_setheader_elements()) - - return Pipeline(self.flow_elements, logger=self.logger, data_file_update_service=self.data_file_update_service) + + return Pipeline(self.flow_elements, logger=self.logger, suppress_process_exceptions=self.suppress_process_exceptions, data_file_update_service=self.data_file_update_service) def add_logger(self, logger): """ @@ -187,7 +196,40 @@ def build_from_configuration(self, config): flow_element = flow_element() flow_elements.append(flow_element) - return Pipeline(flow_elements) + return Pipeline(flow_elements, suppress_process_exceptions=self._get_suppress_process_exceptions(config)) + + def _get_suppress_process_exceptions(self, config): + """ + Resolve the suppress_process_exceptions setting from a configuration. + Looks under 'PipelineOptions.BuildParameters' (mirrors the .NET/Java + config layout for cross-language consistency), then a direct + 'PipelineOptions' key, accepting either snake_case or camelCase, and + finally falls back to the value supplied to the builder constructor + (default False). + + @type config: dict + @param config: pipeline configuration + @rtype: bool + @return: whether processing exceptions should be suppressed + """ + + options = config.get("PipelineOptions", {}) + + def lookup(source): + if not isinstance(source, dict): + return None + for key in ("suppress_process_exceptions", "suppressProcessExceptions"): + if key in source: + return source[key] + return None + + value = lookup(options.get("BuildParameters")) + if value is None: + value = lookup(options) + if value is None: + return self.suppress_process_exceptions + + return value def _map_properties_names(self, mappings, arguments): if arguments is None: diff --git a/fiftyone_pipeline_core/tests/test_core.py b/fiftyone_pipeline_core/tests/test_core.py index db0b62b..f26acde 100644 --- a/fiftyone_pipeline_core/tests/test_core.py +++ b/fiftyone_pipeline_core/tests/test_core.py @@ -158,3 +158,64 @@ def test_build_from_config(self): getValue = fd.get("example1").get("integer") self.assertTrue(getValue == 5) + + # suppress_process_exceptions: default is False from the builder + def test_suppress_process_exceptions_default(self): + pipeline = PipelineBuilder().add(ExampleFlowElement1()).build() + self.assertFalse(pipeline.suppress_process_exceptions) + + # suppress_process_exceptions: set via builder settings + def test_suppress_process_exceptions_builder_setting(self): + pipeline = PipelineBuilder({"suppress_process_exceptions": True})\ + .add(ExampleFlowElement1())\ + .build() + self.assertTrue(pipeline.suppress_process_exceptions) + + # suppress_process_exceptions: read from PipelineOptions.BuildParameters + def test_suppress_process_exceptions_from_config_build_parameters(self): + config = { + "PipelineOptions": { + "Elements": [ + { + "elementName": "ExampleFlowElement1", + "elementPath": "tests.classes.exampleflowelement1" + } + ], + "BuildParameters": { + "suppress_process_exceptions": True + } + } + } + pipeline = PipelineBuilder().build_from_configuration(config) + self.assertTrue(pipeline.suppress_process_exceptions) + + # suppress_process_exceptions: read from a direct key (camelCase accepted) + def test_suppress_process_exceptions_from_config_direct(self): + config = { + "PipelineOptions": { + "Elements": [ + { + "elementName": "ExampleFlowElement1", + "elementPath": "tests.classes.exampleflowelement1" + } + ], + "suppressProcessExceptions": True + } + } + pipeline = PipelineBuilder().build_from_configuration(config) + self.assertTrue(pipeline.suppress_process_exceptions) + + # suppress_process_exceptions: defaults to False from configuration + def test_suppress_process_exceptions_from_config_default(self): + config = { + "PipelineOptions": { + "Elements": [ + { + "elementName": "ExampleFlowElement1", + "elementPath": "tests.classes.exampleflowelement1" + } + ] + } + } + pipeline = PipelineBuilder().build_from_configuration(config) + self.assertFalse(pipeline.suppress_process_exceptions)