Skip to content

Commit

Permalink
Merge 64aa706 into 92d539c
Browse files Browse the repository at this point in the history
  • Loading branch information
Fatal1ty committed Sep 7, 2023
2 parents 92d539c + 64aa706 commit cc48e1b
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 11 deletions.
1 change: 1 addition & 0 deletions mashumaro/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class BaseConfig:
allow_postponed_evaluation: bool = True
dialect: Optional[Type[Dialect]] = None
omit_none: Union[bool, Literal[Sentinel.MISSING]] = Sentinel.MISSING
omit_default: Union[bool, Literal[Sentinel.MISSING]] = Sentinel.MISSING
orjson_options: Optional[int] = 0
json_schema: Dict[str, Any] = {}
discriminator: Optional[Discriminator] = None
Expand Down
101 changes: 90 additions & 11 deletions mashumaro/core/meta/code/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import sys
import types
import typing
import uuid
from contextlib import contextmanager

# noinspection PyProtectedMember
Expand Down Expand Up @@ -43,13 +44,14 @@
is_hashable,
is_init_var,
is_literal,
is_named_tuple,
is_optional,
is_type_var_any,
resolve_type_params,
substitute_type_params,
type_name,
)
from mashumaro.core.meta.types.common import FieldContext, ValueSpec
from mashumaro.core.meta.types.common import FieldContext, NoneType, ValueSpec
from mashumaro.core.meta.types.pack import PackerRegistry
from mashumaro.core.meta.types.unpack import (
SubtypeUnpackerBuilder,
Expand Down Expand Up @@ -215,14 +217,19 @@ def metadatas(self) -> typing.Dict[str, typing.Mapping[str, typing.Any]]:
# https://github.com/python/mypy/issues/1362
}

def get_field_default(self, name: str) -> typing.Any:
def get_field_default(
self, name: str, call_factory: bool = False
) -> typing.Any:
field = self.dataclass_fields.get(name) # type: ignore
# https://github.com/python/mypy/issues/1362
if field:
if field.default is not MISSING:
return field.default
else:
return field.default_factory
if call_factory and field.default_factory is not MISSING:
return field.default_factory()
else:
return field.default_factory
else:
return self.namespace.get(name, MISSING)

Expand Down Expand Up @@ -856,6 +863,10 @@ def _add_pack_method_lines(self, method_name: str) -> None:
)
serialize_by_alias = self.get_config().serialize_by_alias
omit_none = self._get_dialect_or_config_option("omit_none", False)
omit_default = self._get_dialect_or_config_option(
"omit_default", False
)
force_value = omit_default
packers = {}
aliases = {}
nullable_fields = set()
Expand All @@ -864,7 +875,7 @@ def _add_pack_method_lines(self, method_name: str) -> None:
if self.metadatas.get(fname, {}).get("serialize") == "omit":
continue
packer, alias, could_be_none = self._get_field_packer(
fname, ftype, config
fname, ftype, config, force_value
)
packers[fname] = packer
if alias:
Expand All @@ -879,41 +890,73 @@ def _add_pack_method_lines(self, method_name: str) -> None:
and (omit_none or omit_none_feature)
or by_alias_feature
and aliases
or omit_default
):
kwargs = "kwargs"
self.add_line("kwargs = {}")
for fname, packer in packers.items():
if force_value:
self.add_line(f"value = self.{fname}")
alias = aliases.get(fname)
default = self.get_field_default(fname, call_factory=True)
if fname in nullable_fields:
if (
packer == "value"
and not omit_none
and not omit_none_feature
and not (omit_default and default is None)
):
self._pack_method_set_value(
fname, alias, by_alias_feature, f"self.{fname}"
fname=fname,
alias=alias,
by_alias_feature=by_alias_feature,
packed_value=(
"value" if force_value else f"self.{fname}"
),
omit_default=omit_default,
)
continue
self.add_line(f"value = self.{fname}")
if not force_value: # to add it only once
self.add_line(f"value = self.{fname}")
with self.indent("if value is not None:"):
self._pack_method_set_value(
fname, alias, by_alias_feature, packer
fname=fname,
alias=alias,
by_alias_feature=by_alias_feature,
packed_value=packer,
omit_default=(
omit_default and default is not None
),
)
if omit_none and not omit_none_feature:
continue
elif omit_default and default is None:
continue
with self.indent("else:"):
if omit_none_feature:
with self.indent("if not omit_none:"):
self._pack_method_set_value(
fname, alias, by_alias_feature, "None"
fname=fname,
alias=alias,
by_alias_feature=by_alias_feature,
packed_value="None",
omit_default=False,
)
else:
self._pack_method_set_value(
fname, alias, by_alias_feature, "None"
fname=fname,
alias=alias,
by_alias_feature=by_alias_feature,
packed_value="None",
omit_default=False,
)
else:
self._pack_method_set_value(
fname, alias, by_alias_feature, packer
fname=fname,
alias=alias,
by_alias_feature=by_alias_feature,
packed_value=packer,
omit_default=omit_default,
)
else:
kwargs_parts = []
Expand Down Expand Up @@ -962,6 +1005,29 @@ def _pack_method_set_value(
alias: typing.Optional[str],
by_alias_feature: bool,
packed_value: str,
omit_default: bool,
) -> None:
if omit_default:
default = self.get_field_default(fname, call_factory=True)
if default is not MISSING:
default_literal = self._get_field_default_literal(
self.get_field_default(fname, call_factory=True)
)
comp_op = "is not" if default_literal == "None" else "!="
with self.indent(f"if value {comp_op} {default_literal}:"):
return self.__pack_method_set_value(
fname, alias, by_alias_feature, packed_value
)
return self.__pack_method_set_value(
fname, alias, by_alias_feature, packed_value
)

def __pack_method_set_value(
self,
fname: str,
alias: typing.Optional[str],
by_alias_feature: bool,
packed_value: str,
) -> None:
if by_alias_feature and alias is not None:
with self.indent("if by_alias:"):
Expand Down Expand Up @@ -1067,6 +1133,7 @@ def _get_field_packer(
fname: str,
ftype: typing.Type,
config: typing.Type[BaseConfig],
force_value: bool = False,
) -> typing.Tuple[str, typing.Optional[str], bool]:
metadata = self.metadatas.get(fname, {})
alias = metadata.get("alias")
Expand All @@ -1078,7 +1145,7 @@ def _get_field_packer(
or is_optional(ftype, self.get_field_resolved_type_params(fname))
or self.get_field_default(fname) is None
)
value = "value" if could_be_none else f"self.{fname}"
value = "value" if could_be_none or force_value else f"self.{fname}"
packer = PackerRegistry.get(
ValueSpec(
type=ftype,
Expand Down Expand Up @@ -1135,3 +1202,15 @@ def _get_dialect_or_config_option(
if value is not Sentinel.MISSING:
return value
return default

def _get_field_default_literal(self, value: typing.Any) -> str:
if isinstance(
value, (str, int, float, bool, NoneType) # type: ignore
):
return repr(value)
elif isinstance(value, tuple) and not is_named_tuple(type(value)):
return repr(value)
else:
name = f"v_{uuid.uuid4().hex}"
self.ensure_object_imported(value, name)
return name
1 change: 1 addition & 0 deletions mashumaro/dialect.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
class Dialect:
serialization_strategy: Dict[Any, SerializationStrategyValueType] = {}
omit_none: Union[bool, Literal[Sentinel.MISSING]] = Sentinel.MISSING
omit_default: Union[bool, Literal[Sentinel.MISSING]] = Sentinel.MISSING

0 comments on commit cc48e1b

Please sign in to comment.