Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test and lint workflows #4

Merged
merged 5 commits into from
Mar 29, 2024
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
41 changes: 41 additions & 0 deletions .github/workflows/lint-and-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Lint and Test 🧪

on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches:
- dev
- master

jobs:
lint-black:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: psf/black@stable
lint-isort:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: isort/isort-action@v1
test:
name: Run tests 🧪
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install dependencies
run: |
pip install -r requirements.txt
sudo apt-get install -y poppler-utils
- name: Run pytest
run: |
pip install pytest pytest-cov
pytest --cov=src --cov-report=xml --cov-report=html
2 changes: 2 additions & 0 deletions .isort.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[settings]
profile=black
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ local_scheme = "no-local-version"

[tool.setuptools.dynamic]
dependencies = { file = ["requirements.txt"] }

[tool.pytest.ini_options]
pythonpath = "src"
2 changes: 1 addition & 1 deletion src/pdf_watermark/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class DrawingOptions:
def __init__(
self,
watermark: str,
opacity,
opacity: float,
angle: float,
text_color: str,
text_font: str,
Expand Down
48 changes: 33 additions & 15 deletions src/pdf_watermark/watermark.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@
)


class DEFAULTS:
angle = 45
dpi = 300
horizontal_alignment = "center"
horizontal_boxes = 3
image_scale = 1
margin = False
opacity = 0.1
save_as_image = False
text_color = "#000000"
text_font = "Helvetica"
text_size = 12
unselectable = False
vertical_boxes = 6
x = 0.5
y = 0.5


