Repository: Chia-Network/chia-blockchain
Pull request: streamable: Fix default value assignments for `dataclass_from_dict`
This fixes default value assignments after https://github.com/Chia-Network/chia-blockchain/pull/10561. See the new test in c539809db49e1d0d781b3abda0fc70ede50ca2ef failing without a85be6d72f9b6e9ccb9d462b1bb2c7a590dc6418. But this also leads to worse performance due to `__post_init__` being called which at least gets mitigated by https://github.com/Chia-Network/chia-blockchain/pull/11730.

Problem statement:
[Bug]  Chia Farmer not showing plots all betas not showing
### What happened?

Good morning, I installed beta versions 1.3.6174.0, 1.3.6175.0, and 1.3.6179.0 on a test node. None show when selecting the PLOT tab and then the up caret, the plots

### Version

1.3.6179.0

### What platform are you using?

Windows

### What ui mode are you using?

GUI

### Relevant log output

_No response_

Hints from issue discussion:
In version  1.3.6224 still not showing plots....
Are these remote harvesters, or local? Does `chia farm summary` show the plots? Please make sure any remote harvesters are on the same version.
Good evening, This is a farmer with attached disks with plots, as I have been installing these betas and keeping track and posted the results. The plots don't show up, so I lets several betas go through until I posted the errors .  When I fall back to 1.3.5 Plots show up fine. This is a Windows 10 machine. Something from 1.3.5 on something fell out of sync with the showing on plots.
I have the same issue [1.3.6179 GUI on Windows 10]. Single farmer with all local disks. Count of plots is displayed correctly but the list of plots is absent. Screenshot below.
![image](https://user-images.githubusercontent.com/101312815/171769188-31f21773-6b0b-4e79-b030-d934d3971859.png)

Commit message: tests: Test default values with `from_json_dict`

Patch to review:
diff --git a/chia/util/streamable.py b/chia/util/streamable.py
index acb86e2be685..d65719addc25 100644
--- a/chia/util/streamable.py
+++ b/chia/util/streamable.py
@@ -132,7 +132,7 @@ def dataclass_from_dict(klass: Type[Any], item: Any) -> Any:
     """
     if type(item) == klass:
         return item
-    obj = object.__new__(klass)
+
     if klass not in CONVERT_FUNCTIONS_FOR_STREAMABLE_CLASS:
         # For non-streamable dataclasses we can't populate the cache on startup, so we do it here for convert
         # functions only.
@@ -144,9 +144,13 @@ def dataclass_from_dict(klass: Type[Any], item: Any) -> Any:
         fields = FIELDS_FOR_STREAMABLE_CLASS[klass]
         convert_funcs = CONVERT_FUNCTIONS_FOR_STREAMABLE_CLASS[klass]
 
-    for field, convert_func in zip(fields, convert_funcs):
-        object.__setattr__(obj, field.name, convert_func(item[field.name]))
-    return obj
+    return klass(
+        **{
+            field.name: convert_func(item[field.name])
+            for field, convert_func in zip(fields, convert_funcs)
+            if field.name in item
+        }
+    )
 
 
 def function_to_convert_one_item(f_type: Type[Any]) -> ConvertFunctionType:
diff --git a/tests/core/util/test_streamable.py b/tests/core/util/test_streamable.py
index 223fa26accdb..0f080c513ff4 100644
--- a/tests/core/util/test_streamable.py
+++ b/tests/core/util/test_streamable.py
@@ -1,7 +1,7 @@
 from __future__ import annotations
 
 import io
-from dataclasses import dataclass
+from dataclasses import dataclass, field
 from typing import Any, Dict, List, Optional, Tuple, Type
 
 import pytest
@@ -128,15 +128,15 @@ def test_pure_dataclasses_in_dataclass_from_dict() -> None:
     "test_class, input_dict, error",
     [
         [TestDataclassFromDict1, {"a": "asdf", "b": "2", "c": G1Element()}, ValueError],
-        [TestDataclassFromDict1, {"a": 1, "b": "2"}, KeyError],
+        [TestDataclassFromDict1, {"a": 1, "b": "2"}, TypeError],
         [TestDataclassFromDict1, {"a": 1, "b": "2", "c": "asd"}, ValueError],
         [TestDataclassFromDict1, {"a": 1, "b": "2", "c": "00" * G1Element.SIZE}, ValueError],
         [TestDataclassFromDict1, {"a": [], "b": "2", "c": G1Element()}, TypeError],
         [TestDataclassFromDict1, {"a": {}, "b": "2", "c": G1Element()}, TypeError],
         [TestDataclassFromDict2, {"a": "asdf", "b": 1.2345, "c": 1.2345}, TypeError],
         [TestDataclassFromDict2, {"a": 1.2345, "b": {"a": 1, "b": "2"}, "c": 1.2345}, TypeError],
-        [TestDataclassFromDict2, {"a": {"a": 1, "b": "2", "c": G1Element()}, "b": {"a": 1, "b": "2"}}, KeyError],
-        [TestDataclassFromDict2, {"a": {"a": 1, "b": "2"}, "b": {"a": 1, "b": "2"}, "c": 1.2345}, KeyError],
+        [TestDataclassFromDict2, {"a": {"a": 1, "b": "2", "c": G1Element()}, "b": {"a": 1, "b": "2"}}, TypeError],
+        [TestDataclassFromDict2, {"a": {"a": 1, "b": "2"}, "b": {"a": 1, "b": "2"}, "c": 1.2345}, TypeError],
     ],
 )
 def test_dataclass_from_dict_failures(test_class: Type[Any], input_dict: Dict[str, Any], error: Any) -> None:
@@ -145,6 +145,28 @@ def test_dataclass_from_dict_failures(test_class: Type[Any], input_dict: Dict[st
         dataclass_from_dict(test_class, input_dict)
 
 
+@streamable
+@dataclass(frozen=True)
+class TestFromJsonDictDefaultValues(Streamable):
+    a: uint64 = uint64(1)
+    b: str = "default"
+    c: List[uint64] = field(default_factory=list)
+
+
+@pytest.mark.parametrize(
+    "input_dict, output_dict",
+    [
+        [{}, {"a": 1, "b": "default", "c": []}],
+        [{"a": 2}, {"a": 2, "b": "default", "c": []}],
+        [{"b": "not_default"}, {"a": 1, "b": "not_default", "c": []}],
+        [{"c": [1, 2]}, {"a": 1, "b": "default", "c": [1, 2]}],
+        [{"a": 2, "b": "not_default", "c": [1, 2]}, {"a": 2, "b": "not_default", "c": [1, 2]}],
+    ],
+)
+def test_from_json_dict_default_values(input_dict: Dict[str, object], output_dict: Dict[str, object]) -> None:
+    assert TestFromJsonDictDefaultValues.from_json_dict(input_dict).to_json_dict() == output_dict
+
+
 def test_basic_list() -> None:
     a = [1, 2, 3]
     assert is_type_List(type(a))


Code review comments:
diff_hunk: @@ -145,6 +145,28 @@ def test_dataclass_from_dict_failures(test_class: Type[Any], input_dict: Dict[st
         dataclass_from_dict(test_class, input_dict)
 
 
+@streamable
+@dataclass(frozen=True)
+class TestFromJsonDictDefaultValues(Streamable):
+    a: uint64 = uint64(1)
+    b: str = "default"
+    c: List[uint64] = field(default_factory=list)
+
+
+@pytest.mark.parametrize(
+    "input_dict, output_dict",
+    [
+        [{}, {"a": 1, "b": "default", "c": []}],
+        [{"a": 2}, {"a": 2, "b": "default", "c": []}],
+        [{"b": "not_default"}, {"a": 1, "b": "not_default", "c": []}],
+        [{"c": [1, 2]}, {"a": 1, "b": "default", "c": [1, 2]}],
+        [{"a": 2, "b": "not_default", "c": [1, 2]}, {"a": 2, "b": "not_default", "c": [1, 2]}],
+    ],
+)
+def test_from_json_dict_default_values(input_dict: Dict[str, object], output_dict: Dict[str, object]) -> None:
+    assert TestFromJsonDictDefaultValues.from_json_dict(input_dict).to_json_dict() == output_dict
path: tests/core/util/test_streamable.py
text: @user1:
as kyle pointed out to me earlier, it's risky to compare dictionaries directly like this. The problematic case then was that `int` can be compared to `bool`. I would think it would be safer to convert both dictionaries to strings  and compare those.