Repository: tefra/xsdata
Pull request #780 (2023-04-21 09:24:29)

Make pretty print indentation configurable
## 📒 Description

Hello, I'd like to be able to customize the indentation when pretty print is enabled. So this patch is adding a setting `SerializerConfig.pretty_print_indent`, allowing to set indentation string per level when `pretty_print` is `True`.

Simple example:
```
from xsdata.formats.dataclass.serializers.config import SerializerConfig
from xsdata.formats.dataclass.serializers import XmlSerializer
from xsdata.formats.dataclass.serializers.writers.lxml import LxmlEventWriter
from xsdata.formats.dataclass.serializers.writers.native import XmlEventWriter
import dataclasses

@dataclasses.dataclass
class Bar:
    nested_a: int
    nested_b: int

@dataclasses.dataclass
class Foo:
    foo: int
    bar: Bar

f = Foo(1, Bar(2, 3))

print(XmlSerializer(
    config=SerializerConfig(pretty_print=True, pretty_print_indent="            "),
    writer=LxmlEventWriter
).render(f))

print(XmlSerializer(
    config=SerializerConfig(pretty_print=True, pretty_print_indent=" "),
    writer=XmlEventWriter
).render(f))
```

Which results in:
```
<?xml version="1.0" encoding="UTF-8"?>
<Foo>
            <foo>1</foo>
            <bar>
                        <nested_a>2</nested_a>
                        <nested_b>3</nested_b>
            </bar>
</Foo>

<?xml version="1.0" encoding="UTF-8"?>
<Foo>
 <foo>1</foo>
 <bar>
  <nested_a>2</nested_a>
  <nested_b>3</nested_b>
 </bar>
</Foo>
```
## 🛫 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=780)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=780&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=780&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=780&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=780&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=780&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=780&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=780&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=780&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=780&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=780&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=780&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=780&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=780) 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=780&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=780&metric=new_duplicated_lines_density&view=list)


tefra: Hi @janhavlin can you also take care of the `JsonSerializer`?

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=780)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=780&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=780&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=780&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=780&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=780&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=780&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=780&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=780&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=780&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=780&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=780&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=780&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=780) 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=780&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=780&metric=new_duplicated_lines_density&view=list)


