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
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Python CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:

runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install poetry
poetry install
- name: Test with unittest
run: |
poetry run coverage run -m unittest discover tests

Copilot AI Jan 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CI workflow will fail when it tries to run the tests because of the incorrect import statements in the test files (see comments on test files). The tests import from encryptpdf instead of passifypdf.encryptpdf, which will cause ImportError when run through Poetry. This needs to be fixed before the CI can pass.

Suggested change
poetry run coverage run -m unittest discover tests
PYTHONPATH=passifypdf poetry run coverage run -m unittest discover tests

Copilot uses AI. Check for mistakes.
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
42 changes: 0 additions & 42 deletions .github/workflows/python-ci.yml

This file was deleted.

47 changes: 20 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,40 @@ Same as encrypt or lock your PDF via a password.
# How to use ?

## Clone
```
git clone git@github.com:SUPAIDEAS/passifypdf.git
```
or
```
```bash
git clone https://github.com/SUPAIDEAS/passifypdf.git
```

## Pull Dependencies (install before usage)
## Install Dependencies
Uses [Poetry](https://python-poetry.org/) for dependency management.

```
pip install -r requirements.txt
```bash
cd passifypdf
pip install poetry
poetry install
```

## Usage
Run the CLI tool using `poetry run`:

Run the "main" from IDE or from CLI:

```
if __name__ == "__main__":
...
```bash
poetry run passifypdf --help
```

## Usage via CLI:

```
python encryptpdf.py
Or activate the shell:
```bash
poetry shell
passifypdf --help
```

Sample Run:
```
change dir to "passifypdf/passifypdf",

Then run,
python encryptpdf.py
Sample Run:
```bash
passifypdf -i input.pdf -o protected.pdf -p mySecretPassword

-------------------------Sample output----------------------
$python encryptpdf.py
Congratulations!
PDF file encrypted successfully and saved as 'Sample_PDF_protected.pdf'
$
# -------------------------Sample output----------------------
# Congratulations!
# PDF file encrypted successfully and saved as 'protected.pdf'
```

## Known Issues
Expand Down
18 changes: 18 additions & 0 deletions docs/CLI_OPTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# CLI Options

The `passifypdf` tool accepts the following command-line arguments:

| Option | Long Option | Required | Description |
| :--- | :--- | :--- | :--- |
| `-i` | `--input` | Yes | Path to the input PDF file. |
| `-o` | `--output` | Yes | Path to the output (encrypted) PDF file. |
| `-p` | `--passwd` | Yes | Password to use for encryption. **Avoid passing sensitive passwords directly on the command line, as they may be exposed via process listings and shell history.** |
| `-v` | `--version` | No | Show the program's version number and exit. |
| `-h` | `--help` | No | Show the help message and exit. |

## Example Usage

```bash
# Using the installed CLI command (after `poetry install`)
passifypdf -i input.pdf -o protected.pdf -p mySecretPassword
```
2 changes: 1 addition & 1 deletion passifypdf/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
"""Unit test package for {{ cookiecutter.project_slug }}."""
"""passifypdf - Protect PDF files with a password of your choice."""
13 changes: 8 additions & 5 deletions passifypdf/cli.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import argparse


def get_arg_parser():
arg_parser = argparse.ArgumentParser(description=("Encrypt a PDF file with a password of your choice."), epilog=("For more information, visit: https://github.com/SUPAIDEAS/passifypdf"))
def get_arg_parser() -> argparse.ArgumentParser:
arg_parser = argparse.ArgumentParser(
description="Encrypt a PDF file with a password of your choice.",
epilog="For more information, visit: https://github.com/SUPAIDEAS/passifypdf"
)
arg_parser.add_argument("-v", "--version", action="version", version="%(prog)s 1.0")
arg_parser.add_argument("-i", "--input", required=True, help="path to the input pdf file")
arg_parser.add_argument("-o", "--output", required=True, help="path to the output encrypted pdf file")
arg_parser.add_argument("-p", "--passwd", required=True, type=str, help="password to encrypt the pdf file")
arg_parser.add_argument("-i", "--input", required=True, help="Path to the input PDF file to be encrypted")
arg_parser.add_argument("-o", "--output", required=True, help="Path where the encrypted PDF file will be saved")
arg_parser.add_argument("-p", "--passwd", required=True, type=str, help="Password to encrypt the PDF file with")
return arg_parser
75 changes: 57 additions & 18 deletions passifypdf/encryptpdf.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,72 @@
from cli import get_arg_parser
"""Core PDF encryption module."""

import sys
from pathlib import Path
from typing import Union

from pypdf import PdfReader, PdfWriter

from .cli import get_arg_parser


def encrypt_pdf(input_pdf: Union[str, Path], output_pdf: Union[str, Path], password: str) -> None:
"""
Encrypts a PDF file with a password.

def encrypt_pdf(input_pdf, output_pdf, password):
reader = PdfReader(input_pdf)
writer = PdfWriter()
Args:
input_pdf (Union[str, Path]): Path to the input PDF file.
output_pdf (Union[str, Path]): Path to the output PDF file.
password (str): Password to encrypt the PDF with.

# Add all pages from the reader to the writer
for page_num in range(len(reader.pages)):
writer.add_page(reader.pages[page_num])
Raises:
FileNotFoundError: If the input PDF file does not exist.
IsADirectoryError: If the input path is a directory.
Exception: For other errors during processing.
"""
input_path = Path(input_pdf)
if not input_path.exists():
raise FileNotFoundError(f"Input file '{input_pdf}' not found.")

# Encrypt with a password
writer.encrypt(password)
if not input_path.is_file():
raise IsADirectoryError(f"Input path '{input_pdf}' is not a file.")

# Write the encrypted PDF to a new PDF file passed as param
with open(output_pdf, "wb") as f:
writer.write(f)
try:
reader = PdfReader(input_path)
writer = PdfWriter()

# Add all pages from the reader to the writer
for page in reader.pages:
writer.add_page(page)

def pipeline(any_thing):
return any_thing
# Encrypt with a password
writer.encrypt(password)

# Write the encrypted PDF to a new PDF file passed as param
with open(output_pdf, "wb") as f:
writer.write(f)

def main():
except Exception as e:
raise Exception(f"Failed to encrypt PDF: {e}")


def main() -> int:
"""
Main function to run the CLI.

Returns:
int: Exit code (0 for success, 1 for failure).
"""
arg_parser = get_arg_parser()
args = arg_parser.parse_args()
encrypt_pdf(args.input, args.output, args.passwd)
print(f"Congratulations!\nPDF file encrypted successfully and saved as '{args.output}'")

try:
encrypt_pdf(args.input, args.output, args.passwd)
print(f"Congratulations!\nPDF file encrypted successfully and saved as '{args.output}'")
return 0
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1


if __name__ == "__main__":
main()
sys.exit(main())
Loading