Repository: Fatal1ty/mashumaro (language: python)

Issue:
Drop Python 3.7 support
**Is your feature request related to a problem? Please describe.**

Python 3.7 reached EOL in June 2023. It’s time to get rid of workarounds made for it.

https://devguide.python.org/versions/


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 3efbd2e9..3eda5e79 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -14,7 +14,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
+        python-version: ["3.8", "3.9", "3.10", "3.11"]
     steps:
       - uses: actions/checkout@v3
       - name: Set up Python ${{ matrix.python-version }}
@@ -41,7 +41,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
+        python-version: ["3.8", "3.9", "3.10", "3.11"]
     steps:
     - uses: actions/checkout@v3
     - name: Set up Python ${{ matrix.python-version }}
@@ -68,7 +68,7 @@ jobs:
     runs-on: windows-latest
     strategy:
       matrix:
-        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
+        python-version: ["3.8", "3.9", "3.10", "3.11"]
     steps:
     - uses: actions/checkout@v3
     - name: Set up Python ${{ matrix.python-version }}
@@ -99,7 +99,7 @@ jobs:
     - name: Set up Python
       uses: actions/setup-python@v4
       with:
-        python-version: 3.7
+        python-version: 3.11
     - name: Install dependencies
       run: pip install coveralls
     - name: Finish coveralls
