From bf214ea2437b7e931e9cd72f3711e2afbe5b4645 Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Fri, 31 Oct 2025 14:01:32 -0500 Subject: [PATCH 1/5] add full DurableClientConverter class --- azure/functions/durable_functions.py | 59 ++++++++++++++++++++++ tests/test_durable_functions.py | 74 ++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/azure/functions/durable_functions.py b/azure/functions/durable_functions.py index 4920830e..6af5c526 100644 --- a/azure/functions/durable_functions.py +++ b/azure/functions/durable_functions.py @@ -133,3 +133,62 @@ 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, int): + return meta.Datum(type='int', value=obj) + elif isinstance(obj, float): + return meta.Datum(type='double', value=obj) + elif isinstance(obj, bool): + return meta.Datum(type='bool', value=obj) + else: + raise NotImplementedError + + @classmethod + def decode(cls, data: meta.Datum, *, trigger_metadata) -> typing.Any: + # Enabling support for Dapr bindings + # https://github.com/Azure/azure-functions-python-worker/issues/1316 + 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..f8b22297 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("hello") + self.assertEqual(datum.type, "string") + self.assertEqual(datum.value, "hello") + + datum = DurableClientConverter.encode(b"data") + self.assertEqual(datum.type, "bytes") + self.assertEqual(datum.value, b"data") + + datum = DurableClientConverter.encode(None) + self.assertIsNone(datum.type) + self.assertIsNone(datum.value) + + datum = DurableClientConverter.encode({"a": 1}) + self.assertEqual(datum.type, "dict") + self.assertEqual(datum.value, {"a": 1}) + + datum = DurableClientConverter.encode([1, 2]) + self.assertEqual(datum.type, "list") + self.assertEqual(datum.value, [1, 2]) + + datum = DurableClientConverter.encode(42) + self.assertEqual(datum.type, "int") + self.assertEqual(datum.value, 42) + + datum = DurableClientConverter.encode(3.14) + self.assertEqual(datum.type, "double") + self.assertEqual(datum.value, 3.14) + + datum = DurableClientConverter.encode(True) + self.assertEqual(datum.type, "bool") + self.assertTrue(datum.value) + + with self.assertRaises(NotImplementedError): + DurableClientConverter.encode(set([1, 2])) + + def test_durable_client_converter_decode(self): + data = Datum(type="string", value="abc") + result = DurableClientConverter.decode(data, trigger_metadata=None) + self.assertEqual(result, "abc") + + data = Datum(type="bytes", value=b"123") + result = DurableClientConverter.decode(data, trigger_metadata=None) + self.assertEqual(result, b"123") + + data = Datum(type="json", value={"key": "val"}) + result = DurableClientConverter.decode(data, trigger_metadata=None) + self.assertEqual(result, {"key": "val"}) + + data = Datum(type=None, value=None) + result = DurableClientConverter.decode(data, trigger_metadata=None) + self.assertIsNone(result) + + result = DurableClientConverter.decode(None, trigger_metadata=None) + self.assertIsNone(result) + + data = Datum(type="weird", value="???") + with self.assertRaises(ValueError): + DurableClientConverter.decode(data, trigger_metadata=None) From c1d196b25631b9735cf9b9d47fccd06a319828a1 Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Fri, 31 Oct 2025 14:05:19 -0500 Subject: [PATCH 2/5] Remove comment --- azure/functions/durable_functions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/azure/functions/durable_functions.py b/azure/functions/durable_functions.py index 6af5c526..3c8e117d 100644 --- a/azure/functions/durable_functions.py +++ b/azure/functions/durable_functions.py @@ -171,8 +171,6 @@ def encode(cls, obj: typing.Any, *, @classmethod def decode(cls, data: meta.Datum, *, trigger_metadata) -> typing.Any: - # Enabling support for Dapr bindings - # https://github.com/Azure/azure-functions-python-worker/issues/1316 if data is None: return None data_type = data.type From 34c103d0ef3e4627463c0bf89beecfe209e57bdd Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Fri, 31 Oct 2025 14:22:55 -0500 Subject: [PATCH 3/5] fix test --- tests/test_durable_functions.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_durable_functions.py b/tests/test_durable_functions.py index f8b22297..eda66bd0 100644 --- a/tests/test_durable_functions.py +++ b/tests/test_durable_functions.py @@ -286,40 +286,40 @@ def test_durable_client_converter_check_output_type_annotation(self): self.assertFalse(DurableClientConverter.check_output_type_annotation(int)) def test_durable_client_converter_encode(self): - datum = DurableClientConverter.encode("hello") + datum = DurableClientConverter.encode("hello", str) self.assertEqual(datum.type, "string") self.assertEqual(datum.value, "hello") - datum = DurableClientConverter.encode(b"data") + datum = DurableClientConverter.encode(b"data", bytes) self.assertEqual(datum.type, "bytes") self.assertEqual(datum.value, b"data") - datum = DurableClientConverter.encode(None) + datum = DurableClientConverter.encode(None, type=None) self.assertIsNone(datum.type) self.assertIsNone(datum.value) - datum = DurableClientConverter.encode({"a": 1}) + datum = DurableClientConverter.encode({"a": 1}, dict) self.assertEqual(datum.type, "dict") self.assertEqual(datum.value, {"a": 1}) - datum = DurableClientConverter.encode([1, 2]) + datum = DurableClientConverter.encode([1, 2], list) self.assertEqual(datum.type, "list") self.assertEqual(datum.value, [1, 2]) - datum = DurableClientConverter.encode(42) + datum = DurableClientConverter.encode(42, int) self.assertEqual(datum.type, "int") self.assertEqual(datum.value, 42) - datum = DurableClientConverter.encode(3.14) + datum = DurableClientConverter.encode(3.14, float) self.assertEqual(datum.type, "double") self.assertEqual(datum.value, 3.14) - datum = DurableClientConverter.encode(True) + datum = DurableClientConverter.encode(True, bool) self.assertEqual(datum.type, "bool") self.assertTrue(datum.value) with self.assertRaises(NotImplementedError): - DurableClientConverter.encode(set([1, 2])) + DurableClientConverter.encode(set([1, 2]), set) def test_durable_client_converter_decode(self): data = Datum(type="string", value="abc") From 3f1fc723b8bfb0434ed11e90f6ee63db54610444 Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Mon, 3 Nov 2025 10:06:17 -0600 Subject: [PATCH 4/5] fix test --- tests/test_durable_functions.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/test_durable_functions.py b/tests/test_durable_functions.py index eda66bd0..cb2df60b 100644 --- a/tests/test_durable_functions.py +++ b/tests/test_durable_functions.py @@ -286,61 +286,61 @@ def test_durable_client_converter_check_output_type_annotation(self): self.assertFalse(DurableClientConverter.check_output_type_annotation(int)) def test_durable_client_converter_encode(self): - datum = DurableClientConverter.encode("hello", str) + datum = DurableClientConverter.encode(obj="hello", expected_type=str) self.assertEqual(datum.type, "string") self.assertEqual(datum.value, "hello") - datum = DurableClientConverter.encode(b"data", bytes) + datum = DurableClientConverter.encode(obj=b"data", expected_type=bytes) self.assertEqual(datum.type, "bytes") self.assertEqual(datum.value, b"data") - datum = DurableClientConverter.encode(None, type=None) + datum = DurableClientConverter.encode(obj=None, expected_type=None) self.assertIsNone(datum.type) self.assertIsNone(datum.value) - datum = DurableClientConverter.encode({"a": 1}, dict) + datum = DurableClientConverter.encode(obj={"a": 1}, expected_type=dict) self.assertEqual(datum.type, "dict") self.assertEqual(datum.value, {"a": 1}) - datum = DurableClientConverter.encode([1, 2], list) + datum = DurableClientConverter.encode(obj=[1, 2], expected_type=list) self.assertEqual(datum.type, "list") self.assertEqual(datum.value, [1, 2]) - datum = DurableClientConverter.encode(42, int) + datum = DurableClientConverter.encode(obj=42, expected_type=int) self.assertEqual(datum.type, "int") self.assertEqual(datum.value, 42) - datum = DurableClientConverter.encode(3.14, float) + datum = DurableClientConverter.encode(obj=3.14, expected_type=float) self.assertEqual(datum.type, "double") self.assertEqual(datum.value, 3.14) - datum = DurableClientConverter.encode(True, bool) + datum = DurableClientConverter.encode(obj=True, expected_type=bool) self.assertEqual(datum.type, "bool") self.assertTrue(datum.value) with self.assertRaises(NotImplementedError): - DurableClientConverter.encode(set([1, 2]), set) + 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, trigger_metadata=None) + result = DurableClientConverter.decode(datum=data, trigger_metadata=None) self.assertEqual(result, "abc") data = Datum(type="bytes", value=b"123") - result = DurableClientConverter.decode(data, trigger_metadata=None) + result = DurableClientConverter.decode(datum=data, trigger_metadata=None) self.assertEqual(result, b"123") data = Datum(type="json", value={"key": "val"}) - result = DurableClientConverter.decode(data, trigger_metadata=None) + result = DurableClientConverter.decode(datum=data, trigger_metadata=None) self.assertEqual(result, {"key": "val"}) data = Datum(type=None, value=None) - result = DurableClientConverter.decode(data, trigger_metadata=None) + result = DurableClientConverter.decode(datum=data, trigger_metadata=None) self.assertIsNone(result) - result = DurableClientConverter.decode(None, trigger_metadata=None) + result = DurableClientConverter.decode(datum=None, trigger_metadata=None) self.assertIsNone(result) data = Datum(type="weird", value="???") with self.assertRaises(ValueError): - DurableClientConverter.decode(data, trigger_metadata=None) + DurableClientConverter.decode(datum=data, trigger_metadata=None) From 80344bac1c2477ef61182e4efa599ff5e3b43b6f Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Mon, 3 Nov 2025 10:41:34 -0600 Subject: [PATCH 5/5] bool is a subclass of int --- azure/functions/durable_functions.py | 4 ++-- tests/test_durable_functions.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/azure/functions/durable_functions.py b/azure/functions/durable_functions.py index 3c8e117d..0ad861ca 100644 --- a/azure/functions/durable_functions.py +++ b/azure/functions/durable_functions.py @@ -160,12 +160,12 @@ def encode(cls, obj: typing.Any, *, 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) - elif isinstance(obj, bool): - return meta.Datum(type='bool', value=obj) else: raise NotImplementedError diff --git a/tests/test_durable_functions.py b/tests/test_durable_functions.py index cb2df60b..043b373d 100644 --- a/tests/test_durable_functions.py +++ b/tests/test_durable_functions.py @@ -323,24 +323,24 @@ def test_durable_client_converter_encode(self): def test_durable_client_converter_decode(self): data = Datum(type="string", value="abc") - result = DurableClientConverter.decode(datum=data, trigger_metadata=None) + result = DurableClientConverter.decode(data=data, trigger_metadata=None) self.assertEqual(result, "abc") data = Datum(type="bytes", value=b"123") - result = DurableClientConverter.decode(datum=data, trigger_metadata=None) + result = DurableClientConverter.decode(data=data, trigger_metadata=None) self.assertEqual(result, b"123") data = Datum(type="json", value={"key": "val"}) - result = DurableClientConverter.decode(datum=data, trigger_metadata=None) + result = DurableClientConverter.decode(data=data, trigger_metadata=None) self.assertEqual(result, {"key": "val"}) data = Datum(type=None, value=None) - result = DurableClientConverter.decode(datum=data, trigger_metadata=None) + result = DurableClientConverter.decode(data=data, trigger_metadata=None) self.assertIsNone(result) - result = DurableClientConverter.decode(datum=None, trigger_metadata=None) + result = DurableClientConverter.decode(data=None, trigger_metadata=None) self.assertIsNone(result) data = Datum(type="weird", value="???") with self.assertRaises(ValueError): - DurableClientConverter.decode(datum=data, trigger_metadata=None) + DurableClientConverter.decode(data=data, trigger_metadata=None)