Skip to content

Commit

Permalink
Merge pull request #248 from reagento/fix/original-field-order-on-dump
Browse files Browse the repository at this point in the history
saving original field order on dumping (#247)
  • Loading branch information
zhPavel authored Mar 4, 2024
2 parents a6dd93f + e7d7987 commit b45f1d5
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 10 deletions.
1 change: 1 addition & 0 deletions docs/changelog/fragments/247.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Model dumping now trying to save the original order of fields inside the dict
24 changes: 18 additions & 6 deletions src/adaptix/_internal/morphing/name_layout/crown_builder.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import math
from abc import ABC, abstractmethod
from dataclasses import dataclass
from itertools import groupby
Expand Down Expand Up @@ -35,13 +36,17 @@ class PathWithLeaf(Generic[LeafCr]):


class BaseCrownBuilder(ABC, Generic[LeafCr, DictCr, ListCr]):
def __init__(self, paths_to_leaves: PathsTo[LeafCr]):
self._paths_to_leaves = paths_to_leaves
self._paths_to_order = {path: i for i, path in enumerate(paths_to_leaves)}

def build_empty_crown(self, *, as_list: bool) -> Union[DictCr, ListCr]:
if as_list:
return self._make_list_crown(current_path=(), paths_with_leaves=[])
return self._make_dict_crown(current_path=(), paths_with_leaves=[])

def build_crown(self, paths_to_leaves: PathsTo[LeafCr]) -> Union[DictCr, ListCr]:
paths_with_leaves = [PathWithLeaf(path, leaf) for path, leaf in paths_to_leaves.items()]
def build_crown(self) -> Union[DictCr, ListCr]:
paths_with_leaves = [PathWithLeaf(path, leaf) for path, leaf in self._paths_to_leaves.items()]
paths_with_leaves.sort(key=lambda x: x.path)
return cast(Union[DictCr, ListCr], self._build_crown(paths_with_leaves, 0))

Expand All @@ -67,10 +72,15 @@ def _get_dict_crown_map(
current_path: KeyPath,
paths_with_leaves: PathedLeaves[LeafCr],
) -> Mapping[str, Union[LeafCr, DictCr, ListCr]]:
return {
cast(str, key): self._build_crown(list(path_group), len(current_path) + 1)
dict_crown_map = {
key: self._build_crown(list(path_group), len(current_path) + 1)
for key, path_group in groupby(paths_with_leaves, lambda x: x.path[len(current_path)])
}
sorted_keys = sorted(
dict_crown_map,
key=lambda key: self._paths_to_order.get((*current_path, key), math.inf),
)
return {key: dict_crown_map[key] for key in sorted_keys}

@abstractmethod
def _make_dict_crown(self, current_path: KeyPath, paths_with_leaves: PathedLeaves[LeafCr]) -> DictCr:
Expand Down Expand Up @@ -98,8 +108,9 @@ def _make_list_crown(self, current_path: KeyPath, paths_with_leaves: PathedLeave


class InpCrownBuilder(BaseCrownBuilder[LeafInpCrown, InpDictCrown, InpListCrown]):
def __init__(self, extra_policies: PathsTo[DictExtraPolicy]):
def __init__(self, extra_policies: PathsTo[DictExtraPolicy], paths_to_leaves: PathsTo[LeafInpCrown]):
self.extra_policies = extra_policies
super().__init__(paths_to_leaves)

def _make_dict_crown(self, current_path: KeyPath, paths_with_leaves: PathedLeaves[LeafInpCrown]) -> InpDictCrown:
return InpDictCrown(
Expand All @@ -115,8 +126,9 @@ def _make_list_crown(self, current_path: KeyPath, paths_with_leaves: PathedLeave


class OutCrownBuilder(BaseCrownBuilder[LeafOutCrown, OutDictCrown, OutListCrown]):
def __init__(self, path_to_sieves: PathsTo[Sieve]):
def __init__(self, path_to_sieves: PathsTo[Sieve], paths_to_leaves: PathsTo[LeafOutCrown]):
self.path_to_sieves = path_to_sieves
super().__init__(paths_to_leaves)

def _make_dict_crown(self, current_path: KeyPath, paths_with_leaves: PathedLeaves[LeafOutCrown]) -> OutDictCrown:
key_to_sieve: Dict[str, Sieve] = {}
Expand Down
8 changes: 4 additions & 4 deletions src/adaptix/_internal/morphing/name_layout/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def _create_input_crown(
paths_to_leaves: PathsTo[LeafInpCrown],
extra_policies: PathsTo[DictExtraPolicy],
) -> BranchInpCrown:
return InpCrownBuilder(extra_policies).build_crown(paths_to_leaves)
return InpCrownBuilder(extra_policies, paths_to_leaves).build_crown()

def _create_empty_input_crown(
self,
Expand All @@ -72,7 +72,7 @@ def _create_empty_input_crown(
*,
as_list: bool,
) -> BranchInpCrown:
return InpCrownBuilder(extra_policies).build_empty_crown(as_list=as_list)
return InpCrownBuilder(extra_policies, {}).build_empty_crown(as_list=as_list)

@static_provision_action
def _provide_output_name_layout(self, mediator: Mediator, request: OutputNameLayoutRequest) -> OutputNameLayout:
Expand Down Expand Up @@ -102,7 +102,7 @@ def _create_output_crown(
paths_to_leaves: PathsTo[LeafOutCrown],
path_to_sieve: PathsTo[Sieve],
) -> BranchOutCrown:
return OutCrownBuilder(path_to_sieve).build_crown(paths_to_leaves)
return OutCrownBuilder(path_to_sieve, paths_to_leaves).build_crown()

def _create_empty_output_crown(
self,
Expand All @@ -112,4 +112,4 @@ def _create_empty_output_crown(
*,
as_list: bool,
):
return OutCrownBuilder(path_to_sieve).build_empty_crown(as_list=as_list)
return OutCrownBuilder(path_to_sieve, {}).build_empty_crown(as_list=as_list)
35 changes: 35 additions & 0 deletions tests/integration/morphing/test_dump_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from dataclasses import dataclass

from adaptix import Retort, name_mapping


def test_simple(accum):
@dataclass
class Example:
c: int
a: int
b: int

retort = Retort(recipe=[accum])

dumper = retort.get_dumper(Example)
assert list(dumper(Example(c=1, a=2, b=3)).items()) == [("c", 1), ("a", 2), ("b", 3)]


def test_name_flatenning(accum):
@dataclass
class Example:
c: int
a: int
e: int
b: int

retort = Retort(
recipe=[
accum,
name_mapping(Example, map={"e": ("q", "e")}),
],
)

dumper = retort.get_dumper(Example)
assert list(dumper(Example(c=1, a=2, e=3, b=4)).items()) == [("c", 1), ("a", 2), ("b", 4), ("q", {"e": 3})]

0 comments on commit b45f1d5

Please sign in to comment.