diff --git a/README.md b/README.md
index 934d008b..2c3d022f 100644
--- a/README.md
+++ b/README.md
@@ -105,8 +105,21 @@ Use pip to install:
 $ pip install mashumaro
 ```
 
-The current version of `mashumaro` supports Python versions 3.7 - 3.11.
-The latest version of `mashumaro` that can be installed on Python 3.6 is 3.1.1.
+The current version of `mashumaro` supports Python versions 3.8 - 3.11.
+
+
+It's not recommended to use any version of Python that has reached its
+[end of life](https://devguide.python.org/versions/) and is no longer receiving
+security updates or bug fixes from the Python development team.
+For convenience, there is a table below that outlines the
+latest version of `mashumaro` that can be installed on unmaintained versions
+of Python.
+
+| Python Version | Latest Version of mashumaro                                        | Python EOL |
+|----------------|--------------------------------------------------------------------|------------|
+| 3.7            | [3.9.1](https://github.com/Fatal1ty/mashumaro/releases/tag/v3.9.1) | 2023-06-27 |
+| 3.6            | [3.1.1](https://github.com/Fatal1ty/mashumaro/releases/tag/v3.1.1) | 2021-12-23 |
+
 
 Changelog
 --------------------------------------------------------------------------------
diff --git a/mashumaro/core/const.py b/mashumaro/core/const.py
index b1b4805d..06bf25c0 100644
--- a/mashumaro/core/const.py
+++ b/mashumaro/core/const.py
@@ -2,11 +2,9 @@
 import sys
 
 __all__ = [
-    "PY_37",
     "PY_38",
     "PY_39",
     "PY_310",
-    "PY_37_MIN",
     "PY_38_MIN",
     "PY_39_MIN",
     "PY_310_MIN",
@@ -17,7 +15,6 @@
 ]
 
 
-PY_37 = sys.version_info.major == 3 and sys.version_info.minor == 7
 PY_38 = sys.version_info.major == 3 and sys.version_info.minor == 8
 PY_39 = sys.version_info.major == 3 and sys.version_info.minor == 9
 PY_310 = sys.version_info.major == 3 and sys.version_info.minor == 10
@@ -28,7 +25,6 @@
 PY_310_MIN = PY_310 or PY_311_MIN
 PY_39_MIN = PY_39 or PY_310_MIN
 PY_38_MIN = PY_38 or PY_39_MIN
-PY_37_MIN = PY_37 or PY_38_MIN
 
 PEP_585_COMPATIBLE = PY_39_MIN  # Type Hinting Generics In Standard Collections
 PEP_586_COMPATIBLE = PY_38_MIN  # Literal Types
diff --git a/mashumaro/core/meta/helpers.py b/mashumaro/core/meta/helpers.py
index 7cf3c173..54c013ac 100644
--- a/mashumaro/core/meta/helpers.py
+++ b/mashumaro/core/meta/helpers.py
@@ -30,7 +30,6 @@
 
 from mashumaro.core.const import (
     PEP_585_COMPATIBLE,
-    PY_37,
     PY_38,
     PY_38_MIN,
     PY_39,
@@ -291,7 +290,7 @@ def is_generic(typ: Type) -> bool:
     with suppress(Exception):
         if hasattr(typ, "__class_getitem__"):
             return True
-    if PY_37 or PY_38:
+    if PY_38:
         # noinspection PyProtectedMember
         # noinspection PyUnresolvedReferences
         return issubclass(typ.__class__, typing._GenericAlias)  # type: ignore
@@ -376,7 +375,7 @@ def get_type_annotations(typ: Type) -> Sequence[Any]:
 
 
 def is_literal(typ: Type) -> bool:
-    if PY_37 or PY_38 or PY_39:
+    if PY_38 or PY_39:
         with suppress(AttributeError):
             return is_generic(typ) and get_generic_name(typ, True) == "Literal"
     elif PY_310_MIN:
@@ -423,9 +422,7 @@ def is_final(typ: Type) -> bool:
 
 
 def is_init_var(typ: Type) -> bool:
-    if PY_37:
-        return get_type_origin(typ) is dataclasses.InitVar
-    elif PY_38_MIN:
+    if PY_38_MIN:
         return isinstance(typ, dataclasses.InitVar)
     else:
         raise NotImplementedError
diff --git a/mashumaro/core/meta/types/common.py b/mashumaro/core/meta/types/common.py
index 0868b1a4..9476f53f 100644
--- a/mashumaro/core/meta/types/common.py
+++ b/mashumaro/core/meta/types/common.py
@@ -1,6 +1,7 @@
 import collections.abc
 import uuid
 from dataclasses import dataclass, field, is_dataclass, replace
+from functools import cached_property
 from typing import (
     TYPE_CHECKING,
     Any,
@@ -14,12 +15,6 @@
     TypeVar,
 )
 
-try:
-    from functools import cached_property  # type: ignore
-except ImportError:
-    # for python 3.7
-    cached_property = property  # type: ignore
-
 from typing_extensions import ParamSpec, TypeAlias
 
 from mashumaro.core.const import PEP_585_COMPATIBLE
diff --git a/mashumaro/core/meta/types/pack.py b/mashumaro/core/meta/types/pack.py
index 55ab9bcd..f6b106aa 100644
--- a/mashumaro/core/meta/types/pack.py
+++ b/mashumaro/core/meta/types/pack.py
@@ -11,8 +11,6 @@
 from fractions import Fraction
 from typing import Any, Callable, List, Optional, Tuple, Type, Union
 
-import typing_extensions
-
 from mashumaro.core.const import PY_39_MIN, PY_311_MIN
 from mashumaro.core.meta.code.lines import CodeLines
 from mashumaro.core.meta.helpers import (
@@ -679,7 +677,7 @@ def inner_expr(
             f"for key, value in m.items()}} "
             f"for m in {spec.expression}.maps]"
         )
-    elif ensure_generic_mapping(spec, args, typing_extensions.OrderedDict):
+    elif ensure_generic_mapping(spec, args, typing.OrderedDict):
         return (
             f'{{{inner_expr(0, "key")}: {inner_expr(1)} '
             f"for key, value in {spec.expression}.items()}}"
diff --git a/mashumaro/core/meta/types/unpack.py b/mashumaro/core/meta/types/unpack.py
index 7cc7a1c4..5c502a6d 100644
--- a/mashumaro/core/meta/types/unpack.py
+++ b/mashumaro/core/meta/types/unpack.py
@@ -16,8 +16,6 @@
 from fractions import Fraction
 from typing import Any, Callable, Iterable, List, Optional, Tuple, Type, Union
 
-import typing_extensions
-
 from mashumaro.core.const import PY_39_MIN, PY_311_MIN
 from mashumaro.core.helpers import parse_timezone
 from mashumaro.core.meta.code.lines import CodeLines
@@ -1079,7 +1077,7 @@ def inner_expr(
             f'collections.ChainMap(*[{{{inner_expr(0, "key")}:{inner_expr(1)} '
             f"for key, value in m.items()}} for m in {spec.expression}])"
         )
-    elif ensure_generic_mapping(spec, args, typing_extensions.OrderedDict):
+    elif ensure_generic_mapping(spec, args, typing.OrderedDict):
         spec.builder.ensure_module_imported(collections)
         return (
             f'collections.OrderedDict({{{inner_expr(0, "key")}: '
diff --git a/mashumaro/jsonschema/schema.py b/mashumaro/jsonschema/schema.py
index a59fa266..35a82b04 100644
--- a/mashumaro/jsonschema/schema.py
+++ b/mashumaro/jsonschema/schema.py
@@ -8,6 +8,7 @@
 from decimal import Decimal
 from enum import Enum
 from fractions import Fraction
+from functools import cached_property
 from typing import (
     Any,
     Callable,
@@ -108,19 +109,12 @@ class Instance:
     origin_type: Type = field(init=False)
     annotations: List[Annotation] = field(init=False, default_factory=list)
 
-    __metadata: Optional[Dict[str, Any]] = field(init=False, default=None)
-
-    @property
-    # TODO: switch to cached_property after dropping python 3.7 support
+    @cached_property
     def metadata(self) -> Dict[str, Any]:
-        if self.__metadata is None:
-            if self.name and self.__owner_builder:
-                self.__metadata = dict(
-                    **self.__owner_builder.metadatas.get(self.name, {})
-                )
-            else:
-                self.__metadata = {}
-        return self.__metadata
+        if self.name and self.__owner_builder:
+            return dict(**self.__owner_builder.metadatas.get(self.name, {}))
+        else:
+            return {}
 
     @property
     def _self_builder(self) -> CodeBuilder:
diff --git a/pyproject.toml b/pyproject.toml
index 1351ec4f..77f01093 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -31,4 +31,4 @@ ensure_newline_before_comments = true
 
 [tool.black]
 line-length = 79
-target-version = ['py37', 'py38', 'py39']
+target-version = ['py38', 'py39', 'py310', 'py311']
diff --git a/setup.py b/setup.py
index 32686aa7..44362821 100644
--- a/setup.py
+++ b/setup.py
@@ -13,7 +13,6 @@
         "License :: OSI Approved :: Apache Software License",
         "Intended Audience :: Developers",
         "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,7 +25,7 @@
     url="https://github.com/Fatal1ty/mashumaro",
     packages=find_packages(include=("mashumaro", "mashumaro.*")),
     package_data={"mashumaro": ["py.typed", "mixins/orjson.pyi"]},
-    python_requires=">=3.7",
+    python_requires=">=3.8",
     install_requires=[
         "typing_extensions>=4.1.0",
     ],


Test patch:
diff --git a/tests/test_data_types.py b/tests/test_data_types.py
index 84236996..a50d1685 100644
--- a/tests/test_data_types.py
+++ b/tests/test_data_types.py
@@ -33,17 +33,18 @@
     MutableSet,
     NewType,
     Optional,
+    OrderedDict,
     Sequence,
     Set,
     Tuple,
 )
 
 import pytest
-from typing_extensions import Final, OrderedDict
+from typing_extensions import Final
 
 from mashumaro import DataClassDictMixin
 from mashumaro.config import BaseConfig
-from mashumaro.core.const import PEP_585_COMPATIBLE, PY_39_MIN, PY_311_MIN
+from mashumaro.core.const import PEP_585_COMPATIBLE, PY_39_MIN
 from mashumaro.exceptions import (
     InvalidFieldValue,
     MissingField,
diff --git a/tests/test_jsonschema/test_jsonschema_generation.py b/tests/test_jsonschema/test_jsonschema_generation.py
index 6b5404dd..659325e2 100644
--- a/tests/test_jsonschema/test_jsonschema_generation.py
+++ b/tests/test_jsonschema/test_jsonschema_generation.py
@@ -29,6 +29,7 @@
     Mapping,
     MutableMapping,
     Optional,
+    OrderedDict,
     Sequence,
     Tuple,
     Union,
@@ -36,13 +37,7 @@
 from uuid import UUID
 
 import pytest
-from typing_extensions import (
-    Annotated,
-    Literal,
-    OrderedDict,
-    TypeVarTuple,
-    Unpack,
-)
+from typing_extensions import Annotated, Literal, TypeVarTuple, Unpack
 
 from mashumaro.config import BaseConfig
 from mashumaro.core.const import PEP_585_COMPATIBLE, PY_39_MIN
diff --git a/tests/test_meta.py b/tests/test_meta.py
index 4f7b6f3c..6f189a70 100644
--- a/tests/test_meta.py
+++ b/tests/test_meta.py
@@ -10,7 +10,6 @@
 from mashumaro import DataClassDictMixin
 from mashumaro.core.const import (
     PEP_585_COMPATIBLE,
-    PY_37,
     PY_38,
     PY_38_MIN,
     PY_310_MIN,
@@ -83,7 +82,6 @@
 
 
 def test_is_generic_unsupported_python(mocker):
-    mocker.patch("mashumaro.core.meta.helpers.PY_37", False)
     mocker.patch("mashumaro.core.meta.helpers.PY_38", False)
     mocker.patch("mashumaro.core.meta.helpers.PY_39_MIN", False)
     with pytest.raises(NotImplementedError):
@@ -91,14 +89,12 @@ def test_is_generic_unsupported_python(mocker):
 
 
 def test_is_init_var_unsupported_python(mocker):
-    mocker.patch("mashumaro.core.meta.helpers.PY_37", False)
     mocker.patch("mashumaro.core.meta.helpers.PY_38_MIN", False)
     with pytest.raises(NotImplementedError):
         is_init_var(int)
 
 
 def test_is_literal_unsupported_python(mocker):
-    mocker.patch("mashumaro.core.meta.helpers.PY_37", False)
     mocker.patch("mashumaro.core.meta.helpers.PY_38", False)
     mocker.patch("mashumaro.core.meta.helpers.PY_39", False)
     mocker.patch("mashumaro.core.meta.helpers.PY_310_MIN", False)
@@ -181,8 +177,8 @@ def test_is_type_var_any():
     assert not is_type_var_any(TMyDataClass)
 
 
-@pytest.mark.skipif(not (PY_37 or PY_38), reason="requires python 3.7..3.8")
-def test_is_type_var_any_list_37_38():
+@pytest.mark.skipif(not PY_38, reason="requires python 3.8")
+def test_is_type_var_any_list_38():
     # noinspection PyProtectedMember
     # noinspection PyUnresolvedReferences
     assert is_type_var_any(typing.List.__args__[0])
@@ -215,7 +211,7 @@ def test_type_name():
         == "typing.Union[int, typing.Any]"
     )
     assert (
-        type_name(typing_extensions.OrderedDict[int, int])
+        type_name(typing.OrderedDict[int, int])
         == "typing.OrderedDict[int, int]"
     )
     assert (
@@ -325,7 +321,7 @@ def test_type_name_short():
         == "Union[int, Any]"
     )
     assert (
-        type_name(typing_extensions.OrderedDict[int, int], short=True)
+        type_name(typing.OrderedDict[int, int], short=True)
         == "OrderedDict[int, int]"
     )
     assert (