From f05b11236325badfecb84f95172f2cec50b0153b Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Mon, 13 Jul 2026 09:41:49 -0400 Subject: [PATCH] packaging: Update min Python version to 3.11 Python 3.10 receives security fixes only and reaches end of life in October 2026, before the next release. Per RFC 8, raise the minimum supported Python version from 3.10 to 3.11. Update the version in REQUIREMENTS.md, pyproject.toml (requires-python, Black, Pylint, Pyright, and isort) and CI (pytest matrix and MIN_PYTHON_VERSION). Update places in code which add code for the older version: - utils/mkdocs.py: datetime.fromisoformat parses the Z suffix used by recent Git natively since 3.11, so the fallback is no longer needed. - Use the datetime.UTC alias added in 3.11. Ruff enables this check (UP017) once it infers the higher target version from requires-python. - Drop the toml extra for bandit. It provided a TOML parser for reading the bandit configuration from pyproject.toml, which the standard library covers since 3.11 with tomllib. Here the deciding version is the one the workflow runs, which is well past 3.11 already. --- .github/workflows/pytest.yml | 2 +- .github/workflows/python-code-quality.yml | 5 ++--- REQUIREMENTS.md | 2 +- lib/init/grass.py | 2 +- pyproject.toml | 10 +++++----- python/grass/script/setup.py | 4 ++-- utils/mkdocs.py | 12 +----------- 7 files changed, 13 insertions(+), 24 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 5d42c064edc..a04dcb0c3a1 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -23,7 +23,7 @@ jobs: os: - ubuntu-24.04 python-version: - - "3.10" + - "3.11" - "3.13" fail-fast: true diff --git a/.github/workflows/python-code-quality.yml b/.github/workflows/python-code-quality.yml index cd5a86d752d..3dfc4ba6783 100644 --- a/.github/workflows/python-code-quality.yml +++ b/.github/workflows/python-code-quality.yml @@ -28,7 +28,7 @@ jobs: env: # renovate: datasource=python-version depName=python PYTHON_VERSION: "3.14" - MIN_PYTHON_VERSION: "3.10" + MIN_PYTHON_VERSION: "3.11" # renovate: datasource=pypi depName=flake8 FLAKE8_VERSION: "7.3.0" # renovate: datasource=pypi depName=pylint @@ -111,8 +111,7 @@ jobs: pipx install "flake8==${FLAKE8_VERSION}" pipx install "pylint==${PYLINT_VERSION}" pipx inject pylint -r .github/workflows/python_requirements.txt -r .github/workflows/optional_requirements.txt - # The extra toml is only needed before Python 3.11 - pipx install "bandit[sarif,toml]==${BANDIT_VERSION}" + pipx install "bandit[sarif]==${BANDIT_VERSION}" - name: Run Flake8 run: | diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 583690b07d9..5f20b381b51 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -34,7 +34,7 @@ for other platforms you may have to install some of them. - **GDAL/OGR >= 3.7** for import and export of most external raster and vector map formats GDAL: [https://gdal.org](https://gdal.org) -- **Python >= 3.10** (for temporal framework, scripts, wxGUI, and ctypes interface) +- **Python >= 3.11** (for temporal framework, scripts, wxGUI, and ctypes interface) [https://www.python.org](https://www.python.org) - **MkDocs** with "Material" theme Python packages for the manual pages: See `man/mkdocs/requirements.txt`. diff --git a/lib/init/grass.py b/lib/init/grass.py index f654b9afcc5..21605f0268c 100755 --- a/lib/init/grass.py +++ b/lib/init/grass.py @@ -2439,7 +2439,7 @@ def main() -> None: fatal(e.args[0]) sys.exit(_("Exiting...")) - start_time = datetime.datetime.now(datetime.timezone.utc) + start_time = datetime.datetime.now(datetime.UTC) # unlock the mapset which is current at the time of turning off # in case mapset was changed diff --git a/pyproject.toml b/pyproject.toml index 7fce135dedc..b4a922f7fb5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [project] name = "grass" -requires-python = ">=3.10" +requires-python = ">=3.11" # *GRASS TODO: keep in sync with MIN_PYTHON_VERSION supported* [tool.black] line-length = 88 required-version = '25' -target-version = ['py310', 'py311', 'py312', 'py313', 'py314'] +target-version = ['py311', 'py312', 'py313', 'py314'] # 'extend-exclude' excludes files or directories in addition to the defaults extend-exclude = ''' ( @@ -603,7 +603,7 @@ load-plugins = [ # version used to run pylint. # *GRASS modified* # *GRASS TODO: keep in sync with MIN_PYTHON_VERSION supported* -py-version = "3.10" +py-version = "3.11" # Add paths to the list of the source roots. Supports globbing patterns. The # source root is an absolute path or a path relative to the current working @@ -880,7 +880,7 @@ include = ["python"] # *GRASS modified* # *GRASS TODO: keep in sync with MIN_PYTHON_VERSION supported* -pythonVersion = "3.10" +pythonVersion = "3.11" [tool.pyrefact] line_length = 88 @@ -888,4 +888,4 @@ line_length = 88 [tool.isort] profile = "black" # *GRASS TODO: keep in sync with MIN_PYTHON_VERSION supported* -py_version = 310 +py_version = 311 diff --git a/python/grass/script/setup.py b/python/grass/script/setup.py index 0102131c423..454bbe5d0e2 100644 --- a/python/grass/script/setup.py +++ b/python/grass/script/setup.py @@ -485,7 +485,7 @@ class SessionHandle: def __init__(self, *, env, active=True, locked=False): self._env = env self._active = active - self._start_time = datetime.datetime.now(datetime.timezone.utc) + self._start_time = datetime.datetime.now(datetime.UTC) self._locked = locked @property @@ -563,7 +563,7 @@ def clean_default_db(*, modified_after=None, env=None, gis_env=None): return if modified_after: modified_time = datetime.datetime.fromtimestamp( - file_stat.st_mtime, tz=datetime.timezone.utc + file_stat.st_mtime, tz=datetime.UTC ) if modified_after >= modified_time: return diff --git a/utils/mkdocs.py b/utils/mkdocs.py index 39e2658016b..a8a7d7848dc 100644 --- a/utils/mkdocs.py +++ b/utils/mkdocs.py @@ -208,17 +208,7 @@ def format_git_commit_date_from_local_git( :return str: output formatted commit datetime """ - try: - date = datetime.fromisoformat( - commit_datetime, - ) - except ValueError: - if commit_datetime.endswith("Z"): - # Python 3.10 and older does not support Z in time, while recent versions - # of Git (2.45.1) use it. Try to help the parsing if Z is in the string. - date = datetime.fromisoformat(commit_datetime[:-1] + "+00:00") - else: - raise + date = datetime.fromisoformat(commit_datetime) return date.strftime(datetime_format)