[click_options] Handle None as default values
None is a valid value for Click option however, typed-settings tries to convert it to a type.
By default typed-settings is using cattrs.GenConverter
see converters.py
And then tries to get_default
The problem is the else clause on L580, if the field has a type, it tries to convert the value to that type, using the converter.
try:
# Use loaded settings value
default = _get_path(settings, path)
except KeyError:
# Use field's default
default = field.default
else:
# If the default was found (no KeyError), convert the input value to
# the proper type.
# See: https://gitlab.com/sscherfke/typed-settings/-/issues/11
if field.type:
default = converter.structure(default, field.type)
The way that default converter works will convert
-
cattrs.structure(None, str)
->'None'
-
cattrs.structure(None, int)
->TypeError
I think that typed-settings should be able to honour default=None
, the question is how?
Maybe simply skip the conversion if default=None
- but it may have larger implications
I looked at typer and it seems to honour the default NoneType
(not sure this is the right place https://github.com/tiangolo/typer/blob/master/typer/core.py#L106)
Also during research of this I hit many open issues in cattrs
about None
and structuring Union
types.
So in the end I made a simple workaround by adding a convertor to the field
import typed_settings as ts
import click
def empty_str_to_none(val: str):
if val == '':
return None
return val
@ts.settings
class SettingsWithStr:
spam_str: str = ts.option(default='', converter=empty_str_to_none)
@click.command()
@ts.click_options(SettingsWithStr, "example")
def cli_str(settings: SettingsWithStr):
print(settings)
cli_str()
While this works for empty strings, empty number would be a problem because cattrs.structure(None, int)
explodes.