Repository: tefra/xsdata

Issue:
Feature request: print xsdata version to console and code comment
It would be nice if `xsdata` would print its version information to

* the console when it generates code
* as a comment into the main `__init__` file or alternatively some readme file


### Rationale

I'm currently debugging a problem that might be solved in a newer xsdata version. To compare the generated code, I have different xsdata versions generate code in different Python virtual environments. Printing the version to the console would ensure that the intended xsdata version was called.

To have comparable code I use the same `--package` argument in all cases. After copying the code to the project it is hard to tell which code was generated with which xsdata version. A comment in `__main__` with the version would help to find out.

Discussion:
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=751)

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


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

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


codecov[bot]: # [Codecov](https://codecov.io/gh/tefra/xsdata/pull/751?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) Report
Base: **100.00**% // Head: **100.00**% // No change to project coverage :thumbsup:
> Coverage data is based on head [(`c468d27`)](https://codecov.io/gh/tefra/xsdata/pull/751?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) compared to base [(`a73385b`)](https://codecov.io/gh/tefra/xsdata/commit/a73385b36598f69fe4291952801079d36678c028?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).
> Patch coverage: 100.00% of modified lines in pull request are covered.

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


```diff
@@            Coverage Diff            @@
##            master      #751   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          101       101           
  Lines         8816      8826   +10     
  Branches      1954      1955    +1     
=========================================
+ Hits          8816      8826   +10     
```


| [Impacted Files](https://codecov.io/gh/tefra/xsdata/pull/751?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) | Coverage Δ | |
|---|---|---|
| [xsdata/codegen/writer.py](https://codecov.io/gh/tefra/xsdata/pull/751?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2NvZGVnZW4vd3JpdGVyLnB5) | `100.00% <100.00%> (ø)` | |
| [xsdata/formats/mixins.py](https://codecov.io/gh/tefra/xsdata/pull/751?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2Zvcm1hdHMvbWl4aW5zLnB5) | `100.00% <100.00%> (ø)` | |
| [xsdata/models/config.py](https://codecov.io/gh/tefra/xsdata/pull/751?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL21vZGVscy9jb25maWcucHk=) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/tefra/xsdata/pull/751?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=751)

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


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

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



Patch:
diff --git a/docs/faq/how-can-i-compare-output-results-between-versions.rst b/docs/faq/how-can-i-compare-output-results-between-versions.rst
new file mode 100644
index 00000000..91fc8834
--- /dev/null
+++ b/docs/faq/how-can-i-compare-output-results-between-versions.rst
@@ -0,0 +1,21 @@
+How can I compare output results between versions
+======================================================
+
+The code generator comes with a config option to include
+generation info to all the output fules.
+
+
+
+.. code-block:: console
+
+    $ xsdata generate --include-header schema.xsd
+
+
+.. code-block:: python
+
+    """This file was generated by xsdata, v22.12, on 2023-02-22 10:22:57
+
+    Generator: DataclassGenerator
+    See: https://xsdata.readthedocs.io/
+    """
+    from dataclasses import dataclass, field
diff --git a/xsdata/cli.py b/xsdata/cli.py
index 9c831843..6d2c5b9b 100644
--- a/xsdata/cli.py
+++ b/xsdata/cli.py
@@ -1,4 +1,5 @@
 import logging
+import platform
 import sys
 import warnings
 from pathlib import Path
@@ -45,6 +46,12 @@ def cli(ctx: click.Context, **kwargs: Any):
     """xsdata command line interface."""
     logger.setLevel(logging.INFO)
     formatwarning_orig = warnings.formatwarning
+    logger.info(
+        "========= xsdata v%s / Python %s / Platform %s =========\n",
+        __version__,
+        platform.python_version(),
+        sys.platform,
+    )
 
     def format_warning(message: Any, category: Any, *args: Any) -> str:
         return (
diff --git a/xsdata/codegen/writer.py b/xsdata/codegen/writer.py
index 57d93ba0..064cd61f 100644
--- a/xsdata/codegen/writer.py
+++ b/xsdata/codegen/writer.py
@@ -32,12 +32,14 @@ class CodeWriter:
         necessary directories and files."""
 
         self.generator.normalize_packages(classes)
+        header = self.generator.render_header()
+
         for result in self.generator.render(classes):
             if result.source.strip():
                 logger.info("Generating package: %s", result.title)
-
+                src_code = header + result.source
                 result.path.parent.mkdir(parents=True, exist_ok=True)
-                result.path.write_text(result.source, encoding="utf-8")
+                result.path.write_text(src_code, encoding="utf-8")
 
     def print(self, classes: List[Class]):
         """Iterate over the designated generator outputs and print them to the
diff --git a/xsdata/formats/mixins.py b/xsdata/formats/mixins.py
index 6fb01ca0..7ad105c1 100644
--- a/xsdata/formats/mixins.py
+++ b/xsdata/formats/mixins.py
@@ -1,10 +1,12 @@
 import abc
+import datetime
 from pathlib import Path
 from typing import Dict
 from typing import Iterator
 from typing import List
 from typing import NamedTuple
 
+from xsdata import __version__
 from xsdata.codegen.models import Class
 from xsdata.exceptions import CodeGenerationError
 from xsdata.models.config import GeneratorConfig
@@ -62,6 +64,19 @@ class AbstractGenerator(abc.ABC):
         """Group the given list of classes by the target module directory."""
         return group_by(classes, lambda x: module_path(x.target_module))
 
+    def render_header(self) -> str:
+        """Generate a header for the writer to prepend on the output files."""
+        if not self.config.output.include_header:
+            return ""
+
+        now = datetime.datetime.now().isoformat(sep=" ", timespec="seconds")
+        return (
+            f'"""This file was generated by xsdata, v{__version__}, on {now}'
+            f"\n\nGenerator: {self.__class__.__qualname__}\n"
+            f"See: https://xsdata.readthedocs.io/\n"
+            '"""\n'
+        )
+
     def normalize_packages(self, classes: List[Class]):
         """
         Normalize the target package and module names by the given output
diff --git a/xsdata/models/config.py b/xsdata/models/config.py
index ea5c78bd..bf085abe 100644
--- a/xsdata/models/config.py
+++ b/xsdata/models/config.py
@@ -246,6 +246,8 @@ class GeneratorOutput:
         default: false, python>=3.7 Only
     :param unnest_classes: Move inner classes to upper level, default: false
     :param ignore_patterns: Ignore pattern restrictions, default: false
+    :param include_header: Include a header with codegen information in the output,
+        default: false
     """
 
     package: str = element(default="generated")
@@ -263,6 +265,7 @@ class GeneratorOutput:
     postponed_annotations: bool = element(default=False)
     unnest_classes: bool = element(default=False)
     ignore_patterns: bool = element(default=False)
+    include_header: bool = element(default=False)
 
     def update(self, **kwargs: Any):
         objects.update(self, **kwargs)


Test patch:
diff --git a/tests/codegen/test_writer.py b/tests/codegen/test_writer.py
index e8e86cbc..42e7f124 100644
--- a/tests/codegen/test_writer.py
+++ b/tests/codegen/test_writer.py
@@ -16,21 +16,20 @@ from xsdata.utils.testing import FactoryTestCase
 
 
 class NoneGenerator(AbstractGenerator):
-    def __init__(self):
-        pass
-
     def render(self, classes: List[Class]) -> Iterator[GeneratorResult]:
         pass
 
 
 class CodeWriterTests(FactoryTestCase):
     def setUp(self):
-        generator = NoneGenerator()
+        config = GeneratorConfig()
+        generator = NoneGenerator(config)
         self.writer = CodeWriter(generator)
 
+    @mock.patch.object(NoneGenerator, "render_header")
     @mock.patch.object(NoneGenerator, "render")
     @mock.patch.object(NoneGenerator, "normalize_packages")
-    def test_write(self, mock_normalize_packages, mock_render):
+    def test_write(self, mock_normalize_packages, mock_render, mock_render_header):
         classes = ClassFactory.list(2)
         with TemporaryDirectory() as tmpdir:
             mock_render.return_value = [
@@ -38,10 +37,11 @@ class CodeWriterTests(FactoryTestCase):
                 GeneratorResult(Path(f"{tmpdir}/bar/b.py"), "file", "bBb"),
                 GeneratorResult(Path(f"{tmpdir}/c.py"), "file", " "),
             ]
+            mock_render_header.return_value = "// Head\n"
             self.writer.write(classes)
 
-            self.assertEqual("aAa", Path(f"{tmpdir}/foo/a.py").read_text())
-            self.assertEqual("bBb", Path(f"{tmpdir}/bar/b.py").read_text())
+            self.assertEqual("// Head\naAa", Path(f"{tmpdir}/foo/a.py").read_text())
+            self.assertEqual("// Head\nbBb", Path(f"{tmpdir}/bar/b.py").read_text())
             self.assertFalse(Path(f"{tmpdir}/c.py").exists())
             mock_normalize_packages.assert_called_once_with(classes)
 
diff --git a/tests/formats/test_mixins.py b/tests/formats/test_mixins.py
index 8a5d5901..d37ad9c6 100644
--- a/tests/formats/test_mixins.py
+++ b/tests/formats/test_mixins.py
@@ -1,7 +1,9 @@
+import datetime
 from typing import Iterator
 from typing import List
 from unittest import mock
 
+from xsdata import __version__
 from xsdata.codegen.models import Class
 from xsdata.exceptions import CodeGenerationError
 from xsdata.formats.mixins import AbstractGenerator
@@ -52,3 +54,24 @@ class AbstractGeneratorTests(FactoryTestCase):
         self.assertEqual(
             "Class `class_B` has not been assigned to a package", str(cm.exception)
         )
+
+    @mock.patch("datetime.datetime", wraps=datetime.datetime)
+    def test_render_header(self, mock_datetime):
+        actual = self.generator.render_header()
+        self.assertEqual("", actual)
+        mock_datetime.now.return_value = datetime.datetime(
+            year=2023, month=2, day=22, hour=10, minute=20, second=25
+        )
+
+        self.generator.config.output.include_header = True
+        actual = self.generator.render_header()
+        now_iso_format = "2023-02-22 10:20:25"
+        expected = (
+            f'"""This file was generated by xsdata, v{__version__}, on {now_iso_format}\n'
+            "\n"
+            "Generator: NoneGenerator\n"
+            "See: https://xsdata.readthedocs.io/\n"
+            '"""\n'
+        )
+
+        self.assertEqual(expected, actual)
diff --git a/tests/models/test_config.py b/tests/models/test_config.py
index 30a64872..e046e444 100644
--- a/tests/models/test_config.py
+++ b/tests/models/test_config.py
@@ -39,6 +39,7 @@ class GeneratorConfigTests(TestCase):
             "    <PostponedAnnotations>false</PostponedAnnotations>\n"
             "    <UnnestClasses>false</UnnestClasses>\n"
             "    <IgnorePatterns>false</IgnorePatterns>\n"
+            "    <IncludeHeader>false</IncludeHeader>\n"
             "  </Output>\n"
             "  <Conventions>\n"
             '    <ClassName case="pascalCase" safePrefix="type"/>\n'
@@ -99,6 +100,7 @@ class GeneratorConfigTests(TestCase):
             "    <PostponedAnnotations>false</PostponedAnnotations>\n"
             "    <UnnestClasses>false</UnnestClasses>\n"
             "    <IgnorePatterns>false</IgnorePatterns>\n"
+            "    <IncludeHeader>false</IncludeHeader>\n"
             "  </Output>\n"
             "  <Conventions>\n"
             '    <ClassName case="pascalCase" safePrefix="type"/>\n'
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 7ac73a67..7e9fb6d0 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1,3 +1,5 @@
+import platform
+import sys
 import tempfile
 from pathlib import Path
 from unittest import mock
@@ -6,6 +8,7 @@ from unittest import TestCase
 from click.testing import CliRunner
 
 from tests import fixtures_dir
+from xsdata import __version__
 from xsdata.cli import cli
 from xsdata.cli import resolve_source
 from xsdata.codegen.transformer import SchemaTransformer
@@ -91,8 +94,16 @@ class CliTests(TestCase):
 
         self.assertIsNone(result.exception)
         self.assertEqual(GeneratorConfig.create(), GeneratorConfig.read(output_path))
-        mock_info.assert_called_once_with(
-            "Initializing configuration file %s", str(output_path)
+        mock_info.assert_has_calls(
+            [
+                mock.call(
+                    "========= xsdata v%s / Python %s / Platform %s =========\n",
+                    __version__,
+                    platform.python_version(),
+                    sys.platform,
+                ),
+                mock.call("Initializing configuration file %s", str(output_path)),
+            ]
         )
         output_path.unlink()
 
@@ -111,8 +122,16 @@ class CliTests(TestCase):
 
         self.assertIsNone(result.exception)
         self.assertNotEqual("20.8", GeneratorConfig.read(output_path))
-        mock_info.assert_called_once_with(
-            "Updating configuration file %s", str(output_path)
+        mock_info.assert_has_calls(
+            [
+                mock.call(
+                    "========= xsdata v%s / Python %s / Platform %s =========\n",
+                    __version__,
+                    platform.python_version(),
+                    sys.platform,
+                ),
+                mock.call("Updating configuration file %s", str(output_path)),
+            ]
         )
         output_path.unlink()