Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Set up python
uses: astral-sh/setup-uv@v6
with:
python-version: 3.11
python-version: 3.12

- name: Run tests
run: uv run --with '.[test,stats]' pytest --durations 0 --cov .
Expand Down
15 changes: 7 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ default_install_hook_types: [pre-commit, commit-msg]

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.10
rev: v0.15.16
hooks:
- id: ruff-check
args: [--fix]
Expand All @@ -26,15 +26,14 @@ repos:
args: [--check-filenames]

- repo: https://github.com/crate-ci/typos
rev: v1.45.0
rev: v1.47.2
hooks:
- id: typos

- repo: local
- repo: https://github.com/astral-sh/ty-pre-commit
rev: v0.0.48
hooks:
- id: ty
name: ty check
entry: ty check
language: python
types: [python, jupyter]
additional_dependencies: [ty, pytest, pandas, requests]
args: [--isolated, --all-extras]
# pre-commit hook: override global UV_FROZEN=1, which would otherwise require uv.lock.
env: { UV_FROZEN: "0" }
26 changes: 20 additions & 6 deletions pdf_compressor/ilovepdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def __init__(
*,
verbose: bool = False,
password: str = "",
**kwargs: Any,
debug: bool = False,
) -> None:
"""Creates a new task object to interact with the API.

Expand All @@ -135,9 +135,10 @@ def __init__(
and processing files. Defaults to False.
password (str, optional): Password to open PDFs in case they have one.
Defaults to "".
**kwargs: Additional keyword arguments to pass to ILovePDF.__init__().
debug (bool, optional): Whether to ask iLovePDF for debug responses.
Defaults to False.
"""
super().__init__(public_key, **kwargs)
super().__init__(public_key, debug=debug)

self.files: dict[str, str] = {}
self._task_id = ""
Expand Down Expand Up @@ -326,7 +327,13 @@ class Compress(Task):
"""

def __init__(
self, public_key: str, compression_level: str = "recommended", **kwargs: Any
self,
public_key: str,
compression_level: str = "recommended",
*,
verbose: bool = False,
password: str = "",
debug: bool = False,
) -> None:
"""Subclass of Task for using the iLovePDF compression tool.

Expand All @@ -335,12 +342,19 @@ def __init__(
https://developer.ilovepdf.com/signup.
compression_level (str, optional): How hard to squeeze the file size.
'extreme' noticeably degrades image quality. Defaults to 'recommended'.
**kwargs: Additional keyword arguments to pass to Task.__init__().
verbose (bool, optional): Whether to print progress messages while uploading
and processing files. Defaults to False.
password (str, optional): Password to open PDFs in case they have one.
Defaults to "".
debug (bool, optional): Whether to ask iLovePDF for debug responses.
Defaults to False.

Raises:
ValueError: If the compression level is invalid.
"""
super().__init__(public_key, tool="compress", **kwargs)
super().__init__(
public_key, tool="compress", verbose=verbose, password=password, debug=debug
)

if compression_level not in (valid_levels := ("low", "recommended", "extreme")):
raise ValueError(
Expand Down
6 changes: 3 additions & 3 deletions pdf_compressor/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from argparse import ArgumentParser
from glob import glob
from importlib.metadata import version
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING

from pdf_compressor.ilovepdf import Compress, ILovePDF
from pdf_compressor.utils import ROOT, del_or_keep_compressed, load_dotenv
Expand Down Expand Up @@ -178,7 +178,7 @@ def main(argv: Sequence[str] | None = None) -> int:

return 0

return compress(**vars(args)) # ty: ignore[missing-argument]
return compress(**vars(args))


def compress(
Expand All @@ -195,7 +195,7 @@ def compress(
on_bad_files: str = "error",
write_stats_path: str = "",
password: str = "",
**kwargs: Any, # noqa: ARG001
**kwargs: object, # noqa: ARG001
) -> int:
"""Compress PDFs using iLovePDF's API.

Expand Down
13 changes: 9 additions & 4 deletions pdf_compressor/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
if TYPE_CHECKING:
from collections.abc import Sequence

type CompressionStats = dict[str, dict[str, object]]

ROOT = dirname(dirname(abspath(__file__)))


Expand Down Expand Up @@ -83,7 +85,7 @@ def del_or_keep_compressed(
suffix: str,
min_size_reduction: int,
verbose: bool = False,
) -> dict[str, dict[str, object]]:
) -> CompressionStats:
"""Check whether compressed PDFs are smaller than original and relocate if so.

Relocates each compressed file to same directory as the original either with suffix
Expand All @@ -103,8 +105,11 @@ def del_or_keep_compressed(
False.

Returns:
pd.DataFrame: Table with original and compressed file sizes.
""" # noqa: DOC501
CompressionStats: Original and compressed file size stats.

Raises:
RuntimeError: If called without in-place mode or suffix after validation.
"""
if (n_files := len(pdfs)) == 1:
compressed_files = [downloaded_file]
else: # if multiple files were uploaded, downloaded_file is a ZIP archive
Expand All @@ -115,7 +120,7 @@ def del_or_keep_compressed(

total_orig_size = total_compressed_size = 0

stats = {}
stats: CompressionStats = {}

for idx, orig_path in enumerate(pdfs):
if n_files > 1:
Expand Down
14 changes: 3 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["uv_build>=0.7.19"]
requires = ["uv_build>=0.11.20"]
build-backend = "uv_build"

[project]
Expand All @@ -13,12 +13,11 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
requires-python = ">=3.11"
requires-python = ">=3.12"
dependencies = ["requests >= 2.25.0"]
license = { file = "license" }

Expand All @@ -42,22 +41,16 @@ testpaths = ["tests"]
addopts = "-p no:warnings"

[tool.ruff]
target-version = "py311"
target-version = "py312"

[tool.ruff.lint]
select = ["ALL"]
ignore = [
"ANN001", # missing type annotation for self
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed in `**kwargs
"C901", # Function is too complex
"COM",
"CPY001", # missing copyright
"EM", # Exception must not use a string literal
"FURB103", # open and write should be replaced by Path(file_path).write_bytes()
"PLC1901", # x == "" can be simplified to `not x`
"PLR0912", # too many branches
"PLR0913", # too many arguments
"PLR0914", # too many local variables
"PLR0915", # too many statements
"PTH", # Prefer pathlib over os.path
"SIM105", # Use contextlib.suppress(FileNotFoundError) instead of try-except-pass
Expand All @@ -66,7 +59,6 @@ ignore = [
]
pydocstyle.convention = "google"
isort.split-on-trailing-comma = false
preview = true

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def mock_send_request(
method: str,
endpoint: str,
payload: dict[str, Any] | None = None,
**_kwargs: Any,
**_kwargs: object,
) -> MagicMock:
if method == "post" and endpoint == "process" and payload is not None:
# Check that each file in the payload has the correct password
Expand Down
Loading