From 4f46fedd731664547fc2b5e61f1d8d2c157d2dd3 Mon Sep 17 00:00:00 2001 From: boxydog Date: Thu, 30 May 2024 09:02:23 -0500 Subject: [PATCH 1/2] More Ruff checks, and make it fix --- .pre-commit-config.yaml | 2 +- pyproject.toml | 99 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d6cfac07e..0e65a9659 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: rev: v0.1.15 hooks: - id: ruff + args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - args: ["--check"] exclude: ^pelican/tests/output/ diff --git a/pyproject.toml b/pyproject.toml index 3ca06df43..39694ffc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,3 +111,102 @@ source-includes = [ [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" + + +[tool.ruff.lint] +# see https://docs.astral.sh/ruff/configuration/#using-pyprojecttoml +# "F" contains autoflake, see https://github.com/astral-sh/ruff/issues/1647 +# add more rules +select = [ + # default Ruff checkers as of ruff 0.1.3: E4, E7, E9, F + "E4", + "E7", + "E9", + "F", # pyflakes + + # the rest in alphabetical order: + # TODO: "A", # flake8-builtins + # TODO: "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + # TODO: "BLE", # flake8-blind-except + # TODO: Do I want "COM", # flake8-commas + "C4", # flake8-comprehensions + # TODO: "DJ", # flake8-django + # TODO: "DTZ", # flake8-datetimez + # TODO: "EM", # flake8-errmsg + "EXE", # flake8-executable + # TODO: "FURB", # refurb + # TODO: "FBT", # flake8-boolean-trap + # TODO: "G", # flake8-logging-format + "I", # isort + "ICN", # flake8-import-conventions + "INP", # flake8-no-pep420 + # TODO: "INT", # flake8-gettext + "ISC", # flake8-implicit-str-concat + # TODO: "LOG", # flake8-logging + "PERF", # perflint + "PIE", # flake8-pie + "PL", # pylint + "PYI", # flake8-pyi + # TODO: "RET", # flake8-return + "RSE", # flake8-raise + "RUF", + # TODO: "SIM", # flake8-simplify + "SLF", # flake8-self + "SLOT", # flake8-slots + "TID", # flake8-tidy-imports + "UP", # pyupgrade + "Q", # flake8-quotes + "TCH", # flake8-type-checking + "T10", # flake8-debugger + "T20", # flake8-print + # TODO: "S", # flake8-bandit + "YTT", # flake8-2020 + # TODO: add more flake8 rules + ] + +ignore = [ + # suppression in order of # of violations in Dec 2023: + "B007", # unused-loop-control-variable + "T201", # print + "PLW2901", # redefined-loop-name + "SLF001", # private-member-access + "RUF001", # ambiguous-unicode-character-string + "PLR2004", # magic-value-comparison + "PLR0912", # too-many-branches + "PLR0913", # too-many-arguments + "RUF005", # collection-literal-concatenation + "RUF012", # mutable-class-default + "PLR0915", # too-many-statements + "INP001", # implicit-namespace-package + "RUF015", # unnecessary-iterable-allocation-for-first-element + "PLR1722", # sys-exit-alias + "ISC001", # single-line-implicit-string-concatenation + "C408", # unnecessary-collection-call + "B904", # raise-without-from-inside-except + "UP007", # use `|` operator for union type annotations (PEP 604) + "UP031", # printf-string-formatting + "PLR5501", # collapsible-else-if + "PERF203", # try-except-in-loop + "B006", # mutable-argument-default + "PLR1714", # repeated-equality-comparison + "PERF401", # manual-list-comprehension + # TODO: these only have one violation each in Dec 2023: + "SLOT000", # no-slots-in-str-subclass + "PYI024", # collections-named-tuple + "PLW0603", # global-statement + "PIE800", # unnecessary-spread + "ISC003", # explicit-string-concatenation + "EXE002", # shebang-missing-executable-file + "C401", # unnecessary-generator-set + "C416", # unnecessary `list` comprehension + "B028", # no-explicit-stacklevel + "B008", # function-call-in-default-argument +] + +[tool.ruff.lint.extend-per-file-ignores] + +"pelican/__init__.py" = [ + # allow imports after a call to a function, see the file + "E402" +] From 6d8597addb17d5fa3027ead91427939e8e4e89ec Mon Sep 17 00:00:00 2001 From: boxydog Date: Thu, 30 May 2024 09:05:36 -0500 Subject: [PATCH 2/2] The ruff and ruff-format fixes --- pelican/__init__.py | 8 ++--- pelican/__main__.py | 1 - pelican/contents.py | 13 ++++--- pelican/plugins/_utils.py | 1 - pelican/plugins/signals.py | 2 +- pelican/readers.py | 4 +-- pelican/rstdirectives.py | 1 - pelican/settings.py | 6 ++-- pelican/tests/build_test/test_build_files.py | 2 +- pelican/tests/support.py | 4 +-- pelican/tests/test_cache.py | 1 - pelican/tests/test_contents.py | 2 -- pelican/tests/test_generators.py | 2 +- pelican/tests/test_importer.py | 14 ++++---- pelican/tests/test_paginator.py | 1 - pelican/tests/test_plugins.py | 2 +- pelican/tests/test_readers.py | 1 - pelican/tests/test_settings.py | 1 - pelican/tools/pelican_import.py | 1 - pelican/tools/pelican_quickstart.py | 6 ++-- pelican/tools/pelican_themes.py | 14 ++------ pelican/urlwrappers.py | 2 +- pelican/utils.py | 37 +++++++++----------- pelican/writers.py | 1 - 24 files changed, 48 insertions(+), 79 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index 2fd69f1bc..aef4b124b 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -19,10 +19,10 @@ # pelican.log has to be the first pelican module to be loaded # because logging.setLoggerClass has to be called before logging.getLogger -from pelican.log import console +from pelican.log import console # noqa: I001 from pelican.log import init as init_logging from pelican.generators import ( - ArticlesGenerator, # noqa: I100 + ArticlesGenerator, PagesGenerator, SourceFileGenerator, StaticGenerator, @@ -354,8 +354,8 @@ def parse_arguments(argv=None): "--settings", dest="settings", help="The settings of the application, this is " - "automatically set to {} if a file exists with this " - "name.".format(DEFAULT_CONFIG_NAME), + f"automatically set to {DEFAULT_CONFIG_NAME} if a file exists with this " + "name.", ) parser.add_argument( diff --git a/pelican/__main__.py b/pelican/__main__.py index 17aead3bb..41a1f712a 100644 --- a/pelican/__main__.py +++ b/pelican/__main__.py @@ -4,6 +4,5 @@ from . import main - if __name__ == "__main__": main() diff --git a/pelican/contents.py b/pelican/contents.py index c640df698..9532c5232 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -17,6 +17,9 @@ from pelican.plugins import signals from pelican.settings import DEFAULT_CONFIG, Settings + +# Import these so that they're available when you import from pelican.contents. +from pelican.urlwrappers import Author, Category, Tag, URLWrapper # NOQA from pelican.utils import ( deprecated_attribute, memoized, @@ -28,9 +31,6 @@ truncate_html_words, ) -# Import these so that they're available when you import from pelican.contents. -from pelican.urlwrappers import Author, Category, Tag, URLWrapper # NOQA - logger = logging.getLogger(__name__) @@ -370,13 +370,13 @@ def _find_path(path: str) -> Optional[Content]: def _get_intrasite_link_regex(self) -> re.Pattern: intrasite_link_regex = self.settings["INTRASITE_LINK_REGEX"] - regex = r""" + regex = rf""" (?P<[^\>]+ # match tag with all url-value attributes (?:href|src|poster|data|cite|formaction|action|content)\s*=\s*) (?P["\']) # require value to be quoted - (?P{}(?P.*?)) # the url value - (?P=quote)""".format(intrasite_link_regex) + (?P{intrasite_link_regex}(?P.*?)) # the url value + (?P=quote)""" return re.compile(regex, re.X) def _update_content(self, content: str, siteurl: str) -> str: @@ -465,7 +465,6 @@ def _get_summary(self) -> str: @summary.setter def summary(self, value: str): """Dummy function""" - pass @property def status(self) -> str: diff --git a/pelican/plugins/_utils.py b/pelican/plugins/_utils.py index 805ed049f..9dfc8f814 100644 --- a/pelican/plugins/_utils.py +++ b/pelican/plugins/_utils.py @@ -6,7 +6,6 @@ import pkgutil import sys - logger = logging.getLogger(__name__) diff --git a/pelican/plugins/signals.py b/pelican/plugins/signals.py index 27177367f..c36f595df 100644 --- a/pelican/plugins/signals.py +++ b/pelican/plugins/signals.py @@ -1,4 +1,4 @@ -from blinker import signal, Signal +from blinker import Signal, signal from ordered_set import OrderedSet # Signals will call functions in the order of connection, i.e. plugin order diff --git a/pelican/readers.py b/pelican/readers.py index 60b9765a7..e9b07582f 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -22,7 +22,7 @@ try: from markdown import Markdown except ImportError: - Markdown = False # NOQA + Markdown = False # Metadata processors have no way to discard an unwanted value, so we have # them return this value instead to signal that it should be discarded later. @@ -607,8 +607,8 @@ def read_file( # eventually filter the content with typogrify if asked so if self.settings["TYPOGRIFY"]: - from typogrify.filters import typogrify import smartypants + from typogrify.filters import typogrify typogrify_dashes = self.settings["TYPOGRIFY_DASHES"] if typogrify_dashes == "oldschool": diff --git a/pelican/rstdirectives.py b/pelican/rstdirectives.py index 0a5494244..41bfc3d2e 100644 --- a/pelican/rstdirectives.py +++ b/pelican/rstdirectives.py @@ -2,7 +2,6 @@ from docutils import nodes, utils from docutils.parsers.rst import Directive, directives, roles - from pygments import highlight from pygments.formatters import HtmlFormatter from pygments.lexers import TextLexer, get_lexer_by_name diff --git a/pelican/settings.py b/pelican/settings.py index 47457ec1f..2cd6fb004 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -267,9 +267,7 @@ def _printf_s_to_format_field(printf_string: str, format_field: str) -> str: TEST_STRING = "PELICAN_PRINTF_S_DEPRECATION" expected = printf_string % TEST_STRING - result = printf_string.replace("{", "{{").replace("}", "}}") % "{{{}}}".format( - format_field - ) + result = printf_string.replace("{", "{{").replace("}", "}}") % f"{{{format_field}}}" if result.format(**{format_field: TEST_STRING}) != expected: raise ValueError(f"Failed to safely replace %s with {{{format_field}}}") @@ -412,7 +410,7 @@ def handle_deprecated_settings(settings: Settings) -> Settings: ) logger.warning(message) if old_values.get("SLUG"): - for f in {"CATEGORY", "TAG"}: + for f in ("CATEGORY", "TAG"): if old_values.get(f): old_values[f] = old_values["SLUG"] + old_values[f] old_values["AUTHOR"] = old_values.get("AUTHOR", []) diff --git a/pelican/tests/build_test/test_build_files.py b/pelican/tests/build_test/test_build_files.py index 9aad990da..c80253db2 100644 --- a/pelican/tests/build_test/test_build_files.py +++ b/pelican/tests/build_test/test_build_files.py @@ -1,6 +1,6 @@ -from re import match import tarfile from pathlib import Path +from re import match from zipfile import ZipFile import pytest diff --git a/pelican/tests/support.py b/pelican/tests/support.py index 160604412..f43468b2c 100644 --- a/pelican/tests/support.py +++ b/pelican/tests/support.py @@ -261,9 +261,7 @@ def assertLogCountEqual(self, count=None, msg=None, **kwargs): self.assertEqual( actual, count, - msg="expected {} occurrences of {!r}, but found {}".format( - count, msg, actual - ), + msg=f"expected {count} occurrences of {msg!r}, but found {actual}", ) diff --git a/pelican/tests/test_cache.py b/pelican/tests/test_cache.py index 6dc91b2c5..a1bbc5590 100644 --- a/pelican/tests/test_cache.py +++ b/pelican/tests/test_cache.py @@ -6,7 +6,6 @@ from pelican.generators import ArticlesGenerator, PagesGenerator from pelican.tests.support import get_context, get_settings, unittest - CUR_DIR = os.path.dirname(__file__) CONTENT_DIR = os.path.join(CUR_DIR, "content") diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index 9dc7b70d7..89219029a 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -13,7 +13,6 @@ from pelican.tests.support import LoggedTestCase, get_context, get_settings, unittest from pelican.utils import path_to_url, posixize_path, truncate_html_words - # generate one paragraph, enclosed with

TEST_CONTENT = str(generate_lorem_ipsum(n=1)) TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False) @@ -297,7 +296,6 @@ def test_template(self): def test_signal(self): def receiver_test_function(sender): receiver_test_function.has_been_called = True - pass receiver_test_function.has_been_called = False diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 8c257b550..920d9061a 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -13,11 +13,11 @@ TemplatePagesGenerator, ) from pelican.tests.support import ( + TestCaseWithCLocale, can_symlink, get_context, get_settings, unittest, - TestCaseWithCLocale, ) from pelican.writers import Writer diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 916c1183d..469184cd5 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -5,11 +5,11 @@ from pelican.settings import DEFAULT_CONFIG from pelican.tests.support import ( + TestCaseWithCLocale, mute, skipIfNoExecutable, temporary_folder, unittest, - TestCaseWithCLocale, ) from pelican.tools.pelican_import import ( blogger2fields, @@ -19,12 +19,12 @@ download_attachments, fields2pelican, get_attachments, - tumblr2fields, - wp2fields, + medium_slug, mediumpost2fields, mediumposts2fields, strip_medium_post_content, - medium_slug, + tumblr2fields, + wp2fields, ) from pelican.utils import path_to_file_url, slugify @@ -41,7 +41,7 @@ try: from bs4 import BeautifulSoup except ImportError: - BeautifulSoup = False # NOQA + BeautifulSoup = False try: import bs4.builder._lxml as LXML @@ -532,9 +532,7 @@ def test_attachments_associated_with_correct_post(self): self.assertEqual(self.attachments[post], {expected_invalid}) else: self.fail( - "all attachments should match to a " "filename or None, {}".format( - post - ) + "all attachments should match to a " f"filename or None, {post}" ) def test_download_attachments(self): diff --git a/pelican/tests/test_paginator.py b/pelican/tests/test_paginator.py index 2160421f7..6a7dbe02f 100644 --- a/pelican/tests/test_paginator.py +++ b/pelican/tests/test_paginator.py @@ -7,7 +7,6 @@ from pelican.settings import DEFAULT_CONFIG from pelican.tests.support import get_settings, unittest - # generate one paragraph, enclosed with

