Tighten ruff rules and modernize style
This commit is contained in:
+3
-2
@@ -25,10 +25,11 @@
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
needs_sphinx = '8'
|
||||
|
||||
import datetime
|
||||
import datetime as dt
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
@@ -63,7 +64,7 @@ master_doc = 'index'
|
||||
# General information about the project.
|
||||
project = 'ocrmypdf'
|
||||
|
||||
year = str(datetime.date.today().year)
|
||||
year = str(dt.date.today().year)
|
||||
copyright = (
|
||||
f'{year}, James R. Barlow. '
|
||||
+ 'Licensed under Creative Commons Attribution-ShareAlike 4.0'
|
||||
|
||||
+42
-38
@@ -96,7 +96,9 @@ with st.expander("Optimization after OCR"):
|
||||
png_quality = st.slider(
|
||||
"PNG quality", min_value=0, max_value=100, value=75, key="png_quality"
|
||||
)
|
||||
jbig2_threshold = st.number_input("JBIG2 threshold", value=0.85, key="jbig2_threshold")
|
||||
jbig2_threshold = st.number_input(
|
||||
"JBIG2 threshold", value=0.85, key="jbig2_threshold"
|
||||
)
|
||||
|
||||
with st.expander("Advanced options"):
|
||||
jobs = st.slider(
|
||||
@@ -192,45 +194,47 @@ if uploaded:
|
||||
args.append(f"--jbig2-threshold={jbig2_threshold}")
|
||||
if jobs:
|
||||
args.append(f"--jobs={jobs}")
|
||||
input_file = NamedTemporaryFile(delete=True, suffix=f"_{uploaded.name}")
|
||||
input_file.write(uploaded.getvalue())
|
||||
input_file.flush()
|
||||
input_file.seek(0)
|
||||
args.append(str(input_file.name))
|
||||
output_file = NamedTemporaryFile(delete=True, suffix=".pdf")
|
||||
args.append(str(output_file.name))
|
||||
with NamedTemporaryFile(delete=True, suffix=f"_{uploaded.name}") as input_file:
|
||||
input_file.write(uploaded.getvalue())
|
||||
input_file.flush()
|
||||
input_file.seek(0)
|
||||
args.append(str(input_file.name))
|
||||
with NamedTemporaryFile(delete=True, suffix=".pdf") as output_file:
|
||||
args.append(str(output_file.name))
|
||||
|
||||
st.session_state['running'] = (
|
||||
'run_button' in st.session_state and st.session_state.run_button
|
||||
)
|
||||
if st.button(
|
||||
"Run OCRmyPDF",
|
||||
disabled=st.session_state.get("running", False),
|
||||
key='run_button',
|
||||
):
|
||||
st.session_state['running'] = True
|
||||
args = [sys.executable, '-u', '-m', "ocrmypdf"] + args
|
||||
st.session_state['running'] = (
|
||||
'run_button' in st.session_state and st.session_state.run_button
|
||||
)
|
||||
if st.button(
|
||||
"Run OCRmyPDF",
|
||||
disabled=st.session_state.get("running", False),
|
||||
key='run_button',
|
||||
):
|
||||
st.session_state['running'] = True
|
||||
args = [sys.executable, '-u', '-m', "ocrmypdf"] + args
|
||||
|
||||
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
with st.container(border=True):
|
||||
while proc.poll() is None:
|
||||
line = proc.stderr.readline()
|
||||
if line:
|
||||
st.html("<code>" + line.decode().strip() + "</code>")
|
||||
proc = subprocess.Popen(
|
||||
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
with st.container(border=True):
|
||||
while proc.poll() is None:
|
||||
line = proc.stderr.readline()
|
||||
if line:
|
||||
st.html("<code>" + line.decode().strip() + "</code>")
|
||||
|
||||
if proc.returncode != 0:
|
||||
st.error(f"ocrmypdf failed with exit code {proc.returncode}")
|
||||
st.session_state['running'] = False
|
||||
st.stop()
|
||||
if proc.returncode != 0:
|
||||
st.error(f"ocrmypdf failed with exit code {proc.returncode}")
|
||||
st.session_state['running'] = False
|
||||
st.stop()
|
||||
|
||||
if Path(output_file.name).stat().st_size == 0:
|
||||
st.error("No output PDF file was generated")
|
||||
st.stop()
|
||||
if Path(output_file.name).stat().st_size == 0:
|
||||
st.error("No output PDF file was generated")
|
||||
st.stop()
|
||||
|
||||
st.download_button(
|
||||
label="Download output PDF",
|
||||
data=output_file.read(),
|
||||
file_name=uploaded.name,
|
||||
mime="application/pdf",
|
||||
)
|
||||
st.session_state['running'] = False
|
||||
st.download_button(
|
||||
label="Download output PDF",
|
||||
data=output_file.read(),
|
||||
file_name=uploaded.name,
|
||||
mime="application/pdf",
|
||||
)
|
||||
st.session_state['running'] = False
|
||||
|
||||
+1
-4
@@ -39,10 +39,7 @@ script_dir = Path(__file__).parent
|
||||
# set archive_dir to a path for backup original documents. Leave empty if not required.
|
||||
archive_dir = "/pdfbak"
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
start_dir = Path(sys.argv[1])
|
||||
else:
|
||||
start_dir = Path(".")
|
||||
start_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(".")
|
||||
|
||||
if len(sys.argv) > 2:
|
||||
log_file = Path(sys.argv[2])
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""Helper script for bisecting PDFs to find a page with an issue."""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
+5
-8
@@ -7,12 +7,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime as dt
|
||||
import json
|
||||
import logging
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Any
|
||||
@@ -48,7 +48,7 @@ class LoggingLevelEnum(str, Enum):
|
||||
def get_output_path(root: Path, basename: str, output_dir_year_month: bool) -> Path:
|
||||
assert '/' not in basename, "basename must not contain '/'"
|
||||
if output_dir_year_month:
|
||||
today = datetime.today()
|
||||
today = dt.datetime.today()
|
||||
output_directory_year_month = root / str(today.year) / f'{today.month:02d}'
|
||||
if not output_directory_year_month.exists():
|
||||
output_directory_year_month.mkdir(parents=True, exist_ok=True)
|
||||
@@ -140,7 +140,7 @@ class HandleObserverEvent(PatternMatchingEventHandler):
|
||||
ignore_patterns=None,
|
||||
ignore_directories=False,
|
||||
case_sensitive=False,
|
||||
settings={},
|
||||
settings=None,
|
||||
):
|
||||
super().__init__(
|
||||
patterns=patterns,
|
||||
@@ -148,7 +148,7 @@ class HandleObserverEvent(PatternMatchingEventHandler):
|
||||
ignore_directories=ignore_directories,
|
||||
case_sensitive=case_sensitive,
|
||||
)
|
||||
self._settings = settings
|
||||
self._settings = settings if settings else {}
|
||||
|
||||
def on_any_event(self, event):
|
||||
if event.event_type in ['created']:
|
||||
@@ -302,10 +302,7 @@ def main(
|
||||
'output_dir_year_month': output_dir_year_month,
|
||||
},
|
||||
)
|
||||
if use_polling:
|
||||
observer = PollingObserver()
|
||||
else:
|
||||
observer = Observer()
|
||||
observer = PollingObserver() if use_polling else Observer()
|
||||
observer.schedule(handler, input_dir, recursive=True)
|
||||
observer.start()
|
||||
print(f"Watching {input_dir} for new PDFs. Press Ctrl+C to exit.")
|
||||
|
||||
+3
-1
@@ -4,6 +4,8 @@
|
||||
|
||||
"""Run the OCRmyPDF web service."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
@@ -13,7 +15,7 @@ except ImportError:
|
||||
raise ImportError(
|
||||
'You need to install streamlit in the Python environment '
|
||||
'to run the web service.\n'
|
||||
)
|
||||
) from None
|
||||
|
||||
if __name__ == '__main__':
|
||||
os.execvp(
|
||||
|
||||
+17
-7
@@ -130,6 +130,7 @@ exclude = ["src/ocrmypdf/_version.py"] # Autogenerated
|
||||
"UP", # pyupgrade
|
||||
"SIM", # simplify
|
||||
"B", # flake8-bugbear
|
||||
"ICN", # flake8-import-conventions
|
||||
]
|
||||
ignore = [
|
||||
"B028", # warning with no explicit stacklevel
|
||||
@@ -139,13 +140,20 @@ ignore = [
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
known-first-party = ["ocrmypdf"]
|
||||
required-imports = ["from __future__ import annotations"]
|
||||
|
||||
[tool.ruff.lint.flake8-import-conventions]
|
||||
# Prohibit explicit imports from the 'datetime' module
|
||||
banned-from = ["datetime"]
|
||||
# Optionally, suggest an alias for 'import datetime' (e.g., as dt)
|
||||
extend-aliases = { "datetime" = "dt" }
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google"
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"docs/conf.py" = ["D100", "D101", "D105"]
|
||||
"tests/*.py" = ["D100", "D101", "D102", "D103", "D105"]
|
||||
"tests/*.py" = ["D100", "D101", "D102", "D103", "D105", "E501"]
|
||||
"misc/*.py" = ["D103", "D101", "D102"]
|
||||
"src/ocrmypdf/builtin_plugins/*.py" = ["D103", "D102", "D105"]
|
||||
|
||||
@@ -154,11 +162,7 @@ quote-style = "preserve"
|
||||
|
||||
[dependency-groups]
|
||||
# Developer-only tools - use `uv sync --group <name>`
|
||||
dev = [
|
||||
"mypy>=1.13.0",
|
||||
"ipykernel>=6.29.5",
|
||||
"reportlab>=4.4.4",
|
||||
]
|
||||
dev = ["mypy>=1.13.0", "ipykernel>=6.29.5", "reportlab>=4.4.4"]
|
||||
test = [
|
||||
# Core testing framework
|
||||
"coverage[toml]>=6.2",
|
||||
@@ -175,5 +179,11 @@ test = [
|
||||
# Extended test capabilities (merged from extended_test)
|
||||
"pymupdf>=1.24.14",
|
||||
]
|
||||
docs = ["myst-parser>=4.0.1", "sphinx", "sphinx-issues", "sphinx-rtd-theme", "sphinxcontrib-mermaid"]
|
||||
docs = [
|
||||
"myst-parser>=4.0.1",
|
||||
"sphinx",
|
||||
"sphinx-issues",
|
||||
"sphinx-rtd-theme",
|
||||
"sphinxcontrib-mermaid",
|
||||
]
|
||||
streamlit-dev = ["streamlit>=1.40.2", "streamlit-pdf-viewer>=0.0.19"]
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
# Enforce English hegemony
|
||||
from __future__ import annotations
|
||||
|
||||
DEFAULT_LANGUAGE = 'eng'
|
||||
|
||||
# Default rotation threshold
|
||||
|
||||
@@ -111,6 +111,15 @@ def rasterize_pdf(
|
||||
"""Rasterize one page of a PDF at resolution raster_dpi in canvas units.
|
||||
|
||||
Args:
|
||||
input_file: The PDF file to rasterize.
|
||||
output_file: The file to write the rasterized PDF to.
|
||||
raster_device: The Ghostscript raster device to use to rasterize the PDF.
|
||||
raster_dpi: Resolution in dots per inch at which to rasterize page.
|
||||
pageno: Page number to rasterize (beginning at page 1).
|
||||
page_dpi: Resolution, overriding output image DPI.
|
||||
rotation: Cardinal angle, clockwise, to rotate page.
|
||||
filter_vector: If True, remove vector graphics objects.
|
||||
stop_on_error: If True, stop rasterizing on the first error.
|
||||
use_cropbox: If True, rasterize the CropBox instead of MediaBox.
|
||||
Default is False (use MediaBox).
|
||||
"""
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime as dt
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -53,7 +53,7 @@ def get_docinfo(base_pdf: Pdf, context: PdfContext) -> dict[str, str]:
|
||||
|
||||
pdfmark['/Creator'] = f'{PROGRAM_NAME} {OCRMYPF_VERSION} / {creator_tag}'
|
||||
pdfmark['/Producer'] = f'pikepdf {PIKEPDF_VERSION}'
|
||||
pdfmark['/ModDate'] = encode_pdf_date(datetime.now(timezone.utc))
|
||||
pdfmark['/ModDate'] = encode_pdf_date(dt.datetime.now(dt.UTC))
|
||||
return pdfmark
|
||||
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ from ocrmypdf._validation import (
|
||||
)
|
||||
from ocrmypdf.exceptions import ExitCode
|
||||
from ocrmypdf.helpers import available_cpu_count
|
||||
from ocrmypdf.hocrtransform.ocr_element import OcrElement
|
||||
from ocrmypdf.models.ocr_element import OcrElement
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ This module provides font infrastructure for the fpdf2 PDF renderer. It includes
|
||||
- MultiFontManager: Automatic font selection for multilingual documents
|
||||
- SystemFontProvider: System font discovery
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from ocrmypdf.font.font_manager import FontManager
|
||||
from ocrmypdf.font.font_provider import (
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
This module provides the PDF renderer using fpdf2 for creating
|
||||
searchable OCR text layers.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from ocrmypdf.fpdf_renderer.renderer import (
|
||||
DebugRenderOptions,
|
||||
|
||||
@@ -25,7 +25,6 @@ from typing import (
|
||||
|
||||
import img2pdf
|
||||
import pikepdf
|
||||
from deprecation import deprecated
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""Simple CLI for testing HOCR to PDF conversion using fpdf2 renderer."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
Derived from
|
||||
https://www.loc.gov/standards/iso639-2/ascii_8bits.html
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
+4
-3
@@ -23,9 +23,10 @@ class Gs106WarningFilter(logging.Filter):
|
||||
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
# Allow all records except the expected Ghostscript 10.6.x warning
|
||||
if "Ghostscript 10.6.x contains JPEG encoding errors" in record.getMessage():
|
||||
return False
|
||||
return True
|
||||
return (
|
||||
"Ghostscript 10.6.x contains JPEG encoding errors"
|
||||
not in record.getMessage()
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Test JSON serialization of OcrOptions for multiprocessing compatibility."""
|
||||
from __future__ import annotations
|
||||
|
||||
import multiprocessing
|
||||
from io import BytesIO
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import datetime as dt
|
||||
import warnings
|
||||
from datetime import timezone
|
||||
from shutil import copyfile
|
||||
|
||||
import pikepdf
|
||||
@@ -198,10 +197,7 @@ def test_creation_date_preserved(output_type, resources, infile, outpdf):
|
||||
|
||||
# We expect that the modified date is quite recent
|
||||
date_after = decode_pdf_date(str(after['/ModDate']))
|
||||
assert (
|
||||
seconds_between_dates(date_after, datetime.datetime.now(timezone.utc))
|
||||
< 1000
|
||||
)
|
||||
assert seconds_between_dates(date_after, dt.datetime.now(dt.UTC)) < 1000
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -10,6 +10,7 @@ This tests the fpdf2 renderer with various language groups:
|
||||
- CJK (Chinese Simplified/Traditional, Japanese, Korean)
|
||||
- Devanagari (Hindi, Sanskrit)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
@@ -119,7 +119,7 @@ def test_unpaper_args_invalid(resources, outpdf):
|
||||
def test_unpaper_image_too_big(resources, outdir, caplog):
|
||||
with patch('ocrmypdf._exec.unpaper.UNPAPER_IMAGE_PIXEL_LIMIT', 42):
|
||||
infile = resources / 'crom.png'
|
||||
unpaper.clean(infile, outdir / 'out.png', dpi=300) == infile
|
||||
assert unpaper.clean(infile, outdir / 'out.png', dpi=300) == infile
|
||||
|
||||
assert any(
|
||||
'too large for cleaning' in rec.message
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import datetime
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime as dt
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
@@ -43,8 +45,8 @@ def test_watcher(tmp_path, resources, year_month):
|
||||
if year_month:
|
||||
assert (
|
||||
output_dir
|
||||
/ f'{datetime.date.today().year}'
|
||||
/ f'{datetime.date.today().month:02d}'
|
||||
/ f'{dt.date.today().year}'
|
||||
/ f'{dt.date.today().month:02d}'
|
||||
/ 'trivial.pdf'
|
||||
).exists()
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user