def generic_watermark_parameters(f):
@wraps(f)
@click.argument("file")
Expand All @@ -26,62 +44,62 @@ def generic_watermark_parameters(f):
"--opacity",
type=float,
help="Watermark opacity between 0 (invisible) and 1 (no transparency).",
default=0.1,
default=DEFAULTS.opacity,
)
@click.option(
"-a",
"--angle",
type=float,
help="Watermark inclination in degrees.",
default=45,
default=DEFAULTS.angle,
)
@click.option(
"-tc",
"--text-color",
type=str,
help="Text color in hexadecimal format, e.g. #000000.",
default="#000000",
default=DEFAULTS.text_color,
)
@click.option(
"-tf",
"--text-font",
type=str,
help="Text font to use. Supported fonts are those supported by reportlab.",
default="Helvetica",
default=DEFAULTS.text_font,
)
@click.option(
"-ts",
"--text-size",
type=int,
help="Text font size.",
default=12,
default=DEFAULTS.text_size,
)
@click.option(
"--unselectable",
type=bool,
is_flag=True,
help="Make the watermark text unselectable. This works by drawing the text as an image, and thus results in a larger file size.",
default=False,
default=DEFAULTS.unselectable,
)
@click.option(
"-is",
"--image-scale",
type=float,
help="Scale factor for the image. Note that before this factor is applied, the image is already scaled down to fit in the boxes.",
default=1,
default=DEFAULTS.image_scale,
)
@click.option(
"--save-as-image",
type=bool,
is_flag=True,
help="Convert each PDF page to an image. This makes removing the watermark more difficult but also increases the file size.",
default=False,
default=DEFAULTS.save_as_image,
)
@click.option(
"--dpi",
type=int,
help="DPI to use when saving the PDF as an image.",
default=300,
default=DEFAULTS.dpi,
)
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
Expand All @@ -105,21 +123,21 @@ def cli():
"--y",
type=float,
help="Position of the watermark with respect to the vertical direction. Must be between 0 and 1.",
default=0.5,
default=DEFAULTS.y,
)
@click.option(
"-x",
"--x",
type=float,
help="Position of the watermark with respect to the horizontal direction. Must be between 0 and 1.",
default=0.5,
default=DEFAULTS.x,
)
@click.option(
"-ha",
"--horizontal-alignment",
type=str,
help="Alignment of the watermark with respect to the horizontal direction. Can be one of 'left', 'right' and 'center'.",
default="center",
default=DEFAULTS.horizontal_alignment,
)
@generic_watermark_parameters
def insert(
Expand Down Expand Up @@ -174,22 +192,22 @@ def insert(
"--horizontal-boxes",
type=int,
help="Number of repetitions of the watermark along the horizontal direction.",
default=3,
default=DEFAULTS.horizontal_boxes,
)
@click.option(
"-v",
"--vertical-boxes",
type=int,
help="Number of repetitions of the watermark along the vertical direction.",
default=6,
default=DEFAULTS.vertical_boxes,
)
@click.option(
"-m",
"--margin",
type=bool,
is_flag=True,
help="Wether to leave a margin around the page or not. When False (default), the watermark will be cut on the PDF edges.",
default=False,
default=DEFAULTS.margin,
)
@generic_watermark_parameters
def grid(
Expand Down
Empty file added tests/__init__.py
Empty file.
Binary file added tests/fixtures/0.pdf
Binary file not shown.
Binary file added tests/fixtures/1.pdf
Binary file not shown.
Binary file added tests/fixtures/input.pdf
Binary file not shown.
85 changes: 85 additions & 0 deletions tests/test_add_watermark_from_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""
Test the outputs of the CLI with a bunch of different options on simple features. These tests are far from perfect.
"""

import os

import numpy as np
import pytest
from pdf2image import convert_from_path

from pdf_watermark.handler import add_watermark_from_options
from pdf_watermark.options import (
DrawingOptions,
FilesOptions,
GridOptions,
InsertOptions,
)
from pdf_watermark.watermark import DEFAULTS

INPUT = "tests/fixtures/input.pdf"
OUTPUT = "output.pdf"
FIXTURES = ["tests/fixtures/0.pdf", "tests/fixtures/1.pdf"]


@pytest.fixture(autouse=True)
def cleanup():
yield
os.remove(OUTPUT)


DRAWING_OPTIONS_FIXTURES = [
DrawingOptions(
watermark="watermark",
opacity=DEFAULTS.opacity,
angle=DEFAULTS.angle,
text_color=DEFAULTS.text_color,
text_font=DEFAULTS.text_font,
text_size=DEFAULTS.text_size,
unselectable=DEFAULTS.unselectable,
image_scale=DEFAULTS.image_scale,
save_as_image=DEFAULTS.save_as_image,
dpi=DEFAULTS.dpi,
)
]

FILES_OPTIONS_FIXTURES = [FilesOptions(INPUT, OUTPUT)]

GRID_OPTIONS_FIXTURES = [
GridOptions(
horizontal_boxes=DEFAULTS.horizontal_boxes,
vertical_boxes=DEFAULTS.vertical_boxes,
margin=DEFAULTS.margin,
)
]

INSERT_OPTIONS_FIXTURES = [
InsertOptions(
y=DEFAULTS.y,
x=DEFAULTS.x,
horizontal_alignment=DEFAULTS.horizontal_alignment,
)
]


def assert_pdfs_are_close(path_1: str, path_2: str, epsilon: float = 1e-10):
"""This function checks that two PDFs are close enough. We chose to convert the PDFs to images and then compare their L1 norms, because other techniques (hashing for instance) might break easily."""
images_1 = convert_from_path(path_1)
images_2 = convert_from_path(path_2)

for im1, im2 in zip(images_1, images_2):
assert np.sum(np.abs(np.array(im1) - np.array(im2))) < epsilon


def test_add_watermark_from_options():
index = 0
for files_options in FILES_OPTIONS_FIXTURES:
for drawing_options in DRAWING_OPTIONS_FIXTURES:
for specific_options in GRID_OPTIONS_FIXTURES + INSERT_OPTIONS_FIXTURES:
add_watermark_from_options(
files_options=files_options,
drawing_options=drawing_options,
specific_options=specific_options,
)
assert_pdfs_are_close(OUTPUT, FIXTURES[index])
index += 1
Loading