Repository: PrefectHQ/prefect

Issue:
Try/Catch accidentally suppresses errors in serializers
## Description
<!-- A clear description of the bug -->
https://github.com/PrefectHQ/prefect/blob/master/src/prefect/engine/task_runner.py#L892 
A Flow using the PandasSerializer and the S3Result failed to serialize a `datetime.timedelta` in a column. But, that returned an `ArrowNotImplementedError`. the serializer didn't catch it, the result didn't catch it, and it fell through to the line pointed at which allowed the Task to Succeed in a case where it should have failed.


## Expected Behavior
<!-- What did you expect to happen instead? -->
Task should have failed


## Reproduction
<!-- A minimal example that exhibits the behavior. -->




## Environment
<!-- Any additional information about your environment

Optionally run `prefect diagnostics` from the command line and paste the information here. -->



Discussion:

Resolving patch:
diff --git a/src/prefect/engine/result/base.py b/src/prefect/engine/result/base.py
--- a/src/prefect/engine/result/base.py
+++ b/src/prefect/engine/result/base.py
@@ -29,6 +29,12 @@
 from prefect.utilities import logging
 
 
+# Subclass of `NotImplementedError` to make it easier to distinguish this error
+# in consuming code
+class ResultNotImplementedError(NotImplementedError):
+    """Indicates a Result feature isn't implemented"""
+
+
 class Result:
     """
     A representation of the result of a Prefect task; this class contains
@@ -182,7 +188,7 @@ def exists(self, location: str, **kwargs: Any) -> bool:
         Returns:
             - bool: whether or not the target result exists.
         """
-        raise NotImplementedError(
+        raise ResultNotImplementedError(
             "Not implemented on the base Result class - if you are seeing this error you "
             "might be trying to use features that require choosing a Result subclass; "
             "see https://docs.prefect.io/core/concepts/results.html"
@@ -198,7 +204,7 @@ def read(self, location: str) -> "Result":
         Returns:
             - Any: The value saved to the result.
         """
-        raise NotImplementedError(
+        raise ResultNotImplementedError(
             "Not implemented on the base Result class - if you are seeing this error you "
             "might be trying to use features that require choosing a Result subclass; "
             "see https://docs.prefect.io/core/concepts/results.html"
@@ -217,7 +223,7 @@ def write(self, value_: Any, **kwargs: Any) -> "Result":
         Returns:
             - Result: a new result object with the appropriately formatted location destination
         """
-        raise NotImplementedError(
+        raise ResultNotImplementedError(
             "Not implemented on the base Result class - if you are seeing this error you "
             "might be trying to use features that require choosing a Result subclass; "
             "see https://docs.prefect.io/core/concepts/results.html"
diff --git a/src/prefect/engine/task_runner.py b/src/prefect/engine/task_runner.py
--- a/src/prefect/engine/task_runner.py
+++ b/src/prefect/engine/task_runner.py
@@ -18,7 +18,7 @@
 from prefect import config
 from prefect.core import Edge, Task
 from prefect.engine import signals
-from prefect.engine.result import Result
+from prefect.engine.result.base import Result, ResultNotImplementedError
 from prefect.engine.runner import ENDRUN, Runner, call_state_handlers
 from prefect.engine.state import (
     Cached,
@@ -889,7 +889,7 @@ def get_task_run_state(self, state: State, inputs: Dict[str, Result]) -> State:
                     **raw_inputs,
                 }
                 result = self.result.write(value, **formatting_kwargs)
-            except NotImplementedError:
+            except ResultNotImplementedError:
                 result = self.result.from_value(value=value)
         else:
             result = self.result.from_value(value=value)
@@ -978,7 +978,7 @@ def check_for_retry(self, state: State, inputs: Dict[str, Result]) -> State:
                         loop_result = self.result.write(
                             loop_result.value, **formatting_kwargs
                         )
-                    except NotImplementedError:
+                    except ResultNotImplementedError:
                         pass
 
                 state_context = {"_loop_count": prefect.context["task_loop_count"]}


Test patch:
diff --git a/tests/engine/result/test_base.py b/tests/engine/result/test_base.py
--- a/tests/engine/result/test_base.py
+++ b/tests/engine/result/test_base.py
@@ -7,6 +7,7 @@
 
 import prefect
 from prefect.engine.result import Result, NoResultType
+from prefect.engine.result.base import ResultNotImplementedError
 
 
 class TestInitialization:
@@ -31,12 +32,12 @@ def test_result_inits_with_value(self):
 def test_has_abstract_interfaces(abstract_interface: str):
     """
     Tests to make sure that calling the abstract interfaces directly
-    on the base `Result` class results in `NotImplementedError`s.
+    on the base `Result` class results in `ResultNotImplementedError`s.
     """
     r = Result(value=3)
 
     func = getattr(r, abstract_interface)
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(ResultNotImplementedError):
         func(None)