From 3624bcdbf41f570471d24d59dc7ccb24f536e3fe Mon Sep 17 00:00:00 2001 From: boxydog Date: Thu, 30 May 2024 13:18:03 -0500 Subject: [PATCH 1/3] More ruff fixes: stop ignoring C408, UP007, PLR5501, B006 --- pyproject.toml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5546d1cf0..b7e39a4a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -182,14 +182,17 @@ ignore = [ "INP001", # implicit-namespace-package "RUF015", # unnecessary-iterable-allocation-for-first-element "PLR1722", # sys-exit-alias + # ruff-format wants us to ignore ISC001. I don't love that, but okay. + # "warning: The following rules may cause conflicts when used with the formatter: + # `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, + # either by removing them from the `select` or `extend-select` configuration, + # or adding them to the `ignore` configuration." "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 has minimal performance impact, and you have to catch the exception + # inside the loop if you want to ignore it, so let's ignore PERF203. "PERF203", # try-except-in-loop - "B006", # mutable-argument-default # TODO: these only have one violation each in Dec 2023: "SLOT000", # no-slots-in-str-subclass "PYI024", # collections-named-tuple From 7577dd7603f7cb3a09922d1edb65b6eafb6e2ac7 Mon Sep 17 00:00:00 2001 From: boxydog Date: Thu, 30 May 2024 13:21:12 -0500 Subject: [PATCH 2/3] More ruff fixes in files: stop ignoring C408, UP007, PLR5501, B006 --- pelican/__init__.py | 6 +++--- pelican/contents.py | 4 ++-- pelican/generators.py | 6 ++++-- pelican/paginator.py | 5 ++--- pelican/readers.py | 4 ++-- pelican/rstdirectives.py | 2 +- pelican/settings.py | 8 +++---- pelican/tests/test_contents.py | 28 ++++++++++++------------ pelican/tests/test_importer.py | 12 +++++------ pelican/tests/test_readers.py | 2 +- pelican/tests/test_utils.py | 4 ++-- pelican/tools/pelican_import.py | 4 ++-- pelican/tools/pelican_quickstart.py | 27 ++++++++++++----------- pelican/urlwrappers.py | 7 +++--- pelican/utils.py | 33 ++++++++++++----------------- pelican/writers.py | 2 +- 16 files changed, 72 insertions(+), 82 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index d63f88c3a..ab6b0225f 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -417,7 +417,7 @@ def parse_arguments(argv=None): "--relative-urls", dest="relative_paths", action="store_true", - help="Use relative urls in output, " "useful for site development", + help="Use relative urls in output, useful for site development", ) parser.add_argument( @@ -433,7 +433,7 @@ def parse_arguments(argv=None): "--ignore-cache", action="store_true", dest="ignore_cache", - help="Ignore content cache " "from previous runs by not loading cache files.", + help="Ignore content cache from previous runs by not loading cache files.", ) parser.add_argument( @@ -488,7 +488,7 @@ def parse_arguments(argv=None): "-b", "--bind", dest="bind", - help="IP to bind to when serving files via HTTP " "(default: 127.0.0.1)", + help="IP to bind to when serving files via HTTP (default: 127.0.0.1)", ) parser.add_argument( diff --git a/pelican/contents.py b/pelican/contents.py index 9532c5232..21c662963 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -72,7 +72,7 @@ def __init__( self._context = context self.translations = [] - local_metadata = dict() + local_metadata = {} local_metadata.update(metadata) # set metadata as attributes @@ -357,7 +357,7 @@ def _find_path(path: str) -> Optional[Content]: origin = joiner(siteurl, Author(path, self.settings).url) else: logger.warning( - "Replacement Indicator '%s' not recognized, " "skipping replacement", + "Replacement Indicator '%s' not recognized, skipping replacement", what, ) diff --git a/pelican/generators.py b/pelican/generators.py index 076c8d386..fdcc62ce4 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -156,7 +156,7 @@ def _include_path(self, path, extensions=None): return False - def get_files(self, paths, exclude=[], extensions=None): + def get_files(self, paths, exclude=None, extensions=None): """Return a list of files to use, based on rules :param paths: the list pf paths to search (relative to self.path) @@ -164,6 +164,8 @@ def get_files(self, paths, exclude=[], extensions=None): :param extensions: the list of allowed extensions (if False, all extensions are allowed) """ + if exclude is None: + exclude = [] # backward compatibility for older generators if isinstance(paths, str): paths = [paths] @@ -1068,7 +1070,7 @@ def _link_staticfile(self, sc): except OSError as err: if err.errno == errno.EXDEV: # 18: Invalid cross-device link logger.debug( - "Cross-device links not valid. " "Creating symbolic links instead." + "Cross-device links not valid. Creating symbolic links instead." ) self.fallback_to_symlinks = True self._link_staticfile(sc) diff --git a/pelican/paginator.py b/pelican/paginator.py index e1d508813..92cb26b17 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -131,9 +131,8 @@ def _from_settings(self, key): if not self.has_next(): rule = p break - else: - if p.min_page <= self.number: - rule = p + elif p.min_page <= self.number: + rule = p if not rule: return "" diff --git a/pelican/readers.py b/pelican/readers.py index e9b07582f..422f39fc2 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -199,7 +199,7 @@ def __init__(self, *args, **kwargs): self._language_code = lang_code else: logger.warning( - "Docutils has no localization for '%s'." " Using 'en' instead.", + "Docutils has no localization for '%s'. Using 'en' instead.", lang_code, ) self._language_code = "en" @@ -320,7 +320,7 @@ def _parse_metadata(self, meta): elif not DUPLICATES_DEFINITIONS_ALLOWED.get(name, True): if len(value) > 1: logger.warning( - "Duplicate definition of `%s` " "for %s. Using first one.", + "Duplicate definition of `%s` for %s. Using first one.", name, self._source_path, ) diff --git a/pelican/rstdirectives.py b/pelican/rstdirectives.py index 41bfc3d2e..9022ac830 100644 --- a/pelican/rstdirectives.py +++ b/pelican/rstdirectives.py @@ -78,7 +78,7 @@ class abbreviation(nodes.Inline, nodes.TextElement): pass -def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): +def abbr_role(typ, rawtext, text, lineno, inliner, options=None, content=None): text = utils.unescape(text) m = _abbr_re.search(text) if m is None: diff --git a/pelican/settings.py b/pelican/settings.py index ea6fae774..214d88a3c 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -447,7 +447,7 @@ def handle_deprecated_settings(settings: Settings) -> Settings: and not isinstance(settings[key], Path) and "%s" in settings[key] ): - logger.warning("%%s usage in %s is deprecated, use {lang} " "instead.", key) + logger.warning("%%s usage in %s is deprecated, use {lang} instead.", key) try: settings[key] = _printf_s_to_format_field(settings[key], "lang") except ValueError: @@ -470,7 +470,7 @@ def handle_deprecated_settings(settings: Settings) -> Settings: and not isinstance(settings[key], Path) and "%s" in settings[key] ): - logger.warning("%%s usage in %s is deprecated, use {slug} " "instead.", key) + logger.warning("%%s usage in %s is deprecated, use {slug} instead.", key) try: settings[key] = _printf_s_to_format_field(settings[key], "slug") except ValueError: @@ -614,7 +614,7 @@ def configure_settings(settings: Settings) -> Settings: if key in settings and not isinstance(settings[key], types): value = settings.pop(key) logger.warn( - "Detected misconfigured %s (%s), " "falling back to the default (%s)", + "Detected misconfigured %s (%s), falling back to the default (%s)", key, value, DEFAULT_CONFIG[key], @@ -676,7 +676,7 @@ def configure_settings(settings: Settings) -> Settings: if any(settings.get(k) for k in feed_keys): if not settings.get("SITEURL"): logger.warning( - "Feeds generated without SITEURL set properly may" " not be valid" + "Feeds generated without SITEURL set properly may not be valid" ) if "TIMEZONE" not in settings: diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index 89219029a..d248c3bb6 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -314,7 +314,7 @@ def test_get_content(self): args["settings"] = settings # Tag - args["content"] = "A simple test, with a " 'link' + args["content"] = 'A simple test, with a link' page = Page(**args) content = page.get_content("http://notmyidea.org") self.assertEqual( @@ -326,9 +326,7 @@ def test_get_content(self): ) # Category - args["content"] = ( - "A simple test, with a " 'link' - ) + args["content"] = 'A simple test, with a link' page = Page(**args) content = page.get_content("http://notmyidea.org") self.assertEqual( @@ -350,7 +348,7 @@ def test_intrasite_link(self): # Classic intrasite link via filename args["content"] = ( - "A simple test, with a " 'link' + 'A simple test, with a link' ) content = Page(**args).get_content("http://notmyidea.org") self.assertEqual( @@ -401,7 +399,7 @@ def test_intrasite_link(self): # also test for summary in metadata parsed = ( - "A simple summary test, with a " 'link' + 'A simple summary test, with a link' ) linked = ( "A simple summary test, with a " @@ -594,7 +592,7 @@ def test_intrasite_link_markdown_spaces(self): # An intrasite link via filename with %20 as a space args["content"] = ( - "A simple test, with a " 'link' + 'A simple test, with a link' ) content = Page(**args).get_content("http://notmyidea.org") self.assertEqual( @@ -834,10 +832,10 @@ def test_attach_to_ignores_subsequent_calls(self): otherdir_settings = self.settings.copy() otherdir_settings.update( - dict( - PAGE_SAVE_AS=os.path.join("otherpages", "{slug}.html"), - PAGE_URL="otherpages/{slug}.html", - ) + { + "PAGE_SAVE_AS": os.path.join("otherpages", "{slug}.html"), + "PAGE_URL": "otherpages/{slug}.html", + } ) otherdir_page = Page( content="other page", @@ -892,7 +890,7 @@ def test_attach_to_does_not_override_an_override(self): """ customstatic = Static( content=None, - metadata=dict(save_as="customfoo.jpg", url="customfoo.jpg"), + metadata={"save_as": "customfoo.jpg", "url": "customfoo.jpg"}, settings=self.settings, source_path=os.path.join("dir", "foo.jpg"), context=self.settings.copy(), @@ -1066,9 +1064,9 @@ def test_not_save_as_draft(self): static = Static( content=None, - metadata=dict( - status="draft", - ), + metadata={ + "status": "draft", + }, settings=self.settings, source_path=os.path.join("dir", "foo.jpg"), context=self.settings.copy(), diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index e69e321fb..46d83432f 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -71,7 +71,7 @@ def test_recognise_kind_and_title(self): ) comment_titles = {x[0] for x in test_posts if x[8] == "comment"} self.assertEqual( - {"Mishka, always a pleasure to read your " "adventures!..."}, comment_titles + {"Mishka, always a pleasure to read your adventures!..."}, comment_titles ) def test_recognise_status_with_correct_filename(self): @@ -478,7 +478,7 @@ def test_galleries_added_to_header(self): attachments=["output/test1", "output/test2"], ) self.assertEqual( - header, ("test\n####\n" ":attachments: output/test1, " "output/test2\n\n") + header, ("test\n####\n:attachments: output/test1, output/test2\n\n") ) def test_galleries_added_to_markdown_header(self): @@ -521,10 +521,10 @@ def test_attachments_associated_with_correct_post(self): self.assertEqual(self.attachments[post], expected) elif post == "with-excerpt": expected_invalid = ( - "http://thisurlisinvalid.notarealdomain/" "not_an_image.jpg" + "http://thisurlisinvalid.notarealdomain/not_an_image.jpg" ) expected_pelikan = ( - "http://en.wikipedia.org/wiki/" "File:Pelikan_Walvis_Bay.jpg" + "http://en.wikipedia.org/wiki/File:Pelikan_Walvis_Bay.jpg" ) self.assertEqual( self.attachments[post], {expected_invalid, expected_pelikan} @@ -533,9 +533,7 @@ def test_attachments_associated_with_correct_post(self): expected_invalid = "http://thisurlisinvalid.notarealdomain" self.assertEqual(self.attachments[post], {expected_invalid}) else: - self.fail( - "all attachments should match to a " f"filename or None, {post}" - ) + self.fail(f"all attachments should match to a filename or None, {post}") def test_download_attachments(self): real_file = os.path.join(CUR_DIR, "content/article.rst") diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index e49c8b74e..928eb2635 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -591,7 +591,7 @@ def test_article_with_footnote(self): "modified": SafeDatetime(2012, 11, 1), "multiline": [ "Line Metadata should be handle properly.", - "See syntax of Meta-Data extension of " "Python Markdown package:", + "See syntax of Meta-Data extension of Python Markdown package:", "If a line is indented by 4 or more spaces,", "that line is assumed to be an additional line of the value", "for the previous keyword.", diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index bd3f0d001..6c25cf455 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -922,14 +922,14 @@ class TestSanitisedJoin(unittest.TestCase): def test_detect_parent_breakout(self): with self.assertRaisesRegex( RuntimeError, - "Attempted to break out of output directory to " "(.*?:)?/foo/test", + "Attempted to break out of output directory to (.*?:)?/foo/test", ): # (.*?:)? accounts for Windows root utils.sanitised_join("/foo/bar", "../test") def test_detect_root_breakout(self): with self.assertRaisesRegex( RuntimeError, - "Attempted to break out of output directory to " "(.*?:)?/test", + "Attempted to break out of output directory to (.*?:)?/test", ): # (.*?:)? accounts for Windows root utils.sanitised_join("/foo/bar", "/test") diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 3e1f31dbf..c9742d54a 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -1095,7 +1095,7 @@ def fields2pelican( if posts_require_pandoc: logger.error( - "Pandoc must be installed to import the following posts:" "\n {}".format( + "Pandoc must be installed to import the following posts:\n {}".format( "\n ".join(posts_require_pandoc) ) ) @@ -1232,7 +1232,7 @@ def main(): exit(error) if args.wp_attach and input_type != "wordpress": - error = "You must be importing a wordpress xml " "to use the --wp-attach option" + error = "You must be importing a wordpress xml to use the --wp-attach option" exit(error) if input_type == "blogger": diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index c00a252ca..2f62e4bc9 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -103,11 +103,10 @@ def ask(question, answer=str, default=None, length=None): break else: print("You must enter something") + elif length and len(r) != length: + print(f"Entry must be {length} characters long") else: - if length and len(r) != length: - print(f"Entry must be {length} characters long") - else: - break + break return r @@ -169,7 +168,7 @@ def ask_timezone(question, default, tzurl): r = tz_dict[r] break else: - print("Please enter a valid time zone:\n" f" (check [{tzurl}])") + print(f"Please enter a valid time zone:\n (check [{tzurl}])") return r @@ -253,7 +252,7 @@ def main(): default=True, ): CONF["siteurl"] = ask( - "What is your URL prefix? (see " "above example; no trailing slash)", + "What is your URL prefix? (see above example; no trailing slash)", str, CONF["siteurl"], ) @@ -266,7 +265,7 @@ def main(): if CONF["with_pagination"]: CONF["default_pagination"] = ask( - "How many articles per page " "do you want?", + "How many articles per page do you want?", int, CONF["default_pagination"], ) @@ -296,7 +295,7 @@ def main(): "What is your username on that server?", str, CONF["ftp_user"] ) CONF["ftp_target_dir"] = ask( - "Where do you want to put your " "web site on that server?", + "Where do you want to put your web site on that server?", str, CONF["ftp_target_dir"], ) @@ -314,7 +313,7 @@ def main(): "What is your username on that server?", str, CONF["ssh_user"] ) CONF["ssh_target_dir"] = ask( - "Where do you want to put your " "web site on that server?", + "Where do you want to put your web site on that server?", str, CONF["ssh_target_dir"], ) @@ -338,23 +337,23 @@ def main(): ) if ask( - "Do you want to upload your website using " "Rackspace Cloud Files?", + "Do you want to upload your website using Rackspace Cloud Files?", answer=bool, default=False, ): CONF["cloudfiles"] = (True,) CONF["cloudfiles_username"] = ask( - "What is your Rackspace " "Cloud username?", + "What is your Rackspace Cloud username?", str, CONF["cloudfiles_username"], ) CONF["cloudfiles_api_key"] = ask( - "What is your Rackspace " "Cloud API key?", + "What is your Rackspace Cloud API key?", str, CONF["cloudfiles_api_key"], ) CONF["cloudfiles_container"] = ask( - "What is the name of your " "Cloud Files container?", + "What is the name of your Cloud Files container?", str, CONF["cloudfiles_container"], ) @@ -384,7 +383,7 @@ def main(): except OSError as e: print(f"Error: {e}") - conf_python = dict() + conf_python = {} for key, value in CONF.items(): conf_python[key] = repr(value) render_jinja_template("pelicanconf.py.jinja2", conf_python, "pelicanconf.py") diff --git a/pelican/urlwrappers.py b/pelican/urlwrappers.py index 4ed385f91..8023613cd 100644 --- a/pelican/urlwrappers.py +++ b/pelican/urlwrappers.py @@ -115,11 +115,10 @@ def _from_settings(self, key, get_page_name=False): if not isinstance(value, str): logger.warning("%s is set to %s", setting, value) return value + elif get_page_name: + return os.path.splitext(value)[0].format(**self.as_dict()) else: - if get_page_name: - return os.path.splitext(value)[0].format(**self.as_dict()) - else: - return value.format(**self.as_dict()) + return value.format(**self.as_dict()) page_name = property( functools.partial(_from_settings, key="URL", get_page_name=True) diff --git a/pelican/utils.py b/pelican/utils.py index 78e3e807c..b33eaa22a 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -25,9 +25,7 @@ Collection, Generator, Iterable, - Optional, Sequence, - Union, ) import dateutil.parser @@ -167,7 +165,7 @@ def __call__(self, *args) -> Any: self.cache[args] = value return value - def __repr__(self) -> Optional[str]: + def __repr__(self) -> str | None: return self.func.__doc__ def __get__(self, obj: Any, objtype): @@ -181,8 +179,8 @@ def deprecated_attribute( old: str, new: str, since: tuple[int, ...], - remove: Optional[tuple[int, ...]] = None, - doc: Optional[str] = None, + remove: tuple[int, ...] | None = None, + doc: str | None = None, ): """Attribute deprecation decorator for gentle upgrades @@ -296,9 +294,7 @@ def normalize_unicode(text: str) -> str: return value.strip() -def copy( - source: str, destination: str, ignores: Optional[Iterable[str]] = None -) -> None: +def copy(source: str, destination: str, ignores: Iterable[str] | None = None) -> None: """Recursively copy source into destination. If source is a file, destination has to be a file as well. @@ -364,7 +360,7 @@ def walk_error(err): copy_file(src_path, dst_path) else: logger.warning( - "Skipped copy %s (not a file or " "directory) to %s", + "Skipped copy %s (not a file or directory) to %s", src_path, dst_path, ) @@ -474,7 +470,7 @@ def __init__(self, max_words: int) -> None: self.words_found = 0 self.open_tags = [] self.last_word_end = None - self.truncate_at: Optional[int] = None + self.truncate_at: int | None = None def feed(self, *args, **kwargs) -> None: try: @@ -573,11 +569,10 @@ def _handle_ref(self, name: str, char: str) -> None: if self.last_word_end is None: if self._word_prefix_regex.match(char): self.last_word_end = ref_end + elif self._word_regex.match(char): + self.last_word_end = ref_end else: - if self._word_regex.match(char): - self.last_word_end = ref_end - else: - self.add_last_word() + self.add_last_word() def handle_entityref(self, name: str) -> None: """ @@ -638,7 +633,7 @@ def truncate_html_words(s: str, num: int, end_text: str = "…") -> str: def process_translations( content_list: list[Content], - translation_id: Optional[Union[str, Collection[str]]] = None, + translation_id: str | Collection[str] | None = None, ) -> tuple[list[Content], list[Content]]: """Finds translations and returns them. @@ -739,7 +734,7 @@ def _warn_source_paths(msg, items, *extra): def order_content( content_list: list[Content], - order_by: Union[str, Callable[[Content], Any], None] = "slug", + order_by: str | Callable[[Content], Any] | None = "slug", ) -> list[Content]: """Sorts content. @@ -841,7 +836,7 @@ def wait_for_changes( def set_date_tzinfo( - d: datetime.datetime, tz_name: Optional[str] = None + d: datetime.datetime, tz_name: str | None = None ) -> datetime.datetime: """Set the timezone for dates that don't have tzinfo""" if tz_name and not d.tzinfo: @@ -857,7 +852,7 @@ def mkdir_p(path: str) -> None: os.makedirs(path, exist_ok=True) -def split_all(path: Union[str, pathlib.Path, None]) -> Optional[Sequence[str]]: +def split_all(path: str | pathlib.Path | None) -> Sequence[str] | None: """Split a path into a list of components While os.path.split() splits a single component off the back of @@ -911,7 +906,7 @@ def maybe_pluralize(count: int, singular: str, plural: str) -> str: @contextmanager def temporary_locale( - temp_locale: Optional[str] = None, lc_category: int = locale.LC_ALL + temp_locale: str | None = None, lc_category: int = locale.LC_ALL ) -> Generator[None, None, None]: """ Enable code to run in a context with a temporary locale diff --git a/pelican/writers.py b/pelican/writers.py index cce01b585..683ae30b5 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -21,7 +21,7 @@ class Writer: def __init__(self, output_path, settings=None): self.output_path = output_path - self.reminder = dict() + self.reminder = {} self.settings = settings or {} self._written_files = set() self._overridden_files = set() From 3569dede01cb3e7fe0b8813ebb9a8581d7895a93 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Thu, 30 May 2024 21:40:27 +0200 Subject: [PATCH 3/3] Ignore more Ruff fixes in `git blame` --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index fddd0764d..2d787f0b9 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -11,3 +11,5 @@ db241feaa445375dc05e189e69287000ffe5fa8e 0bd02c00c078fe041b65fbf4eab13601bb42676d # Apply more Ruff checks to code 9d30c5608a58d202b1c02d55651e6ac746bfb173 +# Apply yet more Ruff checks to code +7577dd7603f7cb3a09922d1edb65b6eafb6e2ac7