From 1702b55b7da01e9b03d5aa3a6134fd829cf7e7a2 Mon Sep 17 00:00:00 2001
From: Gabriel Gerlero <gerlero@users.noreply.github.com>
Date: Mon, 19 Feb 2024 12:27:39 -0300
Subject: [PATCH] Drop support for Python < 3.9

---
 .github/workflows/ci.yml |  2 +-
 electrolytes/__init__.py | 35 ++++++++++++--------------------
 electrolytes/__main__.py | 44 +++++++++++++++++-----------------------
 pyproject.toml           |  6 +-----
 4 files changed, 34 insertions(+), 53 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9bb4672..6650a54 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,7 +38,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']
+        python-version: ['3.9', '3.10', '3.11', '3.12']
       fail-fast: false
     steps:
     - name: Checkout
diff --git a/electrolytes/__init__.py b/electrolytes/__init__.py
index 461e97f..cb015ad 100644
--- a/electrolytes/__init__.py
+++ b/electrolytes/__init__.py
@@ -1,16 +1,7 @@
-import sys
 import pkgutil
 from pathlib import Path
-from typing import Collection, Iterator, List, Sequence, Dict, Optional, Any
-
-if sys.version_info >= (3, 9):
-    from typing import Annotated
-else:
-    from typing_extensions import Annotated
-if sys.version_info >= (3, 8):
-    from functools import cached_property
-else:
-    from backports.cached_property import cached_property
+from typing import Collection, Iterator, Sequence, Optional, Any, Annotated
+from functools import cached_property
 from contextlib import ContextDecorator
 from warnings import warn
 
@@ -31,16 +22,16 @@
 class Constituent(BaseModel, populate_by_name=True, frozen=True):
     id: Optional[int] = None
     name: str
