Repository: tefra/xsdata
Pull request #789 (2023-05-15 17:09:26)

Pycodeserializer should skip default field values
## 📒 Description

The pycode serializer should skip default values from the final output


Resolves #xxxx

## 🔗 What I've Done

> Write a description of the steps taken to resolve the issue


## 💬 Comments

> A place to write any comments to the reviewer.
>

## 🛫 Checklist

- [ ] Updated docs
- [ ] Added unit-tests
- [ ] [Sample tests](https://github.com/tefra/xsdata-samples) pass
- [ ] [W3C tests](https://github.com/tefra/xsdata-w3c-tests) pass


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=789)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=789&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=789&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=789&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=789&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=789&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=789&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=789&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=789&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=789&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=789&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=789&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=789&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=789) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=789&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=789&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/xsdata/formats/dataclass/compat.py b/xsdata/formats/dataclass/compat.py
index 587b3fb7..c0db5bf9 100644
--- a/xsdata/formats/dataclass/compat.py
+++ b/xsdata/formats/dataclass/compat.py
@@ -5,6 +5,7 @@ from dataclasses import is_dataclass
 from dataclasses import MISSING
 from typing import Any
 from typing import Dict
+from typing import Optional
 from typing import Set
 from typing import Tuple
 from typing import Type
@@ -55,7 +56,7 @@ class ClassType(abc.ABC):
         """Return the models fields in the correct mro ordering."""
 
     @abc.abstractmethod
-    def default_value(self, field: Any) -> Any:
+    def default_value(self, field: Any, default: Optional[Any] = None) -> Any:
         """Return the default value or factory of the given model field."""
 
     @abc.abstractmethod
@@ -124,15 +125,14 @@ class Dataclasses(ClassType):
     def get_fields(self, obj: Any) -> Tuple[Any, ...]:
         return fields(obj)
 
-    def default_value(self, field: Field) -> Any:
-        # Ignore type because of https://github.com/python/mypy/issues/6910
-        if field.default_factory is not MISSING:  # type: ignore
-            return field.default_factory  # type: ignore
+    def default_value(self, field: Field, default: Optional[Any] = None) -> Any:
+        if field.default_factory is not MISSING:
+            return field.default_factory
 
         if field.default is not MISSING:
             return field.default
 
-        return None
+        return default
 
     def default_choice_value(self, choice: Dict) -> Any:
         factory = choice.get("default_factory")
diff --git a/xsdata/formats/dataclass/serializers/code.py b/xsdata/formats/dataclass/serializers/code.py
index 4d04c3e2..f5e21988 100644
--- a/xsdata/formats/dataclass/serializers/code.py
+++ b/xsdata/formats/dataclass/serializers/code.py
@@ -18,6 +18,9 @@ from xsdata.utils.objects import literal_value
 spaces = "    "
 
 
+unset = object()
+
+
 @dataclass
 class PycodeSerializer(AbstractSerializer):
     """
@@ -123,16 +126,25 @@ class PycodeSerializer(AbstractSerializer):
         yield f"{obj.__class__.__qualname__}(\n"
 
         next_level = level + 1
-        for index, f in enumerate(self.context.class_type.get_fields(obj)):
+        index = 0
+        for f in self.context.class_type.get_fields(obj):
             if not f.init:
                 continue
 
+            value = getattr(obj, f.name, types)
+            default = self.context.class_type.default_value(f, default=unset)
+            if default is not unset and (
+                (callable(default) and default() == value) or default == value
+            ):
+                continue
+
             if index:
                 yield f",\n{spaces * next_level}{f.name}="
             else:
                 yield f"{spaces * next_level}{f.name}="
 
-            value = getattr(obj, f.name, types)
             yield from self.write_object(value, next_level, types)
 
+            index += 1
+
         yield f"\n{spaces * level})"


Test patch:
diff --git a/tests/formats/dataclass/serializers/test_code.py b/tests/formats/dataclass/serializers/test_code.py
index cf339f12..0485a836 100644
--- a/tests/formats/dataclass/serializers/test_code.py
+++ b/tests/formats/dataclass/serializers/test_code.py
@@ -1,5 +1,7 @@
 from unittest import TestCase
 
+from tests.fixtures.books import BookForm
+from tests.fixtures.books import Books
 from tests.fixtures.books.fixtures import books
 from xsdata.formats.dataclass.serializers import PycodeSerializer
 from xsdata.models.enums import Namespace
@@ -44,6 +46,24 @@ class PycodeSerializerTests(TestCase):
 
         self.assertEqual(expected, result)
 
+    def test_write_class_with_default_values(self):
+        books = Books(book=[BookForm(author="me")])
+        result = self.serializer.render(books, var_name="books")
+        expected = (
+            "from tests.fixtures.books.books import BookForm\n"
+            "from tests.fixtures.books.books import Books\n"
+            "\n"
+            "\n"
+            "books = Books(\n"
+            "    book=[\n"
+            "        BookForm(\n"
+            '            author="me"\n'
+            "        ),\n"
+            "    ]\n"
+            ")\n"
+        )
+        self.assertEqual(expected, result)
+
     def test_write_object_with_empty_array(self):
         iterator = self.serializer.write_object([], 0, set())
         self.assertEqual("[]", "".join(iterator))