diff --git a/azure/functions/durable_functions.py b/azure/functions/durable_functions.py index 4920830e..0ad861ca 100644 --- a/azure/functions/durable_functions.py +++ b/azure/functions/durable_functions.py @@ -133,3 +133,60 @@ class DurableClientConverter(meta.InConverter, @classmethod def has_implicit_output(cls) -> bool: return False + + @classmethod + def has_trigger_support(cls) -> bool: + return False + + @classmethod + def check_input_type_annotation(cls, pytype: type) -> bool: + return issubclass(pytype, (str, bytes)) + + @classmethod + def check_output_type_annotation(cls, pytype: type) -> bool: + return issubclass(pytype, (str, bytes, bytearray)) + + @classmethod + def encode(cls, obj: typing.Any, *, + expected_type: typing.Optional[type]) -> meta.Datum: + if isinstance(obj, str): + return meta.Datum(type='string', value=obj) + + elif isinstance(obj, (bytes, bytearray)): + return meta.Datum(type='bytes', value=bytes(obj)) + elif obj is None: + return meta.Datum(type=None, value=obj) + elif isinstance(obj, dict): + return meta.Datum(type='dict', value=obj) + elif isinstance(obj, list): + return meta.Datum(type='list', value=obj) + elif isinstance(obj, bool): + return meta.Datum(type='bool', value=obj) + elif isinstance(obj, int): + return meta.Datum(type='int', value=obj) + elif isinstance(obj, float): + return meta.Datum(type='double', value=obj) + else: + raise NotImplementedError + + @classmethod + def decode(cls, data: meta.Datum, *, trigger_metadata) -> typing.Any: + if data is None: + return None + data_type = data.type + + if data_type == 'string': + result = data.value + elif data_type == 'bytes': + result = data.value + elif data_type == 'json': + result = data.value + elif data_type is None: + result = None + else: + raise ValueError( + 'unexpected type of data received for the "generic" binding ', + repr(data_type) + ) + + return result diff --git a/tests/test_durable_functions.py b/tests/test_durable_functions.py index a81648be..043b373d 100644 --- a/tests/test_durable_functions.py +++ b/tests/test_durable_functions.py @@ -270,3 +270,77 @@ def test_enitity_trigger_converter_encode(self): self.assertEqual(result.type, "json") self.assertEqual(result.python_value, {'dummy_key': 'dummy_value'}) + + def test_durable_client_converter_has_trigger_support(self): + self.assertFalse(DurableClientConverter.has_trigger_support()) + + def test_durable_client_converter_check_input_type_annotation(self): + self.assertTrue(DurableClientConverter.check_input_type_annotation(str)) + self.assertTrue(DurableClientConverter.check_input_type_annotation(bytes)) + self.assertFalse(DurableClientConverter.check_input_type_annotation(int)) + + def test_durable_client_converter_check_output_type_annotation(self): + self.assertTrue(DurableClientConverter.check_output_type_annotation(str)) + self.assertTrue(DurableClientConverter.check_output_type_annotation(bytes)) + self.assertTrue(DurableClientConverter.check_output_type_annotation(bytearray)) + self.assertFalse(DurableClientConverter.check_output_type_annotation(int)) + + def test_durable_client_converter_encode(self): + datum = DurableClientConverter.encode(obj="hello", expected_type=str) + self.assertEqual(datum.type, "string") + self.assertEqual(datum.value, "hello") + + datum = DurableClientConverter.encode(obj=b"data", expected_type=bytes) + self.assertEqual(datum.type, "bytes") + self.assertEqual(datum.value, b"data") + + datum = DurableClientConverter.encode(obj=None, expected_type=None) + self.assertIsNone(datum.type) + self.assertIsNone(datum.value) + + datum = DurableClientConverter.encode(obj={"a": 1}, expected_type=dict) + self.assertEqual(datum.type, "dict") + self.assertEqual(datum.value, {"a": 1}) + + datum = DurableClientConverter.encode(obj=[1, 2], expected_type=list) + self.assertEqual(datum.type, "list") + self.assertEqual(datum.value, [1, 2]) + + datum = DurableClientConverter.encode(obj=42, expected_type=int) + self.assertEqual(datum.type, "int") + self.assertEqual(datum.value, 42) + + datum = DurableClientConverter.encode(obj=3.14, expected_type=float) + self.assertEqual(datum.type, "double") + self.assertEqual(datum.value, 3.14) + + datum = DurableClientConverter.encode(obj=True, expected_type=bool) + self.assertEqual(datum.type, "bool") + self.assertTrue(datum.value) + + with self.assertRaises(NotImplementedError): + DurableClientConverter.encode(obj=set([1, 2]), expected_type=set) + + def test_durable_client_converter_decode(self): + data = Datum(type="string", value="abc") + result = DurableClientConverter.decode(data=data, trigger_metadata=None) + self.assertEqual(result, "abc") + + data = Datum(type="bytes", value=b"123") + result = DurableClientConverter.decode(data=data, trigger_metadata=None) + self.assertEqual(result, b"123") + + data = Datum(type="json", value={"key": "val"}) + result = DurableClientConverter.decode(data=data, trigger_metadata=None) + self.assertEqual(result, {"key": "val"}) + + data = Datum(type=None, value=None) + result = DurableClientConverter.decode(data=data, trigger_metadata=None) + self.assertIsNone(result) + + result = DurableClientConverter.decode(data=None, trigger_metadata=None) + self.assertIsNone(result) + + data = Datum(type="weird", value="???") + with self.assertRaises(ValueError): + DurableClientConverter.decode(data=data, trigger_metadata=None)