-    u_neg: Annotated[List[float], Field(alias="uNeg")] = (
+    u_neg: Annotated[list[float], Field(alias="uNeg")] = (
         []
     )  # [-neg_count, -neg_count+1, -neg_count+2, ..., -1]
-    u_pos: Annotated[List[float], Field(alias="uPos")] = (
+    u_pos: Annotated[list[float], Field(alias="uPos")] = (
         []
     )  # [+1, +2, +3, ..., +pos_count]
-    pkas_neg: Annotated[List[float], Field(alias="pKaNeg")] = (
+    pkas_neg: Annotated[list[float], Field(alias="pKaNeg")] = (
         []
     )  # [-neg_count, -neg_count+1, -neg_count+2, ..., -1]
-    pkas_pos: Annotated[List[float], Field(alias="pKaPos")] = (
+    pkas_pos: Annotated[list[float], Field(alias="pKaPos")] = (
         []
     )  # [+1, +2, +3, ..., +pos_count]
     neg_count: Annotated[int, Field(alias="negCount", validate_default=True)] = None  # type: ignore
@@ -109,7 +100,7 @@ def _all_uppercase(cls, v: str, info: ValidationInfo) -> str:
         return v
 
     @field_validator("pkas_neg", "pkas_pos")
-    def _pka_lengths(cls, v: List[float], info: ValidationInfo) -> List[float]:
+    def _pka_lengths(cls, v: list[float], info: ValidationInfo) -> list[float]:
         assert isinstance(info.field_name, str)
         if len(v) != len(info.data[f"u_{info.field_name[5:]}"]):
             raise ValueError(f"len({info.field_name}) != len(u_{info.field_name[5:]})")
@@ -134,16 +125,16 @@ def _pkas_not_increasing(self) -> "Constituent":
         return self
 
 
-_StoredConstituents = TypeAdapter(Dict[str, List[Constituent]])
+_StoredConstituents = TypeAdapter(dict[str, list[Constituent]])
 
 
 def _load_constituents(
-    data: bytes, context: Optional[Dict[str, str]] = None
-) -> List[Constituent]:
+    data: bytes, context: Optional[dict[str, str]] = None
+) -> list[Constituent]:
     return _StoredConstituents.validate_json(data, context=context)["constituents"]
 
 
-def _dump_constituents(constituents: List[Constituent]) -> bytes:
+def _dump_constituents(constituents: list[Constituent]) -> bytes:
     return _StoredConstituents.dump_json(
         {"constituents": constituents}, by_alias=True, indent=4
     )
@@ -158,7 +149,7 @@ def __init__(self, user_constituents_file: Path) -> None:
         self._user_constituents_dirty = False
 
     @cached_property
-    def _default_constituents(self) -> Dict[str, Constituent]:
+    def _default_constituents(self) -> dict[str, Constituent]:
         data = pkgutil.get_data(__package__, "db1.json")
         if data is None:
             raise RuntimeError("failed to load default constituents")
@@ -166,7 +157,7 @@ def _default_constituents(self) -> Dict[str, Constituent]:
         return {c.name: c for c in constituents}
 
     @cached_property
-    def _user_constituents(self) -> Dict[str, Constituent]:
+    def _user_constituents(self) -> dict[str, Constituent]:
         try:
             with self:
                 user_data = self._user_constituents_file.read_bytes()
diff --git a/electrolytes/__main__.py b/electrolytes/__main__.py
index 2caba99..5f9f42a 100644
--- a/electrolytes/__main__.py
+++ b/electrolytes/__main__.py
@@ -1,10 +1,4 @@
-import sys
-from typing import Tuple, List, Optional
-
-if sys.version_info >= (3, 9):
-    from typing import Annotated
-else:
-    from typing_extensions import Annotated
+from typing import Optional, Annotated
 
 import typer
 
@@ -14,11 +8,11 @@
 app = typer.Typer()
 
 
-def complete_name(incomplete: str) -> List[str]:
+def complete_name(incomplete: str) -> list[str]:
     return [name for name in database if name.startswith(incomplete.upper())]
 
 
-def complete_name_user_defined(incomplete: str) -> List[str]:
+def complete_name_user_defined(incomplete: str) -> list[str]:
     return [
         name for name in database.user_defined() if name.startswith(incomplete.upper())
     ]
@@ -27,18 +21,18 @@ def complete_name_user_defined(incomplete: str) -> List[str]:
 @app.command()
 def add(
     name: Annotated[str, typer.Argument(autocompletion=complete_name_user_defined)],
-    p1: Annotated[Tuple[float, float], typer.Option("+1", help="Mobility (*1e-9) and pKa for +1", show_default=False)] = (None, None),  # type: ignore
-    p2: Annotated[Tuple[float, float], typer.Option("+2", help="Mobility (*1e-9) and pKa for +2", show_default=False)] = (None, None),  # type: ignore
-    p3: Annotated[Tuple[float, float], typer.Option("+3", help="Mobility (*1e-9) and pKa for +3", show_default=False)] = (None, None),  # type: ignore
-    p4: Annotated[Tuple[float, float], typer.Option("+4", help="Mobility (*1e-9) and pKa for +4", show_default=False)] = (None, None),  # type: ignore
-    p5: Annotated[Tuple[float, float], typer.Option("+5", help="Mobility (*1e-9) and pKa for +5", show_default=False)] = (None, None),  # type: ignore
-    p6: Annotated[Tuple[float, float], typer.Option("+6", help="Mobility (*1e-9) and pKa for +6", show_default=False)] = (None, None),  # type: ignore
-    m1: Annotated[Tuple[float, float], typer.Option("-1", help="Mobility (*1e-9) and pKa for -1", show_default=False)] = (None, None),  # type: ignore
-    m2: Annotated[Tuple[float, float], typer.Option("-2", help="Mobility (*1e-9) and pKa for -2", show_default=False)] = (None, None),  # type: ignore
-    m3: Annotated[Tuple[float, float], typer.Option("-3", help="Mobility (*1e-9) and pKa for -3", show_default=False)] = (None, None),  # type: ignore
-    m4: Annotated[Tuple[float, float], typer.Option("-4", help="Mobility (*1e-9) and pKa for -4", show_default=False)] = (None, None),  # type: ignore
-    m5: Annotated[Tuple[float, float], typer.Option("-5", help="Mobility (*1e-9) and pKa for -5", show_default=False)] = (None, None),  # type: ignore
-    m6: Annotated[Tuple[float, float], typer.Option("-6", help="Mobility (*1e-9) and pKa for -6", show_default=False)] = (None, None),  # type: ignore
+    p1: Annotated[tuple[float, float], typer.Option("+1", help="Mobility (*1e-9) and pKa for +1", show_default=False)] = (None, None),  # type: ignore
+    p2: Annotated[tuple[float, float], typer.Option("+2", help="Mobility (*1e-9) and pKa for +2", show_default=False)] = (None, None),  # type: ignore
+    p3: Annotated[tuple[float, float], typer.Option("+3", help="Mobility (*1e-9) and pKa for +3", show_default=False)] = (None, None),  # type: ignore
+    p4: Annotated[tuple[float, float], typer.Option("+4", help="Mobility (*1e-9) and pKa for +4", show_default=False)] = (None, None),  # type: ignore
+    p5: Annotated[tuple[float, float], typer.Option("+5", help="Mobility (*1e-9) and pKa for +5", show_default=False)] = (None, None),  # type: ignore
+    p6: Annotated[tuple[float, float], typer.Option("+6", help="Mobility (*1e-9) and pKa for +6", show_default=False)] = (None, None),  # type: ignore
+    m1: Annotated[tuple[float, float], typer.Option("-1", help="Mobility (*1e-9) and pKa for -1", show_default=False)] = (None, None),  # type: ignore
+    m2: Annotated[tuple[float, float], typer.Option("-2", help="Mobility (*1e-9) and pKa for -2", show_default=False)] = (None, None),  # type: ignore
+    m3: Annotated[tuple[float, float], typer.Option("-3", help="Mobility (*1e-9) and pKa for -3", show_default=False)] = (None, None),  # type: ignore
+    m4: Annotated[tuple[float, float], typer.Option("-4", help="Mobility (*1e-9) and pKa for -4", show_default=False)] = (None, None),  # type: ignore
+    m5: Annotated[tuple[float, float], typer.Option("-5", help="Mobility (*1e-9) and pKa for -5", show_default=False)] = (None, None),  # type: ignore
+    m6: Annotated[tuple[float, float], typer.Option("-6", help="Mobility (*1e-9) and pKa for -6", show_default=False)] = (None, None),  # type: ignore
     force: Annotated[
         bool,
         typer.Option(
@@ -55,7 +49,7 @@ def add(
         typer.echo("Error: at least one of the +1 or -1 options is required", err=True)
         raise typer.Exit(code=1)
 
-    neg: List[Tuple[float, float]] = []
+    neg: list[tuple[float, float]] = []
     any_omitted = False
     for i, m in enumerate([m1, m2, m3, m4, m5, m6]):
         if m[0] is None:
@@ -67,7 +61,7 @@ def add(
         else:
             neg.insert(0, m)
 
-    pos: List[Tuple[float, float]] = []
+    pos: list[tuple[float, float]] = []
     any_omitted = False
     for i, p in enumerate([p1, p2, p3, p4, p5, p6]):
         if p[0] is None:
@@ -104,7 +98,7 @@ def add(
 @app.command()
 def info(
     names: Annotated[
-        Optional[List[str]],
+        Optional[list[str]],
         typer.Argument(help="Component names", autocompletion=complete_name),
     ] = None
 ) -> None:
@@ -182,7 +176,7 @@ def ls(
 @app.command()
 def rm(
     names: Annotated[
-        List[str], typer.Argument(autocompletion=complete_name_user_defined)
+        list[str], typer.Argument(autocompletion=complete_name_user_defined)
     ],
     force: Annotated[
         Optional[bool], typer.Option("-f", help="Ignore non-existent components")
diff --git a/pyproject.toml b/pyproject.toml
index e2ff0bf..d80ae21 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
 name = "electrolytes"
 description = "Electrolyte database manager"
 readme = "README.md"
-requires-python = ">=3.7"
+requires-python = ">=3.9"
 authors = [{name = "Gabriel S. Gerlero", email = "ggerlero@cimec.unl.edu.ar"}]
 classifiers = [
     "Development Status :: 4 - Beta",
@@ -17,8 +17,6 @@ classifiers = [
     "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
     "Operating System :: OS Independent",
     "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",
     "Programming Language :: Python :: 3.11",
@@ -34,8 +32,6 @@ dependencies = [
     "typer>=0.9.0,<0.10",
     "pydantic>=2.0.3,<3",
     "filelock==3.*",
-    "typing-extensions>=3.7.4.3,<5; python_version<'3.9'",
-    "backports.cached_property>=1.0.2,<2; python_version<'3.8'",
 ]
 
 dynamic = ["version"]