diff --git a/fgpyo/util/inspect.py b/fgpyo/util/inspect.py index 944ece77..b35f307e 100644 --- a/fgpyo/util/inspect.py +++ b/fgpyo/util/inspect.py @@ -70,6 +70,9 @@ def split_at_given_level( return out_vals +NoneType = type(None) + + def _get_parser( cls: Type, type_: TypeAlias, parsers: Optional[Dict[type, Callable[[str], Any]]] = None ) -> partial: @@ -226,7 +229,7 @@ def dict_parse(dict_string: str) -> Dict[Any, Any]: return types.make_enum_parser(type_) elif types.is_constructible_from_str(type_): return functools.partial(type_) - elif isinstance(type_, type(type(None))): + elif type_ == NoneType: return functools.partial(types.none_parser) elif types.get_origin_type(type_) is Union: return types.make_union_parser( diff --git a/fgpyo/util/tests/test_inspect.py b/fgpyo/util/tests/test_inspect.py index 2801ed5b..91f463ce 100644 --- a/fgpyo/util/tests/test_inspect.py +++ b/fgpyo/util/tests/test_inspect.py @@ -1,6 +1,7 @@ from typing import Optional import attr +import pytest from fgpyo.util.inspect import attr_from from fgpyo.util.inspect import attribute_has_default @@ -45,3 +46,23 @@ def test_attribute_has_default() -> None: assert attribute_has_default(fields_dict["optional_no_default"]) assert attribute_has_default(fields_dict["optional_with_default_none"]) assert attribute_has_default(fields_dict["optional_with_default_some"]) + + +class Foo: + pass + + +@attr.s(auto_attribs=True, frozen=True) +class Bar: + foo: Foo + + +# Test for regression #94 - the call to attr_from succeeds when the check for None type +# in inspect._get_parser is done incorrectly. +def test_attr_from_custom_type_without_parser_fails() -> None: + with pytest.raises(AssertionError): + attr_from( + cls=Bar, + kwargs={"foo": ""}, + parsers={}, + )