TEST_CONTENT = str(generate_lorem_ipsum(n=1)) TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False) diff --git a/pelican/tests/test_plugins.py b/pelican/tests/test_plugins.py index 55fa8a6a3..69a0384c8 100644 --- a/pelican/tests/test_plugins.py +++ b/pelican/tests/test_plugins.py @@ -1,7 +1,6 @@ import os from contextlib import contextmanager -import pelican.tests.dummy_plugins.normal_plugin.normal_plugin as normal_plugin from pelican.plugins._utils import ( get_namespace_plugins, get_plugin_name, @@ -9,6 +8,7 @@ plugin_enabled, ) from pelican.plugins.signals import signal +from pelican.tests.dummy_plugins.normal_plugin import normal_plugin from pelican.tests.support import unittest diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index 040498945..e49c8b74e 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -5,7 +5,6 @@ from pelican.tests.support import get_settings, unittest from pelican.utils import SafeDatetime - CUR_DIR = os.path.dirname(__file__) CONTENT_PATH = os.path.join(CUR_DIR, "content") diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index f370f7eba..84f7a5c98 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -3,7 +3,6 @@ import os from os.path import abspath, dirname, join - from pelican.settings import ( DEFAULT_CONFIG, DEFAULT_THEME, diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index eb343860d..3e1f31dbf 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -22,7 +22,6 @@ from pelican.settings import DEFAULT_CONFIG from pelican.utils import SafeDatetime, slugify - logger = logging.getLogger(__name__) diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index a4dc98e19..c00a252ca 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -169,7 +169,7 @@ def ask_timezone(question, default, tzurl): r = tz_dict[r] break else: - print("Please enter a valid time zone:\n" " (check [{}])".format(tzurl)) + print("Please enter a valid time zone:\n" f" (check [{tzurl}])") return r @@ -205,14 +205,14 @@ def main(): args = parser.parse_args() print( - """Welcome to pelican-quickstart v{v}. + f"""Welcome to pelican-quickstart v{__version__}. This script will help you create a new Pelican-based website. Please answer the following questions so this script can generate the files needed by Pelican. - """.format(v=__version__) + """ ) project = os.path.join(os.environ.get("VIRTUAL_ENV", os.curdir), ".project") diff --git a/pelican/tools/pelican_themes.py b/pelican/tools/pelican_themes.py index c5b49b9f3..fa59b8fdc 100755 --- a/pelican/tools/pelican_themes.py +++ b/pelican/tools/pelican_themes.py @@ -240,15 +240,11 @@ def install(path, v=False, u=False): except OSError as e: err( "Cannot change permissions of files " - "or directory in `{r}':\n{e}".format(r=theme_path, e=str(e)), + f"or directory in `{theme_path}':\n{e!s}", die=False, ) except Exception as e: - err( - "Cannot copy `{p}' to `{t}':\n{e}".format( - p=path, t=theme_path, e=str(e) - ) - ) + err(f"Cannot copy `{path}' to `{theme_path}':\n{e!s}") def symlink(path, v=False): @@ -268,11 +264,7 @@ def symlink(path, v=False): try: os.symlink(path, theme_path) except Exception as e: - err( - "Cannot link `{p}' to `{t}':\n{e}".format( - p=path, t=theme_path, e=str(e) - ) - ) + err(f"Cannot link `{path}' to `{theme_path}':\n{e!s}") def is_broken_link(path): diff --git a/pelican/urlwrappers.py b/pelican/urlwrappers.py index 6d705d4c1..4ed385f91 100644 --- a/pelican/urlwrappers.py +++ b/pelican/urlwrappers.py @@ -98,7 +98,7 @@ def __str__(self): return self.name def __repr__(self): - return f"<{type(self).__name__} {repr(self._name)}>" + return f"<{type(self).__name__} {self._name!r}>" def _from_settings(self, key, get_page_name=False): """Returns URL information as defined in settings. diff --git a/pelican/utils.py b/pelican/utils.py index 86698aee0..7017c4583 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -23,14 +23,10 @@ Any, Callable, Collection, - Dict, Generator, Iterable, - List, Optional, Sequence, - Tuple, - Type, Union, ) @@ -40,9 +36,8 @@ from zoneinfo import ZoneInfo except ModuleNotFoundError: from backports.zoneinfo import ZoneInfo -from markupsafe import Markup - import watchfiles +from markupsafe import Markup if TYPE_CHECKING: from pelican.contents import Content @@ -158,7 +153,7 @@ class memoized: def __init__(self, func: Callable) -> None: self.func = func - self.cache: Dict[Any, Any] = {} + self.cache: dict[Any, Any] = {} def __call__(self, *args) -> Any: if not isinstance(args, Hashable): @@ -185,8 +180,8 @@ def __get__(self, obj: Any, objtype): def deprecated_attribute( old: str, new: str, - since: Tuple[int, ...], - remove: Optional[Tuple[int, ...]] = None, + since: tuple[int, ...], + remove: Optional[tuple[int, ...]] = None, doc: Optional[str] = None, ): """Attribute deprecation decorator for gentle upgrades @@ -256,7 +251,7 @@ def pelican_open( def slugify( value: str, - regex_subs: Iterable[Tuple[str, str]] = (), + regex_subs: Iterable[tuple[str, str]] = (), preserve_case: bool = False, use_unicode: bool = False, ) -> str: @@ -642,9 +637,9 @@ def truncate_html_words(s: str, num: int, end_text: str = "…") -> str: def process_translations( - content_list: List[Content], + content_list: list[Content], translation_id: Optional[Union[str, Collection[str]]] = None, -) -> Tuple[List[Content], List[Content]]: +) -> tuple[list[Content], list[Content]]: """Finds translations and returns them. For each content_list item, populates the 'translations' attribute, and @@ -674,14 +669,14 @@ def process_translations( content_list.sort(key=attrgetter(*translation_id)) except TypeError: raise TypeError( - "Cannot unpack {}, 'translation_id' must be falsy, a" - " string or a collection of strings".format(translation_id) + f"Cannot unpack {translation_id}, 'translation_id' must be falsy, a" + " string or a collection of strings" ) except AttributeError: raise AttributeError( - "Cannot use {} as 'translation_id', there " + f"Cannot use {translation_id} as 'translation_id', there " "appear to be items without these metadata " - "attributes".format(translation_id) + "attributes" ) for id_vals, items in groupby(content_list, attrgetter(*translation_id)): @@ -702,7 +697,7 @@ def process_translations( return index, translations -def get_original_items(items: List[Content], with_str: str) -> List[Content]: +def get_original_items(items: list[Content], with_str: str) -> list[Content]: def _warn_source_paths(msg, items, *extra): args = [len(items)] args.extend(extra) @@ -743,9 +738,9 @@ def _warn_source_paths(msg, items, *extra): def order_content( - content_list: List[Content], + content_list: list[Content], order_by: Union[str, Callable[[Content], Any], None] = "slug", -) -> List[Content]: +) -> list[Content]: """Sorts content. order_by can be a string of an attribute or sorting function. If order_by @@ -807,8 +802,8 @@ def order_content( def wait_for_changes( settings_file: str, - reader_class: Type["Readers"], - settings: "Settings", + reader_class: type[Readers], + settings: Settings, ): content_path = settings.get("PATH", "") theme_path = settings.get("THEME", "") diff --git a/pelican/writers.py b/pelican/writers.py index 1c41b4ee8..746811e12 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -4,7 +4,6 @@ from urllib.parse import urljoin from feedgenerator import Atom1Feed, Rss201rev2Feed, get_tag_uri - from markupsafe import Markup from pelican.paginator import Paginator