Repository: tefra/xsdata

Issue:
SerializerConfig introduced 2 issues
Hello,

1st issue is that the constructor interface for XmlSerializer changed.

```python
XmlSerializer(encoding=None)
```

no longer works. This is a smaller issue as I can change my code to support the new library (but it's still a change that isn't backwards compatible)

2nd issue is that the XmlWriter no longer supports encoding=None. This is a blocker, as I have a rest API that will not accept a payload with an encoding.

Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

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

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo.png' alt='No Coverage information' width='16' height='16' />](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=358) No Coverage information  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=358&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=358&metric=new_duplicated_lines_density&view=list)


codecov[bot]: # [Codecov](https://codecov.io/gh/tefra/xsdata/pull/358?src=pr&el=h1) Report
> Merging [#358](https://codecov.io/gh/tefra/xsdata/pull/358?src=pr&el=desc) (7851d90) into [master](https://codecov.io/gh/tefra/xsdata/commit/a2c51f5bcdcaf2be620a43c9f80f831da16cefc8?el=desc) (a2c51f5) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/tefra/xsdata/pull/358/graphs/tree.svg?width=650&height=150&src=pr&token=YzDDLtywvl)](https://codecov.io/gh/tefra/xsdata/pull/358?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##            master      #358   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           71        71           
  Lines         5725      5727    +2     
  Branches       995       996    +1     
=========================================
+ Hits          5725      5727    +2     
```


| [Impacted Files](https://codecov.io/gh/tefra/xsdata/pull/358?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [xsdata/formats/dataclass/serializers/config.py](https://codecov.io/gh/tefra/xsdata/pull/358/diff?src=pr&el=tree#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL3NlcmlhbGl6ZXJzL2NvbmZpZy5weQ==) | `100.00% <100.00%> (ø)` | |
| [xsdata/formats/dataclass/serializers/mixins.py](https://codecov.io/gh/tefra/xsdata/pull/358/diff?src=pr&el=tree#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL3NlcmlhbGl6ZXJzL21peGlucy5weQ==) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/tefra/xsdata/pull/358?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/tefra/xsdata/pull/358?src=pr&el=footer). Last update [a2c51f5...7851d90](https://codecov.io/gh/tefra/xsdata/pull/358?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

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

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo.png' alt='No Coverage information' width='16' height='16' />](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=358) No Coverage information  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=358&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=358&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/docs/xml.rst b/docs/xml.rst
index 47ba1e30..61e62dcd 100644
--- a/docs/xml.rst
+++ b/docs/xml.rst
@@ -167,6 +167,9 @@ context instance between them to save on memory and processing.
        * - xml_version
          - str
          - XML Version number, default: ``1.0``
+       * - xml_declaration
+         - bool
+         - Generate XML declaration, default ``True``
        * - pretty_print
          - bool
          - Enable pretty output, default ``False``
diff --git a/xsdata/formats/dataclass/serializers/config.py b/xsdata/formats/dataclass/serializers/config.py
index e7369247..4a408c4a 100644
--- a/xsdata/formats/dataclass/serializers/config.py
+++ b/xsdata/formats/dataclass/serializers/config.py
@@ -8,6 +8,7 @@ class SerializerConfig:
     """
     :param encoding: Text encoding
     :param xml_version: XML Version number (1.0|1.1)
+    :param xml_declaration: Generate XML declaration
     :param pretty_print: Enable pretty output
     :param schema_location: Specify the xsi:schemaLocation attribute value
     :param no_namespace_schema_location: Specify the xsi:noNamespaceSchemaLocation
@@ -16,6 +17,7 @@ class SerializerConfig:
 
     encoding: str = field(default="UTF-8")
     xml_version: str = field(default="1.0")
+    xml_declaration: bool = field(default=True)
     pretty_print: bool = field(default=False)
     schema_location: Optional[str] = field(default=None)
     no_namespace_schema_location: Optional[str] = field(default=None)
diff --git a/xsdata/formats/dataclass/serializers/mixins.py b/xsdata/formats/dataclass/serializers/mixins.py
index 76b8a597..c4b44989 100644
--- a/xsdata/formats/dataclass/serializers/mixins.py
+++ b/xsdata/formats/dataclass/serializers/mixins.py
@@ -91,8 +91,9 @@ class XmlWriter:
         self.handler.endDocument()
 
     def start_document(self):
-        self.output.write(f'<?xml version="{self.config.xml_version}"')
-        self.output.write(f' encoding="{self.config.encoding}"?>\n')
+        if self.config.xml_declaration:
+            self.output.write(f'<?xml version="{self.config.xml_version}"')
+            self.output.write(f' encoding="{self.config.encoding}"?>\n')
 
     def start_tag(self, qname: str):
         """


Test patch:
diff --git a/tests/formats/dataclass/serializers/writers/test_lxml.py b/tests/formats/dataclass/serializers/writers/test_lxml.py
index d8cb5ff9..2d92f070 100644
--- a/tests/formats/dataclass/serializers/writers/test_lxml.py
+++ b/tests/formats/dataclass/serializers/writers/test_lxml.py
@@ -41,6 +41,14 @@ class LxmlEventWriterTests(TestCase):
 
         self.assertEqual('<?xml version="1.1" encoding="US-ASCII"?>', xml_declaration)
 
+    def test_declaration_disabled(self):
+        self.serializer.config.xml_declaration = False
+        actual = self.serializer.render(books, {None: "urn:books"})
+        expected = fixtures_dir.joinpath("books/books_default_ns.xml").read_text()
+        xml_declaration, expected = expected.split("\n", 1)
+
+        self.assertEqual(expected, actual)
+
     def test_pretty_print_false(self):
         self.serializer.config.pretty_print = False
         actual = self.serializer.render(books)
diff --git a/tests/formats/dataclass/serializers/writers/test_native.py b/tests/formats/dataclass/serializers/writers/test_native.py
index 30185195..309a9a78 100644
--- a/tests/formats/dataclass/serializers/writers/test_native.py
+++ b/tests/formats/dataclass/serializers/writers/test_native.py
@@ -35,6 +35,14 @@ class XmlEventWriterTests(TestCase):
 
         self.assertEqual('<?xml version="1.1" encoding="US-ASCII"?>', xml_declaration)
 
+    def test_declaration_disabled(self):
+        self.serializer.config.xml_declaration = False
+        actual = self.serializer.render(books, {None: "urn:books"})
+        expected = fixtures_dir.joinpath("books/books_default_ns.xml").read_text()
+        xml_declaration, expected = expected.split("\n", 1)
+
+        self.assertEqual(expected, actual)
+
     def test_pretty_print_false(self):
         self.serializer.config.pretty_print = False
         actual = self.serializer.render(books)