From 3825c7b8e7890ef507de4099f5ed9c1c3cf9ec8a Mon Sep 17 00:00:00 2001 From: Martin Thoma Date: Fri, 23 Dec 2022 16:55:43 +0100 Subject: [PATCH 1/3] MAINT: Move PyPDF2 to pypdf (#1513) All lowercase, no number. See #1488 --- .github/ISSUE_TEMPLATE/bug-report.md | 2 +- .github/ISSUE_TEMPLATE/feature-request.md | 4 +- .github/SECURITY.md | 2 +- .github/workflows/benchmark.yaml | 4 +- .github/workflows/github-ci.yaml | 6 +- .gitignore | 4 +- CONTRIBUTORS.md | 14 ++-- Makefile | 10 +-- README.md | 65 +++++++++--------- docs/conf.py | 2 +- docs/dev/deprecations.md | 8 +-- docs/dev/intro.md | 20 +++--- docs/dev/testing.md | 4 +- docs/index.rst | 12 ++-- docs/meta/comparisons.md | 23 +++---- docs/meta/faq.md | 23 ++++--- docs/meta/history.md | 20 +++++- docs/meta/project-governance.md | 52 +++++++------- docs/modules/AnnotationBuilder.rst | 2 +- docs/modules/Destination.rst | 2 +- docs/modules/DocumentInformation.rst | 2 +- docs/modules/Field.rst | 2 +- docs/modules/Fit.rst | 2 +- docs/modules/PageObject.rst | 2 +- docs/modules/PageRange.rst | 2 +- docs/modules/PaperSize.rst | 2 +- docs/modules/PdfMerger.rst | 2 +- docs/modules/PdfReader.rst | 2 +- docs/modules/PdfWriter.rst | 2 +- docs/modules/RectangleObject.rst | 2 +- docs/modules/Transformation.rst | 2 +- docs/modules/XmpInformation.rst | 2 +- docs/user/add-watermark.md | 4 +- docs/user/adding-pdf-annotations.md | 14 ++-- docs/user/cropping-and-transforming.md | 20 +++--- docs/user/encryption-decryption.md | 4 +- docs/user/extract-images.md | 2 +- docs/user/extract-text.md | 32 ++++----- docs/user/file-size.md | 10 +-- docs/user/forms.md | 6 +- docs/user/installation.md | 37 +++++----- docs/user/merging-pdfs.md | 6 +- docs/user/metadata.md | 4 +- docs/user/pdf-version-support.md | 12 ++-- docs/user/reading-pdf-annotations.md | 8 +-- docs/user/robustness.md | 8 +-- docs/user/streaming-data.md | 8 +-- docs/user/suppress-warnings.md | 20 +++--- make_changelog.py | 2 +- mutmut-test.sh | 2 +- {PyPDF2 => pypdf}/__init__.py | 12 ++-- {PyPDF2 => pypdf}/_cmap.py | 2 +- {PyPDF2 => pypdf}/_codecs/__init__.py | 0 {PyPDF2 => pypdf}/_codecs/adobe_glyphs.py | 0 {PyPDF2 => pypdf}/_codecs/pdfdoc.py | 0 {PyPDF2 => pypdf}/_codecs/std.py | 0 {PyPDF2 => pypdf}/_codecs/symbol.py | 0 {PyPDF2 => pypdf}/_codecs/zapfding.py | 0 {PyPDF2 => pypdf}/_encryption.py | 0 {PyPDF2 => pypdf}/_merger.py | 10 +-- {PyPDF2 => pypdf}/_page.py | 32 ++++----- {PyPDF2 => pypdf}/_protocols.py | 0 {PyPDF2 => pypdf}/_reader.py | 22 +++--- {PyPDF2 => pypdf}/_security.py | 0 {PyPDF2 => pypdf}/_utils.py | 14 ++-- {PyPDF2 => pypdf}/_version.py | 0 {PyPDF2 => pypdf}/_writer.py | 36 +++++----- {PyPDF2 => pypdf}/constants.py | 0 {PyPDF2 => pypdf}/errors.py | 2 +- {PyPDF2 => pypdf}/filters.py | 4 +- {PyPDF2 => pypdf}/generic/__init__.py | 0 {PyPDF2 => pypdf}/generic/_annotations.py | 0 {PyPDF2 => pypdf}/generic/_base.py | 0 {PyPDF2 => pypdf}/generic/_data_structures.py | 8 +-- {PyPDF2 => pypdf}/generic/_fit.py | 0 {PyPDF2 => pypdf}/generic/_outline.py | 0 {PyPDF2 => pypdf}/generic/_rectangle.py | 12 ++-- {PyPDF2 => pypdf}/generic/_utils.py | 0 {PyPDF2 => pypdf}/pagerange.py | 2 +- {PyPDF2 => pypdf}/papersizes.py | 0 {PyPDF2 => pypdf}/py.typed | 0 {PyPDF2 => pypdf}/types.py | 0 {PyPDF2 => pypdf}/xmp.py | 2 +- pyproject.toml | 14 ++-- ..._Vicksburg_Sample_OCR-crazyones-merged.pdf | Bin 217097 -> 217096 bytes ...e-177.pdf => selenium-pypdf-issue-177.pdf} | Bin setup.cfg | 18 ++--- tests/__init__.py | 4 +- tests/bench.py | 16 ++--- tests/test_cmap.py | 10 +-- tests/test_constants.py | 2 +- tests/test_encryption.py | 20 +++--- tests/test_filters.py | 16 ++--- tests/test_generic.py | 16 ++--- tests/test_javascript.py | 2 +- tests/test_merger.py | 38 +++++----- tests/test_page.py | 24 +++---- tests/test_pagerange.py | 2 +- tests/test_papersizes.py | 2 +- tests/test_reader.py | 40 +++++------ tests/test_security.py | 4 +- tests/test_utils.py | 28 ++++---- tests/test_workflows.py | 26 +++---- tests/test_writer.py | 52 +++++++------- tests/test_xmp.py | 24 +++---- 105 files changed, 515 insertions(+), 503 deletions(-) rename {PyPDF2 => pypdf}/__init__.py (65%) rename {PyPDF2 => pypdf}/_cmap.py (99%) rename {PyPDF2 => pypdf}/_codecs/__init__.py (100%) rename {PyPDF2 => pypdf}/_codecs/adobe_glyphs.py (100%) rename {PyPDF2 => pypdf}/_codecs/pdfdoc.py (100%) rename {PyPDF2 => pypdf}/_codecs/std.py (100%) rename {PyPDF2 => pypdf}/_codecs/symbol.py (100%) rename {PyPDF2 => pypdf}/_codecs/zapfding.py (100%) rename {PyPDF2 => pypdf}/_encryption.py (100%) rename {PyPDF2 => pypdf}/_merger.py (98%) rename {PyPDF2 => pypdf}/_page.py (98%) rename {PyPDF2 => pypdf}/_protocols.py (100%) rename {PyPDF2 => pypdf}/_reader.py (99%) rename {PyPDF2 => pypdf}/_security.py (100%) rename {PyPDF2 => pypdf}/_utils.py (97%) rename {PyPDF2 => pypdf}/_version.py (100%) rename {PyPDF2 => pypdf}/_writer.py (98%) rename {PyPDF2 => pypdf}/constants.py (100%) rename {PyPDF2 => pypdf}/errors.py (91%) rename {PyPDF2 => pypdf}/filters.py (99%) rename {PyPDF2 => pypdf}/generic/__init__.py (100%) rename {PyPDF2 => pypdf}/generic/_annotations.py (100%) rename {PyPDF2 => pypdf}/generic/_base.py (100%) rename {PyPDF2 => pypdf}/generic/_data_structures.py (99%) rename {PyPDF2 => pypdf}/generic/_fit.py (100%) rename {PyPDF2 => pypdf}/generic/_outline.py (100%) rename {PyPDF2 => pypdf}/generic/_rectangle.py (95%) rename {PyPDF2 => pypdf}/generic/_utils.py (100%) rename {PyPDF2 => pypdf}/pagerange.py (99%) rename {PyPDF2 => pypdf}/papersizes.py (100%) rename {PyPDF2 => pypdf}/py.typed (100%) rename {PyPDF2 => pypdf}/types.py (100%) rename {PyPDF2 => pypdf}/xmp.py (99%) rename resources/{selenium-PyPDF2-issue-177.pdf => selenium-pypdf-issue-177.pdf} (100%) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 73404bde32..1db3979ab1 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -17,7 +17,7 @@ Which environment were you using when you encountered the problem? $ python -m platform # TODO: Your output goes here -$ python -c "import PyPDF2;print(PyPDF2.__version__)" +$ python -c "import pypdf;print(pypdf.__version__)" # TODO: Your output goes here ``` diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index 7356968a63..c844e8195a 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -1,6 +1,6 @@ --- name: Request a Feature -about: What do you think is missing in PyPDF2? +about: What do you think is missing in pypdf? title: '' labels: Feature Request assignees: MartinThoma @@ -16,7 +16,7 @@ Explain briefly what you want to achive. How would your feature be used? (Remove this if it is not applicable.) ```python -from PyPDF2 import PdfReader, PdfWriter +from pypdf import PdfReader, PdfWriter ... # your new feature in action! ``` diff --git a/.github/SECURITY.md b/.github/SECURITY.md index ff2ad27ef7..685ebd5654 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -11,7 +11,7 @@ If you find a potential security issue, please report it to info@martin-thoma.de We will try to find a fix in a timely manner and will then issue a security advisory together with the update via GitHub -([example](https://github.com/py-pdf/PyPDF2/security/advisories/GHSA-xcjx-m2pj-8g79)). +([example](https://github.com/py-pdf/pypdf/security/advisories/GHSA-xcjx-m2pj-8g79)). If you don't get a reaction within 30 days, please open a public issue on GitHub. diff --git a/.github/workflows/benchmark.yaml b/.github/workflows/benchmark.yaml index 9dc3854d07..33e662fe48 100644 --- a/.github/workflows/benchmark.yaml +++ b/.github/workflows/benchmark.yaml @@ -1,4 +1,4 @@ -name: Benchmarking PyPDF2 +name: Benchmarking pypdf on: push: branches: @@ -27,7 +27,7 @@ jobs: - name: Install requirements (Python 3) run: | pip install -r requirements/ci.txt - - name: Install PyPDF2 + - name: Install pypdf run: | pip install . - name: Run benchmark diff --git a/.github/workflows/github-ci.yaml b/.github/workflows/github-ci.yaml index 38821bf6e9..9c89fe9e02 100644 --- a/.github/workflows/github-ci.yaml +++ b/.github/workflows/github-ci.yaml @@ -66,7 +66,7 @@ jobs: run: | pip uninstall pycryptodome -y if: matrix.use-cryptodome == 'false' - - name: Install PyPDF2 + - name: Install pypdf run: | pip install . - name: Test with flake8 @@ -77,7 +77,7 @@ jobs: python -m coverage run --parallel-mode -m pytest tests -vv - name: Test with mypy run : | - mypy PyPDF2 --show-error-codes --disallow-untyped-defs --disallow-incomplete-defs + mypy pypdf --show-error-codes --disallow-untyped-defs --disallow-incomplete-defs - name: Upload coverage data uses: actions/upload-artifact@v3 with: @@ -105,7 +105,7 @@ jobs: - name: Test running installed package working-directory: /tmp - run: python -c "import PyPDF2;print(PyPDF2.__version__)" + run: python -c "import pypdf;print(pypdf.__version__)" # - name: Release to pypi if tagged. # if: startsWith(github.ref, 'refs/tags') diff --git a/.gitignore b/.gitignore index 4dfd2ef51b..409d2d1b60 100644 --- a/.gitignore +++ b/.gitignore @@ -28,10 +28,10 @@ docs/_build/ # Files generated by some of the scripts dont_commit_*.pdf -PyPDF2-output.pdf +pypdf-output.pdf annotated-pdf-link.pdf Image9.png -PyPDF2_pdfLocation.txt +pypdf_pdfLocation.txt .python-version tests/pdf_cache/ diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 20b25ee718..5d9e9bb0f2 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,15 +1,15 @@ # Contributors -PyPDF2 had a lot of contributors since it started with pyPdf in 2005. We are +pypdf had a lot of contributors since it started with pyPdf in 2005. We are a free software project without any company affiliation. We cannot pay contributors, but we do value their contributions. A lot of time, effort, and expertise went into this project. With this list, we recognize those awesome people πŸ€— The list is definitely not complete. You can find more contributors via the git -history and [GitHubs 'Contributors' feature](https://github.com/py-pdf/PyPDF2/graphs/contributors). +history and [GitHubs 'Contributors' feature](https://github.com/py-pdf/pypdf/graphs/contributors). -## Contributors to the pyPdf / PyPDF2 project +## Contributors to the pypdf (formerly pyPdf / PyPDF2) project * [DL6ER](https://github.com/DL6ER) * [ediamondscience](https://github.com/ediamondscience) @@ -27,7 +27,7 @@ history and [GitHubs 'Contributors' feature](https://github.com/py-pdf/PyPDF2/gr * [Rogmann, Sascha](https://github.com/srogmann) * [sietzeberends](https://github.com/sietzeberends) * [StΓΌber, Timo](https://github.com/omit66) -* [Thoma, Martin](https://github.com/MartinThoma): Maintainer of PyPDF2 since April 2022. I hope to build a great community with many awesome contributors. [LinkedIn](https://www.linkedin.com/in/martin-thoma/) | [StackOverflow](https://stackoverflow.com/users/562769/martin-thoma) | [Blog](https://martin-thoma.com/) +* [Thoma, Martin](https://github.com/MartinThoma): Maintainer of pypdf since April 2022. I hope to build a great community with many awesome contributors. [LinkedIn](https://www.linkedin.com/in/martin-thoma/) | [StackOverflow](https://stackoverflow.com/users/562769/martin-thoma) | [Blog](https://martin-thoma.com/) * [WevertonGomes](https://github.com/WevertonGomesCosta) * ztravis @@ -40,10 +40,10 @@ Contributors are: (1) Bugs: with complete MCVE (2) Well-described feature requests (3) Potentially some more. - The maintainers of PyPDF2 have the last call on that one. -* Community work: This is exceptional. If the maintainers of PyPDF2 see people + The maintainers of pypdf have the last call on that one. +* Community work: This is exceptional. If the maintainers of pypdf see people being super helpful in answering issues / discussions or being very active on - Stackoverflow, we also consider them being contributors to PyPDF2. + Stackoverflow, we also consider them being contributors to pypdf. Contributors can add themselves or ask via an Github Issue to be added. diff --git a/Makefile b/Makefile index 69abb0e68c..2aff54ed46 100644 --- a/Makefile +++ b/Makefile @@ -16,13 +16,13 @@ upload: clean: pyclean . - rm -rf tests/__pycache__ PyPDF2/__pycache__ Image9.png htmlcov docs/_build dist dont_commit_merged.pdf dont_commit_writer.pdf PyPDF2.egg-info PyPDF2_pdfLocation.txt .pytest_cache .mypy_cache .benchmarks + rm -rf tests/__pycache__ pypdf/__pycache__ Image9.png htmlcov docs/_build dist dont_commit_merged.pdf dont_commit_writer.pdf pypdf.egg-info pypdf_pdfLocation.txt .pytest_cache .mypy_cache .benchmarks test: - pytest tests --cov --cov-report term-missing -vv --cov-report html --durations=3 --timeout=60 PyPDF2 + pytest tests --cov --cov-report term-missing -vv --cov-report html --durations=3 --timeout=60 pypdf testtype: - pytest tests --cov --cov-report term-missing -vv --cov-report html --durations=3 --timeout=30 --typeguard-packages=PyPDF2 + pytest tests --cov --cov-report term-missing -vv --cov-report html --durations=3 --timeout=30 --typeguard-packages=pypdf mutation-test: mutmut run @@ -35,7 +35,7 @@ benchmark: pytest tests/bench.py mypy: - mypy PyPDF2 --ignore-missing-imports --check-untyped --strict + mypy pypdf --ignore-missing-imports --check-untyped --strict pylint: - pylint PyPDF2 + pylint pypdf diff --git a/README.md b/README.md index f6f8673ae4..b18170c921 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,44 @@ -[![PyPI version](https://badge.fury.io/py/PyPDF2.svg)](https://badge.fury.io/py/PyPDF2) -[![Python Support](https://img.shields.io/pypi/pyversions/PyPDF2.svg)](https://pypi.org/project/PyPDF2/) -[![](https://img.shields.io/badge/-documentation-green)](https://pypdf2.readthedocs.io/en/stable/) -[![GitHub last commit](https://img.shields.io/github/last-commit/py-pdf/PyPDF2)](https://github.com/py-pdf/PyPDF2) -[![codecov](https://codecov.io/gh/py-pdf/PyPDF2/branch/main/graph/badge.svg?token=id42cGNZ5Z)](https://codecov.io/gh/py-pdf/PyPDF2) +[![PyPI version](https://badge.fury.io/py/pypdf.svg)](https://badge.fury.io/py/pypdf) +[![Python Support](https://img.shields.io/pypi/pyversions/pypdf.svg)](https://pypi.org/project/pypdf/) +[![](https://img.shields.io/badge/-documentation-green)](https://pypdf.readthedocs.io/en/stable/) +[![GitHub last commit](https://img.shields.io/github/last-commit/py-pdf/pypdf)](https://github.com/py-pdf/pypdf) +[![codecov](https://codecov.io/gh/py-pdf/pypdf/branch/main/graph/badge.svg?token=id42cGNZ5Z)](https://codecov.io/gh/py-pdf/pypdf) -# PyPDF2 +# pypdf -> **NOTE**: The PyPDF2 project is going back to its roots. PyPDF2==3.0.X will be -> the last version of PyPDF2. Patches for this version will still be applied, but -> development will continue with [`pypdf==3.0.0`](https://pypi.org/project/pyPdf/). - -PyPDF2 is a free and open-source pure-python PDF library capable of splitting, -[merging](https://pypdf2.readthedocs.io/en/stable/user/merging-pdfs.html), -[cropping, and transforming](https://pypdf2.readthedocs.io/en/stable/user/cropping-and-transforming.html) +pypdf is a free and open-source pure-python PDF library capable of splitting, +[merging](https://pypdf.readthedocs.io/en/stable/user/merging-pdfs.html), +[cropping, and transforming](https://pypdf.readthedocs.io/en/stable/user/cropping-and-transforming.html) the pages of PDF files. It can also add custom data, viewing options, and -[passwords](https://pypdf2.readthedocs.io/en/stable/user/encryption-decryption.html) -to PDF files. PyPDF2 can -[retrieve text](https://pypdf2.readthedocs.io/en/stable/user/extract-text.html) +[passwords](https://pypdf.readthedocs.io/en/stable/user/encryption-decryption.html) +to PDF files. pypdf can +[retrieve text](https://pypdf.readthedocs.io/en/stable/user/extract-text.html) and -[metadata](https://pypdf2.readthedocs.io/en/stable/user/metadata.html) +[metadata](https://pypdf.readthedocs.io/en/stable/user/metadata.html) from PDFs as well. ## Installation -You can install PyPDF2 via pip: +You can install pypdf via pip: ``` -pip install PyPDF2 +pip install pypdf ``` -If you plan to use PyPDF2 for encrypting or decrypting PDFs that use AES, you +If you plan to use pypdf for encrypting or decrypting PDFs that use AES, you will need to install some extra dependencies. Encryption using RC4 is supported using the regular installation. ``` -pip install PyPDF2[crypto] +pip install pypdf[crypto] ``` ## Usage ```python -from PyPDF2 import PdfReader +from pypdf import PdfReader reader = PdfReader("example.pdf") number_of_pages = len(reader.pages) @@ -50,36 +46,37 @@ page = reader.pages[0] text = page.extract_text() ``` -PyPDF2 can do a lot more, e.g. splitting, merging, reading and creating +pypdf can do a lot more, e.g. splitting, merging, reading and creating annotations, decrypting and encrypting, and more. -Please see [the documentation](https://pypdf2.readthedocs.io/en/stable/) +Please see [the documentation](https://pypdf.readthedocs.io/en/stable/) for more usage examples! A lot of questions are asked and answered -[on StackOverflow](https://stackoverflow.com/questions/tagged/pypdf2). +[on StackOverflow](https://stackoverflow.com/questions/tagged/pypdf) +(formerly tagged with [PyPDF2](https://stackoverflow.com/questions/tagged/pypdf2)). ## Contributions -Maintaining PyPDF2 is a collaborative effort. You can support PyPDF2 by writing +Maintaining pypdf is a collaborative effort. You can support pypdf by writing documentation, helping to narrow down issues, and adding code. ### Q&A -The experience PyPDF2 users have covers the whole range from beginners who +The experience pypdf users have covers the whole range from beginners who want to make their live easier to experts who developed software before PDF -existed. You can contribute to the PyPDF2 community by answering questions -on [StackOverflow](https://stackoverflow.com/questions/tagged/pypdf2), -helping in [discussions](https://github.com/py-pdf/PyPDF2/discussions), +existed. You can contribute to the pypdf community by answering questions +on [StackOverflow](https://stackoverflow.com/questions/tagged/pypdf), +helping in [discussions](https://github.com/py-pdf/pypdf/discussions), and asking users who report issues for [MCVE](https://stackoverflow.com/help/minimal-reproducible-example)'s (Code + example PDF!). ### Issues A good bug ticket includes a MCVE - a minimal complete verifiable example. -For PyPDF2, this means that you must upload a PDF that causes the bug to occur +For pypdf, this means that you must upload a PDF that causes the bug to occur as well as the code you're executing with all of the output. Use -`print(PyPDF2.__version__)` to tell us which version you're using. +`print(pypdf.__version__)` to tell us which version you're using. ### Code @@ -87,13 +84,13 @@ All code contributions are welcome, but smaller ones have a better chance to get included in a timely manner. Adding unit tests for new features or test cases for bugs you've fixed help us to ensure that the Pull Request (PR) is fine. -PyPDF2 includes a test suite which can be executed with `pytest`: +pypdf includes a test suite which can be executed with `pytest`: ```bash $ pytest ===================== test session starts ===================== platform linux -- Python 3.6.15, pytest-7.0.1, pluggy-1.0.0 -rootdir: /home/moose/GitHub/Martin/PyPDF2 +rootdir: /home/moose/GitHub/Martin/pypdf plugins: cov-3.0.0 collected 233 items diff --git a/docs/conf.py b/docs/conf.py index 43a8394e10..ba0cb0c58d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,7 +21,7 @@ # -- Project information ----------------------------------------------------- -project = "PyPDF2" +project = "pypdf" copyright = "2006 - 2008, Mathieu Fenniak" author = "Mathieu Fenniak" diff --git a/docs/dev/deprecations.md b/docs/dev/deprecations.md index 89f778e44e..63edccb48e 100644 --- a/docs/dev/deprecations.md +++ b/docs/dev/deprecations.md @@ -1,6 +1,6 @@ # The Deprecation Process -PyPDF2 strives to be an excellent library for its current users and for new +pypdf strives to be an excellent library for its current users and for new ones. We are careful with introducing potentially breaking changes, but we will do them if they provide value for the community on the long run. @@ -9,7 +9,7 @@ users can rely on the following procedure. ## Semantic Versioning -PyPDF2 uses [semantic versioning](https://semver.org/). If you want to avoid +pypdf uses [semantic versioning](https://semver.org/). If you want to avoid breaking changes, please use dependency pinning (also known as version pinning). In Python, this is done by specifying the exact version you want to use in a `requirements.txt` file. A tool that can support you is `pip-compile` from @@ -18,9 +18,9 @@ In Python, this is done by specifying the exact version you want to use in a If you are using [Poetry](https://pypi.org/project/poetry/) it is done with the `poetry.lock` file. -## How PyPDF2 deprecates features +## How pypdf deprecates features -Assume the current version of PyPDF2 is `x.y.z`. After a discussion (e.g. via +Assume the current version of pypdf is `x.y.z`. After a discussion (e.g. via GitHub issues) we decided to remove a class / function / method. This is how we do it: diff --git a/docs/dev/intro.md b/docs/dev/intro.md index f8e8f12b0b..21b69de39d 100644 --- a/docs/dev/intro.md +++ b/docs/dev/intro.md @@ -1,7 +1,7 @@ # Developer Intro -PyPDF2 is a library and hence its users are developers. This document is not for -the users, but for people who want to work on PyPDF2 itself. +pypdf is a library and hence its users are developers. This document is not for +the users, but for people who want to work on pypdf itself. ## Installing Requirements @@ -11,11 +11,11 @@ pip install -r requirements/dev.txt ## Running Tests -See [testing PyPDF2 with pytest](testing.md) +See [testing pypdf with pytest](testing.md) ## The sample-files git submodule The reason for having the submodule `sample-files` is that we want to keep -the size of the PyPDF2 repository small while we also want to have an extensive +the size of the pypdf repository small while we also want to have an extensive test suite. Those two goals contradict each other. The `resources` folder should contain a select set of core examples that cover @@ -34,21 +34,21 @@ git submodule update --init Git is a command line application for version control. If you don't know it, you can [play ohmygit](https://ohmygit.org/) to learn it. -GitHub is the service where the PyPDF2 project is hosted. While git is free and +GitHub is the service where the pypdf project is hosted. While git is free and open source, GitHub is a paid service by Microsoft - but for free in lot of cases. [pre-commit](https://pypi.org/project/pre-commit/) is a command line application that uses git hooks to automatically execute code. This allows you to avoid style issues and other code quality issues. After you entered `pre-commit install` -once in your local copy of PyPDF2, it will automatically be executed when +once in your local copy of pypdf, it will automatically be executed when you `git commit`. ## Commit Messages Having a clean commit message helps people to quickly understand what the commit was about, without actually looking at the changes. The first line of the -commit message is used to [auto-generate the CHANGELOG](https://github.com/py-pdf/PyPDF2/blob/main/make_changelog.py). For this reason, the format should be: +commit message is used to [auto-generate the CHANGELOG](https://github.com/py-pdf/pypdf/blob/main/make_changelog.py). For this reason, the format should be: ``` PREFIX: DESCRIPTION @@ -66,18 +66,18 @@ The `PREFIX` can be: * `DEP`: A deprecation - either marking something as "this is going to be removed" or actually removing it. * `PI`: A performance improvement. This could also be a reduction in the - file size of PDF files generated by PyPDF2. + file size of PDF files generated by pypdf. * `ROB`: A robustness change. Dealing better with broken PDF files. * `DOC`: A documentation change. * `TST`: Adding / adjusting tests. * `DEV`: Developer experience improvements - e.g. pre-commit or setting up CI * `MAINT`: Quite a lot of different stuff. Performance improvements are for sure the most interesting changes in here. Refactorings as well. -* `STY`: A style change. Something that makes PyPDF2 code more consistent. +* `STY`: A style change. Something that makes pypdf code more consistent. Typically a small change. ## Benchmarks We need to keep an eye on performance and thus we have a few benchmarks. -See [py-pdf.github.io/PyPDF2/dev/bench](https://py-pdf.github.io/PyPDF2/dev/bench/) +See [py-pdf.github.io/pypdf/dev/bench](https://py-pdf.github.io/PyPDF2/dev/bench/) diff --git a/docs/dev/testing.md b/docs/dev/testing.md index 597a57b33f..283faa8c3d 100644 --- a/docs/dev/testing.md +++ b/docs/dev/testing.md @@ -1,10 +1,10 @@ # Testing -PyPDF2 uses [`pytest`](https://docs.pytest.org/en/7.1.x/) for testing. +pypdf uses [`pytest`](https://docs.pytest.org/en/7.1.x/) for testing. ## De-selecting groups of tests -PyPDF2 makes use of the following pytest markers: +pypdf makes use of the following pytest markers: * `slow`: Tests that require more than 5 seconds * `samples`: Tests that require the [the `sample-files` git submodule](https://github.com/py-pdf/sample-files) to be initialized. As of October 2022, this is about 25 MB. diff --git a/docs/index.rst b/docs/index.rst index 83202212d4..7615b821a6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,18 +1,18 @@ -.. PyPDF2 documentation main file, created by +.. pypdf documentation main file, created by sphinx-quickstart on Thu Apr 7 20:13:19 2022. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to PyPDF2 +Welcome to pypdf ================= -PyPDF2 is a `free `_ and open +pypdf is a `free `_ and open source pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files. It can also add custom data, viewing options, and passwords to PDF files. -PyPDF2 can retrieve text and metadata from PDFs as well. +pypdf can retrieve text and metadata from PDFs as well. -You can contribute to `PyPDF2 on GitHub `_. +You can contribute to `pypdf on GitHub `_. .. toctree:: :caption: User Guide @@ -67,7 +67,7 @@ You can contribute to `PyPDF2 on GitHub `_. dev/testing .. toctree:: - :caption: About PyPDF2 + :caption: About pypdf :maxdepth: 1 meta/CHANGELOG diff --git a/docs/meta/comparisons.md b/docs/meta/comparisons.md index a4e6748da7..adce275389 100644 --- a/docs/meta/comparisons.md +++ b/docs/meta/comparisons.md @@ -1,9 +1,9 @@ -# PyPDF2 vs X +# pypdf vs X -PyPDF2 is a [free] and open source pure-python PDF library capable of +pypdf is a [free] and open source pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files. It can also add custom data, viewing options, and passwords to PDF -files. PyPDF2 can retrieve text and metadata from PDFs as well. +files. pypdf can retrieve text and metadata from PDFs as well. ## PyMuPDF and PikePDF @@ -16,14 +16,13 @@ are powered by C libraries which makes installation harder and might cause security concerns. For MuPDF you might also need to buy a commercial license. -A core feature of PyPDF2 is that it's pure Python. That means there is +A core feature of pypdf is that it's pure Python. That means there is no C dependency. It has been used for over 10 years and for this reason a lot of support via StackOverflow and examples on the internet. -## pyPDF +## pypdf -PyPDF2 was forked from pyPDF. pyPDF has been unmaintained for a long -time. +PyPDF2 was merged back into `pypdf`. The development continues at `pypdf`. ## PyPDF3 and PyPDF4 @@ -31,15 +30,15 @@ Developing and maintaining open source software is extremely time-intensive and in the case of PyPDF2 not paid at all. Having a continuous support is hard. -PyPDF2 was initially released in 2012 on PyPI and received releases +pypdf was initially released in 2012 on PyPI and received releases until 2016. From 2016 to 2022 there was no update - but people were still using it. -As PyPDF2 is free software, there were attempts to fork it and continue +As pypdf is free software, there were attempts to fork it and continue the development. PyPDF3 was first released in 2018 and still receives updates. PyPDF4 has only one release from 2018. -I, Martin Thoma, the current maintainer of PyPDF2, hope that we can +I, Martin Thoma, the current maintainer of pypdf, hope that we can bring the community back to one path of development. Let's see. [free]: https://en.wikipedia.org/wiki/Free_software @@ -52,7 +51,7 @@ bring the community back to one path of development. Let's see. ## pdfrw and pdfminer I don't have experience with either of those libraries. Please add a -comparison if you know PyPDF2 and [`pdfrw`](https://pypi.org/project/pdfrw/) or +comparison if you know pypdf and [`pdfrw`](https://pypi.org/project/pdfrw/) or [`pdfminer.six`](https://pypi.org/project/pdfminer.six/)! Please be aware that there is also @@ -68,4 +67,4 @@ And there are more: ## Document Generation There are (Python) [tools to generate PDF documents](https://github.com/py-pdf/awesome-pdf#generators). -PyPDF2 is not one of them. +pypdf is not one of them. diff --git a/docs/meta/faq.md b/docs/meta/faq.md index 878fe68c97..cce968ec24 100644 --- a/docs/meta/faq.md +++ b/docs/meta/faq.md @@ -1,12 +1,13 @@ # Frequently-Asked Questions -## How is PyPDF2 related to pyPdf? +## How is pypdf related to PyPDF2? -PyPDF2 is a fork from the no-longer-maintained pyPdf approved by the -latter's founder. +PyPDF2 was a fork from the original pyPdf. After several years, the fork was +merged back into `pypdf` (now all lowercase). ## Which Python versions are supported? +pypdf 3.0+ supports Python 3.6 and later. PyPDF2 2.0+ supports Python 3.6 and later. PyPDF2 1.27.10 supported Python 2.7 to 3.10. @@ -15,12 +16,12 @@ PyPDF2 1.27.10 supported Python 2.7 to 3.10. [this sort of thing]: https://github.com/py-pdf/PyPDF2/issues/24 [GitHub issue]: https://github.com/py-pdf/PyPDF2/issues -## Who uses PyPDF2? +## Who uses pypdf? pyPdf is vendored [into](https://github.com/Buyanbat/XacCRM/tree/ee78e8df967182f661b6494a86444501e7d89c8f/report/pyPdf) [several](https://github.com/MyBook/calibre/tree/ca1efe3c21f6553e096dab745b3cdeb36244a5a9/src/pyPdf) [projects](https://github.com/Giacomo-De-Florio-Dev/Make_Your_PDF_Safe/tree/ec439f92243d12d54ae024668792470c6b40ee96/MakeYourPDFsafe_V1.3/PyPDF2). That means the code of pyPdf was copied into that project. -Projects that depend on PyPDF2: +Projects that depend on pypdf: * [Camelot](https://github.com/camelot-dev/camelot): A Python library to extract tabular data from PDFs * [edi](https://github.com/OCA/edi): Electronic Data Interchange modules @@ -34,21 +35,21 @@ Projects that depend on PyPDF2: * [doc2text](https://github.com/jlsutherland/doc2text) * [pdfalyzer](https://pypi.org/project/pdfalyzer/): A PDF analysis tool for visualizing the inner tree-like data structure of a PDF in spectacularly large and colorful diagrams as well as scanning the binary streams embedded in the PDF for hidden potentially malicious content. -## How do I cite PyPDF2? +## How do I cite pypdf? In BibTeX format: ``` -@misc{pypdf2, - title = {The {PyPDF2} library}, +@misc{pypdf, + title = {The {pypdf} library}, author = {Mathieu Fenniak and Matthew Stamy and pubpub-zz and Martin Thoma and Matthew Peveler and - exiledkingcc and {PyPDF2 Contributors}}, + exiledkingcc and {pypdf Contributors}}, year = {2022}, - url = {https://pypi.org/project/PyPDF2/} - note = {See https://pypdf2.readthedocs.io/en/latest/meta/CONTRIBUTORS.html for all contributors} + url = {https://pypi.org/project/pypdf/} + note = {See https://pypdf.readthedocs.io/en/latest/meta/CONTRIBUTORS.html for all contributors} } ``` diff --git a/docs/meta/history.md b/docs/meta/history.md index 26343072a1..be1f56ddb3 100644 --- a/docs/meta/history.md +++ b/docs/meta/history.md @@ -1,4 +1,4 @@ -# History of PyPDF2 +# History of pypdf ## The Origins: pyPdf (2005-2010) @@ -49,10 +49,26 @@ It never got the user base from PyPDF2. PyPDF4 only had one release in 2018. -## PyPDF2: Reborn (2022-Today) +## PyPDF2: Reborn (2022) Martin Thoma took over maintenance of PyPDF2 in April 2022. +[pubpub-zz](https://github.com/pubpub-zz) was extremely active, especially +for text extraction. + +[Matthew Peveler](https://github.com/MasterOdin) helped a lot with reviews +and general project decisions. + +[exiledkingcc](https://github.com/exiledkingcc) added support for modern +encryption schemes. + + +## pypdf: Back to the Roots (2023-Today) + +In order to make things simpler for beginners, PyPDF2 was merged back into +pypdf. Now all lowercase, without a number. We hope that the folks who +develop PyPDF3 and PyPDF4 also join us. + [Mathieu Fenniak]: https://mathieu.fenniak.net/ [pyfpdf]: https://github.com/reingart/pyfpdf diff --git a/docs/meta/project-governance.md b/docs/meta/project-governance.md index d196533aae..9a76ba5e13 100644 --- a/docs/meta/project-governance.md +++ b/docs/meta/project-governance.md @@ -1,16 +1,16 @@ # Project Governance -This document describes how the PyPDF2 project is managed. It describes the +This document describes how the pypdf project is managed. It describes the different actors, their roles, and the responsibilities they have. ## Terminology -* The **project** is PyPDF2 - a free and open-source pure-python PDF library +* The **project** is pypdf - a free and open-source pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files. - It includes the [code, issues, and discussions on GitHub](https://github.com/py-pdf/PyPDF2), - and [the documentation on ReadTheDocs](https://pypdf2.readthedocs.io/en/latest/), - [the package on PyPI](https://pypi.org/project/PyPDF2/), and - [the website on GitHub](https://py-pdf.github.io/PyPDF2/dev/bench/). + It includes the [code, issues, and discussions on GitHub](https://github.com/py-pdf/pypdf), + and [the documentation on ReadTheDocs](https://pypdf.readthedocs.io/en/latest/), + [the package on PyPI](https://pypi.org/project/pypdf/), and + [the website on GitHub](https://py-pdf.github.io/pypdf/dev/bench/). * A **maintainer** is a person who has technical permissions to change one or more part of the projects. It is a person who is driven to keep the project running and improving. @@ -20,31 +20,31 @@ capable of splitting, merging, cropping, and transforming the pages of PDF files help to ask questions on existing issues to make them easier to answer, participate in discussions, and help to improve the documentation. Contributors are similar to maintainers, but without technial permissions. -* A **user** is a person who imports PyPDF2 into their code. All PyPDF2 users - are developers, but not developers who know the internals of PyPDF2. They only - use the public interface of PyPDF2. They will likely have less knowledge about +* A **user** is a person who imports pypdf into their code. All pypdf users + are developers, but not developers who know the internals of pypdf. They only + use the public interface of pypdf. They will likely have less knowledge about PDF than contributors. * The **community** is all of that - the users, the contributors, and the maintainers. -## Governance, Leadership, and Steering PyPDF2 forward +## Governance, Leadership, and Steering pypdf forward -PyPDF2 is a free and open source project with over 100 contributors and likely +pypdf is a free and open source project with over 100 contributors and likely (way) more than 1000 users. -As PyPDF2 does not have any formal relationship with any company and no funding, +As pypdf does not have any formal relationship with any company and no funding, all the work done by the community are voluntary contributions. People don't get paid, but choose to spend their free time to create software of which many more are profiting. This has to be honored and respected. Despite such a big community, the project was dormant from 2016 to 2022. There were still questions asked, issues reported, and pull requests created. -But the maintainer didn't have the time to move PyPDF2 forward. During that +But the maintainer didn't have the time to move pypdf forward. During that time, nobody else stepped up to become the new maintainer. -For this reason, PyPDF2 has the **Benevolent Dictator** +For this reason, pypdf has the **Benevolent Dictator** governance model. The benevolent dictator is a maintainer with all technical permissions - -most importantly the permission to push new PyPDF2 versions on PyPI. +most importantly the permission to push new pypdf versions on PyPI. Being benevolent, the benevolent dictator listens for decisions to the community and tries their best to make decisions from which the overall community profits - the @@ -52,7 +52,7 @@ current one and the potential future one. Being a dictator, the benevolent dicta the power and the right to make decisions on their own - also against some members of the community. -As PyPDF2 is free software, parts of the community can split off (fork the code) +As pypdf is free software, parts of the community can split off (fork the code) and create a new community. This should limit the harm a bad benevolent dictator can do. @@ -73,7 +73,7 @@ The community can expect the following: * The **benevolent dictator** tries their best to make decisions from which the overall community profits. The benevolent dictator is aware that his/her decisions can shape the overall community. Once the benevolent dictator notices that she/he doesn't have the time - to advance PyPDF2, he/she looks for a new benevolent dictator. As it is expected + to advance pypdf, he/she looks for a new benevolent dictator. As it is expected that the benevolent dictator will step down at some point of their choice (hopefully before their death), it is NOT a benevolent dictator for life (BDFL). @@ -89,26 +89,26 @@ The community can expect the following: a unit test have a higher chance to get merged soon - simply because it's easier for maintainers to see that the contribution will not harm the overall project. Their contributions are documented in the git history and in the - public issues. [Let us know](https://github.com/py-pdf/PyPDF2/discussions/798) + public issues. [Let us know](https://github.com/py-pdf/pypdf/discussions/798) if you would appriciate something else! * Every **community member** uses a respectful language. We are all human, we get upset about things we care and other things than what's visible on the - internet go on in our live. PyPDF2 does not pay its contributors - keep all + internet go on in our live. pypdf does not pay its contributors - keep all of that in mind when you interact with others. We are here because we want to help others. ### Issues and Discussions -An issue is any technical description that aims at bringing PyPDF2 forward: +An issue is any technical description that aims at bringing pypdf forward: -* Bugs tickets: Something went wrong because PyPDF2 developers made a mistake. -* Feature requests: PyPDF2 does not support all features of the PDF specifications. +* Bugs tickets: Something went wrong because pypdf developers made a mistake. +* Feature requests: pypdf does not support all features of the PDF specifications. There are certainly also convenience methods that would help users a lot. * Robustness requests: There are many broken PDFs around. In some cases, we can deal with that. It's kind of a mixture between a bug ticket and a feature request. -* Performance tickets: PyPDF2 could be faster - let us know about your specific +* Performance tickets: pypdf could be faster - let us know about your specific scenario. Any comment that is in those technial descriptions which is not helping the @@ -116,7 +116,7 @@ discussion can be deleted. This is especially true for "me too" comments on bugs or "bump" comments for desired features. People can express this with πŸ‘ / πŸ‘Ž reactions. -[Discussions](https://github.com/py-pdf/PyPDF2/discussions) are open. No comments +[Discussions](https://github.com/py-pdf/pypdf/discussions) are open. No comments will be deleted there - except if they are clearly unrelated spam or only try to insult people (luckily, the community was very respectful so far 🀞) @@ -126,9 +126,9 @@ try to insult people (luckily, the community was very respectful so far 🀞) The maintainers follow [semantic versioning](https://semver.org/). Most importantly, that means that breaking changes will have a major version bump. -Be aware that unintentional breaking changes might still happen. The PyPDF2 +Be aware that unintentional breaking changes might still happen. The pypdf maintainers do their best to fix that in a timely manner - please -[report such issues](https://github.com/py-pdf/PyPDF2/issues)! +[report such issues](https://github.com/py-pdf/pypdf/issues)! ## People diff --git a/docs/modules/AnnotationBuilder.rst b/docs/modules/AnnotationBuilder.rst index 8ad7e54a59..4f521aeecf 100644 --- a/docs/modules/AnnotationBuilder.rst +++ b/docs/modules/AnnotationBuilder.rst @@ -1,7 +1,7 @@ The AnnotationBuilder Class --------------------------- -.. autoclass:: PyPDF2.generic.AnnotationBuilder +.. autoclass:: pypdf.generic.AnnotationBuilder :members: :no-undoc-members: :show-inheritance: diff --git a/docs/modules/Destination.rst b/docs/modules/Destination.rst index 33e1334c68..40315a9aa0 100644 --- a/docs/modules/Destination.rst +++ b/docs/modules/Destination.rst @@ -1,7 +1,7 @@ The Destination Class --------------------- -.. autoclass:: PyPDF2.generic.Destination +.. autoclass:: pypdf.generic.Destination :members: :undoc-members: :show-inheritance: diff --git a/docs/modules/DocumentInformation.rst b/docs/modules/DocumentInformation.rst index 41103b0a1e..8c48601d0f 100644 --- a/docs/modules/DocumentInformation.rst +++ b/docs/modules/DocumentInformation.rst @@ -1,7 +1,7 @@ The DocumentInformation Class ----------------------------- -.. autoclass:: PyPDF2.DocumentInformation +.. autoclass:: pypdf.DocumentInformation :members: :undoc-members: :show-inheritance: diff --git a/docs/modules/Field.rst b/docs/modules/Field.rst index cf5573be13..573701897d 100644 --- a/docs/modules/Field.rst +++ b/docs/modules/Field.rst @@ -1,7 +1,7 @@ The Field Class --------------- -.. autoclass:: PyPDF2.generic.Field +.. autoclass:: pypdf.generic.Field :members: :undoc-members: :show-inheritance: diff --git a/docs/modules/Fit.rst b/docs/modules/Fit.rst index caec874bcf..0cef4743a7 100644 --- a/docs/modules/Fit.rst +++ b/docs/modules/Fit.rst @@ -1,7 +1,7 @@ The Fit Class ------------- -.. autoclass:: PyPDF2.generic.Fit +.. autoclass:: pypdf.generic.Fit :members: :undoc-members: :show-inheritance: diff --git a/docs/modules/PageObject.rst b/docs/modules/PageObject.rst index 8ca9c41fd2..2274ec6bdc 100644 --- a/docs/modules/PageObject.rst +++ b/docs/modules/PageObject.rst @@ -1,7 +1,7 @@ The PageObject Class -------------------- -.. autoclass:: PyPDF2._page.PageObject +.. autoclass:: pypdf._page.PageObject :members: :undoc-members: :show-inheritance: diff --git a/docs/modules/PageRange.rst b/docs/modules/PageRange.rst index c7f8254704..d135e9ffb9 100644 --- a/docs/modules/PageRange.rst +++ b/docs/modules/PageRange.rst @@ -1,7 +1,7 @@ The PageRange Class ------------------- -.. autoclass:: PyPDF2.PageRange +.. autoclass:: pypdf.PageRange :members: :undoc-members: :show-inheritance: diff --git a/docs/modules/PaperSize.rst b/docs/modules/PaperSize.rst index 70c583a3ce..0487678522 100644 --- a/docs/modules/PaperSize.rst +++ b/docs/modules/PaperSize.rst @@ -1,7 +1,7 @@ The PaperSize Class ------------------------ -.. autoclass:: PyPDF2.PaperSize +.. autoclass:: pypdf.PaperSize :members: :undoc-members: :show-inheritance: diff --git a/docs/modules/PdfMerger.rst b/docs/modules/PdfMerger.rst index 24f44da37b..9f48634821 100644 --- a/docs/modules/PdfMerger.rst +++ b/docs/modules/PdfMerger.rst @@ -1,7 +1,7 @@ The PdfMerger Class ------------------- -.. autoclass:: PyPDF2.PdfMerger +.. autoclass:: pypdf.PdfMerger :members: :undoc-members: :show-inheritance: diff --git a/docs/modules/PdfReader.rst b/docs/modules/PdfReader.rst index 1aa0506a0e..6854d8d0a7 100644 --- a/docs/modules/PdfReader.rst +++ b/docs/modules/PdfReader.rst @@ -1,7 +1,7 @@ The PdfReader Class ------------------- -.. autoclass:: PyPDF2.PdfReader +.. autoclass:: pypdf.PdfReader :members: :undoc-members: :show-inheritance: diff --git a/docs/modules/PdfWriter.rst b/docs/modules/PdfWriter.rst index 2ceddcdd19..04693e72c5 100644 --- a/docs/modules/PdfWriter.rst +++ b/docs/modules/PdfWriter.rst @@ -1,7 +1,7 @@ The PdfWriter Class ------------------- -.. autoclass:: PyPDF2.PdfWriter +.. autoclass:: pypdf.PdfWriter :members: :undoc-members: :show-inheritance: diff --git a/docs/modules/RectangleObject.rst b/docs/modules/RectangleObject.rst index a0ae1f30b0..0516d6f332 100644 --- a/docs/modules/RectangleObject.rst +++ b/docs/modules/RectangleObject.rst @@ -1,7 +1,7 @@ The RectangleObject Class ------------------------- -.. autoclass:: PyPDF2.generic.RectangleObject +.. autoclass:: pypdf.generic.RectangleObject :members: :undoc-members: :show-inheritance: diff --git a/docs/modules/Transformation.rst b/docs/modules/Transformation.rst index 5fbba321b7..780e81841f 100644 --- a/docs/modules/Transformation.rst +++ b/docs/modules/Transformation.rst @@ -1,7 +1,7 @@ The Transformation Class ------------------------ -.. autoclass:: PyPDF2.Transformation +.. autoclass:: pypdf.Transformation :members: :undoc-members: :show-inheritance: diff --git a/docs/modules/XmpInformation.rst b/docs/modules/XmpInformation.rst index 49a6641cbb..245137eede 100644 --- a/docs/modules/XmpInformation.rst +++ b/docs/modules/XmpInformation.rst @@ -1,7 +1,7 @@ The XmpInformation Class ------------------------- -.. autoclass:: PyPDF2.xmp.XmpInformation +.. autoclass:: pypdf.xmp.XmpInformation :members: :undoc-members: :show-inheritance: diff --git a/docs/user/add-watermark.md b/docs/user/add-watermark.md index 0f0cb70927..923be591a5 100644 --- a/docs/user/add-watermark.md +++ b/docs/user/add-watermark.md @@ -13,7 +13,7 @@ content stays the same. from pathlib import Path from typing import Union, Literal, List -from PyPDF2 import PdfWriter, PdfReader +from pypdf import PdfWriter, PdfReader def stamp( @@ -49,7 +49,7 @@ def stamp( from pathlib import Path from typing import Union, Literal, List -from PyPDF2 import PdfWriter, PdfReader +from pypdf import PdfWriter, PdfReader def watermark( diff --git a/docs/user/adding-pdf-annotations.md b/docs/user/adding-pdf-annotations.md index 16a39bf554..54c451547a 100644 --- a/docs/user/adding-pdf-annotations.md +++ b/docs/user/adding-pdf-annotations.md @@ -3,7 +3,7 @@ ## Attachments ```python -from PyPDF2 import PdfWriter +from pypdf import PdfWriter writer = PdfWriter() writer.add_blank_page(width=200, height=200) @@ -22,11 +22,11 @@ If you want to add text in a box like this ![](free-text-annotation.png) -you can use the {py:class}`AnnotationBuilder `: +you can use the {py:class}`AnnotationBuilder `: ```python -from PyPDF2 import PdfReader, PdfWriter -from PyPDF2.generic import AnnotationBuilder +from pypdf import PdfReader, PdfWriter +from pypdf.generic import AnnotationBuilder # Fill the writer with the pages you want pdf_path = os.path.join(RESOURCE_ROOT, "crazyones.pdf") @@ -66,7 +66,7 @@ If you want to add a line like this: ![](annotation-line.png) -you can use the {py:class}`AnnotationBuilder `: +you can use the {py:class}`AnnotationBuilder `: ```python pdf_path = os.path.join(RESOURCE_ROOT, "crazyones.pdf") @@ -95,7 +95,7 @@ If you want to add a rectangle like this: ![](annotation-square.png) -you can use the {py:class}`AnnotationBuilder `: +you can use the {py:class}`AnnotationBuilder `: ```python pdf_path = os.path.join(RESOURCE_ROOT, "crazyones.pdf") @@ -122,7 +122,7 @@ This method uses the "square" annotation type of the PDF format. ## Link If you want to add a link, you can use -the {py:class}`AnnotationBuilder `: +the {py:class}`AnnotationBuilder `: ```python pdf_path = os.path.join(RESOURCE_ROOT, "crazyones.pdf") diff --git a/docs/user/cropping-and-transforming.md b/docs/user/cropping-and-transforming.md index 860c8a46f5..ffc56c5041 100644 --- a/docs/user/cropping-and-transforming.md +++ b/docs/user/cropping-and-transforming.md @@ -1,7 +1,7 @@ # Cropping and Transforming PDFs ```python -from PyPDF2 import PdfWriter, PdfReader +from pypdf import PdfWriter, PdfReader reader = PdfReader("example.pdf") writer = PdfWriter() @@ -26,7 +26,7 @@ writer.add_page(page3) writer.add_js("this.print({bUI:true,bSilent:false,bShrinkToFit:true});") # write to document-output.pdf -with open("PyPDF2-output.pdf", "wb") as fp: +with open("pypdf-output.pdf", "wb") as fp: writer.write(fp) ``` @@ -34,11 +34,11 @@ with open("PyPDF2-output.pdf", "wb") as fp: The most typical rotation is a clockwise rotation of the page by multiples of 90 degrees. That is done when the orientation of the page is wrong. You can -do that with the [`rotate` method](https://pypdf2.readthedocs.io/en/latest/modules/PageObject.html#PyPDF2._page.PageObject.rotate) +do that with the [`rotate` method](https://pypdf.readthedocs.io/en/latest/modules/PageObject.html#pypdf._page.PageObject.rotate) of the `PageObject` class: ```python -from PyPDF2 import PdfWriter, PdfReader +from pypdf import PdfWriter, PdfReader reader = PdfReader("input.pdf") writer = PdfWriter() @@ -65,7 +65,7 @@ contents and does not change the mediabox or cropbox. is the result of ```python -from PyPDF2 import PdfReader, PdfWriter, Transformation +from pypdf import PdfReader, PdfWriter, Transformation # Get the data reader_base = PdfReader("labeled-edges-center-image.pdf") @@ -88,7 +88,7 @@ with open("merged-foo.pdf", "wb") as fp: ![](merge-45-deg-rot.png) ```python -from PyPDF2 import PdfReader, PdfWriter, Transformation +from pypdf import PdfReader, PdfWriter, Transformation # Get the data reader_base = PdfReader("labeled-edges-center-image.pdf") @@ -132,7 +132,7 @@ op = Transformation().rotate(45).translate(tx=50) ## Scaling -PyPDF2 offers two ways to scale: The page itself and the contents on a page. +pypdf offers two ways to scale: The page itself and the contents on a page. Typically, you want to combine both. ![](scaling.png) @@ -140,7 +140,7 @@ Typically, you want to combine both. ### Scaling a Page (the Canvas) ```python -from PyPDF2 import PdfReader, PdfWriter +from pypdf import PdfReader, PdfWriter # Read the input reader = PdfReader("resources/side-by-side-subfig.pdf") @@ -159,7 +159,7 @@ If you wish to have more control, you can adjust the various page boxes directly: ```python -from PyPDF2.generic import RectangleObject +from pypdf.generic import RectangleObject mb = page.mediabox @@ -176,7 +176,7 @@ The content is scaled towords the origin of the coordinate system. Typically, that is the lower-left corner. ```python -from PyPDF2 import PdfReader, PdfWriter, Transformation +from pypdf import PdfReader, PdfWriter, Transformation # Read the input reader = PdfReader("resources/side-by-side-subfig.pdf") diff --git a/docs/user/encryption-decryption.md b/docs/user/encryption-decryption.md index a397554448..b95b33f86d 100644 --- a/docs/user/encryption-decryption.md +++ b/docs/user/encryption-decryption.md @@ -8,7 +8,7 @@ Add a password to a PDF (encrypt it): ```python -from PyPDF2 import PdfReader, PdfWriter +from pypdf import PdfReader, PdfWriter reader = PdfReader("example.pdf") writer = PdfWriter() @@ -30,7 +30,7 @@ with open("encrypted-pdf.pdf", "wb") as f: Remove the password from a PDF (decrypt it): ```python -from PyPDF2 import PdfReader, PdfWriter +from pypdf import PdfReader, PdfWriter reader = PdfReader("encrypted-pdf.pdf") writer = PdfWriter() diff --git a/docs/user/extract-images.md b/docs/user/extract-images.md index a6175387bf..3bce343a37 100644 --- a/docs/user/extract-images.md +++ b/docs/user/extract-images.md @@ -7,7 +7,7 @@ Every page of a PDF document can contain an arbitrary amount of images. The names of the files may not be unique. ```python -from PyPDF2 import PdfReader +from pypdf import PdfReader reader = PdfReader("example.pdf") diff --git a/docs/user/extract-text.md b/docs/user/extract-text.md index ab2b2751f6..dca9895694 100644 --- a/docs/user/extract-text.md +++ b/docs/user/extract-text.md @@ -3,7 +3,7 @@ You can extract text from a PDF like this: ```python -from PyPDF2 import PdfReader +from pypdf import PdfReader reader = PdfReader("example.pdf") page = reader.pages[0] @@ -20,7 +20,7 @@ print(page.extract_text(0)) print(page.extract_text((0, 90))) ``` -Refer to [extract\_text](../modules/PageObject.html#PyPDF2._page.PageObject.extract_text) for more details. +Refer to [extract\_text](../modules/PageObject.html#pypdf._page.PageObject.extract_text) for more details. ## Using a visitor @@ -41,10 +41,10 @@ operand, operand-arguments, current transformation matrix and text matrix. ### Example 1: Ignore header and footer -The following example reads the text of page 4 of [this PDF document](https://github.com/py-pdf/PyPDF2/blob/main/resources/GeoBase_NHNC1_Data_Model_UML_EN.pdf), but ignores header (y < 720) and footer (y > 50). +The following example reads the text of page 4 of [this PDF document](https://github.com/py-pdf/pypdf/blob/main/resources/GeoBase_NHNC1_Data_Model_UML_EN.pdf), but ignores header (y < 720) and footer (y > 50). ```python -from PyPDF2 import PdfReader +from pypdf import PdfReader reader = PdfReader("GeoBase_NHNC1_Data_Model_UML_EN.pdf") page = reader.pages[3] @@ -66,13 +66,13 @@ print(text_body) ### Example 2: Extract rectangles and texts into a SVG-file -The following example converts page 3 of [this PDF document](https://github.com/py-pdf/PyPDF2/blob/main/resources/GeoBase_NHNC1_Data_Model_UML_EN.pdf) into a +The following example converts page 3 of [this PDF document](https://github.com/py-pdf/pypdf/blob/main/resources/GeoBase_NHNC1_Data_Model_UML_EN.pdf) into a [SVG file](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics). Such a SVG export may help to understand whats going on in a page. ```python -from PyPDF2 import PdfReader +from pypdf import PdfReader import svgwrite reader = PdfReader("GeoBase_NHNC1_Data_Model_UML_EN.pdf") @@ -144,11 +144,11 @@ the way PDF stores information just makes it hard to achieve that: instead an image. You notice that when you cannot copy the text. Then there are PDF files that contain an image and a text layer in the background. That typically happens when a document was scanned. Although the scanning - software (OCR) is pretty good today, it still fails once in a while. PyPDF2 - is no OCR software; it will not be able to detect those failures. PyPDF2 + software (OCR) is pretty good today, it still fails once in a while. pypdf + is no OCR software; it will not be able to detect those failures. pypdf will also never be able to extract text from images. -And finally there are issues that PyPDF2 will deal with. If you find such a +And finally there are issues that pypdf will deal with. If you find such a text extraction bug, please share the PDF with us so we can work on it! ## OCR vs Text Extraction @@ -158,7 +158,7 @@ images. Software which does this is called *OCR software*. The [tesseract OCR engine](https://github.com/tesseract-ocr/tesseract) is the most commonly known Open Source OCR software. -PyPDF2 is **not** OCR software. +pypdf is **not** OCR software. ### Digitally-born vs Scanned PDF files @@ -169,10 +169,10 @@ text on screen or print it. For this reason text extraction from PDFs is hard. If you scan a document, the resulting PDF typically shows the image of the scan. Scanners then also run OCR software and put the recognized text in the background of the image. This result of the scanners OCR software can be extracted by -PyPDF2. However, in such cases it's recommended to directly use OCR software as +pypdf. However, in such cases it's recommended to directly use OCR software as errors can accumulate: The OCR software is not perfect in recognizing the text. Then it stores the text in a format that is not meant for text extraction and -PyPDF2 might make mistakes parsing that. +pypdf might make mistakes parsing that. Hence I would distinguish three types of PDF documents: @@ -194,15 +194,15 @@ PDF file is digitally-born, you can just render it to an image. I would recommend not to do that. -Text extraction software like PyPDF2 can use more information from the +Text extraction software like pypdf can use more information from the PDF than just the image. It can know about fonts, encodings, typical character distances and similar topics. -That means PyPDF2 has a clear advantage when it +That means pypdf has a clear advantage when it comes to characters which are easy to confuse such as `oO0ΓΆ`. -**PyPDF2 will never confuse characters**. It just reads what is in the file. +**pypdf will never confuse characters**. It just reads what is in the file. -PyPDF2 also has an edge when it comes to characters which are rare, e.g. +pypdf also has an edge when it comes to characters which are rare, e.g. 🀰. OCR software will not be able to recognize smileys correctly. diff --git a/docs/user/file-size.md b/docs/user/file-size.md index a3dfef3036..314595d3a2 100644 --- a/docs/user/file-size.md +++ b/docs/user/file-size.md @@ -12,7 +12,7 @@ be embedded once and referenced twice. This can be done by reading and writing the file: ```python -from PyPDF2 import PdfReader, PdfWriter +from pypdf import PdfReader, PdfWriter reader = PdfReader("big-old-file.pdf") writer = PdfWriter() @@ -34,7 +34,7 @@ reduction (from 5.7 MB to 0.8 MB) within a real PDF. ```python -from PyPDF2 import PdfReader, PdfWriter +from pypdf import PdfReader, PdfWriter reader = PdfReader("example.pdf") writer = PdfWriter() @@ -50,14 +50,14 @@ with open("out.pdf", "wb") as f: ## Lossless Compression -PyPDF2 supports the FlateDecode filter which uses the zlib/deflate compression +pypdf supports the FlateDecode filter which uses the zlib/deflate compression method. It is a lossless compression, meaning the resulting PDF looks exactly the same. -Deflate compression can be applied to a page via [`page.compress_content_streams`](https://pypdf2.readthedocs.io/en/latest/modules/PageObject.html#PyPDF2._page.PageObject.compress_content_streams): +Deflate compression can be applied to a page via [`page.compress_content_streams`](https://pypdf.readthedocs.io/en/latest/modules/PageObject.html#pypdf._page.PageObject.compress_content_streams): ```python -from PyPDF2 import PdfReader, PdfWriter +from pypdf import PdfReader, PdfWriter reader = PdfReader("example.pdf") writer = PdfWriter() diff --git a/docs/user/forms.md b/docs/user/forms.md index 711723b744..4a61657aae 100644 --- a/docs/user/forms.md +++ b/docs/user/forms.md @@ -3,7 +3,7 @@ ## Reading form fields ```python -from PyPDF2 import PdfReader +from pypdf import PdfReader reader = PdfReader("form.pdf") fields = reader.get_form_text_fields() @@ -13,7 +13,7 @@ fields == {"key": "value", "key2": "value2"} ## Filling out forms ```python -from PyPDF2 import PdfReader, PdfWriter +from pypdf import PdfReader, PdfWriter reader = PdfReader("form.pdf") writer = PdfWriter() @@ -27,7 +27,7 @@ writer.update_page_form_field_values( writer.pages[0], {"fieldname": "some filled in text"} ) -# write "output" to PyPDF2-output.pdf +# write "output" to pypdf-output.pdf with open("filled-out.pdf", "wb") as output_stream: writer.write(output_stream) ``` diff --git a/docs/user/installation.md b/docs/user/installation.md index a2d5b24442..20077f31fa 100644 --- a/docs/user/installation.md +++ b/docs/user/installation.md @@ -1,65 +1,66 @@ # Installation -There are several ways to install PyPDF2. The most common option is to use pip. +There are several ways to install pypdf. The most common option is to use pip. ## pip -PyPDF2 requires Python 3.6+ to run. +pypdf requires Python 3.6+ to run. Typically Python comes with `pip`, a package installer. Using it you can -install PyPDF2: +install pypdf: ```bash -pip install PyPDF2 +pip install pypdf ``` If you are not a super-user (a system administrator / root), you can also just -install PyPDF2 for your current user: +install pypdf for your current user: ```bash -pip install --user PyPDF2 +pip install --user pypdf ``` ### Optional dependencies -PyPDF2 tries to be as self-contained as possible, but for some tasks the amount +pypdf tries to be as self-contained as possible, but for some tasks the amount of work to properly maintain the code would be too high. This is especially the case for cryptography and image formats. If you simply want to install all optional dependencies, run: ``` -pip install PyPDF2[full] +pip install pypdf[full] ``` Alternatively, you can install just some: -If you plan to use PyPDF2 for encrypting or decrypting PDFs that use AES, you +If you plan to use pypdf for encrypting or decrypting PDFs that use AES, you will need to install some extra dependencies. Encryption using RC4 is supported using the regular installation. ``` -pip install PyPDF2[crypto] +pip install pypdf[crypto] ``` If you plan to use image extraction, you need Pillow: ``` -pip install PyPDF2[image] +pip install pypdf[image] ``` ## Python Version Support -| Python | 3.10 | 3.9 | 3.8 | 3.7 | 3.6 | 2.7 | -| ---------------------- | ---- | --- | --- | --- | --- | --- | -| PyPDF2>=2.0 | YES | YES | YES | YES | YES | | -| PyPDF2 1.20.0 - 1.28.4 | YES | YES | YES | YES | YES | YES | -| PyPDF2 1.15.0 - 1.20.0 | | | | | | YES | +| Python | 3.11 | 3.10 | 3.9 | 3.8 | 3.7 | 3.6 | 2.7 | +| ---------------------- | ---- | ---- | --- | --- | --- | --- | --- | +| pypdf>=3.0 | YES | YES | YES | YES | YES | YES | | +| PyPDF2>=2.0 | YES | YES | YES | YES | YES | YES | | +| PyPDF2 1.20.0 - 1.28.4 | | YES | YES | YES | YES | YES | YES | +| PyPDF2 1.15.0 - 1.20.0 | | | | | | | YES | ## Anaconda -Anaconda users can [install PyPDF2 via conda-forge](https://anaconda.org/conda-forge/pypdf2). +Anaconda users can [install pypdf via conda-forge](https://anaconda.org/conda-forge/pypdf). ## Development Version @@ -67,5 +68,5 @@ Anaconda users can [install PyPDF2 via conda-forge](https://anaconda.org/conda-f In case you want to use the current version under development: ```bash -pip install git+https://github.com/py-pdf/PyPDF2.git +pip install git+https://github.com/py-pdf/pypdf.git ``` diff --git a/docs/user/merging-pdfs.md b/docs/user/merging-pdfs.md index ff5d20ff73..de08558424 100644 --- a/docs/user/merging-pdfs.md +++ b/docs/user/merging-pdfs.md @@ -3,7 +3,7 @@ ## Basic Example ```python -from PyPDF2 import PdfWriter +from pypdf import PdfWriter merger = PdfWriter() @@ -21,7 +21,7 @@ by Paul Rooney. ## Showing more merging options ```python -from PyPDF2 import PdfWriter +from pypdf import PdfWriter merger = PdfWriter() @@ -50,7 +50,7 @@ output.close() ## append `append` has been slighlty extended in `PdfWriter`. -see [pdfWriter.append](../modules/PdfWriter.html#PyPDF2.PdfWriter.append) for more details +see [pdfWriter.append](../modules/PdfWriter.html#pypdf.PdfWriter.append) for more details **parameters:** diff --git a/docs/user/metadata.md b/docs/user/metadata.md index b970f2aab9..58fb4ee8f9 100644 --- a/docs/user/metadata.md +++ b/docs/user/metadata.md @@ -3,7 +3,7 @@ ## Reading metadata ```python -from PyPDF2 import PdfReader +from pypdf import PdfReader reader = PdfReader("example.pdf") @@ -22,7 +22,7 @@ print(meta.title) ## Writing metadata ```python -from PyPDF2 import PdfReader, PdfWriter +from pypdf import PdfReader, PdfWriter reader = PdfReader("example.pdf") writer = PdfWriter() diff --git a/docs/user/pdf-version-support.md b/docs/user/pdf-version-support.md index 4ac0adf13d..73bb69cd1f 100644 --- a/docs/user/pdf-version-support.md +++ b/docs/user/pdf-version-support.md @@ -13,12 +13,12 @@ PDF comes in the following versions: * 2017: 2.0 The general format didn't change, but new features got added. It can be that -PyPDF2 can do the operations you want on PDF 2.0 files without fully supporting +pypdf can do the operations you want on PDF 2.0 files without fully supporting all features of PDF 2.0. -## PDF Feature Support by PyPDF2 +## PDF Feature Support by pypdf -| Feature | PDF-Version | PyPDF2 Support | +| Feature | PDF-Version | pypdf Support | | --------------------------------------- | ----------- | -------------- | | Transparent Graphics | 1.4 | ? | | CMaps | 1.4 | βœ… | @@ -31,8 +31,8 @@ all features of PDF 2.0. See [History of PDF](https://en.wikipedia.org/wiki/History_of_PDF) for more features. -Some PDF features are not supported by PyPDF2, but other libraries can be used +Some PDF features are not supported by pypdf, but other libraries can be used for them: -* [pyHanko](https://pyhanko.readthedocs.io/en/latest/index.html): Cryptographically sign a PDF ([#302](https://github.com/py-pdf/PyPDF2/issues/302)) -* [camelot-py](https://pypi.org/project/camelot-py/): Table Extraction ([#231](https://github.com/py-pdf/PyPDF2/issues/231)) +* [pyHanko](https://pyhanko.readthedocs.io/en/latest/index.html): Cryptographically sign a PDF ([#302](https://github.com/py-pdf/pypdf/issues/302)) +* [camelot-py](https://pypi.org/project/camelot-py/): Table Extraction ([#231](https://github.com/py-pdf/pypdf/issues/231)) diff --git a/docs/user/reading-pdf-annotations.md b/docs/user/reading-pdf-annotations.md index fc87377d31..959dbe8a0e 100644 --- a/docs/user/reading-pdf-annotations.md +++ b/docs/user/reading-pdf-annotations.md @@ -19,7 +19,7 @@ PDF 1.7 defines 25 different annotation types: In general, annotations can be read like this: ```python -from PyPDF2 import PdfReader +from pypdf import PdfReader reader = PdfReader("commented.pdf") @@ -36,7 +36,7 @@ Reading the most common ones is described here. ## Text ```python -from PyPDF2 import PdfReader +from pypdf import PdfReader reader = PdfReader("example.pdf") @@ -51,7 +51,7 @@ for page in reader.pages: ## Highlights ```python -from PyPDF2 import PdfReader +from pypdf import PdfReader reader = PdfReader("commented.pdf") @@ -67,7 +67,7 @@ for page in reader.pages: ## Attachments ```python -from PyPDF2 import PdfReader +from pypdf import PdfReader reader = PdfReader("example.pdf") diff --git a/docs/user/robustness.md b/docs/user/robustness.md index f1b96ea384..7c9b508d1d 100644 --- a/docs/user/robustness.md +++ b/docs/user/robustness.md @@ -25,17 +25,17 @@ Writing a parser you can go two paths: Either you try to be forgiving and try to figure out what the user intended, or you are strict and just tell the user that they should fix their stuff. -PyPDF2 gives you the option to be strict or not. +pypdf gives you the option to be strict or not. -PyPDF2 has three core objects and all of them have a `strict` parameter: +pypdf has three core objects and all of them have a `strict` parameter: * [`PdfReader`](../modules/PdfReader.md) * [`PdfWriter`](../modules/PdfWriter.md) * [`PdfMerger`](../modules/PdfMerger.md) -Choosing `strict=True` means that PyPDF2 will raise an exception if a PDF does +Choosing `strict=True` means that pypdf will raise an exception if a PDF does not follow the specification. -Choosing `strict=False` means that PyPDF2 will try to be forgiving and do +Choosing `strict=False` means that pypdf will try to be forgiving and do something reasonable, but it will log a warning message. It is a best-effort approach. diff --git a/docs/user/streaming-data.md b/docs/user/streaming-data.md index 78f960039e..8c9ac3a8c8 100644 --- a/docs/user/streaming-data.md +++ b/docs/user/streaming-data.md @@ -1,9 +1,9 @@ -# Streaming Data with PyPDF2 +# Streaming Data with pypdf In some cases you might want to avoid saving things explicitly as a file to disk, e.g. when you want to store the PDF in a database or AWS S3. -PyPDF2 supports streaming data to a file-like object and here is how. +pypdf supports streaming data to a file-like object and here is how. ```python from io import BytesIO @@ -31,7 +31,7 @@ and want to set `my-secret-password`: from io import BytesIO import boto3 -from PyPDF2 import PdfReader, PdfWriter +from pypdf import PdfReader, PdfWriter reader = PdfReader(BytesIO(raw_bytes_data)) @@ -65,7 +65,7 @@ For AWS S3 it works like this: from io import BytesIO import boto3 -from PyPDF2 import PdfReader +from pypdf import PdfReader s3 = boto3.client("s3") diff --git a/docs/user/suppress-warnings.md b/docs/user/suppress-warnings.md index 70b66de6f6..5a260e5fae 100644 --- a/docs/user/suppress-warnings.md +++ b/docs/user/suppress-warnings.md @@ -1,17 +1,17 @@ # Exceptions, Warnings, and Log messages -PyPDF2 makes use of 3 mechanisms to show that something went wrong: +pypdf makes use of 3 mechanisms to show that something went wrong: * **Log messages** are informative messages that can be used for post-mortem analysis. Most of the time, users can ignore them. They come in different *levels*, such as info / warning / error indicating the severity. - Examples are non-standard compliant PDF files which PyPDF2 can deal with. + Examples are non-standard compliant PDF files which pypdf can deal with. * **Warnings** are avoidable issues, such as using deprecated classes / - functions / parameters. Another example is missing capabilities of PyPDF2. - In those cases, PyPDF2 users should adjust their code. Warnings + functions / parameters. Another example is missing capabilities of pypdf. + In those cases, pypdf users should adjust their code. Warnings are issued by the `warnings` module - those are different from the log-level "warning". -* **Exceptions** are error-cases that PyPDF2 users should explicitly handle. +* **Exceptions** are error-cases that pypdf users should explicitly handle. In the `strict=True` mode, most log messages with the warning level will become exceptions. This can be useful in applications where you can force to user to fix the broken PDF. @@ -22,7 +22,7 @@ PyPDF2 makes use of 3 mechanisms to show that something went wrong: Exeptions need to be catched if you want to handle them. For example, you could want to read the text from a PDF as a part of a search function. -Most PDF files don't follow the specifications. In this case PyPDF2 needs to +Most PDF files don't follow the specifications. In this case pypdf needs to guess which kinds of mistakes were potentially done when the PDF file was created. See [the robustness page](robustness.md) for the related issues. @@ -30,7 +30,7 @@ As a users, you likely don't care about it. If it's readable in any way, you want the text. You might use pdfminer.six as a fallback and do this: ```python -from PyPDF2 import PdfReader +from pypdf import PdfReader from pdfminer.high_level import extract_text as fallback_text_extraction text = "" @@ -42,7 +42,7 @@ except Exception as exc: text = fallback_text_extraction("example.pdf") ``` -You could also capture [`PyPDF2.errors.PyPdfError`](https://github.com/py-pdf/PyPDF2/blob/main/PyPDF2/errors.py) +You could also capture [`pypdf.errors.PyPdfError`](https://github.com/py-pdf/pypdf/blob/main/pypdf/errors.py) if you prefer something more specific. ## Warnings @@ -61,14 +61,14 @@ see all warnings. This is especially true for Continuous Integration (CI). ## Log messages -Log messages can be noisy in some cases. PyPDF2 hopefully is having a reasonable +Log messages can be noisy in some cases. pypdf hopefully is having a reasonable level of log messages, but you can reduce which types of messages you want to see: ```python import logging -logger = logging.getLogger("PyPDF2") +logger = logging.getLogger("pypdf") logger.setLevel(logging.ERROR) ``` diff --git a/make_changelog.py b/make_changelog.py index fbbed61b85..d12edf0fea 100644 --- a/make_changelog.py +++ b/make_changelog.py @@ -27,7 +27,7 @@ def main(changelog_path: str): today = datetime.now() header = f"Version {new_version}, {today:%Y-%m-%d}\n" header = header + "-" * (len(header) - 1) + "\n" - trailer = f"\n[Full Changelog](https://github.com/py-pdf/PyPDF2/compare/{git_tag}...{new_version})\n\n" + trailer = f"\n[Full Changelog](https://github.com/py-pdf/pypdf/compare/{git_tag}...{new_version})\n\n" new_entry = header + changes + trailer print(new_entry) diff --git a/mutmut-test.sh b/mutmut-test.sh index 39dfb22722..9a8bb1d730 100755 --- a/mutmut-test.sh +++ b/mutmut-test.sh @@ -1,3 +1,3 @@ #!/bin/bash -e pytest -x -mypy PyPDF2 --show-error-codes --disallow-untyped-defs --disallow-incomplete-defs --ignore-missing-imports +mypy pypdf --show-error-codes --disallow-untyped-defs --disallow-incomplete-defs --ignore-missing-imports diff --git a/PyPDF2/__init__.py b/pypdf/__init__.py similarity index 65% rename from PyPDF2/__init__.py rename to pypdf/__init__.py index 08b03d5ec0..3ddd264f1e 100644 --- a/PyPDF2/__init__.py +++ b/pypdf/__init__.py @@ -1,10 +1,10 @@ """ -PyPDF2 is a free and open-source pure-python PDF library capable of splitting, +pypdf is a free and open-source pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files. It can also add -custom data, viewing options, and passwords to PDF files. PyPDF2 can retrieve +custom data, viewing options, and passwords to PDF files. pypdf can retrieve text and metadata from PDFs as well. -You can read the full docs at https://pypdf2.readthedocs.io/. +You can read the full docs at https://pypdf.readthedocs.io/. """ from ._encryption import PasswordType @@ -22,9 +22,9 @@ "PaperSize", "DocumentInformation", "parse_filename_page_ranges", - "PdfFileMerger", # will be removed in PyPDF2 3.0.0; use PdfMerger instead - "PdfFileReader", # will be removed in PyPDF2 3.0.0; use PdfReader instead - "PdfFileWriter", # will be removed in PyPDF2 3.0.0; use PdfWriter instead + "PdfFileMerger", # will be removed in pypdf==4.0.0; use PdfMerger instead + "PdfFileReader", # will be removed in pypdf==4.0.0; use PdfReader instead + "PdfFileWriter", # will be removed in pypdf==4.0.0; use PdfWriter instead "PdfMerger", "PdfReader", "PdfWriter", diff --git a/PyPDF2/_cmap.py b/pypdf/_cmap.py similarity index 99% rename from PyPDF2/_cmap.py rename to pypdf/_cmap.py index db082a820b..096b1fdb33 100644 --- a/PyPDF2/_cmap.py +++ b/pypdf/_cmap.py @@ -228,7 +228,7 @@ def prepare_cm(ft: DictionaryObject) -> bytes: if j >= 0: if j == 0: # string is empty: stash a placeholder here (see below) - # see https://github.com/py-pdf/PyPDF2/issues/1111 + # see https://github.com/py-pdf/pypdf/issues/1111 content = b"." else: content = ll[i][:j].replace(b" ", b"") diff --git a/PyPDF2/_codecs/__init__.py b/pypdf/_codecs/__init__.py similarity index 100% rename from PyPDF2/_codecs/__init__.py rename to pypdf/_codecs/__init__.py diff --git a/PyPDF2/_codecs/adobe_glyphs.py b/pypdf/_codecs/adobe_glyphs.py similarity index 100% rename from PyPDF2/_codecs/adobe_glyphs.py rename to pypdf/_codecs/adobe_glyphs.py diff --git a/PyPDF2/_codecs/pdfdoc.py b/pypdf/_codecs/pdfdoc.py similarity index 100% rename from PyPDF2/_codecs/pdfdoc.py rename to pypdf/_codecs/pdfdoc.py diff --git a/PyPDF2/_codecs/std.py b/pypdf/_codecs/std.py similarity index 100% rename from PyPDF2/_codecs/std.py rename to pypdf/_codecs/std.py diff --git a/PyPDF2/_codecs/symbol.py b/pypdf/_codecs/symbol.py similarity index 100% rename from PyPDF2/_codecs/symbol.py rename to pypdf/_codecs/symbol.py diff --git a/PyPDF2/_codecs/zapfding.py b/pypdf/_codecs/zapfding.py similarity index 100% rename from PyPDF2/_codecs/zapfding.py rename to pypdf/_codecs/zapfding.py diff --git a/PyPDF2/_encryption.py b/pypdf/_encryption.py similarity index 100% rename from PyPDF2/_encryption.py rename to pypdf/_encryption.py diff --git a/PyPDF2/_merger.py b/pypdf/_merger.py similarity index 98% rename from PyPDF2/_merger.py rename to pypdf/_merger.py index 4d7a659d8c..cb75217957 100644 --- a/PyPDF2/_merger.py +++ b/pypdf/_merger.py @@ -155,7 +155,7 @@ def merge( (previously referred to as a 'bookmark') to be applied at the beginning of the included file by supplying the text of the outline item. - :param pages: can be a :class:`PageRange` + :param pages: can be a :class:`PageRange` or a ``(start, stop[, step])`` tuple to merge only the specified range of pages from the source document into the output document. @@ -173,7 +173,7 @@ def merge( warnings.warn( ( f"{old_term} is deprecated as an argument and will be " - f"removed in PyPDF2=4.0.0. Use {new_term} instead" + f"removed in pypdf=4.0.0. Use {new_term} instead" ), DeprecationWarning, ) @@ -306,7 +306,7 @@ def append( (previously referred to as a 'bookmark') to be applied at the beginning of the included file by supplying the text of the outline item. - :param pages: can be a :class:`PageRange` + :param pages: can be a :class:`PageRange` or a ``(start, stop[, step])`` tuple to merge only the specified range of pages from the source document into the output document. @@ -692,7 +692,7 @@ def add_outline_item( warnings.warn( ( f"{old_term} is deprecated as an argument and will be " - f"removed in PyPDF2==4.0.0. Use {new_term} instead" + f"removed in pypdf==4.0.0. Use {new_term} instead" ), DeprecationWarning, ) @@ -797,7 +797,7 @@ def add_named_destination( warnings.warn( ( f"{old_term} is deprecated as an argument and will be " - f"removed in PyPDF2==4.0.0. Use {new_term} instead" + f"removed in pypdf==4.0.0. Use {new_term} instead" ), DeprecationWarning, ) diff --git a/PyPDF2/_page.py b/pypdf/_page.py similarity index 98% rename from PyPDF2/_page.py rename to pypdf/_page.py index ed385bb3d9..f9ed8f5926 100644 --- a/PyPDF2/_page.py +++ b/pypdf/_page.py @@ -206,7 +206,7 @@ class Transformation: Example ------- - >>> from PyPDF2 import Transformation + >>> from pypdf import Transformation >>> op = Transformation().scale(sx=2, sy=3).translate(tx=10, ty=20) >>> page.add_transformation(op) """ @@ -336,10 +336,10 @@ class PageObject(DictionaryObject): PageObject represents a single page within a PDF file. Typically this object will be created by accessing the - :meth:`get_page()` method of the - :class:`PdfReader` class, but it is + :meth:`get_page()` method of the + :class:`PdfReader` class, but it is also possible to create an empty page with the - :meth:`create_blank_page()` static method. + :meth:`create_blank_page()` static method. Args: pdf: PDF file the page belongs to. @@ -362,7 +362,7 @@ def __init__( warnings.warn( ( "indirect_ref is deprecated and will be removed in " - "PyPDF2 4.0.0. Use indirect_reference instead of indirect_ref." + "pypdf 4.0.0. Use indirect_reference instead of indirect_ref." ), DeprecationWarning, ) @@ -375,7 +375,7 @@ def __init__( def indirect_ref(self) -> Optional[IndirectObject]: # deprecated warnings.warn( ( - "indirect_ref is deprecated and will be removed in PyPDF2 4.0.0" + "indirect_ref is deprecated and will be removed in pypdf 4.0.0" "Use indirect_reference instead of indirect_ref." ), DeprecationWarning, @@ -466,7 +466,7 @@ def images(self) -> List[File]: """ Get a list of all images of the page. - This requires pillow. You can install it via 'pip install PyPDF2[image]'. + This requires pillow. You can install it via 'pip install pypdf[image]'. For the moment, this does NOT include inline images. They will be added in future. @@ -1086,7 +1086,7 @@ def add_transformation( Args: ctm: A 6-element tuple containing the operands of the transformation matrix. Alternatively, a - :py:class:`Transformation` + :py:class:`Transformation` object can be passed. See :doc:`/user/cropping-and-transforming`. @@ -1789,8 +1789,8 @@ def extract_text( For example in some PDF files this can be useful to parse tables. Args: - Tj_sep: Deprecated. Kept for compatibility until PyPDF2 4.0.0 - TJ_sep: Deprecated. Kept for compatibility until PyPDF2 4.0.0 + Tj_sep: Deprecated. Kept for compatibility until pypdf 4.0.0 + TJ_sep: Deprecated. Kept for compatibility until pypdf 4.0.0 orientations: list of orientations text_extraction will look for default = (0, 90, 180, 270) note: currently only 0(Up),90(turned Left), 180(upside Down), @@ -1841,7 +1841,7 @@ def extract_text( raise TypeError(f"Invalid positional parameter {args[0]}") if Tj_sep is not None or TJ_sep is not None: warnings.warn( - "parameters Tj_Sep, TJ_sep depreciated, and will be removed in PyPDF2 4.0.0.", + "parameters Tj_Sep, TJ_sep depreciated, and will be removed in pypdf 4.0.0.", DeprecationWarning, ) @@ -1913,7 +1913,7 @@ def _get_fonts(self) -> Tuple[Set[str], Set[str]]: mediabox = _create_rectangle_accessor(PG.MEDIABOX, ()) """ - A :class:`RectangleObject`, expressed in default user space units, + A :class:`RectangleObject`, expressed in default user space units, defining the boundaries of the physical medium on which the page is intended to be displayed or printed. """ @@ -1940,7 +1940,7 @@ def mediaBox(self, value: RectangleObject) -> None: # pragma: no cover cropbox = _create_rectangle_accessor("/CropBox", (PG.MEDIABOX,)) """ - A :class:`RectangleObject`, expressed in default user space units, + A :class:`RectangleObject`, expressed in default user space units, defining the visible region of default user space. When the page is displayed or printed, its contents are to be clipped (cropped) to this rectangle and then imposed on the output medium in some @@ -1964,7 +1964,7 @@ def cropBox(self, value: RectangleObject) -> None: # pragma: no cover bleedbox = _create_rectangle_accessor("/BleedBox", ("/CropBox", PG.MEDIABOX)) """ - A :class:`RectangleObject`, expressed in default user space units, + A :class:`RectangleObject`, expressed in default user space units, defining the region to which the contents of the page should be clipped when output in a production environment. """ @@ -1986,7 +1986,7 @@ def bleedBox(self, value: RectangleObject) -> None: # pragma: no cover trimbox = _create_rectangle_accessor("/TrimBox", ("/CropBox", PG.MEDIABOX)) """ - A :class:`RectangleObject`, expressed in default user space units, + A :class:`RectangleObject`, expressed in default user space units, defining the intended dimensions of the finished page after trimming. """ @@ -2007,7 +2007,7 @@ def trimBox(self, value: RectangleObject) -> None: # pragma: no cover artbox = _create_rectangle_accessor("/ArtBox", ("/CropBox", PG.MEDIABOX)) """ - A :class:`RectangleObject`, expressed in default user space units, + A :class:`RectangleObject`, expressed in default user space units, defining the extent of the page's meaningful content as intended by the page's creator. """ diff --git a/PyPDF2/_protocols.py b/pypdf/_protocols.py similarity index 100% rename from PyPDF2/_protocols.py rename to pypdf/_protocols.py diff --git a/PyPDF2/_reader.py b/pypdf/_reader.py similarity index 99% rename from PyPDF2/_reader.py rename to pypdf/_reader.py index 0a9144766a..18f837c426 100644 --- a/PyPDF2/_reader.py +++ b/pypdf/_reader.py @@ -119,13 +119,13 @@ def convertToInt( class DocumentInformation(DictionaryObject): """ A class representing the basic document metadata provided in a PDF File. - This class is accessible through :py:class:`PdfReader.metadata`. + This class is accessible through :py:class:`PdfReader.metadata`. All text properties of the document metadata have *two* properties, eg. author and author_raw. The non-raw property will always return a ``TextStringObject``, making it ideal for a case where the metadata is being displayed. The raw property can sometimes return - a ``ByteStringObject``, if PyPDF2 was unable to decode the string's + a ``ByteStringObject``, if pypdf was unable to decode the string's text encoding; this requires additional safety in the caller and therefore is not as commonly accessed. """ @@ -325,7 +325,7 @@ def __init__( self._override_encryption = True # Some documents may not have a /ID, use two empty # byte strings instead. Solves - # https://github.com/mstamy2/PyPDF2/issues/608 + # https://github.com/mstamy2/pypdf/issues/608 id_entry = self.trailer.get(TK.ID) id1_entry = id_entry[0].get_object().original_bytes if id_entry else b"" encrypt_entry = cast( @@ -484,7 +484,7 @@ def _get_page(self, page_number: int) -> PageObject: :param int page_number: The page number to retrieve (pages begin at zero) - :return: a :class:`PageObject` instance. + :return: a :class:`PageObject` instance. """ # ensure that we're not trying to access an encrypted PDF # assert not self.trailer.has_key(TK.ENCRYPT) @@ -507,7 +507,7 @@ def namedDestinations(self) -> Dict[str, Any]: # pragma: no cover def named_destinations(self) -> Dict[str, Any]: """ A read-only dictionary which maps names to - :class:`Destinations` + :class:`Destinations` """ return self._get_named_destinations() @@ -528,7 +528,7 @@ def get_fields( :param fileobj: A file object (usually a text file) to write a report to on all interactive form fields found. :return: A dictionary where each key is a field name, and each - value is a :class:`Field` object. By + value is a :class:`Field` object. By default, the mapping name is used for keys. ``None`` if form data could not be located. """ @@ -679,7 +679,7 @@ def _get_named_destinations( Retrieve the named destinations present in the document. :return: a dictionary which maps names to - :class:`Destinations`. + :class:`Destinations`. """ if retval is None: retval = {} @@ -740,7 +740,7 @@ def outline(self) -> OutlineType: Read-only property for the outline (i.e., a collection of 'outline items' which are also known as 'bookmarks') present in the document. - :return: a nested list of :class:`Destinations`. + :return: a nested list of :class:`Destinations`. """ return self._get_outline() @@ -844,7 +844,7 @@ def get_page_number(self, page: PageObject) -> int: Retrieve page number of a given PageObject :param PageObject page: The page to get page number. Should be - an instance of :class:`PageObject` + an instance of :class:`PageObject` :return: the page number or -1 if page not found """ return self._get_page_number_by_indirect(page.indirect_reference) @@ -982,7 +982,7 @@ def _build_outline_item(self, node: DictionaryObject) -> Optional[Destination]: @property def pages(self) -> List[PageObject]: - """Read-only property that emulates a list of :py:class:`Page` objects.""" + """Read-only property that emulates a list of :py:class:`Page` objects.""" return _VirtualList(self._get_num_pages, self._get_page) # type: ignore @property @@ -1921,7 +1921,7 @@ def is_encrypted(self) -> bool: """ Read-only boolean property showing whether this PDF file is encrypted. Note that this property, if true, will remain true even after the - :meth:`decrypt()` method is called. + :meth:`decrypt()` method is called. """ return TK.ENCRYPT in self.trailer diff --git a/PyPDF2/_security.py b/pypdf/_security.py similarity index 100% rename from PyPDF2/_security.py rename to pypdf/_security.py diff --git a/PyPDF2/_utils.py b/pypdf/_utils.py similarity index 97% rename from PyPDF2/_utils.py rename to pypdf/_utils.py index b6f090b8d5..0ef507f145 100644 --- a/PyPDF2/_utils.py +++ b/pypdf/_utils.py @@ -70,10 +70,10 @@ StreamType = IO StrByteType = Union[str, StreamType] -DEPR_MSG_NO_REPLACEMENT = "{} is deprecated and will be removed in PyPDF2 {}." -DEPR_MSG_NO_REPLACEMENT_HAPPENED = "{} is deprecated and was removed in PyPDF2 {}." -DEPR_MSG = "{} is deprecated and will be removed in PyPDF2 3.0.0. Use {} instead." -DEPR_MSG_HAPPENED = "{} is deprecated and was removed in PyPDF2 {}. Use {} instead." +DEPR_MSG_NO_REPLACEMENT = "{} is deprecated and will be removed in pypdf {}." +DEPR_MSG_NO_REPLACEMENT_HAPPENED = "{} is deprecated and was removed in pypdf {}." +DEPR_MSG = "{} is deprecated and will be removed in pypdf 3.0.0. Use {} instead." +DEPR_MSG_HAPPENED = "{} is deprecated and was removed in pypdf {}. Use {} instead." def _get_max_pdf_version_header(header1: bytes, header2: bytes) -> bytes: @@ -249,7 +249,7 @@ def mark_location(stream: StreamType) -> None: # Mainly for debugging radius = 5000 stream.seek(-radius, 1) - with open("PyPDF2_pdfLocation.txt", "wb") as output_fh: + with open("pypdf_pdfLocation.txt", "wb") as output_fh: output_fh.write(stream.read(radius)) output_fh.write(b"HERE") output_fh.write(stream.read(radius)) @@ -395,8 +395,8 @@ def logger_warning(msg: str, src: str) -> None: - warnings.warn should be used if the user needs to fix their code, e.g. DeprecationWarnings - logger_warning should be used if the user needs to know that an issue was - handled by PyPDF2, e.g. a non-compliant PDF being read in a way that - PyPDF2 could apply a robustness fix to still read it. This applies mainly + handled by pypdf, e.g. a non-compliant PDF being read in a way that + pypdf could apply a robustness fix to still read it. This applies mainly to strict=False mode. """ logging.getLogger(src).warning(msg) diff --git a/PyPDF2/_version.py b/pypdf/_version.py similarity index 100% rename from PyPDF2/_version.py rename to pypdf/_version.py diff --git a/PyPDF2/_writer.py b/pypdf/_writer.py similarity index 98% rename from PyPDF2/_writer.py rename to pypdf/_writer.py index b2e92cdb10..0d5dad6c2e 100644 --- a/PyPDF2/_writer.py +++ b/pypdf/_writer.py @@ -133,7 +133,7 @@ class PdfWriter: """ This class supports writing PDF files out, given pages produced by another - class (typically :class:`PdfReader`). + class (typically :class:`PdfReader`). """ def __init__(self, fileobj: StrByteType = "") -> None: @@ -158,7 +158,7 @@ def __init__(self, fileobj: StrByteType = "") -> None: info.update( { NameObject("/Producer"): create_string_object( - codecs.BOM_UTF16_BE + "PyPDF2".encode("utf-16be") + codecs.BOM_UTF16_BE + "pypdf".encode("utf-16be") ) } ) @@ -226,7 +226,7 @@ def get_object( else: indirect_reference = ido warnings.warn( - "The parameter 'ido' is depreciated and will be removed in PyPDF2 4.0.0.", + "The parameter 'ido' is depreciated and will be removed in pypdf 4.0.0.", DeprecationWarning, ) assert ( @@ -312,11 +312,11 @@ def add_page( Add a page to this PDF file. Recommended for advanced usage including the adequate excluded_keys - The page is usually acquired from a :class:`PdfReader` + The page is usually acquired from a :class:`PdfReader` instance. :param PageObject page: The page to add to the document. Should be - an instance of :class:`PageObject` + an instance of :class:`PageObject` """ return self._add_page(page, list.append, excluded_keys) @@ -341,7 +341,7 @@ def insert_page( ) -> PageObject: """ Insert a page in this PDF file. The page is usually acquired from a - :class:`PdfReader` instance. + :class:`PdfReader` instance. :param PageObject page: The page to add to the document. :param int index: Position at which the page will be inserted. @@ -409,7 +409,7 @@ def getNumPages(self) -> int: # pragma: no cover @property def pages(self) -> List[PageObject]: - """Property that emulates a list of :class:`PageObject`.""" + """Property that emulates a list of :class:`PageObject`.""" return _VirtualList(self._get_num_pages, self.get_page) # type: ignore def add_blank_page( @@ -492,8 +492,8 @@ def open_destination( PDF catalog). it returns `None` if the entry does not exist is not set. - :param destination:. - the property can be set to a Destination, a Page or an string(NamedDest) or + :param destination: the property can be set to a Destination, + a Page or an string(NamedDest) or None (to remove "/OpenAction") (value stored in "/OpenAction" entry in the Pdf Catalog) @@ -886,7 +886,7 @@ def encrypt( warnings.warn( "Please use 'user_password' instead of 'user_pwd'. " "The 'user_pwd' argument is deprecated and " - "will be removed in PyPDF2 4.0.0." + "will be removed in pypdf 4.0.0." ) user_password = user_pwd if user_password is None: # deprecated @@ -904,7 +904,7 @@ def encrypt( warnings.warn( message=( f"{old_term} is deprecated as an argument and will be " - f"removed in PyPDF2 4.0.0. Use {new_term} instead" + f"removed in pypdf 4.0.0. Use {new_term} instead" ), category=DeprecationWarning, ) @@ -1305,7 +1305,7 @@ def add_outline_item_destination( warnings.warn( message=( f"{old_term} is deprecated as an argument and will be " - f"removed in PyPDF2 4.0.0. Use {new_term} instead" + f"removed in pypdf 4.0.0. Use {new_term} instead" ), category=DeprecationWarning, ) @@ -1569,7 +1569,7 @@ def add_named_destination_object( warnings.warn( message=( f"{old_term} is deprecated as an argument and will be " - f"removed in PyPDF2 4.0.0. Use {new_term} instead" + f"removed in pypdf 4.0.0. Use {new_term} instead" ), category=DeprecationWarning, ) @@ -1613,7 +1613,7 @@ def add_named_destination( warnings.warn( message=( f"{old_term} is deprecated as an argument and will be " - f"removed in PyPDF2 4.0.0. Use {new_term} instead" + f"removed in pypdf 4.0.0. Use {new_term} instead" ), category=DeprecationWarning, ) @@ -1826,7 +1826,7 @@ def add_uri( :param int page_number: index of the page on which to place the URI action. :param str uri: URI of resource to link to. - :param Tuple[int, int, int, int] rect: :class:`RectangleObject` or array of four + :param Tuple[int, int, int, int] rect: :class:`RectangleObject` or array of four integers specifying the clickable rectangular area ``[xLL, yLL, xUR, yUR]``, or string in the form ``"[ xLL yLL xUR yUR ]"``. :param ArrayObject border: if provided, an array describing border-drawing @@ -1836,7 +1836,7 @@ def add_uri( if pagenum is not None: warnings.warn( "The 'pagenum' argument of add_uri is deprecated and will be " - "removed in PyPDF2 4.0.0. Use 'page_number' instead.", + "removed in pypdf 4.0.0. Use 'page_number' instead.", category=DeprecationWarning, ) page_number = pagenum @@ -2299,7 +2299,7 @@ def append( (aka 'bookmark') to identify the beginning of the included file. - :param pages: can be a :class:`PageRange` + :param pages: can be a :class:`PageRange` or a ``(start, stop[, step])`` tuple or a list of pages to be processed to merge only the specified range of pages from the source @@ -2352,7 +2352,7 @@ def merge( (aka 'bookmark') to identify the beginning of the included file. - :param pages: can be a :class:`PageRange` + :param pages: can be a :class:`PageRange` or a ``(start, stop[, step])`` tuple or a list of pages to be processed to merge only the specified range of pages from the source diff --git a/PyPDF2/constants.py b/pypdf/constants.py similarity index 100% rename from PyPDF2/constants.py rename to pypdf/constants.py diff --git a/PyPDF2/errors.py b/pypdf/errors.py similarity index 91% rename from PyPDF2/errors.py rename to pypdf/errors.py index a84b056918..2b9b54fbb3 100644 --- a/PyPDF2/errors.py +++ b/pypdf/errors.py @@ -1,5 +1,5 @@ """ -All errors/exceptions PyPDF2 raises and all of the warnings it uses. +All errors/exceptions pypdf raises and all of the warnings it uses. Please note that broken PDF files might cause other Exceptions. """ diff --git a/PyPDF2/filters.py b/pypdf/filters.py similarity index 99% rename from PyPDF2/filters.py rename to pypdf/filters.py index 11f6a21b84..e3e0a87d03 100644 --- a/PyPDF2/filters.py +++ b/pypdf/filters.py @@ -559,7 +559,7 @@ def _xobj_to_image(x_object_obj: Dict[str, Any]) -> Tuple[Optional[str], bytes]: """ Users need to have the pillow package installed. - It's unclear if PyPDF2 will keep this function here, hence it's private. + It's unclear if pypdf will keep this function here, hence it's private. It might get removed at any point. :return: Tuple[file extension, bytes] @@ -569,7 +569,7 @@ def _xobj_to_image(x_object_obj: Dict[str, Any]) -> Tuple[Optional[str], bytes]: except ImportError: raise ImportError( "pillow is required to do image extraction. " - "It can be installed via 'pip install PyPDF2[image]'" + "It can be installed via 'pip install pypdf[image]'" ) size = (x_object_obj[IA.WIDTH], x_object_obj[IA.HEIGHT]) diff --git a/PyPDF2/generic/__init__.py b/pypdf/generic/__init__.py similarity index 100% rename from PyPDF2/generic/__init__.py rename to pypdf/generic/__init__.py diff --git a/PyPDF2/generic/_annotations.py b/pypdf/generic/_annotations.py similarity index 100% rename from PyPDF2/generic/_annotations.py rename to pypdf/generic/_annotations.py diff --git a/PyPDF2/generic/_base.py b/pypdf/generic/_base.py similarity index 100% rename from PyPDF2/generic/_base.py rename to pypdf/generic/_base.py diff --git a/PyPDF2/generic/_data_structures.py b/pypdf/generic/_data_structures.py similarity index 99% rename from PyPDF2/generic/_data_structures.py rename to pypdf/generic/_data_structures.py index 19f5be9fbe..e571dd98a2 100644 --- a/PyPDF2/generic/_data_structures.py +++ b/pypdf/generic/_data_structures.py @@ -1087,7 +1087,7 @@ class Field(TreeObject): A class representing a field dictionary. This class is accessed through - :meth:`get_fields()` + :meth:`get_fields()` """ def __init__(self, data: Dict[str, Any]) -> None: @@ -1152,8 +1152,8 @@ def altName(self) -> Optional[str]: # pragma: no cover def mapping_name(self) -> Optional[str]: """ Read-only property accessing the mapping name of this field. This - name is used by PyPDF2 as a key in the dictionary returned by - :meth:`get_fields()` + name is used by pypdf as a key in the dictionary returned by + :meth:`get_fields()` """ return self.get(FieldDictionaryAttributes.TM) @@ -1225,7 +1225,7 @@ class Destination(TreeObject): :param str title: Title of this destination. :param IndirectObject page: Reference to the page of this destination. Should - be an instance of :class:`IndirectObject`. + be an instance of :class:`IndirectObject`. :param Fit fit: How the destination is displayed. :raises PdfReadError: If destination type is invalid. diff --git a/PyPDF2/generic/_fit.py b/pypdf/generic/_fit.py similarity index 100% rename from PyPDF2/generic/_fit.py rename to pypdf/generic/_fit.py diff --git a/PyPDF2/generic/_outline.py b/pypdf/generic/_outline.py similarity index 100% rename from PyPDF2/generic/_outline.py rename to pypdf/generic/_outline.py diff --git a/PyPDF2/generic/_rectangle.py b/pypdf/generic/_rectangle.py similarity index 95% rename from PyPDF2/generic/_rectangle.py rename to pypdf/generic/_rectangle.py index 3f41bfd595..ce9876cd2b 100644 --- a/PyPDF2/generic/_rectangle.py +++ b/pypdf/generic/_rectangle.py @@ -8,12 +8,12 @@ class RectangleObject(ArrayObject): """ - This class is used to represent *page boxes* in PyPDF2. These boxes include: - * :attr:`artbox ` - * :attr:`bleedbox ` - * :attr:`cropbox ` - * :attr:`mediabox ` - * :attr:`trimbox ` + This class is used to represent *page boxes* in pypdf. These boxes include: + * :attr:`artbox ` + * :attr:`bleedbox ` + * :attr:`cropbox ` + * :attr:`mediabox ` + * :attr:`trimbox ` """ def __init__( diff --git a/PyPDF2/generic/_utils.py b/pypdf/generic/_utils.py similarity index 100% rename from PyPDF2/generic/_utils.py rename to pypdf/generic/_utils.py diff --git a/PyPDF2/pagerange.py b/pypdf/pagerange.py similarity index 99% rename from PyPDF2/pagerange.py rename to pypdf/pagerange.py index f009adc195..c7fe2ada43 100644 --- a/PyPDF2/pagerange.py +++ b/pypdf/pagerange.py @@ -3,7 +3,7 @@ Copyright (c) 2014, Steve Witham . All rights reserved. This software is available under a BSD license; -see https://github.com/py-pdf/PyPDF2/blob/main/LICENSE +see https://github.com/py-pdf/pypdf/blob/main/LICENSE """ import re diff --git a/PyPDF2/papersizes.py b/pypdf/papersizes.py similarity index 100% rename from PyPDF2/papersizes.py rename to pypdf/papersizes.py diff --git a/PyPDF2/py.typed b/pypdf/py.typed similarity index 100% rename from PyPDF2/py.typed rename to pypdf/py.typed diff --git a/PyPDF2/types.py b/pypdf/types.py similarity index 100% rename from PyPDF2/types.py rename to pypdf/types.py diff --git a/PyPDF2/xmp.py b/pypdf/xmp.py similarity index 99% rename from PyPDF2/xmp.py rename to pypdf/xmp.py index de43282397..1f54cf68fe 100644 --- a/PyPDF2/xmp.py +++ b/pypdf/xmp.py @@ -211,7 +211,7 @@ def get(self: "XmpInformation") -> Optional[Any]: class XmpInformation(PdfObject): """ An object that represents Adobe XMP metadata. - Usually accessed by :py:attr:`xmp_metadata()` + Usually accessed by :py:attr:`xmp_metadata()` :raises PdfReadError: if XML is invalid """ diff --git a/pyproject.toml b/pyproject.toml index 78fceecfee..b380e481ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["flit_core >=3.2,<4"] build-backend = "flit_core.buildapi" [project] -name = "PyPDF2" +name = "pypdf" authors = [{ name = "Mathieu Fenniak", email = "biziqe@mathieu.fenniak.net" }] maintainers = [{ name = "Martin Thoma", email = "info@martin-thoma.de" }] description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" @@ -32,10 +32,10 @@ dependencies = [ ] [project.urls] -Documentation = "https://pypdf2.readthedocs.io/en/latest/" -Source = "https://github.com/py-pdf/PyPDF2" -"Bug Reports" = "https://github.com/py-pdf/PyPDF2/issues" -Changelog = "https://pypdf2.readthedocs.io/en/latest/meta/CHANGELOG.html" +Documentation = "https://pypdf.readthedocs.io/en/latest/" +Source = "https://github.com/py-pdf/pypdf" +"Bug Reports" = "https://github.com/py-pdf/pypdf/issues" +Changelog = "https://pypdf.readthedocs.io/en/latest/meta/CHANGELOG.html" [project.optional-dependencies] full = ["PyCryptodome", "Pillow"] @@ -50,7 +50,7 @@ runner = "./mutmut-test.sh" tests_dir = "tests/" [tool.check-wheel-contents] -package = "./PyPDF2" +package = "./pypdf" [tool.flit.sdist] exclude = [".github/*", "docs/*", "resources/*", "sample-files/*", "sample-files/.github/*", "sample-files/.gitignore", "sample-files/.pre-commit-config.yaml", "requirements/*", "tests/*", ".flake8", ".gitignore", ".gitmodules", ".pylintrc", "tox.ini", "make_changelog.py", "mutmut-test.sh", ".pre-commit-config.yaml", ".gitblame-ignore-revs", "Makefile", "mutmut_config.py"] @@ -73,7 +73,7 @@ include_trailing_comma = true known_third_party = ["pytest", "setuptools"] [tool.coverage.run] -source = ["PyPDF2"] +source = ["pypdf"] branch = true [tool.coverage.report] diff --git a/resources/Seige_of_Vicksburg_Sample_OCR-crazyones-merged.pdf b/resources/Seige_of_Vicksburg_Sample_OCR-crazyones-merged.pdf index fea766cc7a309893cb69bc29a73831405e8e784e..0e9633ac16c138eeaa90d3cf13e9f7cd6e2c006d 100644 GIT binary patch delta 1143 zcmZvaF-}}T42HF=kfN1H6Qy5@m7w!)&)DOFA_WmzAbW;LQAz|QRYZXls2CSRD&uRST zC-d3=A0npI#S)}GE~lUNxJxPCiK&D-s1~U>Y={z4LYAnHc~mCknd-%eh#iF~dqglw zhs{>3{u>cpN(o&@g~vfeMXhchy*G$j>W0%MIx2I5*0T{2ofc2jAzTp=3E90qG{i3p z^u*}wOf1sXne^Dg5`9c1Y4qFLDqdxNTwji{BG>Q7ZLHX~%jfj{sQ4i+Al1aEDSZOC-zpNVKRpHy}+LgOUtY^)^K?XUv-ZaGL VP|j#C)%(@-;DgQ`TqqQZj%5`sl-A}zO^h%(o!s}GNEo9Xwix4NaL!p$n{z@F z<4^bBHt!b~Z&$c#^XPE~JL6}vN0pSwT9%~J%CQ``D(R@&6+U}^Dg97_BBF{`Mk_&C zq|JpKbFBPCI#?UYP_Jtev%#87u(xnWSSw~kXuWTYC6bG~WR{Mpl?VZ3tpu3$mpum0 zB`M~>V=l_dJN^HU6`WL6L#DeD;lxIb8X}HLBF1&!VU&8jA8YB zpY5F{2n)}$M+mXH3lsAtOD6~oDIFt*H-R(N6DO;Yl*}q7&1-cG%GbaC!y$g2A=F=y VF<*Q7d$alSF1A-!pKq?W{{Wja07?J= diff --git a/resources/selenium-PyPDF2-issue-177.pdf b/resources/selenium-pypdf-issue-177.pdf similarity index 100% rename from resources/selenium-PyPDF2-issue-177.pdf rename to resources/selenium-pypdf-issue-177.pdf diff --git a/setup.cfg b/setup.cfg index e931f244f1..9b09288301 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -name = PyPDF2 +name = pypdf author = Mathieu Fenniak author_email = biziqe@mathieu.fenniak.net @@ -12,11 +12,11 @@ long_description_content_type = text/markdown license = BSD-3-Clause -url = https://pypdf2.readthedocs.io/en/latest/ +url = https://pypdf.readthedocs.io/en/latest/ project_urls = - Source = https://github.com/py-pdf/PyPDF2 - Bug Reports = https://github.com/py-pdf/PyPDF2/issues - Changelog = https://pypdf2.readthedocs.io/en/latest/meta/CHANGELOG.html + Source = https://github.com/py-pdf/pypdf + Bug Reports = https://github.com/py-pdf/pypdf/issues + Changelog = https://pypdf.readthedocs.io/en/latest/meta/CHANGELOG.html classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Developers @@ -33,9 +33,9 @@ classifiers = [options] packages = - PyPDF2 - PyPDF2._codecs - PyPDF2.generic + pypdf + pypdf._codecs + pypdf.generic python_requires = >=3.6 install_requires = typing_extensions >= 3.10.0.0; python_version < '3.10' @@ -52,4 +52,4 @@ runner = ./mutmut-test.sh tests_dir = tests/ [tool:check-wheel-contents] -package = ./PyPDF2 +package = ./pypdf diff --git a/tests/__init__.py b/tests/__init__.py index 7f34826bbf..8d748171df 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -3,7 +3,7 @@ import urllib.request from typing import List -from PyPDF2.generic import DictionaryObject, IndirectObject +from pypdf.generic import DictionaryObject, IndirectObject def get_pdf_from_url(url: str, name: str) -> bytes: @@ -40,7 +40,7 @@ def _strip_position(line: str) -> str: Remove the location information. The message - WARNING PyPDF2._reader:_utils.py:364 Xref table not zero-indexed. + WARNING pypdf._reader:_utils.py:364 Xref table not zero-indexed. becomes Xref table not zero-indexed. diff --git a/tests/bench.py b/tests/bench.py index 1caa18319f..a58cb38402 100644 --- a/tests/bench.py +++ b/tests/bench.py @@ -1,9 +1,9 @@ from io import BytesIO from pathlib import Path -import PyPDF2 -from PyPDF2 import PdfReader, Transformation -from PyPDF2.generic import Destination, read_string_from_stream +import pypdf +from pypdf import PdfReader, Transformation +from pypdf.generic import Destination, read_string_from_stream TESTS_ROOT = Path(__file__).parent.resolve() PROJECT_ROOT = TESTS_ROOT.parent @@ -56,21 +56,21 @@ def merge(): pdf_forms = RESOURCE_ROOT / "pdflatex-forms.pdf" pdf_pw = RESOURCE_ROOT / "libreoffice-writer-password.pdf" - merger = PyPDF2.PdfMerger() + merger = pypdf.PdfMerger() # string path: merger.append(pdf_path) merger.append(outline) - merger.append(pdf_path, pages=PyPDF2.pagerange.PageRange(slice(0, 0))) + merger.append(pdf_path, pages=pypdf.pagerange.PageRange(slice(0, 0))) merger.append(pdf_forms) # Merging an encrypted file - reader = PyPDF2.PdfReader(pdf_pw) + reader = pypdf.PdfReader(pdf_pw) reader.decrypt("openpassword") merger.append(reader) # PdfReader object: - merger.append(PyPDF2.PdfReader(pdf_path, "rb"), outline_item=True) + merger.append(pypdf.PdfReader(pdf_path, "rb"), outline_item=True) # File handle with open(pdf_path, "rb") as fh: @@ -88,7 +88,7 @@ def merge(): merger.close() # Check if outline is correct - reader = PyPDF2.PdfReader(write_path) + reader = pypdf.PdfReader(write_path) assert [ el.title for el in reader._get_outline() if isinstance(el, Destination) ] == [ diff --git a/tests/test_cmap.py b/tests/test_cmap.py index fc41b35aea..17740e167a 100644 --- a/tests/test_cmap.py +++ b/tests/test_cmap.py @@ -2,8 +2,8 @@ import pytest -from PyPDF2 import PdfReader -from PyPDF2.errors import PdfReadWarning +from pypdf import PdfReader +from pypdf.errors import PdfReadWarning from . import get_pdf_from_url @@ -82,7 +82,7 @@ def test_bfchar_on_2_chars(): @pytest.mark.external def test_ascii_charset(): # iss #1312 - url = "https://github.com/py-pdf/PyPDF2/files/9472500/main.pdf" + url = "https://github.com/py-pdf/pypdf/files/9472500/main.pdf" name = "ascii charset.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name))) assert "/a" not in reader.pages[0].extract_text() @@ -90,7 +90,7 @@ def test_ascii_charset(): @pytest.mark.external def test_iss1370(): - url = "https://github.com/py-pdf/PyPDF2/files/9667138/cmap1370.pdf" + url = "https://github.com/py-pdf/pypdf/files/9667138/cmap1370.pdf" name = "cmap1370.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name))) reader.pages[0].extract_text() @@ -98,7 +98,7 @@ def test_iss1370(): @pytest.mark.external def test_iss1379(): - url = "https://github.com/py-pdf/PyPDF2/files/9712729/02voc.pdf" + url = "https://github.com/py-pdf/pypdf/files/9712729/02voc.pdf" name = "02voc.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name))) reader.pages[2].extract_text() diff --git a/tests/test_constants.py b/tests/test_constants.py index 62775b25f7..03adf3f798 100644 --- a/tests/test_constants.py +++ b/tests/test_constants.py @@ -1,7 +1,7 @@ import re from typing import Callable -from PyPDF2.constants import PDF_KEYS +from pypdf.constants import PDF_KEYS def test_slash_prefix(): diff --git a/tests/test_encryption.py b/tests/test_encryption.py index eb20582a14..dfe14c760b 100644 --- a/tests/test_encryption.py +++ b/tests/test_encryption.py @@ -2,10 +2,10 @@ import pytest -import PyPDF2 -from PyPDF2 import PasswordType, PdfReader -from PyPDF2._encryption import AlgV5, CryptRC4 -from PyPDF2.errors import DependencyError, PdfReadError +import pypdf +from pypdf import PasswordType, PdfReader +from pypdf._encryption import AlgV5, CryptRC4 +from pypdf.errors import DependencyError, PdfReadError try: from Crypto.Cipher import AES # noqa: F401 @@ -58,13 +58,13 @@ def test_encryption(name, requres_pycryptodome): inputfile = RESOURCE_ROOT / "encryption" / name if requres_pycryptodome and not HAS_PYCRYPTODOME: with pytest.raises(DependencyError) as exc: - ipdf = PyPDF2.PdfReader(inputfile) + ipdf = pypdf.PdfReader(inputfile) ipdf.decrypt("asdfzxcv") dd = dict(ipdf.metadata) assert exc.value.args[0] == "PyCryptodome is required for AES algorithm" return else: - ipdf = PyPDF2.PdfReader(inputfile) + ipdf = pypdf.PdfReader(inputfile) if str(inputfile).endswith("unencrypted.pdf"): assert not ipdf.is_encrypted else: @@ -94,7 +94,7 @@ def test_encryption(name, requres_pycryptodome): @pytest.mark.skipif(not HAS_PYCRYPTODOME, reason="No pycryptodome") def test_both_password(name, user_passwd, owner_passwd): inputfile = RESOURCE_ROOT / "encryption" / name - ipdf = PyPDF2.PdfReader(inputfile) + ipdf = pypdf.PdfReader(inputfile) assert ipdf.is_encrypted assert ipdf.decrypt(user_passwd) == PasswordType.USER_PASSWORD assert ipdf.decrypt(owner_passwd) == PasswordType.OWNER_PASSWORD @@ -117,7 +117,7 @@ def test_get_page_of_encrypted_file_new_algorithm(pdffile, password): IndexError for get_page() of decrypted file """ path = RESOURCE_ROOT / pdffile - PyPDF2.PdfReader(path, password=password).pages[0] + pypdf.PdfReader(path, password=password).pages[0] @pytest.mark.parametrize( @@ -135,9 +135,9 @@ def test_get_page_of_encrypted_file_new_algorithm(pdffile, password): ) @pytest.mark.skipif(not HAS_PYCRYPTODOME, reason="No pycryptodome") def test_encryption_merge(names): - merger = PyPDF2.PdfMerger() + merger = pypdf.PdfMerger() files = [RESOURCE_ROOT / "encryption" / x for x in names] - pdfs = [PyPDF2.PdfReader(x) for x in files] + pdfs = [pypdf.PdfReader(x) for x in files] for pdf in pdfs: if pdf.is_encrypted: pdf.decrypt("asdfzxcv") diff --git a/tests/test_filters.py b/tests/test_filters.py index cc745c2301..a514695521 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -6,16 +6,16 @@ import pytest -from PyPDF2 import PdfReader -from PyPDF2.errors import PdfReadError, PdfStreamError -from PyPDF2.filters import ( +from pypdf import PdfReader +from pypdf.errors import PdfReadError, PdfStreamError +from pypdf.filters import ( ASCII85Decode, ASCIIHexDecode, CCITParameters, CCITTFaxDecode, FlateDecode, ) -from PyPDF2.generic import ArrayObject, DictionaryObject, NumberObject +from pypdf.generic import ArrayObject, DictionaryObject, NumberObject from . import get_pdf_from_url @@ -187,7 +187,7 @@ def test_CCITTFaxDecode(): {"/K": NumberObject(-1), "/Columns": NumberObject(17)} ) - # This was just the result PyPDF2 1.27.9 returned. + # This was just the result pypdf 1.27.9 returned. # It would be awesome if we could check if that is actually correct. assert CCITTFaxDecode.decode(data, parameters) == ( b"II*\x00\x08\x00\x00\x00\x08\x00\x00\x01\x04\x00\x01\x00\x00\x00\x11\x00" @@ -201,7 +201,7 @@ def test_CCITTFaxDecode(): @pytest.mark.external -@patch("PyPDF2._reader.logger_warning") +@patch("pypdf._reader.logger_warning") def test_decompress_zlib_error(mock_logger_warning): url = "https://corpora.tika.apache.org/base/docs/govdocs1/952/952445.pdf" name = "tika-952445.pdf" @@ -209,7 +209,7 @@ def test_decompress_zlib_error(mock_logger_warning): for page in reader.pages: page.extract_text() mock_logger_warning.assert_called_with( - "incorrect startxref pointer(3)", "PyPDF2._reader" + "incorrect startxref pointer(3)", "pypdf._reader" ) @@ -246,5 +246,5 @@ def test_image_without_imagemagic(): page.images assert ( exc.value.args[0] - == "pillow is required to do image extraction. It can be installed via 'pip install PyPDF2[image]'" + == "pillow is required to do image extraction. It can be installed via 'pip install pypdf[image]'" ) diff --git a/tests/test_generic.py b/tests/test_generic.py index b02692d97b..e96872adc8 100644 --- a/tests/test_generic.py +++ b/tests/test_generic.py @@ -5,10 +5,10 @@ import pytest -from PyPDF2 import PdfMerger, PdfReader, PdfWriter -from PyPDF2.constants import CheckboxRadioButtonAttributes -from PyPDF2.errors import PdfReadError, PdfStreamError -from PyPDF2.generic import ( +from pypdf import PdfMerger, PdfReader, PdfWriter +from pypdf.constants import CheckboxRadioButtonAttributes +from pypdf.errors import PdfReadError, PdfStreamError +from pypdf.generic import ( AnnotationBuilder, ArrayObject, BooleanObject, @@ -673,9 +673,9 @@ def test_bool_repr(tmp_path): @pytest.mark.external -@patch("PyPDF2._reader.logger_warning") +@patch("pypdf._reader.logger_warning") def test_issue_997(mock_logger_warning): - url = "https://github.com/py-pdf/PyPDF2/files/8908874/Exhibit_A-2_930_Enterprise_Zone_Tax_Credits_final.pdf" + url = "https://github.com/py-pdf/pypdf/files/8908874/Exhibit_A-2_930_Enterprise_Zone_Tax_Credits_final.pdf" name = "gh-issue-997.pdf" merger = PdfMerger() @@ -685,9 +685,7 @@ def test_issue_997(mock_logger_warning): merger.write(f) merger.close() - mock_logger_warning.assert_called_with( - "Overwriting cache for 0 4", "PyPDF2._reader" - ) + mock_logger_warning.assert_called_with("Overwriting cache for 0 4", "pypdf._reader") # Strict merger = PdfMerger(strict=True) diff --git a/tests/test_javascript.py b/tests/test_javascript.py index c10dc3cc17..8b41fe400b 100644 --- a/tests/test_javascript.py +++ b/tests/test_javascript.py @@ -2,7 +2,7 @@ import pytest -from PyPDF2 import PdfReader, PdfWriter +from pypdf import PdfReader, PdfWriter # Configure path environment TESTS_ROOT = Path(__file__).parent.resolve() diff --git a/tests/test_merger.py b/tests/test_merger.py index f5d0789000..2dd43a214c 100644 --- a/tests/test_merger.py +++ b/tests/test_merger.py @@ -5,10 +5,10 @@ import pytest -import PyPDF2 -from PyPDF2 import PdfMerger, PdfReader, PdfWriter -from PyPDF2.errors import DeprecationError -from PyPDF2.generic import Destination, Fit +import pypdf +from pypdf import PdfMerger, PdfReader, PdfWriter +from pypdf.errors import DeprecationError +from pypdf.generic import Destination, Fit from . import get_pdf_from_url @@ -28,7 +28,7 @@ def merger_operate(merger): # string path: merger.append(pdf_path) merger.append(outline) - merger.append(pdf_path, pages=PyPDF2.pagerange.PageRange(slice(0, 0))) + merger.append(pdf_path, pages=pypdf.pagerange.PageRange(slice(0, 0))) merger.append(pdf_forms) merger.merge(0, pdf_path, import_outline=False) with pytest.raises(NotImplementedError) as exc: @@ -41,16 +41,16 @@ def merger_operate(merger): ) # Merging an encrypted file - reader = PyPDF2.PdfReader(pdf_pw) + reader = pypdf.PdfReader(pdf_pw) reader.decrypt("openpassword") merger.append(reader) # PdfReader object: - r = PyPDF2.PdfReader(pdf_path) + r = pypdf.PdfReader(pdf_path) merger.append(r, outline_item="foo", pages=list(range(len(r.pages)))) # PdfReader object with List: - # merger.append(PyPDF2.PdfReader(pdf_path), outline_item="foo") + # merger.append(pypdf.PdfReader(pdf_path), outline_item="foo") # File handle with open(pdf_path, "rb") as fh: @@ -138,7 +138,7 @@ def merger_operate(merger): def check_outline(tmp_path): # Check if outline is correct - reader = PyPDF2.PdfReader(tmp_path) + reader = pypdf.PdfReader(tmp_path) assert [el.title for el in reader.outline if isinstance(el, Destination)] == [ "Foo", "Bar", @@ -230,7 +230,7 @@ def test_merger_operation_by_new_usage_with_writer(tmp_path): def test_merge_page_exception(): - merger = PyPDF2.PdfMerger() + merger = pypdf.PdfMerger() pdf_path = RESOURCE_ROOT / "crazyones.pdf" with pytest.raises(TypeError) as exc: merger.merge(0, pdf_path, pages="a:b") @@ -239,7 +239,7 @@ def test_merge_page_exception(): def test_merge_page_exception_with_writer(): - merger = PyPDF2.PdfWriter() + merger = pypdf.PdfWriter() pdf_path = RESOURCE_ROOT / "crazyones.pdf" with pytest.raises(TypeError) as exc: merger.merge(0, pdf_path, pages="a:b") @@ -251,21 +251,21 @@ def test_merge_page_exception_with_writer(): def test_merge_page_tuple(): - merger = PyPDF2.PdfMerger() + merger = pypdf.PdfMerger() pdf_path = RESOURCE_ROOT / "crazyones.pdf" merger.merge(0, pdf_path, pages=(0, 1)) merger.close() def test_merge_page_tuple_with_writer(): - merger = PyPDF2.PdfWriter() + merger = pypdf.PdfWriter() pdf_path = RESOURCE_ROOT / "crazyones.pdf" merger.merge(0, pdf_path, pages=(0, 1)) merger.close() def test_merge_write_closed_fh(): - merger = PyPDF2.PdfMerger() + merger = pypdf.PdfMerger() pdf_path = RESOURCE_ROOT / "crazyones.pdf" merger.append(pdf_path) @@ -302,7 +302,7 @@ def test_merge_write_closed_fh(): def test_merge_write_closed_fh_with_writer(): - merger = PyPDF2.PdfWriter() + merger = pypdf.PdfWriter() pdf_path = RESOURCE_ROOT / "crazyones.pdf" merger.append(pdf_path) @@ -648,7 +648,7 @@ def test_sweep_indirect_list_newobj_is_none_with_writer(caplog): @pytest.mark.external def test_iss1145(): # issue with FitH destination with null param - url = "https://github.com/py-pdf/PyPDF2/files/9164743/file-0.pdf" + url = "https://github.com/py-pdf/pypdf/files/9164743/file-0.pdf" name = "iss1145.pdf" merger = PdfMerger() merger.append(PdfReader(BytesIO(get_pdf_from_url(url, name=name)))) @@ -658,7 +658,7 @@ def test_iss1145(): @pytest.mark.external def test_iss1145_with_writer(): # issue with FitH destination with null param - url = "https://github.com/py-pdf/PyPDF2/files/9164743/file-0.pdf" + url = "https://github.com/py-pdf/pypdf/files/9164743/file-0.pdf" name = "iss1145.pdf" merger = PdfWriter() merger.append(PdfReader(BytesIO(get_pdf_from_url(url, name=name)))) @@ -701,7 +701,7 @@ def test_deprecation_bookmark_decorator_output_with_writer(): @pytest.mark.external def test_iss1344(caplog): - url = "https://github.com/py-pdf/PyPDF2/files/9549001/input.pdf" + url = "https://github.com/py-pdf/pypdf/files/9549001/input.pdf" name = "iss1344.pdf" m = PdfMerger() m.append(PdfReader(BytesIO(get_pdf_from_url(url, name=name)))) @@ -716,7 +716,7 @@ def test_iss1344(caplog): @pytest.mark.external def test_iss1344_with_writer(caplog): - url = "https://github.com/py-pdf/PyPDF2/files/9549001/input.pdf" + url = "https://github.com/py-pdf/pypdf/files/9549001/input.pdf" name = "iss1344.pdf" m = PdfWriter() m.append(PdfReader(BytesIO(get_pdf_from_url(url, name=name)))) diff --git a/tests/test_page.py b/tests/test_page.py index ee4af14a05..b58d42ceb5 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -7,11 +7,11 @@ import pytest -from PyPDF2 import PdfReader, PdfWriter, Transformation -from PyPDF2._page import PageObject, set_custom_rtl -from PyPDF2.constants import PageAttributes as PG -from PyPDF2.errors import DeprecationError, PdfReadWarning -from PyPDF2.generic import ( +from pypdf import PdfReader, PdfWriter, Transformation +from pypdf._page import PageObject, set_custom_rtl +from pypdf.constants import PageAttributes as PG +from pypdf.errors import DeprecationError, PdfReadWarning +from pypdf.generic import ( ArrayObject, DictionaryObject, FloatObject, @@ -48,7 +48,7 @@ def get_all_sample_files(): [m for m in all_files_meta["data"] if not m["encrypted"]], ids=[m["path"] for m in all_files_meta["data"] if not m["encrypted"]], ) -@pytest.mark.filterwarnings("ignore::PyPDF2.errors.PdfReadWarning") +@pytest.mark.filterwarnings("ignore::pypdf.errors.PdfReadWarning") def test_read(meta): pdf_path = SAMPLE_ROOT / meta["path"] reader = PdfReader(pdf_path) @@ -295,7 +295,7 @@ def test_extract_text_single_quote_op(): @pytest.mark.external def test_no_ressources_on_text_extract(): - url = "https://github.com/py-pdf/PyPDF2/files/9428434/TelemetryTX_EM.pdf" + url = "https://github.com/py-pdf/pypdf/files/9428434/TelemetryTX_EM.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name="tika-964029.pdf"))) for page in reader.pages: page.extract_text() @@ -304,7 +304,7 @@ def test_no_ressources_on_text_extract(): @pytest.mark.external def test_iss_1142(): # check fix for problem of context save/restore (q/Q) - url = "https://github.com/py-pdf/PyPDF2/files/9150656/ST.2019.PDF" + url = "https://github.com/py-pdf/pypdf/files/9150656/ST.2019.PDF" name = "st2019.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name))) txt = reader.pages[3].extract_text() @@ -335,12 +335,12 @@ def test_iss_1142(): ), # iss 1134: ( - "https://github.com/py-pdf/PyPDF2/files/9150656/ST.2019.PDF", + "https://github.com/py-pdf/pypdf/files/9150656/ST.2019.PDF", "iss_1134.pdf", ), # iss 1: ( - "https://github.com/py-pdf/PyPDF2/files/9432350/Work.Flow.From.Check.to.QA.pdf", + "https://github.com/py-pdf/pypdf/files/9432350/Work.Flow.From.Check.to.QA.pdf", "WFCA.pdf", ), ], @@ -427,7 +427,7 @@ def extract_text_and_rectangles( page: PageObject, rect_filter=None ) -> Tuple[List[PositionedText], List[Rectangle]]: """ - Extracts texts and rectangles of a page of type PyPDF2._page.PageObject. + Extracts texts and rectangles of a page of type pypdf._page.PageObject. This function supports simple coordinate transformations only. The optional rect_filter-lambda can be used to filter wanted rectangles. @@ -865,7 +865,7 @@ def test_read_link_annotation(): @pytest.mark.external def test_no_resources(): - url = "https://github.com/py-pdf/PyPDF2/files/9572045/108.pdf" + url = "https://github.com/py-pdf/pypdf/files/9572045/108.pdf" name = "108.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name))) page_one = reader.pages[0] diff --git a/tests/test_pagerange.py b/tests/test_pagerange.py index 2737c573c6..816d664624 100644 --- a/tests/test_pagerange.py +++ b/tests/test_pagerange.py @@ -1,6 +1,6 @@ import pytest -from PyPDF2.pagerange import PageRange, ParseError, parse_filename_page_ranges +from pypdf.pagerange import PageRange, ParseError, parse_filename_page_ranges def test_equality(): diff --git a/tests/test_papersizes.py b/tests/test_papersizes.py index 35ac2acead..d546b923e7 100644 --- a/tests/test_papersizes.py +++ b/tests/test_papersizes.py @@ -1,6 +1,6 @@ import pytest -from PyPDF2 import papersizes +from pypdf import papersizes def test_din_a0(): diff --git a/tests/test_reader.py b/tests/test_reader.py index fda8f929f2..cb5725cf3b 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -6,11 +6,11 @@ import pytest -from PyPDF2 import PdfReader -from PyPDF2._reader import convert_to_int, convertToInt -from PyPDF2.constants import ImageAttributes as IA -from PyPDF2.constants import PageAttributes as PG -from PyPDF2.errors import ( +from pypdf import PdfReader +from pypdf._reader import convert_to_int, convertToInt +from pypdf.constants import ImageAttributes as IA +from pypdf.constants import PageAttributes as PG +from pypdf.errors import ( DeprecationError, EmptyFileError, FileNotDecryptedError, @@ -18,7 +18,7 @@ PdfReadWarning, WrongPasswordError, ) -from PyPDF2.generic import ( +from pypdf.generic import ( ArrayObject, Destination, DictionaryObject, @@ -44,7 +44,7 @@ @pytest.mark.parametrize( ("src", "num_pages"), - [("selenium-PyPDF2-issue-177.pdf", 1), ("pdflatex-outline.pdf", 4)], + [("selenium-pypdf-issue-177.pdf", 1), ("pdflatex-outline.pdf", 4)], ) def test_get_num_pages(src, num_pages): src = RESOURCE_ROOT / src @@ -632,7 +632,7 @@ def test_decrypt_when_no_id(): """ Decrypt an encrypted file that's missing the 'ID' value in its trailer. - https://github.com/mstamy2/PyPDF2/issues/608 + https://github.com/mstamy2/pypdf/issues/608 """ with open(RESOURCE_ROOT / "encrypted_doc_no_id.pdf", "rb") as inputfile: @@ -740,7 +740,7 @@ def test_convert_to_int_error(): def test_convertToInt_deprecated(): msg = ( - "convertToInt is deprecated and was removed in PyPDF2 3.0.0. " + "convertToInt is deprecated and was removed in pypdf 3.0.0. " "Use convert_to_int instead." ) with pytest.raises( @@ -752,7 +752,7 @@ def test_convertToInt_deprecated(): @pytest.mark.external def test_iss925(): - url = "https://github.com/py-pdf/PyPDF2/files/8796328/1.pdf" + url = "https://github.com/py-pdf/pypdf/files/8796328/1.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name="iss925.pdf"))) for page_sliced in reader.pages: @@ -867,7 +867,7 @@ def test_get_fields(): @pytest.mark.external -@pytest.mark.filterwarnings("ignore::PyPDF2.errors.PdfReadWarning") +@pytest.mark.filterwarnings("ignore::pypdf.errors.PdfReadWarning") def test_get_fields_read_else_block(): # covers also issue 1089 url = "https://corpora.tika.apache.org/base/docs/govdocs1/934/934771.pdf" @@ -885,7 +885,7 @@ def test_get_fields_read_else_block2(): @pytest.mark.external -@pytest.mark.filterwarnings("ignore::PyPDF2.errors.PdfReadWarning") +@pytest.mark.filterwarnings("ignore::pypdf.errors.PdfReadWarning") def test_get_fields_read_else_block3(): url = "https://corpora.tika.apache.org/base/docs/govdocs1/957/957721.pdf" name = "tika-957721.pdf" @@ -1091,7 +1091,7 @@ def test_outline_missing_title(caplog): @pytest.mark.external def test_named_destination(): # 1st case : the named_dest are stored directly as a dictionnary, PDF1.1 style - url = "https://github.com/py-pdf/PyPDF2/files/9197028/lorem_ipsum.pdf" + url = "https://github.com/py-pdf/pypdf/files/9197028/lorem_ipsum.pdf" name = "lorem_ipsum.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name))) assert len(reader.named_destinations) > 0 @@ -1132,7 +1132,7 @@ def test_outline_with_invalid_destinations(): @pytest.mark.external def test_PdfReaderMultipleDefinitions(caplog): # iss325 - url = "https://github.com/py-pdf/PyPDF2/files/9176644/multipledefs.pdf" + url = "https://github.com/py-pdf/pypdf/files/9176644/multipledefs.pdf" name = "multipledefs.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name))) reader.pages[0].extract_text() @@ -1158,11 +1158,11 @@ def test_get_page_number_by_indirect(): @pytest.mark.external def test_corrupted_xref_table(): # issue #1292 - url = "https://github.com/py-pdf/PyPDF2/files/9444747/BreezeManual.orig.pdf" + url = "https://github.com/py-pdf/pypdf/files/9444747/BreezeManual.orig.pdf" name = "BreezeMan1.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name))) reader.pages[0].extract_text() - url = "https://github.com/py-pdf/PyPDF2/files/9444748/BreezeManual.failed.pdf" + url = "https://github.com/py-pdf/pypdf/files/9444748/BreezeManual.failed.pdf" name = "BreezeMan2.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name))) reader.pages[0].extract_text() @@ -1171,7 +1171,7 @@ def test_corrupted_xref_table(): @pytest.mark.external def test_reader(caplog): # iss #1273 - url = "https://github.com/py-pdf/PyPDF2/files/9464742/shiv_resume.pdf" + url = "https://github.com/py-pdf/pypdf/files/9464742/shiv_resume.pdf" name = "shiv_resume.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name))) assert "Previous trailer can not be read" in caplog.text @@ -1189,14 +1189,14 @@ def test_reader(caplog): @pytest.mark.external def test_zeroing_xref(): # iss #328 - url = "https://github.com/py-pdf/PyPDF2/files/9066120/UTA_OSHA_3115_Fall_Protection_Training_09162021_.pdf" + url = "https://github.com/py-pdf/pypdf/files/9066120/UTA_OSHA_3115_Fall_Protection_Training_09162021_.pdf" name = "UTA_OSHA.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name))) len(reader.pages) def test_thread(): - url = "https://github.com/py-pdf/PyPDF2/files/9066120/UTA_OSHA_3115_Fall_Protection_Training_09162021_.pdf" + url = "https://github.com/py-pdf/pypdf/files/9066120/UTA_OSHA_3115_Fall_Protection_Training_09162021_.pdf" name = "UTA_OSHA.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name))) assert reader.threads is None @@ -1208,7 +1208,7 @@ def test_thread(): def test_build_outline_item(caplog): - url = "https://github.com/py-pdf/PyPDF2/files/9464742/shiv_resume.pdf" + url = "https://github.com/py-pdf/pypdf/files/9464742/shiv_resume.pdf" name = "shiv_resume.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name))) outline = reader._build_outline_item( diff --git a/tests/test_security.py b/tests/test_security.py index 97d2c8ddcc..272d812a92 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -1,5 +1,5 @@ -from PyPDF2._security import _alg32 -from PyPDF2.generic import ByteStringObject +from pypdf._security import _alg32 +from pypdf.generic import ByteStringObject def test_alg32_metadata_encrypt(): diff --git a/tests/test_utils.py b/tests/test_utils.py index 1f67878db6..841c8d7122 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -4,9 +4,9 @@ import pytest -import PyPDF2._utils -from PyPDF2 import PdfReader -from PyPDF2._utils import ( +import pypdf._utils +from pypdf import PdfReader +from pypdf._utils import ( File, _get_max_pdf_version_header, _human_readable_bytes, @@ -20,7 +20,7 @@ skip_over_comment, skip_over_whitespace, ) -from PyPDF2.errors import DeprecationError, PdfReadError, PdfStreamError +from pypdf.errors import DeprecationError, PdfReadError, PdfStreamError from . import get_pdf_from_url @@ -93,25 +93,25 @@ def test_matrix_multiply(a, b, expected): def test_mark_location(): stream = io.BytesIO(b"abde" * 6000) mark_location(stream) - os.remove("PyPDF2_pdfLocation.txt") # cleanup + os.remove("pypdf_pdfLocation.txt") # cleanup def test_hex_str(): - assert PyPDF2._utils.hex_str(10) == "0xa" + assert pypdf._utils.hex_str(10) == "0xa" def test_b(): - assert PyPDF2._utils.b_("foo") == b"foo" - assert PyPDF2._utils.b_("πŸ˜€") == "πŸ˜€".encode() - assert PyPDF2._utils.b_("‰") == "‰".encode() - assert PyPDF2._utils.b_("β–·") == "β–·".encode() - assert PyPDF2._utils.b_("δΈ–") == "δΈ–".encode() + assert pypdf._utils.b_("foo") == b"foo" + assert pypdf._utils.b_("πŸ˜€") == "πŸ˜€".encode() + assert pypdf._utils.b_("‰") == "‰".encode() + assert pypdf._utils.b_("β–·") == "β–·".encode() + assert pypdf._utils.b_("δΈ–") == "δΈ–".encode() def test_deprecate_no_replacement(): with pytest.warns(DeprecationWarning) as warn: - PyPDF2._utils.deprecate_no_replacement("foo") - error_msg = "foo is deprecated and will be removed in PyPDF2 3.0.0." + pypdf._utils.deprecate_no_replacement("foo") + error_msg = "foo is deprecated and will be removed in pypdf 3.0.0." assert warn[0].message.args[0] == error_msg @@ -130,7 +130,7 @@ def test_deprecate_no_replacement(): ], ) def test_paeth_predictor(left, up, upleft, expected): - assert PyPDF2._utils.paeth_predictor(left, up, upleft) == expected + assert pypdf._utils.paeth_predictor(left, up, upleft) == expected @pytest.mark.parametrize( diff --git a/tests/test_workflows.py b/tests/test_workflows.py index 7720a6622e..fb8187e70e 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -13,9 +13,9 @@ import pytest -from PyPDF2 import PdfMerger, PdfReader, PdfWriter -from PyPDF2.constants import PageAttributes as PG -from PyPDF2.errors import PdfReadWarning +from pypdf import PdfMerger, PdfReader, PdfWriter +from pypdf.constants import PageAttributes as PG +from pypdf.errors import PdfReadWarning from . import get_pdf_from_url, normalize_warnings @@ -64,8 +64,8 @@ def test_basic_features(tmp_path): password = "secret" writer.encrypt(password) - # finally, write "output" to PyPDF2-output.pdf - write_path = tmp_path / "PyPDF2-output.pdf" + # finally, write "output" to pypdf-output.pdf + write_path = tmp_path / "pypdf-output.pdf" with open(write_path, "wb") as output_stream: writer.write(output_stream) @@ -198,11 +198,11 @@ def test_rotate_45(): (True, "https://arxiv.org/pdf/2201.00022.pdf", [0, 1, 5, 10]), (True, "https://arxiv.org/pdf/2201.00029.pdf", [0, 1, 6, 10]), # #1145 - (True, "https://github.com/py-pdf/PyPDF2/files/9174594/2017.pdf", [0]), + (True, "https://github.com/py-pdf/pypdf/files/9174594/2017.pdf", [0]), # #1145, remaining issue (empty arguments for FlateEncoding) ( True, - "https://github.com/py-pdf/PyPDF2/files/9175966/2015._pb_decode_pg0.pdf", + "https://github.com/py-pdf/pypdf/files/9175966/2015._pb_decode_pg0.pdf", [0], ), # 6 instead of 5: as there is an issue in page 5 (missing objects) @@ -210,15 +210,15 @@ def test_rotate_45(): (True, "https://arxiv.org/pdf/1601.03642.pdf", [0, 1, 5, 7]), ( True, - "https://github.com/py-pdf/PyPDF2/files/3796761/17343_2008_Order_09-Jan-2019.pdf", + "https://github.com/py-pdf/pypdf/files/3796761/17343_2008_Order_09-Jan-2019.pdf", [0, 1], ), ( True, - "https://github.com/py-pdf/PyPDF2/files/8884471/ssi_manwaring.pdf", + "https://github.com/py-pdf/pypdf/files/8884471/ssi_manwaring.pdf", [0, 1], ), - (True, "https://github.com/py-pdf/PyPDF2/files/8884469/999092.pdf", [0, 1]), + (True, "https://github.com/py-pdf/pypdf/files/8884469/999092.pdf", [0, 1]), ( True, "file://" + str(RESOURCE_ROOT / "test Orient.pdf"), @@ -226,10 +226,10 @@ def test_rotate_45(): ), # TODO: preparation of text orientation validation ( True, - "https://github.com/py-pdf/PyPDF2/files/8884470/fdocuments.in_sweet-fundamentals-of-crystallography.pdf", + "https://github.com/py-pdf/pypdf/files/8884470/fdocuments.in_sweet-fundamentals-of-crystallography.pdf", [0, 1, 34, 35, 36, 118, 119, 120, 121], ), - (True, "https://github.com/py-pdf/PyPDF2/files/8884493/998167.pdf", [0]), + (True, "https://github.com/py-pdf/pypdf/files/8884493/998167.pdf", [0]), ( True, "https://corpora.tika.apache.org/base/docs/govdocs1/971/971703.pdf", @@ -875,7 +875,7 @@ def test_get_xmp(url, name, strict): @pytest.mark.external def test_tounicode_is_identity(): - url = "https://github.com/py-pdf/PyPDF2/files/9998335/FP_Thesis.pdf" + url = "https://github.com/py-pdf/pypdf/files/9998335/FP_Thesis.pdf" name = "FP_Thesis.pdf" data = BytesIO(get_pdf_from_url(url, name=name)) reader = PdfReader(data, strict=False) diff --git a/tests/test_writer.py b/tests/test_writer.py index cf3a463805..98c1f3eeb1 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -5,9 +5,9 @@ import pytest -from PyPDF2 import PageObject, PdfMerger, PdfReader, PdfWriter -from PyPDF2.errors import DeprecationError, PageSizeNotDefinedError -from PyPDF2.generic import ( +from pypdf import PageObject, PdfMerger, PdfReader, PdfWriter +from pypdf.errors import DeprecationError, PageSizeNotDefinedError +from pypdf.generic import ( ArrayObject, Fit, IndirectObject, @@ -156,7 +156,7 @@ def test_writer_operations_by_traditional_usage(write_data_here, needs_cleanup): writer_operate(writer) - # finally, write "output" to PyPDF2-output.pdf + # finally, write "output" to pypdf-output.pdf if needs_cleanup: with open(write_data_here, "wb") as output_stream: writer.write(output_stream) @@ -180,7 +180,7 @@ def test_writer_operations_by_semi_traditional_usage(write_data_here, needs_clea with PdfWriter() as writer: writer_operate(writer) - # finally, write "output" to PyPDF2-output.pdf + # finally, write "output" to pypdf-output.pdf if needs_cleanup: with open(write_data_here, "wb") as output_stream: writer.write(output_stream) @@ -206,7 +206,7 @@ def test_writer_operations_by_semi_new_traditional_usage( with PdfWriter() as writer: writer_operate(writer) - # finally, write "output" to PyPDF2-output.pdf + # finally, write "output" to pypdf-output.pdf writer.write(write_data_here) if needs_cleanup: @@ -222,7 +222,7 @@ def test_writer_operations_by_semi_new_traditional_usage( ], ) def test_writer_operation_by_new_usage(write_data_here, needs_cleanup): - # This includes write "output" to PyPDF2-output.pdf + # This includes write "output" to pypdf-output.pdf with PdfWriter(write_data_here) as writer: writer_operate(writer) @@ -247,7 +247,7 @@ def test_remove_images(input_path, ignore_byte_string_object): writer.insert_page(page, 0) writer.remove_images(ignore_byte_string_object=ignore_byte_string_object) - # finally, write "output" to PyPDF2-output.pdf + # finally, write "output" to pypdf-output.pdf tmp_filename = "dont_commit_writer_removed_image.pdf" with open(tmp_filename, "wb") as output_stream: writer.write(output_stream) @@ -281,7 +281,7 @@ def test_remove_text(input_path, ignore_byte_string_object): writer.insert_page(page, 0) writer.remove_text(ignore_byte_string_object=ignore_byte_string_object) - # finally, write "output" to PyPDF2-output.pdf + # finally, write "output" to pypdf-output.pdf tmp_filename = "dont_commit_writer_removed_text.pdf" with open(tmp_filename, "wb") as output_stream: writer.write(output_stream) @@ -351,7 +351,7 @@ def test_remove_text_all_operators(ignore_byte_string_object): writer.insert_page(page, 0) writer.remove_text(ignore_byte_string_object=ignore_byte_string_object) - # finally, write "output" to PyPDF2-output.pdf + # finally, write "output" to pypdf-output.pdf tmp_filename = "dont_commit_writer_removed_text.pdf" with open(tmp_filename, "wb") as output_stream: writer.write(output_stream) @@ -375,7 +375,7 @@ def test_write_metadata(): writer.add_metadata({"/Title": "The Crazy Ones"}) - # finally, write data to PyPDF2-output.pdf + # finally, write data to pypdf-output.pdf tmp_filename = "dont_commit_writer_added_metadata.pdf" with open(tmp_filename, "wb") as output_stream: writer.write(output_stream) @@ -411,7 +411,7 @@ def test_fill_form(): writer.pages[0], {"foo": "some filled in text"} ) - # write "output" to PyPDF2-output.pdf + # write "output" to pypdf-output.pdf tmp_filename = "dont_commit_filled_pdf.pdf" with open(tmp_filename, "wb") as output_stream: writer.write(output_stream) @@ -437,7 +437,7 @@ def test_encrypt(use_128bit, user_password, owner_password): use_128bit=use_128bit, ) - # write "output" to PyPDF2-output.pdf + # write "output" to pypdf-output.pdf tmp_filename = "dont_commit_encrypted.pdf" with open(tmp_filename, "wb") as output_stream: writer.write(output_stream) @@ -450,25 +450,25 @@ def test_encrypt(use_128bit, user_password, owner_password): # Test the user password (str): reader = PdfReader(tmp_filename, password="userpwd") new_text = reader.pages[0].extract_text() - assert reader.metadata.get("/Producer") == "PyPDF2" + assert reader.metadata.get("/Producer") == "pypdf" assert new_text == orig_text # Test the owner password (str): reader = PdfReader(tmp_filename, password="ownerpwd") new_text = reader.pages[0].extract_text() - assert reader.metadata.get("/Producer") == "PyPDF2" + assert reader.metadata.get("/Producer") == "pypdf" assert new_text == orig_text # Test the user password (bytes): reader = PdfReader(tmp_filename, password=b"userpwd") new_text = reader.pages[0].extract_text() - assert reader.metadata.get("/Producer") == "PyPDF2" + assert reader.metadata.get("/Producer") == "pypdf" assert new_text == orig_text # Test the owner password (stbytesr): reader = PdfReader(tmp_filename, password=b"ownerpwd") new_text = reader.pages[0].extract_text() - assert reader.metadata.get("/Producer") == "PyPDF2" + assert reader.metadata.get("/Producer") == "pypdf" assert new_text == orig_text # Cleanup @@ -487,7 +487,7 @@ def test_add_outline_item(): ) writer.add_outline_item("Another", 2, outline_item, None, False, False, Fit.fit()) - # write "output" to PyPDF2-output.pdf + # write "output" to pypdf-output.pdf tmp_filename = "dont_commit_outline_item.pdf" with open(tmp_filename, "wb") as output_stream: writer.write(output_stream) @@ -526,7 +526,7 @@ def test_add_named_destination(): writer.get_object(reader.pages[0].indirect_reference) assert exc.value.args[0] == "pdf must be self" - # write "output" to PyPDF2-output.pdf + # write "output" to pypdf-output.pdf tmp_filename = "dont_commit_named_destination.pdf" with open(tmp_filename, "wb") as output_stream: writer.write(output_stream) @@ -550,24 +550,24 @@ def test_add_uri(): ) writer.add_uri( 2, - "https://pypdf2.readthedocs.io/en/latest/", + "https://pypdf.readthedocs.io/en/latest/", RectangleObject([20, 30, 50, 80]), border=[1, 2, 3], ) writer.add_uri( 3, - "https://pypdf2.readthedocs.io/en/latest/user/adding-pdf-annotations.html", + "https://pypdf.readthedocs.io/en/latest/user/adding-pdf-annotations.html", "[ 200 300 250 350 ]", border=[0, 0, 0], ) writer.add_uri( 3, - "https://pypdf2.readthedocs.io/en/latest/user/adding-pdf-annotations.html", + "https://pypdf.readthedocs.io/en/latest/user/adding-pdf-annotations.html", [100, 200, 150, 250], border=[0, 0, 0], ) - # write "output" to PyPDF2-output.pdf + # write "output" to pypdf-output.pdf tmp_filename = "dont_commit_uri.pdf" with open(tmp_filename, "wb") as output_stream: writer.write(output_stream) @@ -587,7 +587,7 @@ def test_add_link(): DeprecationError, match=( re.escape( - "add_link is deprecated and was removed in PyPDF2 3.0.0. " + "add_link is deprecated and was removed in pypdf 3.0.0. " "Use add_annotation(AnnotationBuilder.link(...)) instead." ) ), @@ -619,7 +619,7 @@ def test_add_link(): border=[0, 0, 0], ) - # write "output" to PyPDF2-output.pdf + # write "output" to pypdf-output.pdf tmp_filename = "dont_commit_link.pdf" with open(tmp_filename, "wb") as output_stream: writer.write(output_stream) @@ -889,7 +889,7 @@ def test_startup_dest(): def test_iss471(): - url = "https://github.com/py-pdf/PyPDF2/files/9139245/book.pdf" + url = "https://github.com/py-pdf/pypdf/files/9139245/book.pdf" name = "book_471.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name))) diff --git a/tests/test_xmp.py b/tests/test_xmp.py index 5b8202ca5c..941abd9e26 100644 --- a/tests/test_xmp.py +++ b/tests/test_xmp.py @@ -4,10 +4,10 @@ import pytest -import PyPDF2.generic -import PyPDF2.xmp -from PyPDF2 import PdfReader -from PyPDF2.errors import PdfReadError +import pypdf.generic +import pypdf.xmp +from pypdf import PdfReader +from pypdf.errors import PdfReadError from . import get_pdf_from_url @@ -29,7 +29,7 @@ def test_read_xmp(src, has_xmp): assert (xmp is None) == (not has_xmp) if has_xmp: for el in xmp.get_element( - about_uri="", namespace=PyPDF2.xmp.RDF_NAMESPACE, name="Artist" + about_uri="", namespace=pypdf.xmp.RDF_NAMESPACE, name="Artist" ): print(f"el={el}") @@ -37,7 +37,7 @@ def test_read_xmp(src, has_xmp): assert xmp.dc_contributor == [] -def get_all_tiff(xmp: PyPDF2.xmp.XmpInformation): +def get_all_tiff(xmp: pypdf.xmp.XmpInformation): data = {} tiff_ns = xmp.get_nodes_in_namespace( about_uri="", namespace="http://ns.adobe.com/tiff/1.0/" @@ -51,7 +51,7 @@ def get_all_tiff(xmp: PyPDF2.xmp.XmpInformation): def test_regression_issue774(): - date = PyPDF2.xmp._converter_date("2021-04-28T12:23:34.123Z") + date = pypdf.xmp._converter_date("2021-04-28T12:23:34.123Z") assert date.year == 2021 assert date.month == 4 assert date.day == 28 @@ -60,10 +60,10 @@ def test_regression_issue774(): assert date.second == 34 assert date.microsecond == 123000 with pytest.raises(ValueError) as exc: - PyPDF2.xmp._converter_date("today") + pypdf.xmp._converter_date("today") assert exc.value.args[0].startswith("Invalid date format") - date = PyPDF2.xmp._converter_date("2021-04-28T12:23:01-03:00") + date = pypdf.xmp._converter_date("2021-04-28T12:23:01-03:00") assert date.year == 2021 assert date.month == 4 assert date.day == 28 @@ -84,7 +84,7 @@ def test_regression_issue914(): ["a", 42, 3.141, False, True], ) def test_identity(x): - assert PyPDF2.xmp._identity(x) == x + assert pypdf.xmp._identity(x) == x @pytest.mark.external @@ -176,7 +176,7 @@ def test_dc_subject(): @pytest.mark.external def test_issue585(): - url = "https://github.com/mstamy2/PyPDF2/files/5536984/test.pdf" + url = "https://github.com/mstamy2/pypdf/files/5536984/test.pdf" name = "mstamy2-5536984.pdf" reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name))) with pytest.raises(PdfReadError) as exc: @@ -185,7 +185,7 @@ def test_issue585(): # def test_getter_bag(): -# f = PyPDF2.xmp._getter_bag("namespace", "name") +# f = pypdf.xmp._getter_bag("namespace", "name") # class Tst: # to replace pdf # strict = False From 0844c938651c8f20154a204146fa584b4114ffbb Mon Sep 17 00:00:00 2001 From: Martin Thoma Date: Fri, 23 Dec 2022 17:12:07 +0100 Subject: [PATCH 2/3] DOC: Update logo --- docs/_static/logo.png | Bin 11043 -> 13676 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/_static/logo.png b/docs/_static/logo.png index dd200d3e8093b7ecf7c0f5bfe6fb3f66d0e0bfde..0c67763463aa3f9a56ae3d324e873abf0fb0859d 100644 GIT binary patch literal 13676 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 From 7d07401f456267480eb8deac85e3fde2f6927366 Mon Sep 17 00:00:00 2001 From: Martin Thoma Date: Fri, 23 Dec 2022 18:27:34 +0100 Subject: [PATCH 3/3] REL: 3.1.0 Move PyPDF2 to pypdf (#1513). This now it's all lowercase, no number in the name. For installation and for import. PyPDF2 will no longer receive updates. The community should move back to its roots. [Full Changelog](https://github.com/py-pdf/pypdf/compare/3.0.0...3.1.0) --- CHANGELOG.md | 8 ++++++++ pypdf/_version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d9e84088c..5d901bf1ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## Version 3.1.0, 2022-12-23 + +Move PyPDF2 to pypdf (#1513). This now it's all lowercase, no number in the +name. For installation and for import. PyPDF2 will no longer receive updates. +The community should move back to its roots. + +[Full Changelog](https://github.com/py-pdf/pypdf/compare/3.0.0...3.1.0) + ## Version 3.0.0, 2022-12-22 ### BREAKING CHANGES ⚠️ diff --git a/pypdf/_version.py b/pypdf/_version.py index 528787cfc8..f5f41e567e 100644 --- a/pypdf/_version.py +++ b/pypdf/_version.py @@ -1 +1 @@ -__version__ = "3.0.0" +__version__ = "3.1.0"