Add support for Python 3.11 and 3.12, drop EOL 3.7 (#15)

* Bump GitHub Actions

* Update pre-commit

* Add noqa for: A004 import statement "format" is shadowing a Python builtin

* Add support for Python 3.11 and 3.12

* Drop support for EOL Python 3.7

* Update .github/workflows/tests.yaml

---------

Co-authored-by: Taneli Hukkinen <3275109+hukkin@users.noreply.github.com>
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index d88b88e..97230e5 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -13,8 +13,8 @@
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v2
-    - uses: actions/setup-python@v2
+    - uses: actions/checkout@v4
+    - uses: actions/setup-python@v4
       with:
         python-version: '3.8'
 
@@ -32,17 +32,18 @@
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        python-version: ['pypy-3.7', '3.7', '3.8', '3.9', '3.10', '3.11-dev']
+        python-version: ['pypy-3.10', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13-dev']
         os: [ubuntu-latest, macos-latest, windows-latest]
-    continue-on-error: ${{ matrix.python-version == '3.11-dev' }}
+    continue-on-error: ${{ matrix.python-version == '3.13-dev' }}
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v4
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
         python-version: ${{ matrix.python-version }}
+        allow-prereleases: true
 
     - name: Install test deps
       run: |
@@ -55,7 +56,7 @@
 
     - name: Report coverage
       if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.10'
-      uses: codecov/codecov-action@v1
+      uses: codecov/codecov-action@v3
 
   allgood:
     runs-on: ubuntu-latest
@@ -71,10 +72,10 @@
     if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
-    - uses: actions/setup-python@v2
+    - uses: actions/checkout@v4
+    - uses: actions/setup-python@v4
       with:
-        python-version: '3.7'
+        python-version: '3.x'
     - name: Install build and publish tools
       run: |
         pip install build twine
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index fabe0db..4c2bfbc 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,12 +1,12 @@
 repos:
 - repo: https://github.com/executablebooks/mdformat
-  rev: 82f84976fc57d5ae61cbf2d457a26a89d4b4eef4  # frozen: 0.7.16
+  rev: 08fba30538869a440b5059de90af03e3502e35fb  # frozen: 0.7.17
   hooks:
   - id: mdformat
     additional_dependencies:
     - mdformat-gfm
 - repo: https://github.com/asottile/yesqa
-  rev: 265e9ff7c83add4949f81bb5fe14f4a743ffb04c  # frozen: v1.4.0
+  rev: f2ae90cf9e1661ade79d0881186ce4fd7ba6ee79  # frozen: v1.5.0
   hooks:
   - id: yesqa
     additional_dependencies:
@@ -17,16 +17,16 @@
   rev: dbf82f2dd09ae41d9355bcd7ab69187a19e6bf2f  # frozen: 5.12.0
   hooks:
   - id: isort
-- repo: https://github.com/psf/black
-  rev: b0d1fba7ac3be53c71fb0d3211d911e629f8aecb  # frozen: 23.1.0
+- repo: https://github.com/psf/black-pre-commit-mirror
+  rev: 34974913abb598ee76541db1900f24fde7dd1d76  # frozen: 23.11.0
   hooks:
   - id: black
 - repo: https://github.com/PyCQA/docformatter
-  rev: 11d9a03865ae1f859d56517daec72e4f9f2401f0  # frozen: v1.5.1
+  rev: dfefe062799848234b4cd60b04aa633c0608025e  # frozen: v1.7.5
   hooks:
   - id: docformatter
 - repo: https://github.com/pre-commit/pre-commit-hooks
-  rev: f71fa2c1f9cf5cb705f73dffe4b21f7c61470ba9  # frozen: v4.4.0
+  rev: c4a0b883114b00d8d76b479c820ce7950211c99b  # frozen: v4.5.0
   hooks:
   - id: check-yaml
 - repo: https://github.com/pre-commit/pygrep-hooks
@@ -36,7 +36,7 @@
   - id: python-check-blanket-noqa
   - id: python-check-blanket-type-ignore
 - repo: https://github.com/PyCQA/flake8
-  rev: c838a5e98878f17889cfce311e1406d252f87ec5  # frozen: 6.0.0
+  rev: 10f4af6dbcf93456ba7df762278ae61ba3120dc6  # frozen: 6.1.0
   hooks:
   - id: flake8
     additional_dependencies:
@@ -44,7 +44,7 @@
     - flake8-builtins
     - flake8-comprehensions
 - repo: https://github.com/pre-commit/mirrors-mypy
-  rev: b176ead5d5b92b1d4e651c118017f8fd32de424a  # frozen: v1.1.1
+  rev: 4c8a8c7d5eb892ab5a6e23e11f36dbba5874d40c  # frozen: v1.7.0
   hooks:
   - id: mypy
     args: ["--scripts-are-modules"]
diff --git a/pyproject.toml b/pyproject.toml
index 916a588..785dd70 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,7 +10,7 @@
     { name = "Taneli Hukkinen", email = "hukkin@users.noreply.github.com" },
 ]
 license = { file = "LICENSE" }
-requires-python = ">=3.7"
+requires-python = ">=3.8"
 readme = "README.md"
 classifiers = [
     "License :: OSI Approved :: MIT License",
@@ -18,10 +18,11 @@
     "Operating System :: Microsoft :: Windows",
     "Operating System :: POSIX :: Linux",
     "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",
+    "Programming Language :: Python :: 3.12",
     "Programming Language :: Python :: Implementation :: CPython",
     "Programming Language :: Python :: Implementation :: PyPy",
     "Topic :: Software Development :: Libraries :: Python Modules",
@@ -55,10 +56,10 @@
 legacy_tox_ini = '''
 [tox]
 # Only run pytest envs when no args given to tox
-envlist = py{37,38,39,310}
+envlist = py{38,39,310,311,312}
 isolated_build = True
 
-[testenv:py{37,38,39,310}]
+[testenv:py{38,39,310,311,312}]
 description = run tests
 deps = -r tests/requirements.txt
 commands =
diff --git a/src/mdurl/__init__.py b/src/mdurl/__init__.py
index cdbb640..be39265 100644
--- a/src/mdurl/__init__.py
+++ b/src/mdurl/__init__.py
@@ -13,6 +13,6 @@
 
 from mdurl._decode import DECODE_COMPONENT_CHARS, DECODE_DEFAULT_CHARS, decode
 from mdurl._encode import ENCODE_COMPONENT_CHARS, ENCODE_DEFAULT_CHARS, encode
-from mdurl._format import format
+from mdurl._format import format  # noqa: A004
 from mdurl._parse import url_parse as parse
 from mdurl._url import URL
diff --git a/src/mdurl/_parse.py b/src/mdurl/_parse.py
index 1bfdd99..8d64748 100644
--- a/src/mdurl/_parse.py
+++ b/src/mdurl/_parse.py
@@ -113,7 +113,7 @@
         self.search: str | None = None
         self.pathname: str | None = None
 
-    def parse(self, url: str, slashes_denote_host: bool) -> "MutableURL":
+    def parse(self, url: str, slashes_denote_host: bool) -> MutableURL:
         lower_proto = ""
         slashes = False
         rest = url
diff --git a/tests/test_format.py b/tests/test_format.py
index 0cf1219..f8e4e03 100644
--- a/tests/test_format.py
+++ b/tests/test_format.py
@@ -1,6 +1,6 @@
 import pytest
 
-from mdurl import format, parse
+from mdurl import format, parse  # noqa: A004
 from tests.fixtures.url import PARSED as FIXTURES