Migrate pre-commit to prek
prek runs local hooks as plain execs against tools uv already provisions, so ruff/mypy can never drift from the versions/config uv.lock pins elsewhere and CI needs no separate hook-cache download. - Add ruff and prek to the uv dev dependency group (ruff wasn't a uv-managed dependency before; pre-commit silently vendored its own). - Replace .pre-commit-config.yaml with prek.toml: keep the pre-commit-hooks repo for generic file checks, convert ruff-format/ ruff-check to local `uv run ruff ...` hooks, and add a local mypy hook that reports but never fails (87 pre-existing errors need a separate cleanup before it can be made blocking). - Add a `lint` job to CI that runs `prek run --all-files` and gate the OS/Python test matrix on it so lint issues fail fast. - Fix the ruff debt (format + lint) uncovered by actually running it, since it was small and mechanical, so the new CI gate starts green.
This commit is contained in:
@@ -14,8 +14,29 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint (prek)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "0.9.x"
|
||||
|
||||
- name: "Set up Python"
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Run prek
|
||||
run: |
|
||||
uv run prek run --all-files
|
||||
|
||||
test_linux:
|
||||
name: Test ${{ matrix.os }} with Python ${{ matrix.python }}
|
||||
needs: lint
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -96,6 +117,7 @@ jobs:
|
||||
|
||||
test_macos:
|
||||
name: Test macOS
|
||||
needs: lint
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -158,6 +180,7 @@ jobs:
|
||||
|
||||
test_windows:
|
||||
name: Test Windows
|
||||
needs: lint
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-case-conflict
|
||||
- id: check-merge-conflict
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
- id: debug-statements
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: "v0.14.11"
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: [--fix]
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.2.0
|
||||
hooks:
|
||||
- id: mypy
|
||||
additional_dependencies:
|
||||
- types-toml
|
||||
- types-setuptools
|
||||
- types-requests
|
||||
- types-Pillow
|
||||
+2
-1
@@ -306,7 +306,8 @@ def bump_version() -> None:
|
||||
|
||||
if not found_at_least_one_file_needing_update:
|
||||
print(
|
||||
f'''error: Didn't find any occurrences of "{find_pattern}" in "{path_pattern}"'''
|
||||
f'''error: Didn't find any occurrences of "{find_pattern}" '''
|
||||
f'''in "{path_pattern}"'''
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -109,7 +109,7 @@ def main():
|
||||
doc1 = pymupdf.open(os.path.join(d, "output1.pdf"))
|
||||
doc2 = pymupdf.open(os.path.join(d, "output2.pdf"))
|
||||
for i, page1_2 in enumerate(zip(doc1, doc2, strict=False)):
|
||||
st.write(f"Page {i+1}")
|
||||
st.write(f"Page {i + 1}")
|
||||
page1, page2 = page1_2
|
||||
col1, col2 = st.columns(2)
|
||||
with col1, st.container(border=True):
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ def main():
|
||||
doc1 = pymupdf.open(os.path.join(d, "1.pdf"))
|
||||
doc2 = pymupdf.open(os.path.join(d, "2.pdf"))
|
||||
for i, page1_2 in enumerate(zip(doc1, doc2, strict=False)):
|
||||
st.write(f"Page {i+1}")
|
||||
st.write(f"Page {i + 1}")
|
||||
page1, page2 = page1_2
|
||||
col1, col2 = st.columns(2)
|
||||
with col1, st.container(border=True):
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ import logging
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
from enum import Enum
|
||||
from enum import StrEnum
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Any
|
||||
|
||||
@@ -35,7 +35,7 @@ app = cyclopts.App(name="ocrmypdf-watcher")
|
||||
log = logging.getLogger('ocrmypdf-watcher')
|
||||
|
||||
|
||||
class LoggingLevelEnum(str, Enum):
|
||||
class LoggingLevelEnum(StrEnum):
|
||||
"""Enum for logging levels."""
|
||||
|
||||
DEBUG = "DEBUG"
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# prek pre-commit configuration — https://prek.j178.dev
|
||||
#
|
||||
# The local/system hooks below invoke the project's OWN pinned tools (ruff/mypy
|
||||
# from uv.lock) and mirror .github/workflows/build.yml's lint job exactly, so
|
||||
# they can never drift from CI's versions or rules. prek installs nothing of
|
||||
# its own for them — "system" language just execs whatever `uv run` resolves.
|
||||
#
|
||||
# The pre-commit/pre-commit-hooks repo hooks below are generic file checks with
|
||||
# no project-local tool equivalent, so they're kept as a normal (non-local) repo.
|
||||
#
|
||||
# Run all checks manually: `uv run prek run --all-files`
|
||||
# Install the git hooks: `uv run prek install`
|
||||
|
||||
default_install_hook_types = ["pre-commit", "pre-push"]
|
||||
default_stages = ["pre-commit"]
|
||||
|
||||
[[repos]]
|
||||
repo = "https://github.com/pre-commit/pre-commit-hooks"
|
||||
rev = "v4.4.0"
|
||||
|
||||
[[repos.hooks]]
|
||||
id = "check-case-conflict"
|
||||
|
||||
[[repos.hooks]]
|
||||
id = "check-merge-conflict"
|
||||
|
||||
[[repos.hooks]]
|
||||
id = "check-toml"
|
||||
|
||||
[[repos.hooks]]
|
||||
id = "check-yaml"
|
||||
|
||||
[[repos.hooks]]
|
||||
id = "debug-statements"
|
||||
|
||||
[[repos]]
|
||||
repo = "local"
|
||||
|
||||
[[repos.hooks]]
|
||||
id = "ruff-format"
|
||||
name = "ruff format (check)"
|
||||
language = "system"
|
||||
entry = "uv run ruff format --check ."
|
||||
types = ["python"]
|
||||
pass_filenames = false
|
||||
require_serial = true
|
||||
|
||||
[[repos.hooks]]
|
||||
id = "ruff-check"
|
||||
name = "ruff check"
|
||||
language = "system"
|
||||
entry = "uv run ruff check ."
|
||||
types = ["python"]
|
||||
pass_filenames = false
|
||||
require_serial = true
|
||||
|
||||
[[repos.hooks]]
|
||||
id = "mypy"
|
||||
name = "mypy (advisory — reports errors, does not fail)"
|
||||
language = "system"
|
||||
# Errors are pre-existing debt (87 across 22 files as of 2026-07); reports but
|
||||
# never fails the hook until that debt is fixed and this can be made blocking.
|
||||
entry = "bash -c 'uv run mypy src/ocrmypdf; exit 0'"
|
||||
types = ["python"]
|
||||
pass_filenames = false
|
||||
require_serial = true
|
||||
@@ -158,6 +158,8 @@ quote-style = "preserve"
|
||||
# Developer-only tools - use `uv sync --group <name>`
|
||||
dev = [
|
||||
"mypy>=1.13.0",
|
||||
"ruff>=0.14.11",
|
||||
"prek>=0.4.8",
|
||||
"ipykernel>=6.29.5",
|
||||
"reportlab>=4.4.4",
|
||||
"cyclopts>=4.5.1",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -274,9 +274,7 @@ class SystemFontProvider:
|
||||
try:
|
||||
matches = list(font_dir.rglob(pattern))
|
||||
if matches:
|
||||
log.debug(
|
||||
"Found system font %s at %s", font_name, matches[0]
|
||||
)
|
||||
log.debug("Found system font %s at %s", font_name, matches[0])
|
||||
return matches[0]
|
||||
except PermissionError:
|
||||
# Skip directories we can't read
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -448,8 +448,13 @@ class Fpdf2PdfRenderer:
|
||||
# entirely (slope=0, no textangle) and produced garbage text in a
|
||||
# bounding box whose shape doesn't match the text content at all.
|
||||
if not self._check_aspect_ratio_plausible(
|
||||
pdf, words, font_size, slope_angle_deg,
|
||||
line_size_width, line_size_height, line_language,
|
||||
pdf,
|
||||
words,
|
||||
font_size,
|
||||
slope_angle_deg,
|
||||
line_size_width,
|
||||
line_size_height,
|
||||
line_language,
|
||||
):
|
||||
return
|
||||
|
||||
@@ -507,13 +512,15 @@ class Fpdf2PdfRenderer:
|
||||
else:
|
||||
word_tz = 100.0
|
||||
|
||||
word_render_data.append(WordRenderData(
|
||||
text=word.text,
|
||||
x_baseline=box_llx,
|
||||
font_family=font_family,
|
||||
word_tz=word_tz,
|
||||
is_rtl=word_is_rtl,
|
||||
))
|
||||
word_render_data.append(
|
||||
WordRenderData(
|
||||
text=word.text,
|
||||
x_baseline=box_llx,
|
||||
font_family=font_family,
|
||||
word_tz=word_tz,
|
||||
is_rtl=word_is_rtl,
|
||||
)
|
||||
)
|
||||
|
||||
if not word_render_data:
|
||||
return
|
||||
@@ -561,9 +568,7 @@ class Fpdf2PdfRenderer:
|
||||
if line_size_width >= line_size_height:
|
||||
return True
|
||||
|
||||
line_text = ' '.join(
|
||||
w.text for w in words if w is not None and w.text
|
||||
)
|
||||
line_text = ' '.join(w.text for w in words if w is not None and w.text)
|
||||
if not line_text:
|
||||
return True
|
||||
|
||||
@@ -603,9 +608,7 @@ class Fpdf2PdfRenderer:
|
||||
line_text[:80],
|
||||
)
|
||||
if not self._logged_aspect_ratio_suppression:
|
||||
log.info(
|
||||
"Suppressing OCR output text with improbable aspect ratio"
|
||||
)
|
||||
log.info("Suppressing OCR output text with improbable aspect ratio")
|
||||
self._logged_aspect_ratio_suppression = True
|
||||
return False
|
||||
|
||||
@@ -679,9 +682,7 @@ class Fpdf2PdfRenderer:
|
||||
ops.append(f'{first_x_baseline:.2f} 0 Td')
|
||||
else:
|
||||
# Direct PDF coordinates
|
||||
page_x, page_y_fpdf = transform_point(
|
||||
baseline_matrix, first_x_baseline, 0
|
||||
)
|
||||
page_x, page_y_fpdf = transform_point(baseline_matrix, first_x_baseline, 0)
|
||||
page_y_pdf = page_height - page_y_fpdf
|
||||
ops.append(f'{page_x:.2f} {page_y_pdf:.2f} Td')
|
||||
|
||||
@@ -698,9 +699,7 @@ class Fpdf2PdfRenderer:
|
||||
pdf._resource_catalog.add(
|
||||
PDFResourceType.FONT, pdf.current_font.i, pdf.page
|
||||
)
|
||||
ops.append(
|
||||
f'/F{pdf.current_font.i} {pdf.font_size_pt:.2f} Tf'
|
||||
)
|
||||
ops.append(f'/F{pdf.current_font.i} {pdf.font_size_pt:.2f} Tf')
|
||||
prev_font_family = word.font_family
|
||||
|
||||
# Relative positioning (for words after the first)
|
||||
@@ -728,12 +727,8 @@ class Fpdf2PdfRenderer:
|
||||
advance = next_word.x_baseline - word.x_baseline
|
||||
|
||||
# Add trailing space for text extraction unless both are CJK
|
||||
if (
|
||||
advance > 0
|
||||
and not (
|
||||
self._is_cjk_only(word.text)
|
||||
and self._is_cjk_only(next_word.text)
|
||||
)
|
||||
if advance > 0 and not (
|
||||
self._is_cjk_only(word.text) and self._is_cjk_only(next_word.text)
|
||||
):
|
||||
text_to_render = word.text + ' '
|
||||
else:
|
||||
@@ -744,9 +739,7 @@ class Fpdf2PdfRenderer:
|
||||
# Use word_tz (fits word into its hOCR bbox) — Td handles
|
||||
# inter-word gaps, so Tz should not stretch to fill them.
|
||||
ops.append(f'{word.word_tz:.2f} Tz')
|
||||
ops.append(
|
||||
self._encode_shaped_text(pdf, text_to_render, word.is_rtl)
|
||||
)
|
||||
ops.append(self._encode_shaped_text(pdf, text_to_render, word.is_rtl))
|
||||
|
||||
prev_x_baseline = word.x_baseline
|
||||
|
||||
@@ -762,9 +755,7 @@ class Fpdf2PdfRenderer:
|
||||
# don't think Tz is still set from our raw operators
|
||||
pdf.font_stretching = 100
|
||||
|
||||
def _encode_shaped_text(
|
||||
self, pdf: FPDF, text: str, is_rtl: bool = False
|
||||
) -> str:
|
||||
def _encode_shaped_text(self, pdf: FPDF, text: str, is_rtl: bool = False) -> str:
|
||||
"""Encode text using HarfBuzz text shaping for complex script support.
|
||||
|
||||
Unlike font.encode_text() which maps unicode characters one-by-one to
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
Derived from
|
||||
https://www.loc.gov/standards/iso639-2/ascii_8bits.html
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple
|
||||
|
||||
@@ -172,10 +172,10 @@ def _interpret_contents(
|
||||
name_index = defaultdict(lambda: [])
|
||||
found_vector = False
|
||||
found_text = False
|
||||
vector_ops = set('S s f F f* B B* b b*'.split())
|
||||
text_showing_ops = set("""TJ Tj " '""".split())
|
||||
image_ops = set('BI ID EI q Q Do cm'.split())
|
||||
color_ops = set('g rg k cs sc scn'.split())
|
||||
vector_ops = set(['S', 's', 'f', 'F', 'f*', 'B', 'B*', 'b', 'b*'])
|
||||
text_showing_ops = set(["TJ", "Tj", '"', "'"])
|
||||
image_ops = set(['BI', 'ID', 'EI', 'q', 'Q', 'Do', 'cm'])
|
||||
color_ops = set(['g', 'rg', 'k', 'cs', 'sc', 'scn'])
|
||||
operator_whitelist = ' '.join(vector_ops | text_showing_ops | image_ops | color_ops)
|
||||
|
||||
for n, graphobj in enumerate(
|
||||
|
||||
@@ -59,6 +59,7 @@ def pdfsimplefont__init__(
|
||||
|
||||
PDFSimpleFont.__init__ = pdfsimplefont__init__
|
||||
|
||||
|
||||
def pdftype3font__pscript5_get_height(self):
|
||||
"""Monkeypatch for PScript5.dll PDFs.
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ def test_language_parameter_mapped_to_languages():
|
||||
Regression test for GitHub issue #1640: the Python API ignored the language
|
||||
parameter, always defaulting to 'eng'.
|
||||
"""
|
||||
from ocrmypdf._options import OcrOptions
|
||||
from ocrmypdf.api import create_options, setup_plugin_infrastructure
|
||||
from ocrmypdf.cli import get_parser
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ class TestFpdf2MultiPageRenderer:
|
||||
for i in range(3):
|
||||
word = OcrElement(
|
||||
ocr_class=OcrClass.WORD,
|
||||
text=f"Page{i+1}",
|
||||
text=f"Page{i + 1}",
|
||||
bbox=BoundingBox(left=100, top=100, right=200, bottom=130),
|
||||
)
|
||||
line = OcrElement(
|
||||
|
||||
+6
-6
@@ -78,9 +78,9 @@ def test_redo_ocr_with_offset_mediabox(resources, outdir):
|
||||
mediabox = list(page.MediaBox)
|
||||
|
||||
# MediaBox origin should be preserved
|
||||
assert (
|
||||
float(mediabox[1]) == y_offset
|
||||
), f"MediaBox Y origin should be preserved at {y_offset}, got {mediabox[1]}"
|
||||
assert float(mediabox[1]) == y_offset, (
|
||||
f"MediaBox Y origin should be preserved at {y_offset}, got {mediabox[1]}"
|
||||
)
|
||||
|
||||
# The content stream should include a CTM with the Y origin translation.
|
||||
# Without the fix, the CTM was omitted for rotation==0, causing a shift.
|
||||
@@ -153,7 +153,7 @@ def test_strip_invisble_text():
|
||||
nr_visible_pre = count('visible', page)
|
||||
ocrmypdf._graft.strip_invisible_text(pdf, page)
|
||||
nr_visible_post = count('visible', page)
|
||||
assert (
|
||||
nr_visible_pre == nr_visible_post
|
||||
), 'Number of visible text elements did not change'
|
||||
assert nr_visible_pre == nr_visible_post, (
|
||||
'Number of visible text elements did not change'
|
||||
)
|
||||
assert count('invisible', page) == 0, 'No invisible elems left'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Test JSON serialization of OcrOptions for multiprocessing compatibility."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import multiprocessing
|
||||
|
||||
@@ -43,7 +43,7 @@ def has_devanagari_font(manager: MultiFontManager) -> bool:
|
||||
# Marker for tests that require CJK fonts
|
||||
requires_cjk = pytest.mark.skipif(
|
||||
"not has_cjk_font(MultiFontManager())",
|
||||
reason="CJK font not available (not installed on system)"
|
||||
reason="CJK font not available (not installed on system)",
|
||||
)
|
||||
|
||||
|
||||
@@ -356,9 +356,7 @@ def test_get_all_fonts(multi_font_manager):
|
||||
class MockFontProvider:
|
||||
"""Mock FontProvider for testing missing fonts."""
|
||||
|
||||
def __init__(
|
||||
self, available_fonts: dict[str, FontManager], fallback: FontManager
|
||||
):
|
||||
def __init__(self, available_fonts: dict[str, FontManager], fallback: FontManager):
|
||||
"""Initialize mock font provider with given fonts."""
|
||||
self._fonts = available_fonts
|
||||
self._fallback = fallback
|
||||
|
||||
@@ -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
|
||||
@@ -208,9 +209,9 @@ class TestArabicScript:
|
||||
for para in page.paragraphs:
|
||||
if para.language in ('ara', 'per'):
|
||||
# Arabic paragraphs should have RTL direction
|
||||
assert (
|
||||
para.direction == 'rtl'
|
||||
), "Arabic paragraph should have RTL direction"
|
||||
assert para.direction == 'rtl', (
|
||||
"Arabic paragraph should have RTL direction"
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -529,9 +530,9 @@ class TestBaselineHandling:
|
||||
for line in page.lines:
|
||||
if line.baseline:
|
||||
# Baseline should be reasonable
|
||||
assert (
|
||||
-1.0 <= line.baseline.slope <= 1.0
|
||||
), "Baseline slope should be reasonable"
|
||||
assert -1.0 <= line.baseline.slope <= 1.0, (
|
||||
"Baseline slope should be reasonable"
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -556,9 +557,9 @@ class TestFontCoverage:
|
||||
]
|
||||
|
||||
for sample in latin_samples:
|
||||
assert multi_font_manager.has_all_glyphs(
|
||||
'NotoSans-Regular', sample
|
||||
), f"NotoSans should cover: {sample}"
|
||||
assert multi_font_manager.has_all_glyphs('NotoSans-Regular', sample), (
|
||||
f"NotoSans should cover: {sample}"
|
||||
)
|
||||
|
||||
def test_noto_sans_arabic_coverage(self, multi_font_manager_arabic):
|
||||
"""Test NotoSansArabic covers Arabic characters."""
|
||||
@@ -602,9 +603,9 @@ class TestFontCoverage:
|
||||
]
|
||||
|
||||
for sample in cjk_samples:
|
||||
assert multi_font_manager.has_all_glyphs(
|
||||
'NotoSansCJK-Regular', sample
|
||||
), f"NotoSansCJK should cover: {sample}"
|
||||
assert multi_font_manager.has_all_glyphs('NotoSansCJK-Regular', sample), (
|
||||
f"NotoSansCJK should cover: {sample}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -511,7 +511,8 @@ class TestFpdf2PdfRendererErrors:
|
||||
def test_invalid_ocr_class(self, multi_font_manager):
|
||||
"""Test that non-page elements are rejected."""
|
||||
line = OcrElement(
|
||||
ocr_class=OcrClass.LINE, bbox=BoundingBox(left=0, top=0, right=100, bottom=50)
|
||||
ocr_class=OcrClass.LINE,
|
||||
bbox=BoundingBox(left=0, top=0, right=100, bottom=50),
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="ocr_page"):
|
||||
@@ -622,9 +623,7 @@ def create_rtl_page(
|
||||
OcrElement(
|
||||
ocr_class=OcrClass.WORD,
|
||||
text=text,
|
||||
bbox=BoundingBox(
|
||||
left=bbox[0], top=bbox[1], right=bbox[2], bottom=bbox[3]
|
||||
),
|
||||
bbox=BoundingBox(left=bbox[0], top=bbox[1], right=bbox[2], bottom=bbox[3]),
|
||||
)
|
||||
for text, bbox in words
|
||||
]
|
||||
@@ -850,8 +849,7 @@ class TestRtlTextExtraction:
|
||||
decoded = ''.join(cmap.get(g, '') for g in glyph_ids)
|
||||
logical = decoded[::-1]
|
||||
assert logical == 'שלום', (
|
||||
f"Expected logical text 'שלום', got {logical!r} "
|
||||
f"(stream: {decoded!r})"
|
||||
f"Expected logical text 'שלום', got {logical!r} (stream: {decoded!r})"
|
||||
)
|
||||
|
||||
def test_rtl_tounicode_one_to_one(self, tmp_path, multi_font_manager):
|
||||
|
||||
+18
-18
@@ -160,9 +160,9 @@ def test_rotated_skew_timeout(resources, outpdf, rasterizer):
|
||||
input_file = resources / 'rotated_skew.pdf'
|
||||
in_pageinfo = PdfInfo(input_file)[0]
|
||||
|
||||
assert (
|
||||
in_pageinfo.height_pixels < in_pageinfo.width_pixels
|
||||
), "Expected the input page to be landscape"
|
||||
assert in_pageinfo.height_pixels < in_pageinfo.width_pixels, (
|
||||
"Expected the input page to be landscape"
|
||||
)
|
||||
assert in_pageinfo.rotation == 90, "Expected a rotated page"
|
||||
|
||||
out = check_ocrmypdf(
|
||||
@@ -184,9 +184,9 @@ def test_rotated_skew_timeout(resources, outpdf, rasterizer):
|
||||
|
||||
assert out_pageinfo.rotation == 0, "Expected no page rotation for output"
|
||||
|
||||
assert (
|
||||
in_pageinfo.width_pixels == h and in_pageinfo.height_pixels == w
|
||||
), "Expected page rotation to be baked in"
|
||||
assert in_pageinfo.width_pixels == h and in_pageinfo.height_pixels == w, (
|
||||
"Expected page rotation to be baked in"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('rasterizer', ['pypdfium', 'ghostscript'])
|
||||
@@ -411,16 +411,16 @@ def test_simulated_scan(outdir):
|
||||
)
|
||||
|
||||
with pikepdf.open(outdir / 'out.pdf') as pdf:
|
||||
assert (
|
||||
pdf.pages[1].mediabox[2] > pdf.pages[1].mediabox[3]
|
||||
), "Wrong orientation: not landscape"
|
||||
assert (
|
||||
pdf.pages[3].mediabox[2] > pdf.pages[3].mediabox[3]
|
||||
), "Wrong orientation: Not landscape"
|
||||
assert pdf.pages[1].mediabox[2] > pdf.pages[1].mediabox[3], (
|
||||
"Wrong orientation: not landscape"
|
||||
)
|
||||
assert pdf.pages[3].mediabox[2] > pdf.pages[3].mediabox[3], (
|
||||
"Wrong orientation: Not landscape"
|
||||
)
|
||||
|
||||
assert (
|
||||
pdf.pages[0].mediabox[2] < pdf.pages[0].mediabox[3]
|
||||
), "Wrong orientation: Not portrait"
|
||||
assert (
|
||||
pdf.pages[2].mediabox[2] < pdf.pages[2].mediabox[3]
|
||||
), "Wrong orientation: Not portrait"
|
||||
assert pdf.pages[0].mediabox[2] < pdf.pages[0].mediabox[3], (
|
||||
"Wrong orientation: Not portrait"
|
||||
)
|
||||
assert pdf.pages[2].mediabox[2] < pdf.pages[2].mediabox[3], (
|
||||
"Wrong orientation: Not portrait"
|
||||
)
|
||||
|
||||
@@ -103,7 +103,10 @@ class TestSystemFontProviderDirectories:
|
||||
patch.object(sys, 'platform', 'win32'),
|
||||
patch.dict(
|
||||
'os.environ',
|
||||
{'WINDIR': r'C:\Windows', 'LOCALAPPDATA': r'C:\Users\Test\AppData\Local'},
|
||||
{
|
||||
'WINDIR': r'C:\Windows',
|
||||
'LOCALAPPDATA': r'C:\Users\Test\AppData\Local',
|
||||
},
|
||||
),
|
||||
):
|
||||
provider._font_dirs = None # Reset cache
|
||||
@@ -113,8 +116,7 @@ class TestSystemFontProviderDirectories:
|
||||
assert len(dirs) == 2
|
||||
assert any('Windows' in d and 'Fonts' in d for d in dir_strs)
|
||||
assert any(
|
||||
'AppData' in d and 'Local' in d and 'Fonts' in d
|
||||
for d in dir_strs
|
||||
'AppData' in d and 'Local' in d and 'Fonts' in d for d in dir_strs
|
||||
)
|
||||
|
||||
def test_font_dirs_cached(self):
|
||||
|
||||
+11
-10
@@ -47,10 +47,13 @@ def make_ocr_opts(input_file='a.pdf', output_file='b.pdf', **kwargs):
|
||||
|
||||
|
||||
def test_old_tesseract_error():
|
||||
with patch(
|
||||
'ocrmypdf._exec.tesseract.version',
|
||||
return_value=TesseractVersion('4.00.00alpha'),
|
||||
), pytest.raises(MissingDependencyError):
|
||||
with (
|
||||
patch(
|
||||
'ocrmypdf._exec.tesseract.version',
|
||||
return_value=TesseractVersion('4.00.00alpha'),
|
||||
),
|
||||
pytest.raises(MissingDependencyError),
|
||||
):
|
||||
vd.check_options(*make_opts_pm(pdf_renderer='sandwich', language='eng'))
|
||||
|
||||
|
||||
@@ -59,9 +62,9 @@ def test_tesseract_not_installed(caplog):
|
||||
not_found.side_effect = FileNotFoundError('tesseract')
|
||||
with pytest.raises(MissingDependencyError, match="Could not find program"):
|
||||
vd.check_options(*make_opts_pm())
|
||||
assert (
|
||||
"'tesseract' could not be executed" in caplog.text
|
||||
), "Error message not printed"
|
||||
assert "'tesseract' could not be executed" in caplog.text, (
|
||||
"Error message not printed"
|
||||
)
|
||||
assert 'install' in caplog.text, "Install advice not printed"
|
||||
not_found.assert_called()
|
||||
|
||||
@@ -87,9 +90,7 @@ def test_mutex_options():
|
||||
|
||||
|
||||
def test_optimizing(caplog):
|
||||
vd.check_options(
|
||||
*make_opts_pm(optimize=0, png_quality=18, jpeg_quality=10)
|
||||
)
|
||||
vd.check_options(*make_opts_pm(optimize=0, png_quality=18, jpeg_quality=10))
|
||||
assert 'will be ignored because' in caplog.text
|
||||
|
||||
|
||||
|
||||
@@ -1434,7 +1434,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ocrmypdf"
|
||||
version = "17.7.0"
|
||||
version = "17.8.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "deprecation" },
|
||||
@@ -1467,8 +1467,10 @@ dev = [
|
||||
{ name = "cyclopts" },
|
||||
{ name = "ipykernel" },
|
||||
{ name = "mypy" },
|
||||
{ name = "prek" },
|
||||
{ name = "pygithub" },
|
||||
{ name = "reportlab" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
docs = [
|
||||
{ name = "myst-parser" },
|
||||
@@ -1503,7 +1505,7 @@ requires-dist = [
|
||||
{ name = "fpdf2", specifier = ">=2.8.0" },
|
||||
{ name = "img2pdf", specifier = ">=0.5" },
|
||||
{ name = "packaging", specifier = ">=20" },
|
||||
{ name = "pdfminer-six", specifier = ">=20220319" },
|
||||
{ name = "pdfminer-six", specifier = ">=20260107" },
|
||||
{ name = "pi-heif" },
|
||||
{ name = "pikepdf", specifier = ">=10" },
|
||||
{ name = "pillow", specifier = ">=10.0.1" },
|
||||
@@ -1523,8 +1525,10 @@ dev = [
|
||||
{ name = "cyclopts", specifier = ">=4.5.1" },
|
||||
{ name = "ipykernel", specifier = ">=6.29.5" },
|
||||
{ name = "mypy", specifier = ">=1.13.0" },
|
||||
{ name = "prek", specifier = ">=0.4.8" },
|
||||
{ name = "pygithub", specifier = ">=2.9.1" },
|
||||
{ name = "reportlab", specifier = ">=4.4.4" },
|
||||
{ name = "ruff", specifier = ">=0.14.11" },
|
||||
]
|
||||
docs = [
|
||||
{ name = "myst-parser", specifier = ">=4.0.1" },
|
||||
@@ -1656,7 +1660,7 @@ name = "pexpect"
|
||||
version = "4.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "ptyprocess", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "ptyprocess" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" }
|
||||
wheels = [
|
||||
@@ -1860,6 +1864,30 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prek"
|
||||
version = "0.4.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/46/e436a6eb9fdb4d3fd08d0ab7fdba19fe03a9e994ec810de57869b853bd8e/prek-0.4.8.tar.gz", hash = "sha256:d15d8bef72ab7b02c7dc01458ac9e05b3131534492b5ce9bb11c4f6f636fa868", size = 494570, upload-time = "2026-07-04T12:05:10.941Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/78/b4149c8913ced2e42debb49e261c4788a1ce431e84226921c2e1a7ea8545/prek-0.4.8-py3-none-linux_armv6l.whl", hash = "sha256:1f8f8cdc65836b571824c965daebb81b449f7e4a43894c58621f5708d5a185ed", size = 5668955, upload-time = "2026-07-04T12:04:41.588Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/5f/7f54a0087b6b2f1751aeb41266d9c15e66fd0055492814798ab818cd0414/prek-0.4.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:bce1798e96d9e3a6e6abf435da7107e81452f69edb3ca7c6f90a457355ea46e2", size = 6030947, upload-time = "2026-07-04T12:04:43.8Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/d6/f2829fc3902920c36b764a386fa303e71a8219dac25cb3827c575e84199a/prek-0.4.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ab3a52db17254d701c3cebb7eea58c8230aa7c1959aacfd5b5f25de18edb15d1", size = 5572593, upload-time = "2026-07-04T12:04:45.763Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/8c/c5589955bcd5e3e33b67d8bc3110818cecac82a38fd6bc8b5dfdc5de421c/prek-0.4.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:b3fcfd620523bbc3f51a21d7cd63449f659b9e2cf3582de12dd5949e23227b8f", size = 5847150, upload-time = "2026-07-04T12:04:47.419Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/9d/1f2dc91bdb79d2c4714b27eac9477a51490fba5b4731330dbbebc76bd345/prek-0.4.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42e65bc8425e9d7f1691a13ca1da2e07807d1ba76c35740833354b945131689e", size = 5573738, upload-time = "2026-07-04T12:04:49.125Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/29/69a7b58e16ecbc5f3989bf4b028018d11a82dcdd320b93d6588d72f32aa7/prek-0.4.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f578492a8e0c9bc6b4bf6dfbba8716f647d4cd0769bf10ad6cf336e3096fd392", size = 5981054, upload-time = "2026-07-04T12:04:50.842Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/cc/9b9850a60c22ed18c7755ebd2d72c6eefb37fac58149d09f6adc4691c2cf/prek-0.4.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4335f9d5beb123a3884a7fe34f57c9f0828f4fbb7666beab4298833459b104f", size = 6751350, upload-time = "2026-07-04T12:04:52.529Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/e5/c425aa7272b430630119e6757def3a2007555ba8cbeb2630e0448e7a8b7f/prek-0.4.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18a8747df9c602e052881d3efb14dd7f7d62a59bd7277ae5171c9e7661d59d84", size = 6243881, upload-time = "2026-07-04T12:04:54.703Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/da/accd3ad07fd2891d3c2777eb42435439fdf11982c51d60f087c0b6b6e102/prek-0.4.8-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:4db639db481d5f854eff9b3d2108889e613b8c15868bcf6bdd777c7cee577436", size = 5848846, upload-time = "2026-07-04T12:04:56.402Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/00/3477704635249f21f5f98ce444cd7690c2aa9dc8d146a045db88ef2cd8c5/prek-0.4.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:c3890a6f92316d2cf44eb50584e8d2b23a596dd70487022e61186a71a2ac0900", size = 5713942, upload-time = "2026-07-04T12:04:58.311Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/e6/3ca4fabaebeadc976d9a92d1d9130674265355ea3b728418bad61583b097/prek-0.4.8-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:fc7e15c24c591a37c6ffce5b25a021b16c299ac2649f183d812b67d665cd6551", size = 5554725, upload-time = "2026-07-04T12:04:59.96Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/46/2ab6aaaeff0cedb8955b2e4032071c8712382bdd423bb849718c3720180d/prek-0.4.8-py3-none-musllinux_1_1_i686.whl", hash = "sha256:36fe721704ff0c7624c1167639e23a5fe658bfd38c314f487219c9afd1eeb733", size = 5838595, upload-time = "2026-07-04T12:05:01.861Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/8b/91398f2b6cd1629d5d8ca8c85b08eca500814a374313b0193f4aaf6ab6c4/prek-0.4.8-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:162e544abc394a8124f3a4ad68efee116bad09440e679dbd1675177335c2a432", size = 6357222, upload-time = "2026-07-04T12:05:03.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/2a/ce5cbfaad36866134a21754640a05ecdba641fcd7ad15aa74cf3443f34f6/prek-0.4.8-py3-none-win32.whl", hash = "sha256:2602e46c8c5da7dfa69f60fcf88c2b57132ac623f49fb08bfb3094298c5f07e3", size = 5354388, upload-time = "2026-07-04T12:05:05.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/03/3bc908bc5f7e430315553e47dfa055f19923a3888f9afe4da19f244b5cbf/prek-0.4.8-py3-none-win_amd64.whl", hash = "sha256:7cb22da60bee41b89c4978c0bea7126a3c0ccc003dae6748cf29b53947815edc", size = 5748221, upload-time = "2026-07-04T12:05:07.559Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/a7/4295e6d5f5028171dfeb115ad38ab76bf3fe0c8df91b70d73c79aa760a94/prek-0.4.8-py3-none-win_arm64.whl", hash = "sha256:da70057f577b15d4bd121bf9dd29ee205fd4b4d75a0cafba062e84d7e8b4378b", size = 5574425, upload-time = "2026-07-04T12:05:09.595Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prompt-toolkit"
|
||||
version = "3.0.52"
|
||||
@@ -2633,6 +2661,31 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.15.20"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
@@ -2679,23 +2732,23 @@ resolution-markers = [
|
||||
"python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "alabaster", marker = "python_full_version < '3.12'" },
|
||||
{ name = "babel", marker = "python_full_version < '3.12'" },
|
||||
{ name = "colorama", marker = "python_full_version < '3.12' and sys_platform == 'win32'" },
|
||||
{ name = "docutils", marker = "python_full_version < '3.12'" },
|
||||
{ name = "imagesize", marker = "python_full_version < '3.12'" },
|
||||
{ name = "jinja2", marker = "python_full_version < '3.12'" },
|
||||
{ name = "packaging", marker = "python_full_version < '3.12'" },
|
||||
{ name = "pygments", marker = "python_full_version < '3.12'" },
|
||||
{ name = "requests", marker = "python_full_version < '3.12'" },
|
||||
{ name = "roman-numerals", marker = "python_full_version < '3.12'" },
|
||||
{ name = "snowballstemmer", marker = "python_full_version < '3.12'" },
|
||||
{ name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.12'" },
|
||||
{ name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.12'" },
|
||||
{ name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.12'" },
|
||||
{ name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.12'" },
|
||||
{ name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.12'" },
|
||||
{ name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.12'" },
|
||||
{ name = "alabaster" },
|
||||
{ name = "babel" },
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "docutils" },
|
||||
{ name = "imagesize" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pygments" },
|
||||
{ name = "requests" },
|
||||
{ name = "roman-numerals" },
|
||||
{ name = "snowballstemmer" },
|
||||
{ name = "sphinxcontrib-applehelp" },
|
||||
{ name = "sphinxcontrib-devhelp" },
|
||||
{ name = "sphinxcontrib-htmlhelp" },
|
||||
{ name = "sphinxcontrib-jsmath" },
|
||||
{ name = "sphinxcontrib-qthelp" },
|
||||
{ name = "sphinxcontrib-serializinghtml" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" }
|
||||
wheels = [
|
||||
@@ -2718,23 +2771,23 @@ resolution-markers = [
|
||||
"python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "alabaster", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "babel", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" },
|
||||
{ name = "docutils", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "imagesize", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "jinja2", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "packaging", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "pygments", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "requests", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "roman-numerals", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "snowballstemmer", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "alabaster" },
|
||||
{ name = "babel" },
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "docutils" },
|
||||
{ name = "imagesize" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pygments" },
|
||||
{ name = "requests" },
|
||||
{ name = "roman-numerals" },
|
||||
{ name = "snowballstemmer" },
|
||||
{ name = "sphinxcontrib-applehelp" },
|
||||
{ name = "sphinxcontrib-devhelp" },
|
||||
{ name = "sphinxcontrib-htmlhelp" },
|
||||
{ name = "sphinxcontrib-jsmath" },
|
||||
{ name = "sphinxcontrib-qthelp" },
|
||||
{ name = "sphinxcontrib-serializinghtml" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" }
|
||||
wheels = [
|
||||
|
||||
Reference in New Issue
Block a user