From 7b26b6d445773067c1d2e6ced58bb17e13eb78b9 Mon Sep 17 00:00:00 2001 From: Mike Taves Date: Thu, 17 Sep 2026 11:49:58 +1200 Subject: [PATCH] Drop Python 3.7 --- .basedpyright/baseline.json | 992 ++++++++++++++-------------- .github/workflows/alpine-test.yml | 2 +- .github/workflows/cygwin-test.yml | 2 +- .github/workflows/dependencies.yml | 4 +- .github/workflows/pythonpackage.yml | 16 +- README.md | 2 +- doc/source/intro.rst | 2 +- fuzzing/fuzz-targets/utils.py | 2 +- git/__init__.py | 2 +- git/config.py | 21 +- git/index/base.py | 5 +- git/objects/blob.py | 6 +- git/objects/commit.py | 7 +- git/objects/submodule/base.py | 10 +- git/objects/tag.py | 9 +- git/objects/tree.py | 7 +- git/types.py | 24 +- git/util.py | 15 +- pyproject.toml | 6 +- test/deprecation/test_types.py | 7 +- test/test_fun.py | 2 +- test/test_git.py | 7 +- test/test_index.py | 8 +- test/test_installation.py | 6 +- test/test_repo.py | 5 +- tox.ini | 12 +- 26 files changed, 550 insertions(+), 631 deletions(-) diff --git a/.basedpyright/baseline.json b/.basedpyright/baseline.json index 79d35235e..bb3104f73 100644 --- a/.basedpyright/baseline.json +++ b/.basedpyright/baseline.json @@ -1,502 +1,494 @@ { - "files": { - "./git/config.py": [ - { - "code": "reportGeneralTypeIssues", - "range": { - "startColumn": 11, - "endColumn": 26, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 43, - "endColumn": 45, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 41, - "endColumn": 48, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 32, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportCallIssue", - "range": { - "startColumn": 25, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 39, - "lineCount": 1 - } - } - ], - "./git/db.py": [ - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 12, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - } - ], - "./git/index/base.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportAssignmentType", - "range": { - "startColumn": 38, - "endColumn": 76, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 51, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 55, - "endColumn": 61, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 12, - "lineCount": 1 - } - }, - { - "code": "reportAssignmentType", - "range": { - "startColumn": 20, - "endColumn": 46, - "lineCount": 1 - } - } - ], - "./git/objects/blob.py": [ - { - "code": "reportIncompatibleVariableOverride", - "range": { - "startColumn": 4, - "endColumn": 8, - "lineCount": 1 - } - } - ], - "./git/objects/commit.py": [ - { - "code": "reportIncompatibleVariableOverride", - "range": { - "startColumn": 4, - "endColumn": 8, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 20, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 20, - "endColumn": 30, - "lineCount": 1 - } - } - ], - "./git/objects/submodule/base.py": [ - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 19, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 19, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 23, - "endColumn": 25, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 23, - "endColumn": 25, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 52, - "endColumn": 84, - "lineCount": 1 - } - } - ], - "./git/objects/tag.py": [ - { - "code": "reportIncompatibleVariableOverride", - "range": { - "startColumn": 4, - "endColumn": 8, - "lineCount": 1 - } - } - ], - "./git/objects/tree.py": [ - { - "code": "reportIncompatibleVariableOverride", - "range": { - "startColumn": 4, - "endColumn": 8, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 19, - "endColumn": 83, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 54, - "lineCount": 1 - } - } - ], - "./git/objects/util.py": [ - { - "code": "reportAssignmentType", - "range": { - "startColumn": 23, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 22, - "endColumn": 26, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 54, - "lineCount": 1 - } - } - ], - "./git/refs/log.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 17, - "endColumn": 22, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 30, - "lineCount": 1 - } - } - ], - "./git/refs/reference.py": [ - { - "code": "reportIncompatibleVariableOverride", - "range": { - "startColumn": 13, - "endColumn": 17, - "lineCount": 1 - } - } - ], - "./git/refs/symbolic.py": [ - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 20, - "lineCount": 1 - } - } - ], - "./git/refs/tag.py": [ - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - } - ], - "./git/remote.py": [ - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 26, - "endColumn": 38, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 26, - "endColumn": 38, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 26, - "endColumn": 38, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 26, - "endColumn": 38, - "lineCount": 1 - } - } - ], - "./git/repo/base.py": [ - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 28, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 21, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 34, - "endColumn": 44, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 65, - "endColumn": 79, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 82, - "endColumn": 102, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 50, - "endColumn": 69, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 68, - "endColumn": 85, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 88, - "endColumn": 111, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 51, - "endColumn": 73, - "lineCount": 1 - } - } - ], - "./git/repo/fun.py": [ - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 20, - "lineCount": 1 - } - } - ], - "./test/deprecation/test_basic.py": [ - { - "code": "reportUnusedExpression", - "range": { - "startColumn": 12, - "endColumn": 62, - "lineCount": 1 - } - } - ] - } + "files": { + "./git/config.py": [ + { + "code": "reportInvalidTypeVarUse", + "range": { + "startColumn": 43, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 41, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 32, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 25, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 39, + "lineCount": 1 + } + } + ], + "./git/db.py": [ + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 14, + "lineCount": 1 + } + } + ], + "./git/index/base.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAssignmentType", + "range": { + "startColumn": 38, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 51, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 55, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportAssignmentType", + "range": { + "startColumn": 20, + "endColumn": 46, + "lineCount": 1 + } + } + ], + "./git/objects/blob.py": [ + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + } + ], + "./git/objects/commit.py": [ + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 20, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 20, + "endColumn": 30, + "lineCount": 1 + } + } + ], + "./git/objects/submodule/base.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 19, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 19, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 23, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 23, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 52, + "endColumn": 84, + "lineCount": 1 + } + } + ], + "./git/objects/tag.py": [ + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + } + ], + "./git/objects/tree.py": [ + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 19, + "endColumn": 83, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 54, + "lineCount": 1 + } + } + ], + "./git/objects/util.py": [ + { + "code": "reportAssignmentType", + "range": { + "startColumn": 23, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 22, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 54, + "lineCount": 1 + } + } + ], + "./git/refs/log.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 17, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 30, + "lineCount": 1 + } + } + ], + "./git/refs/reference.py": [ + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 13, + "endColumn": 17, + "lineCount": 1 + } + } + ], + "./git/refs/symbolic.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 15, + "endColumn": 20, + "lineCount": 1 + } + } + ], + "./git/refs/tag.py": [ + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 14, + "lineCount": 1 + } + } + ], + "./git/remote.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + } + ], + "./git/repo/base.py": [ + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 21, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 34, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 65, + "endColumn": 79, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 82, + "endColumn": 102, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 50, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 68, + "endColumn": 85, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 88, + "endColumn": 111, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 51, + "endColumn": 73, + "lineCount": 1 + } + } + ], + "./git/repo/fun.py": [ + { + "code": "reportReturnType", + "range": { + "startColumn": 11, + "endColumn": 20, + "lineCount": 1 + } + } + ], + "./test/deprecation/test_basic.py": [ + { + "code": "reportUnusedExpression", + "range": { + "startColumn": 12, + "endColumn": 62, + "lineCount": 1 + } + } + ] + } } diff --git a/.github/workflows/alpine-test.yml b/.github/workflows/alpine-test.yml index b10336a15..d87fdd079 100644 --- a/.github/workflows/alpine-test.yml +++ b/.github/workflows/alpine-test.yml @@ -89,7 +89,7 @@ jobs: command -v git python git version python --version - python -c 'import os, sys; print(f"sys.platform={sys.platform!r}, os.name={os.name!r}")' + python -c 'import os, sys; print(f"{sys.platform=}, {os.name=}")' - name: Test with pytest run: | diff --git a/.github/workflows/cygwin-test.yml b/.github/workflows/cygwin-test.yml index e14856e7e..4f7032f31 100644 --- a/.github/workflows/cygwin-test.yml +++ b/.github/workflows/cygwin-test.yml @@ -133,7 +133,7 @@ jobs: command -v git python git version python --version - python -c 'import os, sys; print(f"sys.platform={sys.platform!r}, os.name={os.name!r}")' + python -c 'import os, sys; print(f"{sys.platform=}, {os.name=}")' - name: Test with pytest (${{ matrix.additional-pytest-args }}) run: | diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index aa6f91531..535000696 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -11,12 +11,12 @@ permissions: jobs: test: - runs-on: ${{ matrix.python-version == '3.7' && 'ubuntu-22.04' || 'ubuntu-latest' }} + runs-on: ubuntu-latest strategy: fail-fast: false matrix: project: [smmap, gitdb] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.13t"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.13t"] steps: - uses: actions/checkout@v7 diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index cb9a9f285..3fbbe2ec6 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -17,22 +17,14 @@ jobs: strategy: matrix: os-type: [ubuntu, macos, windows] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"] exclude: - - os-type: macos - python-version: "3.7" # Not available for the ARM-based macOS runners. - os-type: macos python-version: "3.14t" - os-type: macos python-version: "3.15t" include: - - os-ver: latest - - os-type: ubuntu - python-version: "3.7" - os-ver: "22.04" - build-docs: true # We ensure documentation builds, except on very old interpreters. - - python-version: "3.7" - build-docs: false - python-version: "3.8" build-docs: false - experimental: false @@ -43,7 +35,7 @@ jobs: fail-fast: false - runs-on: ${{ matrix.os-type }}-${{ matrix.os-ver }} + runs-on: ${{ matrix.os-type }}-latest defaults: run: @@ -140,7 +132,7 @@ jobs: command -v git python git version python --version - python -c 'import os, sys; print(f"sys.platform={sys.platform!r}, os.name={os.name!r}")' + python -c 'import os, sys; print(f"{sys.platform=}, {os.name=}")' # For debugging hook tests on native Windows systems that may have WSL. - name: Show bash.exe candidates (Windows) @@ -152,7 +144,7 @@ jobs: continue-on-error: true - name: Check types with mypy - if: matrix.python-version != '3.7' && matrix.python-version != '3.8' + if: matrix.python-version != '3.8' run: | mypy --python-version="${PYTHON_VERSION%t}" # Version only, with no "t" for free-threaded. env: diff --git a/README.md b/README.md index c5a069bfe..029b4fa00 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ GitPython needs the `git` executable to be installed on the system and available by setting the `GIT_PYTHON_GIT_EXECUTABLE=` environment variable. - Git (1.7.x or newer) -- Python >= 3.7 +- Python >= 3.8 The list of dependencies are listed in [`./requirements.txt`](https://github.com/gitpython-developers/GitPython/blob/main/requirements.txt) and [`./test-requirements.txt`](https://github.com/gitpython-developers/GitPython/blob/main/test-requirements.txt). The installer takes care of installing them for you. diff --git a/doc/source/intro.rst b/doc/source/intro.rst index e1075b1c9..dec2ec0fa 100644 --- a/doc/source/intro.rst +++ b/doc/source/intro.rst @@ -13,7 +13,7 @@ The object database implementation is optimized for handling large quantities of Requirements ============ -* `Python`_ >= 3.7 +* `Python`_ >= 3.8 * `Git`_ 1.7.0 or newer It should also work with older versions, but it may be that some operations involving remotes will not work as expected. diff --git a/fuzzing/fuzz-targets/utils.py b/fuzzing/fuzz-targets/utils.py index 97e6eab98..34b05f7eb 100644 --- a/fuzzing/fuzz-targets/utils.py +++ b/fuzzing/fuzz-targets/utils.py @@ -49,7 +49,7 @@ def read_lines_from_file(file_path: str) -> list: except FileNotFoundError: print(f"File not found: {file_path}") return [] - except IOError as e: + except OSError as e: print(f"Error reading file {file_path}: {e}") return [] diff --git a/git/__init__.py b/git/__init__.py index 1b2360e3a..ecc6cd94e 100644 --- a/git/__init__.py +++ b/git/__init__.py @@ -295,6 +295,6 @@ def refresh(path: Optional[PathLike] = None) -> None: try: refresh() except Exception as _exc: - raise ImportError("Failed to initialize: {0}".format(_exc)) from _exc + raise ImportError(f"Failed to initialize: {_exc}") from _exc # } END initialize git executable path diff --git a/git/config.py b/git/config.py index f54b4b97e..ce55a0f6a 100644 --- a/git/config.py +++ b/git/config.py @@ -28,6 +28,7 @@ Dict, Generic, List, + OrderedDict, Sequence, Tuple, TypeVar, @@ -47,15 +48,7 @@ T_ConfigParser = TypeVar("T_ConfigParser", bound="GitConfigParser") T_OMD_value = TypeVar("T_OMD_value", str, bytes, int, float, bool, None) -if sys.version_info[:3] < (3, 7, 2): - # typing.Ordereddict not added until Python 3.7.2. - from collections import OrderedDict - - OrderedDict_OMD = OrderedDict -else: - from typing import OrderedDict - - OrderedDict_OMD = OrderedDict[str, List[T_OMD_value]] # type: ignore[assignment, misc] +OrderedDict_OMD = OrderedDict[str, List[T_OMD_value]] # ------------------------------------------------------------- @@ -259,7 +252,7 @@ def setlast(self, key: str, value: Any) -> None: self.getall(key)[-1] = value - def get(self, key: str, default: Union[_T, None] = None) -> Union[_T, None]: + def get(self, key: str, default: Union[_T, None] = None) -> Union[_T, None]: # type: ignore[override] return super().get(self._key(key), [default])[-1] def getall(self, key: str) -> List[_T]: @@ -381,7 +374,7 @@ def __init__( Reference to repository to use if ``[includeIf]`` sections are found in configuration files. """ - cp.RawConfigParser.__init__(self, dict_type=_OMD, allow_no_value=True) + cp.RawConfigParser.__init__(self, dict_type=cast(Any, _OMD), allow_no_value=True) self._dict: Callable[..., _OMD] self._defaults: _OMD self._sections: _OMD @@ -458,7 +451,7 @@ def release(self) -> None: try: self.write() - except IOError: + except OSError: _logger.error("Exception during destruction of GitConfigParser", exc_info=True) except ReferenceError: # This happens in Python 3... and usually means that some state cannot be @@ -759,7 +752,7 @@ def read(self) -> None: # type: ignore[override] with open(file_path, "rb") as fp: file_ok = True self._read(fp, fp.name) - except IOError: + except OSError: continue # Read includes and append those that we didn't handle yet. We expect all @@ -909,7 +902,7 @@ def write(self) -> None: def _assure_writable(self, method_name: str) -> None: if self.read_only: - raise IOError("Cannot execute non-constant method %s.%s" % (self, method_name)) + raise OSError(f"Cannot execute non-constant method {self}.{method_name}") def add_section(self, section: "cp._SectionName") -> None: """Assures added options will stay in order.""" diff --git a/git/index/base.py b/git/index/base.py index 560fc5e2c..edebc296d 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -523,7 +523,7 @@ def _write_path_to_stdin( if proc.stdin is not None: try: proc.stdin.write(("%s\n" % filepath).encode(defenc)) - except IOError as e: + except OSError as e: # Pipe broke, usually because some error happened. raise fmakeexc() from e # END write exception handling @@ -727,8 +727,7 @@ def _store_path(self, filepath: PathLike, fprogress: Callable) -> BaseIndexEntry st = os.lstat(filepath) # Handles non-symlinks as well. if S_ISLNK(st.st_mode): - # In PY3, readlink is a string, but we need bytes. - # In PY2, it was just OS encoded bytes, we assumed UTF-8. + # readlink is a string, but we need bytes. def open_stream() -> BinaryIO: return BytesIO(force_bytes(os.readlink(filepath), encoding=defenc)) else: diff --git a/git/objects/blob.py b/git/objects/blob.py index f7d49c9cc..53d7683fe 100644 --- a/git/objects/blob.py +++ b/git/objects/blob.py @@ -7,12 +7,8 @@ from mimetypes import guess_type import os -import sys -if sys.version_info >= (3, 8): - from typing import Literal -else: - from typing_extensions import Literal +from typing import Literal from . import base diff --git a/git/objects/commit.py b/git/objects/commit.py index 0348d3299..5b73a3432 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -12,7 +12,6 @@ import os import re from subprocess import Popen, PIPE -import sys from time import altzone, daylight, localtime, time, timezone import warnings @@ -41,6 +40,7 @@ IO, Iterator, List, + Literal, Sequence, Tuple, TYPE_CHECKING, @@ -48,11 +48,6 @@ cast, ) -if sys.version_info >= (3, 8): - from typing import Literal -else: - from typing_extensions import Literal - from git.types import PathLike if TYPE_CHECKING: diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index ba281c499..c023280e9 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -54,6 +54,7 @@ Dict, Iterator, List, + Literal, Mapping, Sequence, TYPE_CHECKING, @@ -61,11 +62,6 @@ cast, ) -if sys.version_info >= (3, 8): - from typing import Literal -else: - from typing_extensions import Literal - from git.types import Commit_ish, PathLike, TBD if TYPE_CHECKING: @@ -262,7 +258,7 @@ def _config_parser( try: fp_module = cls._sio_modules(parent_commit) except KeyError as e: - raise IOError( + raise OSError( "Could not find %s file in the tree of parent commit %s" % (cls.k_modules_file, parent_commit) ) from e # END handle exceptions @@ -1738,7 +1734,7 @@ def iter_items( try: pc = repo.commit(parent_commit) # Parent commit instance parser = cls._config_parser(repo, pc, read_only=True) - except (IOError, BadName): + except (OSError, BadName): return # END handle empty iterator diff --git a/git/objects/tag.py b/git/objects/tag.py index 18b4a9ca4..e08d6ef10 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -11,8 +11,6 @@ __all__ = ["TagObject"] -import sys - from git.compat import defenc from git.util import Actor, hex_to_bin @@ -21,15 +19,10 @@ # typing ---------------------------------------------- -from typing import List, TYPE_CHECKING, Union +from typing import List, Literal, TYPE_CHECKING, Union from git.types import AnyGitObject -if sys.version_info >= (3, 8): - from typing import Literal -else: - from typing_extensions import Literal - if TYPE_CHECKING: from git.repo import Repo diff --git a/git/objects/tree.py b/git/objects/tree.py index a3d611c80..33ce0f907 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -6,7 +6,6 @@ __all__ = ["TreeModifier", "Tree"] import os -import sys import git.diff as git_diff from git.util import IterableList, join_path, to_bin_sha @@ -26,6 +25,7 @@ Iterable, Iterator, List, + Literal, Tuple, TYPE_CHECKING, Type, @@ -33,11 +33,6 @@ cast, ) -if sys.version_info >= (3, 8): - from typing import Literal -else: - from typing_extensions import Literal - from git.types import PathLike if TYPE_CHECKING: diff --git a/git/types.py b/git/types.py index 31d40bf3b..04434ac11 100644 --- a/git/types.py +++ b/git/types.py @@ -2,7 +2,6 @@ # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ import os -import sys from typing import ( Any, Callable, @@ -18,22 +17,13 @@ ) import warnings -if sys.version_info >= (3, 8): - from typing import ( - Literal, - Protocol, - SupportsIndex as SupportsIndex, - TypedDict, - runtime_checkable, - ) -else: - from typing_extensions import ( - Literal, - Protocol, - SupportsIndex as SupportsIndex, - TypedDict, - runtime_checkable, - ) +from typing import ( + Literal, + Protocol, + SupportsIndex as SupportsIndex, + TypedDict, + runtime_checkable, +) if TYPE_CHECKING: from git.objects import Commit, Tree, TagObject, Blob diff --git a/git/util.py b/git/util.py index f72e3d7c1..431fb5d2a 100644 --- a/git/util.py +++ b/git/util.py @@ -516,7 +516,7 @@ def decygpath(path: PathLike) -> str: #: Store boolean flags denoting if a specific Git executable -#: is from a Cygwin installation (since `cache_lru()` unsupported on PY2). +#: is from a Cygwin installation. TODO: use @functools.lru_cache(user_function) _is_cygwin_cache: Dict[str, Optional[bool]] = {} @@ -558,8 +558,7 @@ def is_cygwin_git(git_executable: PathLike) -> bool: ... def is_cygwin_git(git_executable: Union[None, PathLike]) -> bool: - # TODO: when py3.7 support is dropped, use the new interpolation f"{variable=}" - _logger.debug(f"sys.platform={sys.platform!r}, git_executable={git_executable!r}") + _logger.debug(f"{sys.platform=}, {git_executable=}") if sys.platform != "cygwin": return False elif git_executable is None: @@ -1140,7 +1139,7 @@ def _obtain_lock_or_raise(self) -> None: return lock_file = self._lock_file_path() if osp.isfile(lock_file): - raise IOError( + raise OSError( "Lock for file %r did already exist, delete %r in case the lock is illegal" % (self._file_path, lock_file) ) @@ -1149,7 +1148,7 @@ def _obtain_lock_or_raise(self) -> None: with open(lock_file, mode="w"): pass except OSError as e: - raise IOError(str(e)) from e + raise OSError(str(e)) from e self._owns_lock = True @@ -1216,7 +1215,7 @@ def _obtain_lock(self) -> None: while True: try: super()._obtain_lock() - except IOError as e: + except OSError as e: # synity check: if the directory leading to the lockfile is not # readable anymore, raise an exception curtime = time.time() @@ -1225,7 +1224,7 @@ def _obtain_lock(self) -> None: self._lock_file_path(), curtime - starttime, ) - raise IOError(msg) from e + raise OSError(msg) from e # END handle missing directory if curtime >= maxtime: @@ -1233,7 +1232,7 @@ def _obtain_lock(self) -> None: maxtime - starttime, self._lock_file_path(), ) - raise IOError(msg) from e + raise OSError(msg) from e # END abort if we wait too long time.sleep(self._check_interval) else: diff --git a/pyproject.toml b/pyproject.toml index 767f491ab..d5b0e6b94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ authors = [ {name = "Michael Trier", email = "mtrier@gmail.com"}, ] readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.8" keywords = ["git"] # license = "BSD-3-Clause" # not done until setuptools >= 77 (Python >= 3.9) classifiers = [ @@ -24,7 +24,6 @@ classifiers = [ "Typing :: Typed", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -92,7 +91,7 @@ ignore_missing_imports = true [tool.basedpyright] typeCheckingMode = "standard" -pythonVersion = "3.7" +pythonVersion = "3.8" include = [ "git", "test/deprecation", @@ -116,7 +115,6 @@ include = ["*/git/*"] omit = ["*/git/ext/*"] [tool.ruff] -target-version = "py37" line-length = 120 # Exclude a variety of commonly ignored directories. exclude = [ diff --git a/test/deprecation/test_types.py b/test/deprecation/test_types.py index d3c6af645..6e5926877 100644 --- a/test/deprecation/test_types.py +++ b/test/deprecation/test_types.py @@ -3,12 +3,7 @@ """Tests for dynamic and static characteristics of git.types module attributes.""" -import sys - -if sys.version_info >= (3, 8): - from typing import Literal -else: - from typing_extensions import Literal +from typing import Literal import pytest diff --git a/test/test_fun.py b/test/test_fun.py index 38f506ff4..8d594915a 100644 --- a/test/test_fun.py +++ b/test/test_fun.py @@ -303,7 +303,7 @@ def test_linked_worktree_traversal(self, rw_dir): statbuf = stat(gitdir) self.assertTrue(statbuf.st_mode & S_IFDIR) - def test_tree_entries_from_data_with_failing_name_decode_py3(self): + def test_tree_entries_from_data_with_failing_name_decode(self): r = tree_entries_from_data(b"100644 \x9f\0aaa") assert r == [(b"aaa", 33188, "\udc9f")], r diff --git a/test/test_git.py b/test/test_git.py index 14fc7dfc3..2ecb1dac5 100644 --- a/test/test_git.py +++ b/test/test_git.py @@ -19,12 +19,7 @@ import sys import tempfile import time -from unittest import skipUnless - -if sys.version_info >= (3, 8): - from unittest import mock -else: - import mock # To be able to examine call_args.kwargs on a mock. +from unittest import mock, skipUnless import ddt diff --git a/test/test_index.py b/test/test_index.py index 469a9dc17..cef470bc1 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -330,9 +330,7 @@ def add_bad_blob(): ## First, fail on purpose adding into index. add_bad_blob() except Exception as ex: - msg_py3 = "required argument is not an integer" - msg_py2 = "cannot convert argument to integer" - assert msg_py2 in str(ex) or msg_py3 in str(ex) + assert "required argument is not an integer" in str(ex) ## The second time should not fail due to stray lock file. try: @@ -1067,11 +1065,9 @@ def test_index_bare_add(self, rw_bare_repo): @with_rw_directory def test_add_utf8P_path(self, rw_dir): - # NOTE: fp is not a Unicode object in Python 2 - # (which is the source of the problem). fp = osp.join(rw_dir, "ø.txt") with open(fp, "wb") as fs: - fs.write("content of ø".encode("utf-8")) + fs.write("content of ø".encode()) r = Repo.init(rw_dir) r.index.add([fp]) diff --git a/test/test_installation.py b/test/test_installation.py index b8e970bdd..e8956d5cf 100644 --- a/test/test_installation.py +++ b/test/test_installation.py @@ -5,7 +5,6 @@ import functools import os import subprocess -import sys from test.lib import TestBase, VirtualEnvironment, requires_symlinks, with_rw_directory @@ -50,9 +49,6 @@ def _set_up_venv(rw_dir): target_is_directory=True, ) - # Turn warnings into exceptions except for Python 3.7 - pywarnings = "error" if tuple(sys.version_info)[0:2] > (3, 7) else "default" - # Create a convenience function to run commands in it. run = functools.partial( subprocess.run, @@ -60,7 +56,7 @@ def _set_up_venv(rw_dir): stderr=subprocess.PIPE, universal_newlines=True, cwd=venv.sources, - env={**os.environ, "PYTHONWARNINGS": pywarnings}, + env={**os.environ, "PYTHONWARNINGS": "error"}, ) return venv, run diff --git a/test/test_repo.py b/test/test_repo.py index b7b5718ff..b2740dfae 100644 --- a/test/test_repo.py +++ b/test/test_repo.py @@ -49,8 +49,7 @@ def iter_flatten(lol): for items in lol: - for item in items: - yield item + yield from items def flatten(lol): @@ -886,7 +885,7 @@ def test_config_writer(self): try: with self.rorepo.config_writer(config_level) as writer: self.assertFalse(writer.read_only) - except IOError: + except OSError: # It's okay not to get a writer for some configuration files if we # have no permissions. pass diff --git a/tox.ini b/tox.ini index fc3f821d6..c880cb4b6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] requires = tox>=4 -env_list = py{37,38,39,310,311,312,313,314,315}, ruff, format, mypy, html, misc +env_list = py{38,39,310,311,312,313,314,315}, ruff, format, mypy, html, misc [testenv] description = Run unit tests @@ -11,7 +11,7 @@ commands = pytest --color=yes {posargs} [testenv:ruff] description = Lint with Ruff -base_python = py{39,310,311,312,38,37} +base_python = py{39,310,311,312,38} deps = ruff set_env = CLICOLOR_FORCE = 1 # Set NO_COLOR to override this. @@ -19,7 +19,7 @@ commands = ruff check . [testenv:format] description = Check formatting with Ruff -base_python = py{39,310,311,312,38,37} +base_python = py{39,310,311,312,38} deps = ruff set_env = CLICOLOR_FORCE = 1 # Set NO_COLOR to override this. @@ -27,7 +27,7 @@ commands = ruff format --check . [testenv:mypy] description = Typecheck with mypy -base_python = py{39,310,311,312,38,37} +base_python = py{39,310,311,312,38} set_env = MYPY_FORCE_COLOR = 1 commands = mypy @@ -35,7 +35,7 @@ ignore_outcome = true [testenv:html] description = Build HTML documentation -base_python = py{39,310,311,312,38,37} +base_python = py{39,310,311,312,38} extras = doc allowlist_externals = make commands = @@ -44,7 +44,7 @@ commands = [testenv:misc] description = Run other checks via pre-commit -base_python = py{39,310,311,312,38,37} +base_python = py{39,310,311,312,38} set_env = SKIP = ruff-format,ruff commands = pre-commit run --all-files