From 714a3d53a16d7be5c5622a98dce75ddbc1ce6a27 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 21 Dec 2024 15:53:13 +0200 Subject: [PATCH] Drop support for EOL Python 3.8 --- .github/workflows/main.yml | 2 -- pelican/contents.py | 12 ++++++------ pelican/generators.py | 8 ++++---- pelican/settings.py | 4 ++-- pelican/tests/test_generators.py | 16 +++------------- pelican/tools/pelican_quickstart.py | 2 +- pelican/utils.py | 11 ++++------- pyproject.toml | 4 +--- tox.ini | 3 +-- 9 files changed, 22 insertions(+), 40 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 319c8fab..9e5a877b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,8 +17,6 @@ jobs: os: [ubuntu, macos, windows] python: ["3.10", "3.11", "3.12", "3.13"] include: - - os: ubuntu - python: "3.8" - os: ubuntu python: "3.9" diff --git a/pelican/contents.py b/pelican/contents.py index 2abdf492..c48715dc 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -6,7 +6,7 @@ import os import re from datetime import timezone from html import unescape -from typing import Any, Dict, Optional, Set, Tuple +from typing import Any, Optional from urllib.parse import ParseResult, unquote, urljoin, urlparse, urlunparse try: @@ -47,7 +47,7 @@ class Content: """ default_template: Optional[str] = None - mandatory_properties: Tuple[str, ...] = () + mandatory_properties: tuple[str, ...] = () @deprecated_attribute(old="filename", new="source_path", since=(3, 2, 0)) def filename(): @@ -56,10 +56,10 @@ class Content: def __init__( self, content: str, - metadata: Optional[Dict[str, Any]] = None, + metadata: Optional[dict[str, Any]] = None, settings: Optional[Settings] = None, source_path: Optional[str] = None, - context: Optional[Dict[Any, Any]] = None, + context: Optional[dict[Any, Any]] = None, ): if metadata is None: metadata = {} @@ -226,7 +226,7 @@ class Content: ) @property - def url_format(self) -> Dict[str, Any]: + def url_format(self) -> dict[str, Any]: """Returns the URL, formatted with the proper values""" metadata = copy.copy(self.metadata) path = self.metadata.get("path", self.get_relative_source_path()) @@ -397,7 +397,7 @@ class Content: hrefs = self._get_intrasite_link_regex() return hrefs.sub(lambda m: self._link_replacer(siteurl, m), content) - def get_static_links(self) -> Set[str]: + def get_static_links(self) -> set[str]: static_links = set() hrefs = self._get_intrasite_link_regex() for m in hrefs.finditer(self._content): diff --git a/pelican/generators.py b/pelican/generators.py index 207c9296..0e6b08ea 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -7,7 +7,7 @@ from collections import defaultdict from functools import partial from itertools import chain, groupby from operator import attrgetter -from typing import List, Optional, Set +from typing import Optional from jinja2 import ( BaseLoader, @@ -158,8 +158,8 @@ class Generator: return False def get_files( - self, paths, exclude: Optional[List[str]] = None, extensions=None - ) -> Set[str]: + self, paths, exclude: Optional[list[str]] = None, extensions=None + ) -> set[str]: """Return a list of files to use, based on rules :param paths: the list pf paths to search (relative to self.path) @@ -253,7 +253,7 @@ class Generator: # return the name of the class for logging purposes return self.__class__.__name__ - def _check_disabled_readers(self, paths, exclude: Optional[List[str]]) -> None: + def _check_disabled_readers(self, paths, exclude: Optional[list[str]]) -> None: """Log warnings for files that would have been processed by disabled readers.""" for fil in self.get_files( paths, exclude=exclude, extensions=self.readers.disabled_extensions diff --git a/pelican/settings.py b/pelican/settings.py index 1c6b5c76..84b7bf71 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -9,7 +9,7 @@ import sys from os.path import isabs from pathlib import Path from types import ModuleType -from typing import Any, Dict, Optional +from typing import Any, Optional from pelican.log import LimitFilter @@ -24,7 +24,7 @@ def load_source(name: str, path: str) -> ModuleType: logger = logging.getLogger(__name__) -Settings = Dict[str, Any] +Settings = dict[str, Any] DEFAULT_THEME = os.path.join( os.path.dirname(os.path.abspath(__file__)), "themes", "notmyidea" diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index e739e180..01add63c 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -1,5 +1,4 @@ import os -import sys from shutil import copy, rmtree from tempfile import mkdtemp from unittest.mock import MagicMock @@ -1528,18 +1527,9 @@ class TestStaticGenerator(unittest.TestCase): self.generator.generate_context() self.generator.generate_output(None) self.assertTrue(os.path.islink(self.endfile)) - - # os.path.realpath is broken on Windows before python3.8 for symlinks. - # This is a (ugly) workaround. - # see: https://bugs.python.org/issue9949 - if os.name == "nt" and sys.version_info < (3, 8): - - def get_real_path(path): - return os.readlink(path) if os.path.islink(path) else path - else: - get_real_path = os.path.realpath - - self.assertEqual(get_real_path(self.endfile), get_real_path(self.startfile)) + self.assertEqual( + os.path.realpath(self.endfile), os.path.realpath(self.startfile) + ) def test_delete_existing_file_before_mkdir(self): with open(self.startfile, "w") as f: diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index 77131a52..77657162 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -3,7 +3,7 @@ import argparse import locale import os -from typing import Mapping +from collections.abc import Mapping from jinja2 import Environment, FileSystemLoader diff --git a/pelican/utils.py b/pelican/utils.py index 69d9dde5..21dbe804 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -11,7 +11,7 @@ import shutil import sys import traceback import urllib -from collections.abc import Hashable +from collections.abc import Collection, Generator, Hashable, Iterable, Sequence from contextlib import contextmanager from functools import partial from html import entities @@ -22,10 +22,6 @@ from typing import ( TYPE_CHECKING, Any, Callable, - Collection, - Generator, - Iterable, - Sequence, ) import dateutil.parser @@ -133,8 +129,9 @@ class DateFormatter: def __call__(self, date: datetime.datetime, date_format: str) -> str: # on OSX, encoding from LC_CTYPE determines the unicode output in PY3 # make sure it's same as LC_TIME - with temporary_locale(self.locale, locale.LC_TIME), temporary_locale( - self.locale, locale.LC_CTYPE + with ( + temporary_locale(self.locale, locale.LC_TIME), + temporary_locale(self.locale, locale.LC_CTYPE), ): formatted = strftime(date, date_format) diff --git a/pyproject.toml b/pyproject.toml index 9359fa9f..813b098c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,6 @@ classifiers = [ "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -28,7 +27,7 @@ classifiers = [ "Topic :: Text Processing :: Markup :: HTML", "Topic :: Text Processing :: Markup :: reStructuredText", ] -requires-python = ">=3.8.1,<4.0" +requires-python = ">=3.9,<4.0" dependencies = [ "blinker>=1.7.0", "docutils>=0.20.1", @@ -39,7 +38,6 @@ dependencies = [ "python-dateutil>=2.8.2", "rich>=13.6.0", "unidecode>=1.3.7", - "backports-zoneinfo>=0.2.1; python_version < \"3.9\"", "watchfiles>=0.21.0", "tzdata; sys_platform == 'win32'", ] diff --git a/tox.ini b/tox.ini index def8cf4d..106e4580 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,8 @@ [tox] -envlist = py{3.8,3.9,3.10,3.11,3.12,3.13},docs +envlist = py{3.9,3.10,3.11,3.12,3.13},docs [testenv] basepython = - py3.8: python3.8 py3.9: python3.9 py3.10: python3.10 py3.11: python3.11