diff --git a/requirements_lint.txt b/requirements_lint.txt index 23ec577b..3e56e304 100644 --- a/requirements_lint.txt +++ b/requirements_lint.txt @@ -4,5 +4,5 @@ # # pip-compile --output-file=requirements_lint.txt requirements_lint.in # -ruff==0.8.3 +ruff==0.9.0 # via -r requirements_lint.in diff --git a/yarrharr/fetch.py b/yarrharr/fetch.py index 1513a449..92fd6cc6 100644 --- a/yarrharr/fetch.py +++ b/yarrharr/fetch.py @@ -418,7 +418,7 @@ def poll(reactor, max_fetch): raise attempt += 1 log.debug( - ("Database lock contention while persisting {count} outcomes:" " will retry (attempt {attempt})"), + ("Database lock contention while persisting {count} outcomes: will retry (attempt {attempt})"), count=len(outcomes), attempt=attempt, ) diff --git a/yarrharr/management/commands/updatecounts.py b/yarrharr/management/commands/updatecounts.py index 304aa277..dc002fe3 100644 --- a/yarrharr/management/commands/updatecounts.py +++ b/yarrharr/management/commands/updatecounts.py @@ -48,7 +48,7 @@ def handle(self, *args, **options): self.stdout.write("{:,d} feeds were audited.".format(feeds)) style = self.style.WARNING if sum(counts.values()) > 0 else self.style.SUCCESS - self.stdout.write(style(("{all:,d} all, {unread:,d} unread," " {fave:,d} fave counters were off.").format_map(counts))) + self.stdout.write(style(("{all:,d} all, {unread:,d} unread, {fave:,d} fave counters were off.").format_map(counts))) def _audit_feed(self, feed, mutate, counts): for name, count in ( diff --git a/yarrharr/templatetags/ytabs.py b/yarrharr/templatetags/ytabs.py index 5399294a..63b72bf6 100644 --- a/yarrharr/templatetags/ytabs.py +++ b/yarrharr/templatetags/ytabs.py @@ -58,7 +58,7 @@ def __init__(self, tab_id: str) -> None: def render(self, context): selected = self.tab_id in context.get("tabs_selected", ()) - return f'id="{html.escape(self.tab_id)}"' f' aria-selected={"true" if selected else "false"}' + return f'id="{html.escape(self.tab_id)}" aria-selected={"true" if selected else "false"}' register.tag("tabattrs", tabattrs) diff --git a/yarrharr/tests/test_application.py b/yarrharr/tests/test_application.py index fb1462f7..e0e9ba23 100644 --- a/yarrharr/tests/test_application.py +++ b/yarrharr/tests/test_application.py @@ -1,4 +1,4 @@ -# Copyright © 2018, 2020 Tom Most +# Copyright © 2018, 2020, 2025 Tom Most # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -404,7 +404,7 @@ def test_logger_namespace(self): log.info("info\n{more}", more="info") log.error("err") - self.assertEqual(("<6>[ns] info\n" "<6> info\n" "<3>[ns] err\n"), fout.getvalue()) + self.assertEqual(("<6>[ns] info\n<6> info\n<3>[ns] err\n"), fout.getvalue()) def test_logger_namespace_failure(self): """ @@ -417,14 +417,14 @@ def test_logger_namespace_failure(self): log.failure("Something went wrong", Failure(Exception("1\n2\n3"))) self.assertEqual( - ( - "<2>[ns] Something went wrong\n" - "<2> Traceback (most recent call last):\n" - "<2> Failure: builtins.Exception: 1\n" - "<2> 2\n" - "<2> 3\n" - ), - fout.getvalue(), + [ + "<2>[ns] Something went wrong", + "<2> Traceback (most recent call last):", + "<2> Failure: builtins.Exception: 1", + "<2> 2", + "<2> 3", + ], + fout.getvalue().splitlines(), ) def test_log_legacy(self): @@ -436,8 +436,14 @@ def test_log_legacy(self): p.msg("m\ns\ng", logLevel=logging.DEBUG) self.assertEqual( - ("<6>[-] msg\n" "<6>[system] msg\n" "<7>[-] m\n" "<7> s\n" "<7> g\n"), - fout.getvalue(), + [ + "<6>[-] msg", + "<6>[system] msg", + "<7>[-] m", + "<7> s", + "<7> g", + ], + fout.getvalue().splitlines(), ) def _get_stdlib_logger(self, name): diff --git a/yarrharr/tests/test_fetch.py b/yarrharr/tests/test_fetch.py index 6c613838..f097b87b 100644 --- a/yarrharr/tests/test_fetch.py +++ b/yarrharr/tests/test_fetch.py @@ -985,7 +985,7 @@ def test_persist_article_sanitize(self): url="https://example.com/blah-blah", date=timezone.now(), guid="49e3c525-724c-44d8-ad0c-d78bd216d003", - raw_content="

