From a7fbf268e0fbaa07f7130983e18fbabb24057cd1 Mon Sep 17 00:00:00 2001 From: Tom Close Date: Tue, 26 Mar 2024 11:39:59 +1100 Subject: [PATCH] added support for None to be implicitly interpreted as NoneType in type-checking/coercing --- pydra/utils/tests/test_typing.py | 72 ++++++++++++++++++++++++++++++++ pydra/utils/typing.py | 7 +++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/pydra/utils/tests/test_typing.py b/pydra/utils/tests/test_typing.py index 665d79327..1c239554d 100644 --- a/pydra/utils/tests/test_typing.py +++ b/pydra/utils/tests/test_typing.py @@ -737,6 +737,46 @@ def test_generic_is_subclass3(): assert not TypeParser.is_subclass(ty.List[float], ty.List[int]) +def test_none_is_subclass1(): + assert TypeParser.is_subclass(None, ty.Union[int, None]) + + +def test_none_is_subclass2(): + assert not TypeParser.is_subclass(None, ty.Union[int, float]) + + +def test_none_is_subclass3(): + assert TypeParser.is_subclass(ty.Tuple[int, None], ty.Tuple[int, None]) + + +def test_none_is_subclass4(): + assert TypeParser.is_subclass(None, None) + + +def test_none_is_subclass5(): + assert not TypeParser.is_subclass(None, int) + + +def test_none_is_subclass6(): + assert not TypeParser.is_subclass(int, None) + + +def test_none_is_subclass7(): + assert TypeParser.is_subclass(None, type(None)) + + +def test_none_is_subclass8(): + assert TypeParser.is_subclass(type(None), None) + + +def test_none_is_subclass9(): + assert TypeParser.is_subclass(type(None), type(None)) + + +def test_none_is_subclass10(): + assert TypeParser.is_subclass(type(None), type(None)) + + @pytest.mark.skipif( sys.version_info < (3, 9), reason="Cannot subscript tuple in < Py3.9" ) @@ -780,3 +820,35 @@ def test_type_is_instance3(): def test_type_is_instance4(): assert TypeParser.is_instance(Json, type) + + +def test_type_is_instance5(): + assert TypeParser.is_instance(None, None) + + +def test_type_is_instance6(): + assert TypeParser.is_instance(None, type(None)) + + +def test_type_is_instance7(): + assert not TypeParser.is_instance(None, int) + + +def test_type_is_instance8(): + assert not TypeParser.is_instance(1, None) + + +def test_type_is_instance9(): + assert TypeParser.is_instance(None, ty.Union[int, None]) + + +def test_type_is_instance10(): + assert TypeParser.is_instance(1, ty.Union[int, None]) + + +def test_type_is_instance11(): + assert not TypeParser.is_instance(None, ty.Union[int, str]) + + +def test_type_is_instance12(): + assert not TypeParser.is_instance((1, None), ty.Tuple[int, None]) diff --git a/pydra/utils/typing.py b/pydra/utils/typing.py index ee8e733e4..a3e441be1 100644 --- a/pydra/utils/typing.py +++ b/pydra/utils/typing.py @@ -603,7 +603,7 @@ def matches_type( def is_instance( cls, obj: object, - candidates: ty.Union[ty.Type[ty.Any], ty.Sequence[ty.Type[ty.Any]]], + candidates: ty.Union[ty.Type[ty.Any], ty.Sequence[ty.Type[ty.Any]], None], ) -> bool: """Checks whether the object is an instance of cls or that cls is typing.Any, extending the built-in isinstance to check nested type args @@ -615,6 +615,8 @@ def is_instance( candidates : type or ty.Iterable[type] the candidate types to check the object against """ + if candidates is None: + candidates = [type(None)] if not isinstance(candidates, ty.Sequence): candidates = [candidates] for candidate in candidates: @@ -656,6 +658,9 @@ def is_subclass( any_ok : bool whether klass=typing.Any should return True or False """ + if klass is None: + # Implicitly convert None to NoneType, like in other typing + klass = type(None) if not isinstance(candidates, ty.Sequence): candidates = [candidates] if ty.Any in candidates: