Repository: tefra/xsdata

Issue:
Remove python 3.7 support
Python 3.7 has reached EOL status, I will keep it for v23.7 and remove official support from v23.8

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

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

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


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/tefra/xsdata/pull/834?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.05`** :warning:
> Comparison is base [(`cb80011`)](https://app.codecov.io/gh/tefra/xsdata/commit/cb80011ebd5b096ec5d0613c95fb01f2c2ff073b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) 99.96% compared to head [(`3166fd8`)](https://app.codecov.io/gh/tefra/xsdata/pull/834?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) 99.92%.

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


```diff
@@            Coverage Diff             @@
##             main     #834      +/-   ##
==========================================
- Coverage   99.96%   99.92%   -0.05%     
==========================================
  Files         104      104              
  Lines        9270     9265       -5     
  Branches     2072     1600     -472     
==========================================
- Hits         9267     9258       -9     
- Misses          0        2       +2     
- Partials        3        5       +2     
```


| [Impacted Files](https://app.codecov.io/gh/tefra/xsdata/pull/834?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) | Coverage Δ | |
|---|---|---|
| [xsdata/models/config.py](https://app.codecov.io/gh/tefra/xsdata/pull/834?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL21vZGVscy9jb25maWcucHk=) | `98.64% <ø> (-1.36%)` | :arrow_down: |
| [xsdata/utils/hooks.py](https://app.codecov.io/gh/tefra/xsdata/pull/834?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL3V0aWxzL2hvb2tzLnB5) | `100.00% <100.00%> (ø)` | |

... and [3 files with indirect coverage changes](https://app.codecov.io/gh/tefra/xsdata/pull/834/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/834?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).


Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c8a03150..a14f736e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -5,7 +5,7 @@ repos:
     rev: v3.9.0
     hooks:
       - id: pyupgrade
-        args: [ --py37-plus ]
+        args: [ --py38-plus ]
   - repo: https://github.com/asottile/reorder_python_imports
     rev: v3.10.0
     hooks:
diff --git a/pyproject.toml b/pyproject.toml
index ab160c56..ea39e4b3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -14,7 +14,6 @@ classifiers = [
     "Programming Language :: Python",
     "Programming Language :: Python :: 3",
     "Programming Language :: Python :: 3 :: Only",
-    "Programming Language :: Python :: 3.7",
     "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
@@ -26,10 +25,9 @@ classifiers = [
     "Topic :: Text Processing :: Markup :: XML",
 ]
 keywords = ["xsd", "wsdl", "schema", "dtd", "binding", "xml", "json", "dataclasses", "generator", "cli"]
-requires-python = ">=3.7"
+requires-python = ">=3.8"
 dependencies = [
     "typing-extensions",
-    'importlib-metadata;python_version<"3.8"',
 ]
 dynamic = ["version"]
 
diff --git a/xsdata/models/config.py b/xsdata/models/config.py
index e08aebe4..bca2c7e8 100644
--- a/xsdata/models/config.py
+++ b/xsdata/models/config.py
@@ -240,7 +240,7 @@ class CompoundFields:
     :param default_name: Default compound field name, default: choice
     :param force_default_name: Always use the default compound field,
         otherwise if the number of elements is less than 4 the generator
-        will try to dynamically create the field name eg.
+        will try to dynamically create the field name e.g.
         hat_or_dress_or_something.
     """
 
@@ -267,7 +267,7 @@ class GeneratorOutput:
         default: false, python>=3.9 Only
     :param union_type: Use PEP-604 union type, default: false, python>=3.10 Only
     :param postponed_annotations: Enable postponed evaluation of annotations,
-        default: false, python>=3.7 Only
+        default: false
     :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,
@@ -328,7 +328,7 @@ class NameConvention:
     """
     Name convention model.
 
-    :param case: Naming scheme, eg camelCase, snakeCase
+    :param case: Naming scheme, e.g. camelCase, snakeCase
     :param safe_prefix: A prefix to be prepended into names that match
         one of the reserved words: and, except, lambda, with, as,
         finally, nonlocal, while, assert, false, none, yield, break,
@@ -378,7 +378,7 @@ class GeneratorAlias:
     Each alias has a source attribute that refers to the original name
     in the schema definition and the target attribute for output name.
     For package and module aliases the source refers to the schema
-    filename or target namespace depending the selected output
+    filename or target namespace depending on the selected output
     structure.
 
     :param source: The source name from schema definition
diff --git a/xsdata/utils/hooks.py b/xsdata/utils/hooks.py
index 7e28a3e6..d69bb7d5 100644
--- a/xsdata/utils/hooks.py
+++ b/xsdata/utils/hooks.py
@@ -1,13 +1,8 @@
-import sys
-
-if sys.version_info >= (3, 8):
-    from importlib import metadata as importlib_metadata
-else:
-    import importlib_metadata
+from importlib import metadata
 
 
 def load_entry_points(name: str):
-    entry_points = importlib_metadata.entry_points()
+    entry_points = metadata.entry_points()
 
     if hasattr(entry_points, "select"):
         plugins = entry_points.select(group=name)  # type: ignore


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 064773e0..3dbc85cc 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -14,15 +14,11 @@ jobs:
       fail-fast: false
       matrix:
         include:
-          - {name: PyPy 3.7, python: 'pypy-3.7', os: ubuntu}
-          - {name: Python 3.7, python: '3.7', os: ubuntu}
           - {name: Python 3.8, python: '3.8', os: ubuntu}
           - {name: Python 3.9, python: '3.9', os: ubuntu}
           - {name: Python 3.10, python: '3.10', os: ubuntu}
           - {name: Python 3.11, python: '3.11', os: ubuntu}
           - {name: Python 3.12, python: '3.12-dev', os: ubuntu}
-          - {name: Windows py37, python: '3.7', os: windows}
-          - {name: MacOS py37, python: '3.7', os: macos}
     steps:
       - uses: actions/checkout@v3
       - name: Set up Python ${{ matrix.python }}
diff --git a/tests/models/test_config.py b/tests/models/test_config.py
index 9eab3570..742f4a3a 100644
--- a/tests/models/test_config.py
+++ b/tests/models/test_config.py
@@ -197,27 +197,6 @@ class GeneratorConfigTests(TestCase):
         else:
             self.assertIsNotNone(OutputFormat(kw_only=True))
 
-    def test_postponed_annotations_requires_37(self):
-        # We need to confuse pyupgrade into not discarding Python version checks,
-        # which are needed to know when the warning is triggered or not
-        # Extracting the version in a local variable before performing the check is
-        # sufficient for that purpose.
-        v = sys.version_info
-        if v < (3, 7):
-            with warnings.catch_warnings(record=True) as w:
-                self.assertFalse(
-                    GeneratorOutput(postponed_annotations=True).postponed_annotations
-                )
-                self.assertEqual(
-                    "postponed annotations requires python >= 3.7, reverting...",
-                    str(w[-1].message),
-                )
-
-        if v >= (3, 7):
-            self.assertTrue(
-                GeneratorOutput(postponed_annotations=True).postponed_annotations
-            )
-
     def test_init_config_with_aliases(self):
         config = GeneratorConfig(
             aliases=GeneratorAliases(
diff --git a/tests/utils/test_hooks.py b/tests/utils/test_hooks.py
index 8167e67d..7e5db5f9 100644
--- a/tests/utils/test_hooks.py
+++ b/tests/utils/test_hooks.py
@@ -5,7 +5,7 @@ from xsdata.utils.hooks import load_entry_points
 
 
 class HooksTests(TestCase):
-    @mock.patch("xsdata.utils.hooks.importlib_metadata.entry_points")
+    @mock.patch("xsdata.utils.hooks.metadata.entry_points")
     def test_load_entry_points_with_mapping_api(self, mock_entry_points):
         first_ep = mock.Mock()
         second_ep = mock.Mock()
@@ -21,7 +21,7 @@ class HooksTests(TestCase):
         second_ep.load.assert_called_once_with()
         self.assertEqual(0, third_ep.load.call_count)
 
-    @mock.patch("xsdata.utils.hooks.importlib_metadata.entry_points")
+    @mock.patch("xsdata.utils.hooks.metadata.entry_points")
     def test_load_entry_points_with_select_api(self, mock_entry_points):
         first_ep = mock.Mock()
         second_ep = mock.Mock()