Hello, world" '!', + raw_content='

Hello, world!', ), ], etag=b'"etag"', @@ -1001,7 +1001,7 @@ def test_persist_article_sanitize(self): article, raw_title="Blah & Blah", title="Blah & Blah", - raw_content=("

Hello, world" '!'), + raw_content=('

Hello, world!'), content="

Hello, world!", ) diff --git a/yarrharr/tests/test_models.py b/yarrharr/tests/test_models.py index 92aab714..42641629 100644 --- a/yarrharr/tests/test_models.py +++ b/yarrharr/tests/test_models.py @@ -122,7 +122,7 @@ def assert_scheduled(self, expected): self.assertEqual( expected, actual, - ("\nNext check should be {} from" "\nnow, but found it is {} from now.").format(expected, actual), + ("\nNext check should be {} from\nnow, but found it is {} from now.").format(expected, actual), ) def test_disabled(self): diff --git a/yarrharr/tests/test_sanitize.py b/yarrharr/tests/test_sanitize.py index 699b56d6..f9328274 100644 --- a/yarrharr/tests/test_sanitize.py +++ b/yarrharr/tests/test_sanitize.py @@ -1,4 +1,4 @@ -# Copyright © 2017, 2018, 2020, 2022 Tom Most +# Copyright © 2017, 2018, 2020, 2022, 2025 Tom Most # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -153,25 +153,28 @@ def test_object_replaced(self): An ```` tag is replaced with its content, omitting any ```` tags as well. """ - html = '' '' "

Flash video

" "
" - self.assertEqual("

Flash video", sanitize_html(html)) + html = [ + '', + '

Flash video

', + ] + self.assertEqual("

Flash video", sanitize_html("".join(html))) def test_object_nest_replaced(self): """ Nested ```` tags are recursively replaced with their content. """ html = ( - "

" - "" - '' - "Level 1
" - "" - '' - "Level 2" - "" - "
" + "

", + "", + '', + "Level 1
", + "", + '', + "Level 2", + "", + "
", ) - self.assertEqual("

Level 1
Level 2", sanitize_html(html)) + self.assertEqual("

Level 1
Level 2", sanitize_html("".join(html))) def test_link_tag_dropped(self): """ @@ -219,9 +222,7 @@ def test_youtube_nocookie_embed_replaced(self): """ A "privacy-enhanced" YouTube embed, which uses the youtube-nocookie.com domain, is replaced with a link. """ - html = ( - '' - ) + html = '' self.assertEqual( ( ' +# Copyright © 2017, 2018, 2019, 2021, 2022, 2023, 2025 Tom Most # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -112,26 +112,26 @@ def test_login_redirect(self): "/label/1234/all/1234/", "/feed/1/unread", "/feed/2/fave/", - "/feed/3/all/4" "/feed/5/all/678/" "/labels/", + "/feed/3/all/4", + "/feed/5/all/678/", + "/labels/", "/labels/add", "/feeds/", "/feeds/add/", "/article/1234/", ] - redirects = [] for next_ in nexts: - response = c.post( - "/login/", - { - "next": next_, - "username": "james", - "password": "hunter2", - }, - ) - redirects.append(response["Location"]) - - self.assertEqual(nexts, redirects) + with self.subTest("login", next=next_): + response = c.post( + "/login/", + { + "next": next_, + "username": "james", + "password": "hunter2", + }, + ) + self.assertEqual(next_, response["Location"]) class LabelListTests(TestCase):