diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..41546ee --- /dev/null +++ b/.github/workflows/ci.yml @@ -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 + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml deleted file mode 100644 index f7fb08b..0000000 --- a/.github/workflows/python-ci.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Python CI - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.9', '3.10', '3.11'] - - 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 pytest pytest-cov - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - - name: Run unit tests - run: | - pytest tests/unittests/ -v --cov=passifypdf --cov-report=xml --cov-report=term - - - name: Run integration tests - run: | - pytest tests/integrationtests/ -v - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 - with: - file: ./coverage.xml - fail_ci_if_error: false diff --git a/README.md b/README.md index 5ceaa39..1c96951 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/CLI_OPTIONS.md b/docs/CLI_OPTIONS.md new file mode 100644 index 0000000..6442abc --- /dev/null +++ b/docs/CLI_OPTIONS.md @@ -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 +``` diff --git a/passifypdf/__init__.py b/passifypdf/__init__.py index 79b42da..f7b86a0 100644 --- a/passifypdf/__init__.py +++ b/passifypdf/__init__.py @@ -1 +1 @@ -"""Unit test package for {{ cookiecutter.project_slug }}.""" +"""passifypdf - Protect PDF files with a password of your choice.""" diff --git a/passifypdf/cli.py b/passifypdf/cli.py index 3f2daa1..7364188 100644 --- a/passifypdf/cli.py +++ b/passifypdf/cli.py @@ -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 \ No newline at end of file diff --git a/passifypdf/encryptpdf.py b/passifypdf/encryptpdf.py index 077eebf..e326cab 100644 --- a/passifypdf/encryptpdf.py +++ b/passifypdf/encryptpdf.py @@ -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() \ No newline at end of file + sys.exit(main()) \ No newline at end of file diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..ce22814 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,126 @@ +# This file is automatically @generated by Poetry 2.3.1 and should not be changed by hand. + +[[package]] +name = "coverage" +version = "7.6.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, +] + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + +[[package]] +name = "pypdf" +version = "4.3.1" +description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "pypdf-4.3.1-py3-none-any.whl", hash = "sha256:64b31da97eda0771ef22edb1bfecd5deee4b72c3d1736b7df2689805076d6418"}, + {file = "pypdf-4.3.1.tar.gz", hash = "sha256:b2f37fe9a3030aa97ca86067a56ba3f9d3565f9a791b305c7355d8392c30d91b"}, +] + +[package.dependencies] +typing_extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} + +[package.extras] +crypto = ["PyCryptodome ; python_version == \"3.6\"", "cryptography ; python_version >= \"3.7\""] +dev = ["black", "flit", "pip-tools", "pre-commit (<2.18.0)", "pytest-cov", "pytest-socket", "pytest-timeout", "pytest-xdist", "wheel"] +docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"] +full = ["Pillow (>=8.0.0)", "PyCryptodome ; python_version == \"3.6\"", "cryptography ; python_version >= \"3.7\""] +image = ["Pillow (>=8.0.0)"] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, + {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, +] + +[metadata] +lock-version = "2.1" +python-versions = "^3.8" +content-hash = "5be578d4d6428e9a32edf0d821c8092d1a83af70283685207885d852fbaa3678" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0aa8348 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,21 @@ +[tool.poetry] +name = "passifypdf" +version = "1.0.0" +description = "Protect PDF by a password of your choice" +authors = ["Nirmal Chandra "] +readme = "README.md" +packages = [{include = "passifypdf"}] + +[tool.poetry.dependencies] +python = "^3.8" +pypdf = "^4.3.1" + +[tool.poetry.group.dev.dependencies] +coverage = "^7.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +passifypdf = "passifypdf.encryptpdf:main" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 15f3f6c..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -pypdf==4.3.1 \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 70c8816..0000000 --- a/setup.py +++ /dev/null @@ -1,16 +0,0 @@ -from setuptools import setup, find_packages - -setup( - name='passifypdf', - version='1.0', - description='Protect PDF by a password of your choice', - long_description='Protect PDF by a password of your choice', - include_package_data=True, - author_email='nirmal.fleet@gmail.com', - author='Nirmal Chandra', - url='https://github.com/SUPAIDEAS/passifypdf.git', - packages=find_packages(exclude=('tests')), - install_requires=[ - 'pypdf==4.3.1' - ] -) diff --git a/tests/integrationtests/test_encryptpdf_integration.py b/tests/integrationtests/test_encryptpdf_integration.py index b11f945..7cebc00 100644 --- a/tests/integrationtests/test_encryptpdf_integration.py +++ b/tests/integrationtests/test_encryptpdf_integration.py @@ -1,8 +1,41 @@ +import os from unittest import TestCase -from encryptpdf import pipeline +from pypdf import PdfReader, PdfWriter + +from passifypdf.encryptpdf import encrypt_pdf class TestPdfIntegrationTests(TestCase): - def test_pipeline_integration(self): - self.assertEquals("awesomePdfProtection_IntegrationTest", pipeline("awesomePdfProtection_IntegrationTest")) + def setUp(self): + self.input_pdf = "test_input.pdf" + self.output_pdf = "test_output.pdf" + self.password = "strongpassword" + + # Create a dummy PDF + writer = PdfWriter() + writer.add_blank_page(width=100, height=100) + with open(self.input_pdf, "wb") as f: + writer.write(f) + + def tearDown(self): + # Cleanup files + if os.path.exists(self.input_pdf): + os.remove(self.input_pdf) + if os.path.exists(self.output_pdf): + os.remove(self.output_pdf) + + def test_encrypt_pdf_integration(self): + # Encrypt the PDF + encrypt_pdf(self.input_pdf, self.output_pdf, self.password) + + # Verify output exists + self.assertTrue(os.path.exists(self.output_pdf)) + + # Verify it is encrypted + reader = PdfReader(self.output_pdf) + self.assertTrue(reader.is_encrypted) + + # Verify we can decrypt it + self.assertTrue(reader.decrypt(self.password)) + self.assertEqual(len(reader.pages), 1) diff --git a/tests/test_encryptpdf_example.py b/tests/test_encryptpdf_example.py deleted file mode 100644 index 9ea4cb6..0000000 --- a/tests/test_encryptpdf_example.py +++ /dev/null @@ -1,8 +0,0 @@ -from unittest import TestCase - -from encryptpdf import pipeline - - -class TestPdfExample(TestCase): - def test_pipeline_example(self): - self.assertEquals("awesomePdfProtection", pipeline("awesomePdfProtection")) diff --git a/tests/unittests/test_encryptpdf.py b/tests/unittests/test_encryptpdf.py index 83fc7df..d8d2aa9 100644 --- a/tests/unittests/test_encryptpdf.py +++ b/tests/unittests/test_encryptpdf.py @@ -1,8 +1,61 @@ from unittest import TestCase - -from encryptpdf import pipeline +from unittest.mock import patch, mock_open +from passifypdf.encryptpdf import encrypt_pdf class TestPdfUnitTests(TestCase): - def test_pipeline(self): - self.assertEquals("awesomePdfProtection-UnitTest", pipeline("awesomePdfProtection-UnitTest")) + + @patch('passifypdf.encryptpdf.PdfReader') + @patch('passifypdf.encryptpdf.PdfWriter') + @patch('passifypdf.encryptpdf.Path') # Mock Path + @patch('builtins.open', new_callable=mock_open) + def test_encrypt_pdf(self, mock_file, mock_path_cls, mock_writer_cls, mock_reader_cls): + # Setup mocks + mock_path_instance = mock_path_cls.return_value + mock_path_instance.exists.return_value = True + mock_path_instance.is_file.return_value = True + + mock_reader_instance = mock_reader_cls.return_value + mock_reader_instance.pages = ['page1', 'page2'] + + mock_writer_instance = mock_writer_cls.return_value + + # Call the function + encrypt_pdf("input.pdf", "output.pdf", "secret") + + # Verify Path existence check + mock_path_cls.assert_called_with("input.pdf") + mock_path_instance.exists.assert_called() + mock_path_instance.is_file.assert_called() + + # Verify PdfReader was called with the path object + mock_reader_cls.assert_called_with(mock_path_instance) + + # Verify pages were added + self.assertEqual(mock_writer_instance.add_page.call_count, 2) + mock_writer_instance.add_page.assert_any_call('page1') + mock_writer_instance.add_page.assert_any_call('page2') + + # Verify encryption + mock_writer_instance.encrypt.assert_called_with("secret") + + # Verify file write + mock_file.assert_called_with("output.pdf", "wb") + mock_writer_instance.write.assert_called_with(mock_file()) + + @patch('passifypdf.encryptpdf.Path') + def test_encrypt_pdf_file_not_found(self, mock_path_cls): + mock_path_instance = mock_path_cls.return_value + mock_path_instance.exists.return_value = False + + with self.assertRaises(FileNotFoundError): + encrypt_pdf("nonexistent.pdf", "output.pdf", "secret") + + @patch('passifypdf.encryptpdf.Path') + def test_encrypt_pdf_is_directory(self, mock_path_cls): + mock_path_instance = mock_path_cls.return_value + mock_path_instance.exists.return_value = True + mock_path_instance.is_file.return_value = False + + with self.assertRaises(IsADirectoryError): + encrypt_pdf("directory", "output.pdf", "secret")