diff --git a/Makefile b/Makefile index 35a4270..7aa435e 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ publish := uv publish --username=__token__ --keyring-provider=subprocess test := $(run) pytest python := $(run) python ruff := $(run) ruff -lint := $(ruff) check --select I +lint := $(ruff) check fmt := $(ruff) format mypy := $(run) mypy spell := $(run) codespell diff --git a/pyproject.toml b/pyproject.toml index 4fd5767..393fbd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,3 +89,26 @@ asyncio_default_fixture_loop_scope = "function" venvPath="." venv=".venv" exclude=[".venv"] + +[tool.ruff.lint] +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + "UP", + # flake8-bugbear + "B", + # flake8-simplify + "SIM", + # isort + "I", +] +ignore = [ + # I think try...expect...pass reads far better. + "SIM105", +] + +[tool.ruff.lint.pycodestyle] +max-line-length = 120 diff --git a/src/braindrop/app/braindrop.py b/src/braindrop/app/braindrop.py index 0a82294..ff87155 100644 --- a/src/braindrop/app/braindrop.py +++ b/src/braindrop/app/braindrop.py @@ -97,7 +97,7 @@ def api_token(self) -> str | None: return self.environmental_token() or token_file().read_text( encoding="utf-8" ) - except IOError: + except OSError: pass return None diff --git a/src/braindrop/app/data/config.py b/src/braindrop/app/data/config.py index b7cdd3a..342022a 100644 --- a/src/braindrop/app/data/config.py +++ b/src/braindrop/app/data/config.py @@ -2,12 +2,12 @@ ############################################################################## # Python imports. +from collections.abc import Iterator from contextlib import contextmanager from dataclasses import asdict, dataclass, field -from functools import lru_cache +from functools import cache from json import dumps, loads from pathlib import Path -from typing import Iterator ############################################################################## # Local imports. @@ -63,7 +63,7 @@ def save_configuration(configuration: Configuration) -> Configuration: ############################################################################## -@lru_cache(maxsize=None) +@cache def load_configuration() -> Configuration: """Load the configuration. diff --git a/src/braindrop/app/data/local.py b/src/braindrop/app/data/local.py index c89ef76..d4c7495 100644 --- a/src/braindrop/app/data/local.py +++ b/src/braindrop/app/data/local.py @@ -2,10 +2,11 @@ ############################################################################## # Python imports. +from collections.abc import Callable, Iterable, Iterator from datetime import datetime from json import dumps, loads from pathlib import Path -from typing import Any, Callable, Final, Iterable, Iterator, Self +from typing import Any, Final, Self ############################################################################## # pytz imports. diff --git a/src/braindrop/app/data/raindrops.py b/src/braindrop/app/data/raindrops.py index 02c93ad..2656f71 100644 --- a/src/braindrop/app/data/raindrops.py +++ b/src/braindrop/app/data/raindrops.py @@ -6,9 +6,11 @@ ############################################################################## # Python imports. +from collections import Counter +from collections.abc import Callable, Iterable, Iterator from dataclasses import dataclass from functools import total_ordering -from typing import Callable, Counter, Iterable, Iterator, Self, TypeAlias +from typing import Self ############################################################################## # Local imports. @@ -81,7 +83,7 @@ def __eq__(self, value: object, /) -> bool: ############################################################################## -Filters: TypeAlias = tuple["Filter", ...] +type Filters = tuple["Filter", ...] """The type of a collection of filters.""" diff --git a/src/braindrop/app/screens/main.py b/src/braindrop/app/screens/main.py index b287ee0..0269747 100644 --- a/src/braindrop/app/screens/main.py +++ b/src/braindrop/app/screens/main.py @@ -2,7 +2,7 @@ ############################################################################## # Python imports. -from typing import Callable +from collections.abc import Callable from webbrowser import open as open_url ############################################################################## diff --git a/src/braindrop/app/screens/raindrop_input.py b/src/braindrop/app/screens/raindrop_input.py index 33aebf7..ee9e70d 100644 --- a/src/braindrop/app/screens/raindrop_input.py +++ b/src/braindrop/app/screens/raindrop_input.py @@ -2,7 +2,7 @@ ############################################################################## # Python imports. -from typing import Iterator +from collections.abc import Iterator ############################################################################## # httpx imports. diff --git a/src/braindrop/app/suggestions/tags.py b/src/braindrop/app/suggestions/tags.py index 0e4c92e..94d56dd 100644 --- a/src/braindrop/app/suggestions/tags.py +++ b/src/braindrop/app/suggestions/tags.py @@ -2,8 +2,9 @@ ############################################################################## # Python imports. -import re -from typing import Final, Iterable, Pattern +from collections.abc import Iterable +from re import Pattern, compile +from typing import Final ############################################################################## # Textual imports. @@ -33,7 +34,7 @@ def __init__(self, tags: Iterable[Tag | TagCount], use_cache: bool = True) -> No self._tags = [tag.tag if isinstance(tag, TagCount) else tag for tag in tags] """The tags to take suggestions from.""" - _SUGGESTABLE: Final[Pattern[str]] = re.compile(r".*[^,\s]$") + _SUGGESTABLE: Final[Pattern[str]] = compile(r".*[^,\s]$") """Regular expression to test if a value deserves a suggestion.""" async def get_suggestion(self, value: str) -> str | None: diff --git a/src/braindrop/app/widgets/raindrop_details.py b/src/braindrop/app/widgets/raindrop_details.py index 0aa12bd..1b1d75a 100644 --- a/src/braindrop/app/widgets/raindrop_details.py +++ b/src/braindrop/app/widgets/raindrop_details.py @@ -2,8 +2,9 @@ ############################################################################## # Python imports. +from collections.abc import Callable from datetime import datetime -from typing import Any, Callable, Final +from typing import Any, Final ############################################################################## # Humanize imports. diff --git a/src/braindrop/app/widgets/raindrops_view.py b/src/braindrop/app/widgets/raindrops_view.py index 477093e..e825b08 100644 --- a/src/braindrop/app/widgets/raindrops_view.py +++ b/src/braindrop/app/widgets/raindrops_view.py @@ -96,9 +96,12 @@ def prompt(self) -> Group: if self._raindrop.excerpt: excerpt = Table.grid() excerpt.add_column(ratio=1, no_wrap=self._compact) - excerpt.add_row( - f"[dim]{escape(self._raindrop.excerpt.splitlines()[0] if self._compact else self._raindrop.excerpt)}[/dim]" + content = escape( + self._raindrop.excerpt.splitlines()[0] + if self._compact + else self._raindrop.excerpt ) + excerpt.add_row(f"[dim]{content}[/dim]") body.append(excerpt) details = Table.grid(expand=True) diff --git a/src/braindrop/raindrop/api.py b/src/braindrop/raindrop/api.py index 088c355..d2e431f 100644 --- a/src/braindrop/raindrop/api.py +++ b/src/braindrop/raindrop/api.py @@ -13,11 +13,12 @@ ############################################################################## # Python imports. from asyncio import sleep +from collections.abc import Awaitable, Callable from dataclasses import dataclass from http import HTTPStatus from json import loads from ssl import SSLCertVerificationError -from typing import Any, Awaitable, Callable, Final, Literal +from typing import Any, Final, Literal ############################################################################## # HTTPX imports. @@ -140,7 +141,7 @@ async def _call( int(error.response.headers["Retry-After"]) if "Retry-After" in error.response.headers else None - ) + ) from None else: raise self.RequestError(str(error)) from None @@ -332,7 +333,7 @@ def gndn(_: int) -> None: if limit.retry_after is None: raise self.RequestError( "Raindrop.io API limit exceeded with no option to retry" - ) + ) from None count_update(-len(raindrops)) await sleep(limit.retry_after) continue diff --git a/src/braindrop/raindrop/raindrop.py b/src/braindrop/raindrop/raindrop.py index 4535857..05fa759 100644 --- a/src/braindrop/raindrop/raindrop.py +++ b/src/braindrop/raindrop/raindrop.py @@ -6,9 +6,10 @@ ############################################################################## # Python imports. +from collections.abc import Iterable from dataclasses import dataclass, field, replace from datetime import datetime -from typing import Any, Final, Iterable, Literal, TypeAlias +from typing import Any, Final, Literal ############################################################################## # Local imports. @@ -17,9 +18,7 @@ from .time_tools import get_time, json_time ############################################################################## -RaindropType: TypeAlias = Literal[ - "link", "article", "image", "video", "document", "audio" -] +type RaindropType = Literal["link", "article", "image", "video", "document", "audio"] """The type of a Raindrop."""