Fatal1ty/mashumaro

Omit `None` values in TOML instead of throwing TypeError

jonaslb opened this issue · 4 comments

Is your feature request related to a problem? Please describe.

Example:

@dataclass
class MyExampleConfig(DataClassTOMLMixin):
    username: str | None = None

MyExampleConfig().to_toml()

Gives the following exception:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-9-68a219996b72> in <module>
----> 1 MyExampleConfig().to_toml()

~/.local/lib/python3.11/site-packages/mashumaro/core/meta/builder.py in to_toml(self, encoder)

~/.local/lib/python3.11/site-packages/tomli_w/_writer.py in dumps(__obj, multiline_strings)
     37 def dumps(__obj: dict[str, Any], *, multiline_strings: bool = False) -> str:
     38     ctx = Context(multiline_strings, {})
---> 39     return "".join(gen_table_chunks(__obj, ctx, name=""))
     40
     41

~/.local/lib/python3.11/site-packages/tomli_w/_writer.py in gen_table_chunks(table, ctx, name, inside_aot)
     71         yielded = True
     72         for k, v in literals:
---> 73             yield f"{format_key_part(k)} = {format_literal(v, ctx)}\n"
     74
     75     for k, v, in_aot in tables:

~/.local/lib/python3.11/site-packages/tomli_w/_writer.py in format_literal(obj, ctx, nest_level)
    100     if isinstance(obj, dict):
    101         return format_inline_table(obj, ctx)
--> 102     raise TypeError(f"Object of type {type(obj)} is not TOML serializable")
    103
    104

TypeError: Object of type <class 'NoneType'> is not TOML serializable

Describe the solution you'd like
It seems that TOML plainly does not represent None values, preferring instead to omit such keys (see toml-lang/toml#30 ). That would be perfectly fine since None in Python is used to indicate missing value. Thus I think the default behavior for TOML should be to just not serialize the None value instead of giving this TypeError.

Describe alternatives you've considered
Currently it seems one needs to use another format e.g. yaml or json if the dataclass has any possibly None values.

I'm stupid, there's an omit_none option, ha.

Good spot!

At this moment you can use omit_none code generation option, as you mentioned, but you will need to set omit_none=True each time you call to_toml with it.

I think it’s time to add a new omit_none option to the Config and Dialect classes to change the default behavior. It could be set to True in TOMLDialect that is used to generate to_toml method. With this new feature None values could be skipped out of the box for TOML.

Let me reopen this issue to keep track of it for the next release.

I have made some changes, which I wrote about above. Now using DataClassTOMLMixin will be more convenient:

@dataclass
class InnerDataClassWithOptionalField(DataClassTOMLMixin):
    x: Optional[int] = None


@dataclass
class DataClass(DataClassTOMLMixin):
    x: Optional[InnerDataClassWithOptionalField] = None
    y: Optional[int] = None


obj = DataClass()
assert obj.to_dict() == {"x": None, "y": None}
assert obj.to_toml() == ""

obj = DataClass(InnerDataClassWithOptionalField())
assert obj.to_dict() == {"x": {"x": None}, "y": None}
assert obj.to_toml() == "[x]\n"
assert DataClass.from_toml("[x]\n") == obj

Starting from 3.2 None values are skipped for TOML.