janhavlin: Sure! Pushed an additional change.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/tefra/xsdata/pull/780?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) Report
Patch coverage: **`100.00`**% and project coverage change: **`-0.13`** :warning:
> Comparison is base [(`4dc9b41`)](https://app.codecov.io/gh/tefra/xsdata/commit/4dc9b41f6e85616927f74bddead80ebee33a995b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) 99.98% compared to head [(`9fdde1c`)](https://app.codecov.io/gh/tefra/xsdata/pull/780?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) 99.86%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #780      +/-   ##
==========================================
- Coverage   99.98%   99.86%   -0.13%     
==========================================
  Files         101      101              
  Lines        8911     8916       +5     
  Branches     1979     1980       +1     
==========================================
- Hits         8910     8904       -6     
- Misses          0        9       +9     
- Partials        1        3       +2     
```


| [Impacted Files](https://app.codecov.io/gh/tefra/xsdata/pull/780?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) | Coverage Δ | |
|---|---|---|
| [xsdata/formats/dataclass/serializers/config.py](https://app.codecov.io/gh/tefra/xsdata/pull/780?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL3NlcmlhbGl6ZXJzL2NvbmZpZy5weQ==) | `100.00% <100.00%> (ø)` | |
| [xsdata/formats/dataclass/serializers/json.py](https://app.codecov.io/gh/tefra/xsdata/pull/780?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL3NlcmlhbGl6ZXJzL2pzb24ucHk=) | `100.00% <100.00%> (ø)` | |
| [...data/formats/dataclass/serializers/writers/lxml.py](https://app.codecov.io/gh/tefra/xsdata/pull/780?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL3NlcmlhbGl6ZXJzL3dyaXRlcnMvbHhtbC5weQ==) | `100.00% <100.00%> (ø)` | |
| [...ta/formats/dataclass/serializers/writers/native.py](https://app.codecov.io/gh/tefra/xsdata/pull/780?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL3NlcmlhbGl6ZXJzL3dyaXRlcnMvbmF0aXZlLnB5) | `100.00% <100.00%> (ø)` | |

... and [3 files with indirect coverage changes](https://app.codecov.io/gh/tefra/xsdata/pull/780/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/tefra/xsdata/pull/780?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).

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=780)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=780&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=780&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=780&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=780&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=780&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=780&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=780&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=780&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=780&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=780&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=780&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=780&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=780) 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=780&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=780&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/xsdata/formats/dataclass/serializers/config.py b/xsdata/formats/dataclass/serializers/config.py
index 8d910b35..2d6241ea 100644
--- a/xsdata/formats/dataclass/serializers/config.py
+++ b/xsdata/formats/dataclass/serializers/config.py
@@ -13,6 +13,7 @@ class SerializerConfig:
     :param xml_version: XML Version number (1.0|1.1)
     :param xml_declaration: Generate XML declaration
     :param pretty_print: Enable pretty output
+    :param pretty_print_indent: Indentation string for each indent level
     :param ignore_default_attributes: Ignore optional attributes with
         default values
     :param schema_location: xsi:schemaLocation attribute value
@@ -27,6 +28,7 @@ class SerializerConfig:
         "xml_version",
         "xml_declaration",
         "pretty_print",
+        "pretty_print_indent",
         "ignore_default_attributes",
         "schema_location",
         "no_namespace_schema_location",
@@ -39,6 +41,7 @@ class SerializerConfig:
         xml_version: str = "1.0",
         xml_declaration: bool = True,
         pretty_print: bool = False,
+        pretty_print_indent: Optional[str] = None,
         ignore_default_attributes: bool = False,
         schema_location: Optional[str] = None,
         no_namespace_schema_location: Optional[str] = None,
@@ -48,6 +51,7 @@ class SerializerConfig:
         self.xml_version = xml_version
         self.xml_declaration = xml_declaration
         self.pretty_print = pretty_print
+        self.pretty_print_indent = pretty_print_indent
         self.ignore_default_attributes = ignore_default_attributes
         self.schema_location = schema_location
         self.no_namespace_schema_location = no_namespace_schema_location
diff --git a/xsdata/formats/dataclass/serializers/json.py b/xsdata/formats/dataclass/serializers/json.py
index 87563018..0b5c1e7a 100644
--- a/xsdata/formats/dataclass/serializers/json.py
+++ b/xsdata/formats/dataclass/serializers/json.py
@@ -11,6 +11,7 @@ from typing import Iterator
 from typing import Optional
 from typing import TextIO
 from typing import Tuple
+from typing import Union
 
 from xsdata.formats.bindings import AbstractSerializer
 from xsdata.formats.converter import converter
@@ -61,7 +62,7 @@ class JsonSerializer(AbstractSerializer):
         :param out: The output stream
         :param obj: The input dataclass instance
         """
-        indent: Optional[int] = None
+        indent: Optional[Union[int, str]] = None
         if self.indent:
             warnings.warn(
                 "JsonSerializer indent property is deprecated, use SerializerConfig",
@@ -69,7 +70,7 @@ class JsonSerializer(AbstractSerializer):
             )
             indent = self.indent
         elif self.config.pretty_print:
-            indent = 2
+            indent = self.config.pretty_print_indent or 2
 
         self.dump_factory(self.convert(obj), out, indent=indent)
 
diff --git a/xsdata/formats/dataclass/serializers/writers/lxml.py b/xsdata/formats/dataclass/serializers/writers/lxml.py
index 6f3ac0b9..4d5730ec 100644
--- a/xsdata/formats/dataclass/serializers/writers/lxml.py
+++ b/xsdata/formats/dataclass/serializers/writers/lxml.py
@@ -2,6 +2,7 @@ from typing import Dict
 from typing import Generator
 from typing import TextIO
 
+from lxml.etree import indent
 from lxml.etree import tostring
 from lxml.sax import ElementTreeContentHandler
 
@@ -37,6 +38,9 @@ class LxmlEventWriter(XmlWriter):
 
         assert isinstance(self.handler, ElementTreeContentHandler)
 
+        if self.config.pretty_print and self.config.pretty_print_indent is not None:
+            indent(self.handler.etree, self.config.pretty_print_indent)
+
         xml = tostring(
             self.handler.etree,
             encoding=self.config.encoding,
diff --git a/xsdata/formats/dataclass/serializers/writers/native.py b/xsdata/formats/dataclass/serializers/writers/native.py
index 3ca4f68c..59d7be0d 100644
--- a/xsdata/formats/dataclass/serializers/writers/native.py
+++ b/xsdata/formats/dataclass/serializers/writers/native.py
@@ -42,7 +42,9 @@ class XmlEventWriter(XmlWriter):
         if self.config.pretty_print:
             if self.current_level:
                 self.handler.ignorableWhitespace("\n")
-                self.handler.ignorableWhitespace("  " * self.current_level)
+                self.handler.ignorableWhitespace(
+                    (self.config.pretty_print_indent or "  ") * self.current_level
+                )
 
             self.current_level += 1
             self.pending_end_element = False
@@ -55,7 +57,9 @@ class XmlEventWriter(XmlWriter):
         self.current_level -= 1
         if self.pending_end_element:
             self.handler.ignorableWhitespace("\n")
-            self.handler.ignorableWhitespace("  " * self.current_level)
+            self.handler.ignorableWhitespace(
+                (self.config.pretty_print_indent or "  ") * self.current_level
+            )
 
         super().end_tag(qname)
 


Test patch:
diff --git a/tests/formats/dataclass/serializers/test_json.py b/tests/formats/dataclass/serializers/test_json.py
index ff805663..989d236a 100644
--- a/tests/formats/dataclass/serializers/test_json.py
+++ b/tests/formats/dataclass/serializers/test_json.py
@@ -138,3 +138,36 @@ class JsonSerializerTests(TestCase):
         expected = expected[:-1]
         actual = [name for name, value in serializer.next_value(book)]
         self.assertEqual(expected, actual)
+
+    def test_pretty_print_indent(self):
+        serializer = JsonSerializer()
+        serializer.config.pretty_print = True
+        serializer.config.pretty_print_indent = "    "
+
+        actual = serializer.render(self.books)
+        expected = f"""{{
+    "book": [
+        {{
+            "author": "{self.expected["book"][0]["author"]}",
+            "title": "{self.expected["book"][0]["title"]}",
+            "genre": "{self.expected["book"][0]["genre"]}",
+            "price": {self.expected["book"][0]["price"]},
+            "pub_date": "{self.expected["book"][0]["pub_date"]}",
+            "review": "{self.expected["book"][0]["review"]}",
+            "id": "{self.expected["book"][0]["id"]}",
+            "lang": "en"
+        }},
+        {{
+            "author": "{self.expected["book"][1]["author"]}",
+            "title": "{self.expected["book"][1]["title"]}",
+            "genre": "{self.expected["book"][1]["genre"]}",
+            "price": null,
+            "pub_date": null,
+            "review": "{self.expected["book"][1]["review"]}",
+            "id": "{self.expected["book"][1]["id"]}",
+            "lang": "en"
+        }}
+    ]
+}}"""
+
+        self.assertEqual(expected, actual)
diff --git a/tests/formats/dataclass/serializers/writers/test_lxml.py b/tests/formats/dataclass/serializers/writers/test_lxml.py
index 2d92f070..c1f75981 100644
--- a/tests/formats/dataclass/serializers/writers/test_lxml.py
+++ b/tests/formats/dataclass/serializers/writers/test_lxml.py
@@ -57,3 +57,12 @@ class LxmlEventWriterTests(TestCase):
         _, actual = actual.split("\n", 1)
         _, expected = expected.split("\n", 1)
         self.assertEqual(expected.replace("  ", "").replace("\n", ""), actual)
+
+    def test_pretty_print_indent(self):
+        self.serializer.config.pretty_print_indent = "    "
+        actual = self.serializer.render(books)
+        expected = fixtures_dir.joinpath("books/books_auto_ns.xml").read_text()
+
+        _, actual = actual.split("\n", 1)
+        _, expected = expected.split("\n", 1)
+        self.assertEqual(expected.replace("  ", "    "), actual)
diff --git a/tests/formats/dataclass/serializers/writers/test_native.py b/tests/formats/dataclass/serializers/writers/test_native.py
index 309a9a78..43f9cee5 100644
--- a/tests/formats/dataclass/serializers/writers/test_native.py
+++ b/tests/formats/dataclass/serializers/writers/test_native.py
@@ -51,3 +51,12 @@ class XmlEventWriterTests(TestCase):
         _, actual = actual.split("\n", 1)
         _, expected = expected.split("\n", 1)
         self.assertEqual(expected.replace("  ", "").replace("\n", ""), actual)
+
+    def test_pretty_print_indent(self):
+        self.serializer.config.pretty_print_indent = "    "
+        actual = self.serializer.render(books)
+        expected = fixtures_dir.joinpath("books/books_auto_ns.xml").read_text()
+
+        _, actual = actual.split("\n", 1)
+        _, expected = expected.split("\n", 1)
+        self.assertEqual(expected.replace("  ", "    "), actual)