From 673b161b397fe54bdaf2d31d8a5c0a020c92ae31 Mon Sep 17 00:00:00 2001 From: victorywys Date: Mon, 17 Jul 2023 13:44:41 +0800 Subject: [PATCH] Add the NoneType hook in type_def (#8) * add support for expanding signiture list to its base classes when **kwargs is contained in a construction method * remove fixed FIXME comment * add a switch to turn off the inherent signature function * disable the inherent function by default and fix a bug in register.get(), where an unexpected exception may be raised * add more tests for better code coverage * fix typos * add typedef hook for NoneType * enable full test in test_config_type_def * remove redundant test call --------- Co-authored-by: Yansen Wang --- tests/test_config_type_def.py | 34 ++++++++++++++++++++++++++++++++++ utilsd/config/type_def.py | 19 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/tests/test_config_type_def.py b/tests/test_config_type_def.py index cc98285..c9c8967 100644 --- a/tests/test_config_type_def.py +++ b/tests/test_config_type_def.py @@ -22,6 +22,16 @@ def test_any(): assert TypeDef.dump(typing.Any, '456') == '456' +def test_none(): + assert TypeDef.load(type(None), None) == None + assert TypeDef.dump(type(None), None) == None + + with pytest.raises(ValidationError, match='must be NoneType'): + TypeDef.load(type(None), 1) + with pytest.raises(ValidationError, match='Expected None'): + TypeDef.dump(type(None), "123") + + def test_unsupported_type(): with pytest.raises(TypeError, match=r'.*Callable\[\[\], str\].*'): TypeDef.load(typing.Callable[[], str], lambda x: x) @@ -197,6 +207,29 @@ class Foo: assert TypeDef.dump(typing.Union[pathlib.Path, None], None) == None +def test_complex_optional_union(): + @dataclass + class Foo: + bar: int = 1 + + assert TypeDef.load(typing.Optional[typing.Union[pathlib.Path, Foo]], '/bin') == pathlib.Path('/bin') + assert TypeDef.load(typing.Optional[typing.Union[pathlib.Path, Foo]], {'bar': 2}).bar == 2 + assert TypeDef.load(typing.Optional[typing.Union[pathlib.Path, Foo]], None) == None + + assert TypeDef.load(typing.Union[pathlib.Path, Foo, type(None)], None) == None + with pytest.raises(ValidationError, match='are exhausted'): + TypeDef.load(typing.Union[pathlib.Path, Foo, type(None)], [1, 2.5, '3']) + + with pytest.raises(ValidationError, match='are exhausted'): + TypeDef.dump(typing.Union[pathlib.Path, Foo, type(None)], "/bin") + + assert TypeDef.dump(typing.Optional[typing.Union[pathlib.Path, str]], '/bin') == '/bin' + assert TypeDef.dump(typing.Optional[typing.Union[pathlib.Path, Foo]], Foo(bar=2))['bar'] == 2 + assert TypeDef.dump(typing.Optional[typing.Union[pathlib.Path, Foo]], None) == None + + assert TypeDef.dump(typing.Union[pathlib.Path, Foo, type(None)], None) == None + + def test_class_config(): class module: def __init__(self, a, b, c=1): @@ -259,3 +292,4 @@ def __init__(self, a: typing.Union[submodule, ClassConfig[submodule]], {'a': {'a': 1, 'b': 2}, 'b': {'a': 3, 'b': 4, 'c': 5}}).b.c == 5 assert TypeDef.load(ClassConfig[module], {'a': {'a': 1, 'b': 2}, 'b': {'a': 3, 'b': 4, 'c': 5}}).build().b._c == 5 + diff --git a/utilsd/config/type_def.py b/utilsd/config/type_def.py index d624a6a..9c531c3 100644 --- a/utilsd/config/type_def.py +++ b/utilsd/config/type_def.py @@ -209,6 +209,24 @@ def to_plain(self, obj, ctx): return obj +class NoneTypeDef(TypeDef): + @classmethod + def new(cls, type_): + if type_ is type(None): + return cls(type_) + return None + + def from_plain(self, plain, ctx): + ctx.mark_cli_anchor_point(type(None)) + return plain + + def to_plain(self, obj, ctx): + if obj is None: + return None + else: + raise TypeError(f'Expected None, got {obj}') + + class OptionalDef(TypeDef): @classmethod def new(cls, type_): @@ -660,6 +678,7 @@ def to_plain(self, obj, ctx): # register all the modules in this file TypeDefRegistry.register_module(module=AnyDef) +TypeDefRegistry.register_module(module=NoneTypeDef) TypeDefRegistry.register_module(module=OptionalDef) TypeDefRegistry.register_module(module=PathDef) TypeDefRegistry.register_module(module=ListDef)