From f5455b6946c8c25c275075e905a8a17e35295c2f Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Thu, 11 Sep 2025 13:38:47 +0200 Subject: [PATCH] chore: Fix for additional linting rules --- papis_extract/__init__.py | 7 +++++-- papis_extract/annotation.py | 14 ++++++-------- papis_extract/exporters/notes.py | 18 ++++++++++-------- papis_extract/extractors/pdf.py | 18 ++++++++---------- papis_extract/extractors/pocketbook.py | 17 ++++++++--------- papis_extract/extractors/readera.py | 6 +++--- pyproject.toml | 18 ++++++++++++++++++ 7 files changed, 58 insertions(+), 40 deletions(-) diff --git a/papis_extract/__init__.py b/papis_extract/__init__.py index ae04015..d1292c9 100644 --- a/papis_extract/__init__.py +++ b/papis_extract/__init__.py @@ -1,4 +1,9 @@ import re +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from papis_extract.annotation import Annotation + from papis_extract.exporter import Exporter import click import papis.cli @@ -9,8 +14,6 @@ import papis.strings from papis.document import Document from papis_extract import extraction -from papis_extract.annotation import Annotation -from papis_extract.exporter import Exporter from papis_extract.exporters import all_exporters from papis_extract.extractors import all_extractors from papis_extract.formatter import Formatter, formatters diff --git a/papis_extract/annotation.py b/papis_extract/annotation.py index 8fd63ac..97bd950 100644 --- a/papis_extract/annotation.py +++ b/papis_extract/annotation.py @@ -1,6 +1,6 @@ import math from dataclasses import dataclass -from typing import Any, Optional, cast +from typing import Any, cast import chevron import papis.config @@ -117,7 +117,7 @@ class Annotation: return color_mapping.get(colorname, "") # mimics the functions in papis.config.{getlist,getint,getfloat} etc. - def _getdict(self, key: str, section: Optional[str] = None) -> dict[str, str]: + def _getdict(self, key: str, section: str | None = None) -> dict[str, str]: """Dict getter :returns: A python dict @@ -126,19 +126,17 @@ class Annotation: """ rawvalue: Any = papis.config.general_get(key, section=section) if isinstance(rawvalue, dict): - return cast(dict[str, str], rawvalue) + return cast("dict[str, str]", rawvalue) try: rawvalue = eval(rawvalue) except Exception: raise SyntaxError( - "The key '{}' must be a valid Python object: {}".format(key, rawvalue) + f"The configuration key '{key}' must be a valid Python dict: {rawvalue}" ) else: if not isinstance(rawvalue, dict): raise SyntaxError( - "The key '{}' must be a valid Python dict. Got: {} (type {!r})".format( - key, rawvalue, type(rawvalue).__name__ - ) + f"The configuration key '{key}' must be a valid Python dict. Got: {rawvalue} (type {type(rawvalue).__name__})" ) - return cast(dict[str, str], rawvalue) + return cast("dict[str, str]", rawvalue) diff --git a/papis_extract/exporters/notes.py b/papis_extract/exporters/notes.py index 2a753d3..ff92c3d 100644 --- a/papis_extract/exporters/notes.py +++ b/papis_extract/exporters/notes.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from pathlib import Path import Levenshtein import papis.commands.edit @@ -69,11 +70,11 @@ class NotesExporter: :type duplicates: bool, optional """ logger.debug("Adding annotations to note...") - notes_path = papis.notes.notes_path_ensured(document) + notes_path = Path(papis.notes.notes_path_ensured(document)) existing: list[str] = [] - with open(notes_path, "r") as file_read: - existing = file_read.readlines() + with notes_path.open("r") as fr: + existing = fr.readlines() new_annotations: list[str] = formatted_annotations if not duplicates: @@ -81,15 +82,16 @@ class NotesExporter: formatted_annotations, existing ) if not new_annotations: + logger.debug("No new annotations to be added.") return - with open(notes_path, "a") as f: + with notes_path.open("a") as fa: # add newline if theres no empty space at file end if len(existing) > 0 and existing[-1].strip() != "": - f.write("\n") + fa.write("\n") # We filter out any empty lines from the annotations filtered_annotations = [annot for annot in new_annotations if annot != ""] - f.write("\n\n".join(filtered_annotations)) + fa.write("\n\n".join(filtered_annotations)) logger.info( f"Wrote {len(filtered_annotations)} " f"{'line' if len(filtered_annotations) == 1 else 'lines'} " @@ -97,11 +99,11 @@ class NotesExporter: ) if git: - msg = "Update notes for '{0}'".format(papis.document.describe(document)) + msg = f"Update annotations for '{papis.document.describe(document)}'" folder = document.get_main_folder() if folder: papis.git.add_and_commit_resources( - folder, [notes_path, document.get_info_file()], msg + folder, [str(notes_path), document.get_info_file()], msg ) def _drop_existing_annotations( diff --git a/papis_extract/extractors/pdf.py b/papis_extract/extractors/pdf.py index 887a88b..6d9acc2 100644 --- a/papis_extract/extractors/pdf.py +++ b/papis_extract/extractors/pdf.py @@ -19,9 +19,7 @@ class PdfExtractor: if not filename.is_file(): logger.error(f"File {str(filename)} not readable.") return False - if not self._is_pdf(filename): - return False - return True + return self._is_pdf(filename) def run(self, filename: Path) -> list[Annotation]: """Extract annotations from a file. @@ -39,15 +37,15 @@ class PdfExtractor: if not quote and not note: continue color: tuple[float, float, float] = cast( - tuple[float, float, float], + "tuple[float, float, float]", ( annot.colors.get("fill") or annot.colors.get("stroke") or (0.0, 0.0, 0.0) ), ) - page_nr: int = cast(int, page.number or 0) - highlight_type: str = cast(str, annot.type[1] or "") + page_nr: int = cast("int", page.number or 0) + highlight_type: str = cast("str", annot.type[1] or "") a = Annotation( file=str(filename), content=quote or "", @@ -83,7 +81,7 @@ class PdfExtractor: should both be included or are the same, using Levenshtein distance. """ - content = cast(str, annotation.info["content"].replace("\n", " ")) + content = cast("str", annotation.info["content"].replace("\n", " ")) written = page.get_textbox(annotation.rect).replace("\n", " ") # highlight with selection in note @@ -94,13 +92,13 @@ class PdfExtractor: if Levenshtein.ratio(content, written) > minimum_similarity: return (content, None) # both a highlight and a note - elif content and written: + if content and written: return (written, content) # an independent note, not a highlight - elif content: + if content: return (None, content) # highlight with selection not in note - elif written: + if written: return (written, None) # just a highlight without any text return (None, None) diff --git a/papis_extract/extractors/pocketbook.py b/papis_extract/extractors/pocketbook.py index 4f4b73a..46ba66b 100644 --- a/papis_extract/extractors/pocketbook.py +++ b/papis_extract/extractors/pocketbook.py @@ -2,7 +2,6 @@ from pathlib import Path import magic -import papis.config import papis.logging from bs4 import BeautifulSoup @@ -13,7 +12,7 @@ logger = papis.logging.get_logger(__name__) class PocketBookExtractor: def can_process(self, filename: Path) -> bool: - if not magic.from_file(filename, mime=True) == "text/xml": + if magic.from_file(filename, mime=True) != "text/xml": return False content = self._read_file(filename) @@ -21,11 +20,11 @@ class PocketBookExtractor: return False html = BeautifulSoup(content, features="xml") - if not html.find( - "meta", {"name": "generator", "content": "PocketBook Bookmarks Export"} - ): - return False - return True + return bool( + html.find( + "meta", {"name": "generator", "content": "PocketBook Bookmarks Export"} + ) + ) def run(self, filename: Path) -> list[Annotation]: """Extract annotations from pocketbook html file. @@ -78,8 +77,8 @@ class PocketBookExtractor: def _read_file(self, filename: Path) -> str: try: - with open(filename) as f: - return f.read() + with filename.open("r") as fr: + return fr.read() except FileNotFoundError: logger.error(f"Could not open file {filename} for extraction.") return "" diff --git a/papis_extract/extractors/readera.py b/papis_extract/extractors/readera.py index 0e9bd63..a968255 100644 --- a/papis_extract/extractors/readera.py +++ b/papis_extract/extractors/readera.py @@ -17,7 +17,7 @@ class ReadEraExtractor: """ def can_process(self, filename: Path) -> bool: - if not magic.from_file(filename, mime=True) == "text/plain": + if magic.from_file(filename, mime=True) != "text/plain": return False content = self._read_file(filename) @@ -83,8 +83,8 @@ class ReadEraExtractor: def _read_file(self, filename: Path) -> list[str]: try: - with open(filename, encoding="utf-8") as f: - return f.readlines() + with filename.open("r") as fr: + return fr.readlines() except FileNotFoundError: logger.error(f"Could not open file {filename} for extraction.") return [] diff --git a/pyproject.toml b/pyproject.toml index 2988dd5..0c84bea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,24 @@ pocketbook = ["beautifulsoup4<5.0.0,>=4.12.3"] [tool.uv] dev-dependencies = ["pytest<9.0.0,>=8.0.0", "pytest-cov<7.0.0,>=6.0.0"] +[tool.ruff.lint] +extend-select = [ + "C4", # Catch incorrect use of comprehensions, dict, list, etc + "F", # Pyflakes rules + "FA", # Enforce from __future__ import annotations + "I", # Sort imports properly + "ICN", # Use common import conventions + "ISC", # Good use of string concatenation + "NPY", # Some numpy-specific things + "PTH", # Use pathlib instead of os.path + "RET", # Good return practices + "SIM", # Common simplification rules + "TC", # Enforce importing certain types in a TYPE_CHECKING block + "TID", # Some good import practices + "UP", # Warn if certain things can changed due to newer Python versions + "W", # PyCodeStyle warnings +] + [build-system] requires = ["hatchling"] build-backend = "hatchling.build"