python-pdffiller 1.1.1__tar.gz → 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/PKG-INFO +13 -17
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/README.rst +11 -15
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/_version.py +1 -1
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/cli/commands/dump_data_fields.py +2 -1
- python_pdffiller-2.0.0/pdffiller/pdf.py +221 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/typing.py +1 -2
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pyproject.toml +3 -2
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/python_pdffiller.egg-info/PKG-INFO +13 -17
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/python_pdffiller.egg-info/SOURCES.txt +3 -1
- python_pdffiller-2.0.0/python_pdffiller.egg-info/requires.txt +3 -0
- python_pdffiller-2.0.0/requirements.txt +3 -0
- python_pdffiller-2.0.0/tests/cli/test_dump_data_field.py +106 -0
- python_pdffiller-2.0.0/tests/cli/test_fill_form.py +103 -0
- python_pdffiller-2.0.0/tests/conftest.py +70 -0
- python_pdffiller-2.0.0/tests/unit/__init__.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/tests/unit/test_form_field.py +6 -5
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/tox.ini +17 -3
- python_pdffiller-1.1.1/pdffiller/io/custom_pdf_writer.py +0 -143
- python_pdffiller-1.1.1/pdffiller/pdf.py +0 -343
- python_pdffiller-1.1.1/python_pdffiller.egg-info/requires.txt +0 -3
- python_pdffiller-1.1.1/requirements.txt +0 -3
- python_pdffiller-1.1.1/tests/conftest.py +0 -28
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/AUTHORS.rst +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/CHANGELOG.md +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/COPYING +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/MANIFEST.in +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/docs/Makefile +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/docs/make.bat +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/docs/source/__init__.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/docs/source/_static/rtd_literal_block.css +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/docs/source/_static/rtd_theme_overrides.css +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/docs/source/_static/terminal_output.css +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/docs/source/changelog.md +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/docs/source/cli-commands.rst +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/docs/source/cli-usage.rst +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/docs/source/commands/dump_data_fields.rst +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/docs/source/commands/fill_form.rst +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/docs/source/conf.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/docs/source/contributing.rst +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/docs/source/index.rst +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/__init__.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/__main__.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/cli/__init__.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/cli/args.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/cli/boolean_action.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/cli/cli.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/cli/command.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/cli/commands/__init__.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/cli/commands/fill_form.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/cli/exit_codes.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/cli/formatters.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/cli/once_argument.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/cli/smart_formatter.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/const.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/exceptions.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/io/__init__.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/io/colors.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/io/output.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/py.typed.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/utils.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/widgets/__init__.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/widgets/base.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/widgets/checkbox.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/widgets/radio.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/widgets/text.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/python_pdffiller.egg-info/dependency_links.txt +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/python_pdffiller.egg-info/entry_points.txt +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/python_pdffiller.egg-info/top_level.txt +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/requirements-dev.txt +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/requirements-doc.txt +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/requirements-lint.txt +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/requirements-test.txt +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/setup.cfg +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/setup.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/tests/__init__.py +0 -0
- {python_pdffiller-1.1.1/tests/unit → python_pdffiller-2.0.0/tests/cli}/__init__.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/tests/data/empty.pdf +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/tests/data/input.pdf +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/tests/unit/test_formatters.py +0 -0
- {python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/tests/unit/test_setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-pdffiller
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Interact with PDF by inspecting or filling it
|
|
5
5
|
Author-email: Jacques Raphanel <jraphanel@sismic.fr>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -26,17 +26,17 @@ Requires-Python: >=3.9
|
|
|
26
26
|
Description-Content-Type: text/x-rst
|
|
27
27
|
License-File: COPYING
|
|
28
28
|
License-File: AUTHORS.rst
|
|
29
|
-
Requires-Dist:
|
|
29
|
+
Requires-Dist: pymupdf==1.24.10
|
|
30
30
|
Requires-Dist: colorama
|
|
31
31
|
Requires-Dist: pyyaml
|
|
32
32
|
Dynamic: license-file
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
pdffiller
|
|
35
|
+
=========
|
|
36
36
|
|
|
37
37
|
|Test| |PyPI| |Python| |Code Style| |Pre-Commit| |License|
|
|
38
38
|
|
|
39
|
-
``
|
|
39
|
+
``python-pdffiller`` is a free and open source pure-Python 3 library for PDF form processing. It contains the essential
|
|
40
40
|
functionalities needed to interact with PDF forms:
|
|
41
41
|
|
|
42
42
|
- Inspect what data a PDF form needs to be filled with.
|
|
@@ -45,9 +45,9 @@ functionalities needed to interact with PDF forms:
|
|
|
45
45
|
Installation
|
|
46
46
|
------------
|
|
47
47
|
|
|
48
|
-
As of first version, ``
|
|
48
|
+
As of first version, ``python-pdffiller`` is compatible with Python 3.9+.
|
|
49
49
|
|
|
50
|
-
Use ``pip`` to install the latest stable version of ``
|
|
50
|
+
Use ``pip`` to install the latest stable version of ``python-pdffiller``:
|
|
51
51
|
|
|
52
52
|
.. code-block:: console
|
|
53
53
|
|
|
@@ -72,7 +72,7 @@ https://github.com/sismicfr/pypdffiller/issues.
|
|
|
72
72
|
Documentation
|
|
73
73
|
-------------
|
|
74
74
|
|
|
75
|
-
The full documentation for CLI and API is available at https://
|
|
75
|
+
The full documentation for CLI and API is available at https://sismicfr.github.io/pypdffiller/
|
|
76
76
|
|
|
77
77
|
Build the docs
|
|
78
78
|
~~~~~~~~~~~~~~
|
|
@@ -106,23 +106,19 @@ We use ``tox`` to manage our environment and build the executable:
|
|
|
106
106
|
Contributing
|
|
107
107
|
------------
|
|
108
108
|
|
|
109
|
-
For guidelines for contributing to ``
|
|
109
|
+
For guidelines for contributing to ``python-pdffiller``, refer to `CONTRIBUTING.rst <https://github.com/sismicfr/pypdffiller/blob/main/CONTRIBUTING.rst>`_.
|
|
110
110
|
|
|
111
111
|
|
|
112
112
|
.. |Test| image:: https://github.com/sismicfr/pypdffiller/workflows/Test/badge.svg
|
|
113
113
|
:target: https://github.com/sismicfr/pypdffiller/actions
|
|
114
114
|
:alt: Test
|
|
115
115
|
|
|
116
|
-
.. |PyPI| image:: https://img.shields.io/pypi/v/
|
|
117
|
-
:target: https://badge.fury.io/py/
|
|
116
|
+
.. |PyPI| image:: https://img.shields.io/pypi/v/python-pdffiller?label=PyPI&logo=pypi
|
|
117
|
+
:target: https://badge.fury.io/py/python-pdffiller
|
|
118
118
|
:alt: PyPI
|
|
119
119
|
|
|
120
|
-
.. |
|
|
121
|
-
:target: https://
|
|
122
|
-
:alt: Docs
|
|
123
|
-
|
|
124
|
-
.. |Python| image:: https://img.shields.io/pypi/pyversions/pypdffiller.svg?label=Python&logo=Python
|
|
125
|
-
:target: https://pypi.python.org/pypi/pypdffiller
|
|
120
|
+
.. |Python| image:: https://img.shields.io/pypi/pyversions/python-pdffiller.svg?label=Python&logo=Python
|
|
121
|
+
:target: https://pypi.python.org/pypi/python-pdffiller
|
|
126
122
|
:alt: Python
|
|
127
123
|
|
|
128
124
|
.. |Code Style| image:: https://img.shields.io/badge/code%20style-black-000000.svg?label=Code%20Style
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
pdffiller
|
|
2
|
+
=========
|
|
3
3
|
|
|
4
4
|
|Test| |PyPI| |Python| |Code Style| |Pre-Commit| |License|
|
|
5
5
|
|
|
6
|
-
``
|
|
6
|
+
``python-pdffiller`` is a free and open source pure-Python 3 library for PDF form processing. It contains the essential
|
|
7
7
|
functionalities needed to interact with PDF forms:
|
|
8
8
|
|
|
9
9
|
- Inspect what data a PDF form needs to be filled with.
|
|
@@ -12,9 +12,9 @@ functionalities needed to interact with PDF forms:
|
|
|
12
12
|
Installation
|
|
13
13
|
------------
|
|
14
14
|
|
|
15
|
-
As of first version, ``
|
|
15
|
+
As of first version, ``python-pdffiller`` is compatible with Python 3.9+.
|
|
16
16
|
|
|
17
|
-
Use ``pip`` to install the latest stable version of ``
|
|
17
|
+
Use ``pip`` to install the latest stable version of ``python-pdffiller``:
|
|
18
18
|
|
|
19
19
|
.. code-block:: console
|
|
20
20
|
|
|
@@ -39,7 +39,7 @@ https://github.com/sismicfr/pypdffiller/issues.
|
|
|
39
39
|
Documentation
|
|
40
40
|
-------------
|
|
41
41
|
|
|
42
|
-
The full documentation for CLI and API is available at https://
|
|
42
|
+
The full documentation for CLI and API is available at https://sismicfr.github.io/pypdffiller/
|
|
43
43
|
|
|
44
44
|
Build the docs
|
|
45
45
|
~~~~~~~~~~~~~~
|
|
@@ -73,23 +73,19 @@ We use ``tox`` to manage our environment and build the executable:
|
|
|
73
73
|
Contributing
|
|
74
74
|
------------
|
|
75
75
|
|
|
76
|
-
For guidelines for contributing to ``
|
|
76
|
+
For guidelines for contributing to ``python-pdffiller``, refer to `CONTRIBUTING.rst <https://github.com/sismicfr/pypdffiller/blob/main/CONTRIBUTING.rst>`_.
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
.. |Test| image:: https://github.com/sismicfr/pypdffiller/workflows/Test/badge.svg
|
|
80
80
|
:target: https://github.com/sismicfr/pypdffiller/actions
|
|
81
81
|
:alt: Test
|
|
82
82
|
|
|
83
|
-
.. |PyPI| image:: https://img.shields.io/pypi/v/
|
|
84
|
-
:target: https://badge.fury.io/py/
|
|
83
|
+
.. |PyPI| image:: https://img.shields.io/pypi/v/python-pdffiller?label=PyPI&logo=pypi
|
|
84
|
+
:target: https://badge.fury.io/py/python-pdffiller
|
|
85
85
|
:alt: PyPI
|
|
86
86
|
|
|
87
|
-
.. |
|
|
88
|
-
:target: https://
|
|
89
|
-
:alt: Docs
|
|
90
|
-
|
|
91
|
-
.. |Python| image:: https://img.shields.io/pypi/pyversions/pypdffiller.svg?label=Python&logo=Python
|
|
92
|
-
:target: https://pypi.python.org/pypi/pypdffiller
|
|
87
|
+
.. |Python| image:: https://img.shields.io/pypi/pyversions/python-pdffiller.svg?label=Python&logo=Python
|
|
88
|
+
:target: https://pypi.python.org/pypi/python-pdffiller
|
|
93
89
|
:alt: Python
|
|
94
90
|
|
|
95
91
|
.. |Code Style| image:: https://img.shields.io/badge/code%20style-black-000000.svg?label=Code%20Style
|
{python_pdffiller-1.1.1 → python_pdffiller-2.0.0}/pdffiller/cli/commands/dump_data_fields.py
RENAMED
|
@@ -30,7 +30,8 @@ def dump_fields_text_formatter(pdf: Pdf) -> None:
|
|
|
30
30
|
|
|
31
31
|
def dump_fields_json_formatter(pdf: Pdf) -> None:
|
|
32
32
|
"""Print output text for dump_fields command as simple text"""
|
|
33
|
-
|
|
33
|
+
if not pdf:
|
|
34
|
+
return
|
|
34
35
|
cli_out_write(json.dumps(pdf.schema, indent=4, ensure_ascii=False))
|
|
35
36
|
|
|
36
37
|
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A module for wrapping PDF form operations, providing a high-level interface
|
|
3
|
+
for filling, and manipulating PDF forms.
|
|
4
|
+
|
|
5
|
+
This module simplifies common tasks such as:
|
|
6
|
+
- Filling PDF forms with data from a dictionary.
|
|
7
|
+
- Fetching PDF forms fields
|
|
8
|
+
|
|
9
|
+
The core class, `Pdf`, encapsulates a PDF document and provides
|
|
10
|
+
methods for interacting with its form fields and content.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from collections import OrderedDict
|
|
14
|
+
|
|
15
|
+
import pymupdf
|
|
16
|
+
|
|
17
|
+
from pdffiller.exceptions import PdfFillerException
|
|
18
|
+
from pdffiller.io.output import PdfFillerOutput
|
|
19
|
+
|
|
20
|
+
from .typing import Any, cast, Dict, List, Optional, PathLike, StreamType, Type
|
|
21
|
+
from .widgets.base import Widget
|
|
22
|
+
from .widgets.checkbox import CheckBoxWidget
|
|
23
|
+
from .widgets.radio import RadioWidget
|
|
24
|
+
from .widgets.text import TextWidget
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PdfAttributes: # pylint: disable=too-few-public-methods
|
|
28
|
+
"""Various constants, enums, and flags to aid readability."""
|
|
29
|
+
|
|
30
|
+
READ_ONLY = 1 << 0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Pdf:
|
|
34
|
+
"""
|
|
35
|
+
A class to wrap PDF form operations, providing a simplified interface
|
|
36
|
+
for common tasks such as filling, creating, and manipulating PDF forms.
|
|
37
|
+
|
|
38
|
+
The `Pdf` class encapsulates a PDF document and provides methods
|
|
39
|
+
for interacting with its form fields (widgets) and content.
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
TYPE_TO_OBJECT: Dict[str, Type[Widget]] = {
|
|
44
|
+
"Text": TextWidget,
|
|
45
|
+
"RadioButton": RadioWidget,
|
|
46
|
+
"CheckBox": CheckBoxWidget,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self, filename: Optional[PathLike] = None, stream: Optional[StreamType] = None
|
|
51
|
+
) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Constructor method for the `Pdf` class.
|
|
54
|
+
|
|
55
|
+
Initializes a new `Pdf` object with the given template PDF and optional keyword arguments.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
filename (Optional[PathLike]): Path to the input pdf
|
|
59
|
+
stream (Optional[StreamType]): An open file-like object containing the PDF data.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
super().__init__()
|
|
63
|
+
self.widgets: OrderedDict[str, Widget] = OrderedDict()
|
|
64
|
+
self._init_helper(filename, stream)
|
|
65
|
+
|
|
66
|
+
def _init_helper(
|
|
67
|
+
self, filename: Optional[PathLike] = None, stream: Optional[StreamType] = None
|
|
68
|
+
) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Helper method to initialize widgets
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
filename (Optional[PathLike]): Path to the input pdf
|
|
74
|
+
stream (Optional[StreamType]): An open file-like object containing the PDF data.
|
|
75
|
+
"""
|
|
76
|
+
if not filename and not stream:
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
output = PdfFillerOutput()
|
|
80
|
+
output.info("loading file in memory")
|
|
81
|
+
loaded_widgets: OrderedDict[str, Widget] = OrderedDict()
|
|
82
|
+
try:
|
|
83
|
+
doc = pymupdf.open(filename=filename, stream=stream)
|
|
84
|
+
except Exception as ex: # pylint: disable=broad-exception-caught
|
|
85
|
+
PdfFillerOutput().error(str(ex))
|
|
86
|
+
raise PdfFillerException(
|
|
87
|
+
f"failed to load {filename or 'file from input string'}"
|
|
88
|
+
) from ex
|
|
89
|
+
|
|
90
|
+
for i, page in enumerate(doc.pages()):
|
|
91
|
+
output.verbose(f"loading page {i+1}/{doc.page_count}")
|
|
92
|
+
for widget in page.widgets():
|
|
93
|
+
button_states = widget.button_states()
|
|
94
|
+
choices = button_states["normal"] if button_states else None
|
|
95
|
+
|
|
96
|
+
if widget.field_name not in loaded_widgets:
|
|
97
|
+
if widget.field_type_string not in self.TYPE_TO_OBJECT:
|
|
98
|
+
output.verbose(f"unsupported {widget.field_type_string} widget type")
|
|
99
|
+
continue
|
|
100
|
+
new_widget = self.TYPE_TO_OBJECT[widget.field_type_string](
|
|
101
|
+
widget.field_name, i, widget.field_value, widget.field_flags & (1 << 0)
|
|
102
|
+
)
|
|
103
|
+
if choices and isinstance(new_widget, CheckBoxWidget):
|
|
104
|
+
new_widget.choices = choices
|
|
105
|
+
elif isinstance(new_widget, TextWidget):
|
|
106
|
+
new_widget.max_length = widget.text_maxlen
|
|
107
|
+
loaded_widgets[widget.field_name] = new_widget
|
|
108
|
+
else:
|
|
109
|
+
new_widget = loaded_widgets[widget.field_name]
|
|
110
|
+
if choices and isinstance(new_widget, CheckBoxWidget):
|
|
111
|
+
for each in choices:
|
|
112
|
+
if new_widget.choices is not None:
|
|
113
|
+
if each not in new_widget.choices:
|
|
114
|
+
new_widget.choices.append(each)
|
|
115
|
+
else:
|
|
116
|
+
new_widget.choices = [each]
|
|
117
|
+
|
|
118
|
+
cast(CheckBoxWidget, loaded_widgets[widget.field_name]).choices = (
|
|
119
|
+
new_widget.choices
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
self.widgets = loaded_widgets
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def schema(self) -> List[Dict[str, Any]]:
|
|
126
|
+
"""
|
|
127
|
+
Returns the JSON schema of the PDF form, describing the structure and data
|
|
128
|
+
types of the form fields.
|
|
129
|
+
|
|
130
|
+
This schema can be used to generate user interfaces or validate data before
|
|
131
|
+
filling the form.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
dict: A dictionary representing the JSON schema of the PDF form.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
return [widget.schema_definition for widget in self.widgets.values()]
|
|
138
|
+
|
|
139
|
+
def fill(
|
|
140
|
+
self,
|
|
141
|
+
input_file: PathLike,
|
|
142
|
+
output_file: PathLike,
|
|
143
|
+
data: Dict[str, str],
|
|
144
|
+
flatten: bool = True,
|
|
145
|
+
) -> "Pdf":
|
|
146
|
+
"""
|
|
147
|
+
Fill the PDF form with data from a dictionary.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
input_file (PathLike): The input file path.
|
|
151
|
+
output_file (PathLike): The output file path.
|
|
152
|
+
data (Dict[str, Union[str, bool, int]]): A dictionary where keys are form field names
|
|
153
|
+
and values are the data to fill the fields with. Values can be strings, booleans,
|
|
154
|
+
or integers.
|
|
155
|
+
flatten (bool): Whether to flatten the form after filling, making the fields read-only
|
|
156
|
+
(default: False).
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Pdf: The `Pdf` object, allowing for method chaining.
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
document = pymupdf.open(filename=input_file)
|
|
163
|
+
except Exception as ex:
|
|
164
|
+
PdfFillerOutput().error(str(ex))
|
|
165
|
+
raise PdfFillerException(f"failed to open {input_file}") from ex
|
|
166
|
+
|
|
167
|
+
output = PdfFillerOutput()
|
|
168
|
+
|
|
169
|
+
output.info("filling pdf with input values")
|
|
170
|
+
# Iterate over all pages and process fields
|
|
171
|
+
for page in document:
|
|
172
|
+
for field in page.widgets():
|
|
173
|
+
if field.field_name in data:
|
|
174
|
+
value = data[field.field_name]
|
|
175
|
+
|
|
176
|
+
# Handling checkboxes
|
|
177
|
+
if (
|
|
178
|
+
field.field_type
|
|
179
|
+
== pymupdf.PDF_WIDGET_TYPE_CHECKBOX # pylint: disable=no-member
|
|
180
|
+
):
|
|
181
|
+
print(f"{field.field_name} => {field.on_state()} vs {value}")
|
|
182
|
+
if value.strip() and "Off" != value.strip():
|
|
183
|
+
output.verbose(
|
|
184
|
+
f"updating checkbox with {value} from {field.field_value}"
|
|
185
|
+
)
|
|
186
|
+
field.field_value = True
|
|
187
|
+
else:
|
|
188
|
+
field.field_value = False
|
|
189
|
+
|
|
190
|
+
# Handling radio buttons
|
|
191
|
+
elif (
|
|
192
|
+
field.field_type
|
|
193
|
+
== pymupdf.PDF_WIDGET_TYPE_RADIOBUTTON # pylint: disable=no-member
|
|
194
|
+
):
|
|
195
|
+
if value == field.on_state():
|
|
196
|
+
output.verbose(
|
|
197
|
+
f"updating radiobutton with {value} from {field.field_value}"
|
|
198
|
+
)
|
|
199
|
+
field.field_value = value
|
|
200
|
+
|
|
201
|
+
# Handling other fields types
|
|
202
|
+
else:
|
|
203
|
+
output.verbose(
|
|
204
|
+
f"updating {field.field_name} with {value} from {field.field_value}"
|
|
205
|
+
)
|
|
206
|
+
field.field_value = value
|
|
207
|
+
|
|
208
|
+
# Update the widget!
|
|
209
|
+
field.update()
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
if flatten:
|
|
213
|
+
output.info("remove all annotations")
|
|
214
|
+
document.bake(annots=False)
|
|
215
|
+
|
|
216
|
+
# Save the modified PDF
|
|
217
|
+
document.save(output_file)
|
|
218
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
219
|
+
output.warning("an error occurs when saving file")
|
|
220
|
+
|
|
221
|
+
return self
|
|
@@ -43,7 +43,7 @@ __all__ = [
|
|
|
43
43
|
"Type",
|
|
44
44
|
"Union",
|
|
45
45
|
"SubParserType",
|
|
46
|
-
"
|
|
46
|
+
"StreamType",
|
|
47
47
|
"TextIO",
|
|
48
48
|
]
|
|
49
49
|
|
|
@@ -52,7 +52,6 @@ ExitCode = Union[str, int, None]
|
|
|
52
52
|
PathLike = Union[str, Path]
|
|
53
53
|
|
|
54
54
|
StreamType = IO[Any]
|
|
55
|
-
StrByteType = Union[PathLike, StreamType]
|
|
56
55
|
|
|
57
56
|
if TYPE_CHECKING:
|
|
58
57
|
# pylint: disable=protected-access,line-too-long
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-pdffiller"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0.0"
|
|
8
8
|
description="Interact with PDF by inspecting or filling it"
|
|
9
9
|
requires-python = ">=3.9"
|
|
10
10
|
license ="MIT"
|
|
@@ -26,7 +26,7 @@ classifiers = [
|
|
|
26
26
|
readme = "README.rst"
|
|
27
27
|
authors = [{ name = "Jacques Raphanel", email = "jraphanel@sismic.fr" }]
|
|
28
28
|
keywords = ["development", "pdf"]
|
|
29
|
-
dependencies = ["
|
|
29
|
+
dependencies = ["pymupdf==1.24.10", "colorama", "pyyaml"]
|
|
30
30
|
|
|
31
31
|
[project.scripts]
|
|
32
32
|
pdffiller = "pdffiller.cli:main"
|
|
@@ -92,6 +92,7 @@ logging_use_named_masks = false
|
|
|
92
92
|
major_on_zero = true
|
|
93
93
|
tag_format = "v{version}"
|
|
94
94
|
build_command = """
|
|
95
|
+
apt update && apt install -y --no-install-recommends pkg-config gcc g++ swig && \
|
|
95
96
|
python -m pip install build -rrequirements.txt && \
|
|
96
97
|
python -m build . && \
|
|
97
98
|
python installer/build.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-pdffiller
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Interact with PDF by inspecting or filling it
|
|
5
5
|
Author-email: Jacques Raphanel <jraphanel@sismic.fr>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -26,17 +26,17 @@ Requires-Python: >=3.9
|
|
|
26
26
|
Description-Content-Type: text/x-rst
|
|
27
27
|
License-File: COPYING
|
|
28
28
|
License-File: AUTHORS.rst
|
|
29
|
-
Requires-Dist:
|
|
29
|
+
Requires-Dist: pymupdf==1.24.10
|
|
30
30
|
Requires-Dist: colorama
|
|
31
31
|
Requires-Dist: pyyaml
|
|
32
32
|
Dynamic: license-file
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
pdffiller
|
|
35
|
+
=========
|
|
36
36
|
|
|
37
37
|
|Test| |PyPI| |Python| |Code Style| |Pre-Commit| |License|
|
|
38
38
|
|
|
39
|
-
``
|
|
39
|
+
``python-pdffiller`` is a free and open source pure-Python 3 library for PDF form processing. It contains the essential
|
|
40
40
|
functionalities needed to interact with PDF forms:
|
|
41
41
|
|
|
42
42
|
- Inspect what data a PDF form needs to be filled with.
|
|
@@ -45,9 +45,9 @@ functionalities needed to interact with PDF forms:
|
|
|
45
45
|
Installation
|
|
46
46
|
------------
|
|
47
47
|
|
|
48
|
-
As of first version, ``
|
|
48
|
+
As of first version, ``python-pdffiller`` is compatible with Python 3.9+.
|
|
49
49
|
|
|
50
|
-
Use ``pip`` to install the latest stable version of ``
|
|
50
|
+
Use ``pip`` to install the latest stable version of ``python-pdffiller``:
|
|
51
51
|
|
|
52
52
|
.. code-block:: console
|
|
53
53
|
|
|
@@ -72,7 +72,7 @@ https://github.com/sismicfr/pypdffiller/issues.
|
|
|
72
72
|
Documentation
|
|
73
73
|
-------------
|
|
74
74
|
|
|
75
|
-
The full documentation for CLI and API is available at https://
|
|
75
|
+
The full documentation for CLI and API is available at https://sismicfr.github.io/pypdffiller/
|
|
76
76
|
|
|
77
77
|
Build the docs
|
|
78
78
|
~~~~~~~~~~~~~~
|
|
@@ -106,23 +106,19 @@ We use ``tox`` to manage our environment and build the executable:
|
|
|
106
106
|
Contributing
|
|
107
107
|
------------
|
|
108
108
|
|
|
109
|
-
For guidelines for contributing to ``
|
|
109
|
+
For guidelines for contributing to ``python-pdffiller``, refer to `CONTRIBUTING.rst <https://github.com/sismicfr/pypdffiller/blob/main/CONTRIBUTING.rst>`_.
|
|
110
110
|
|
|
111
111
|
|
|
112
112
|
.. |Test| image:: https://github.com/sismicfr/pypdffiller/workflows/Test/badge.svg
|
|
113
113
|
:target: https://github.com/sismicfr/pypdffiller/actions
|
|
114
114
|
:alt: Test
|
|
115
115
|
|
|
116
|
-
.. |PyPI| image:: https://img.shields.io/pypi/v/
|
|
117
|
-
:target: https://badge.fury.io/py/
|
|
116
|
+
.. |PyPI| image:: https://img.shields.io/pypi/v/python-pdffiller?label=PyPI&logo=pypi
|
|
117
|
+
:target: https://badge.fury.io/py/python-pdffiller
|
|
118
118
|
:alt: PyPI
|
|
119
119
|
|
|
120
|
-
.. |
|
|
121
|
-
:target: https://
|
|
122
|
-
:alt: Docs
|
|
123
|
-
|
|
124
|
-
.. |Python| image:: https://img.shields.io/pypi/pyversions/pypdffiller.svg?label=Python&logo=Python
|
|
125
|
-
:target: https://pypi.python.org/pypi/pypdffiller
|
|
120
|
+
.. |Python| image:: https://img.shields.io/pypi/pyversions/python-pdffiller.svg?label=Python&logo=Python
|
|
121
|
+
:target: https://pypi.python.org/pypi/python-pdffiller
|
|
126
122
|
:alt: Python
|
|
127
123
|
|
|
128
124
|
.. |Code Style| image:: https://img.shields.io/badge/code%20style-black-000000.svg?label=Code%20Style
|
|
@@ -48,7 +48,6 @@ pdffiller/cli/commands/dump_data_fields.py
|
|
|
48
48
|
pdffiller/cli/commands/fill_form.py
|
|
49
49
|
pdffiller/io/__init__.py
|
|
50
50
|
pdffiller/io/colors.py
|
|
51
|
-
pdffiller/io/custom_pdf_writer.py
|
|
52
51
|
pdffiller/io/output.py
|
|
53
52
|
pdffiller/widgets/__init__.py
|
|
54
53
|
pdffiller/widgets/base.py
|
|
@@ -63,6 +62,9 @@ python_pdffiller.egg-info/requires.txt
|
|
|
63
62
|
python_pdffiller.egg-info/top_level.txt
|
|
64
63
|
tests/__init__.py
|
|
65
64
|
tests/conftest.py
|
|
65
|
+
tests/cli/__init__.py
|
|
66
|
+
tests/cli/test_dump_data_field.py
|
|
67
|
+
tests/cli/test_fill_form.py
|
|
66
68
|
tests/data/empty.pdf
|
|
67
69
|
tests/data/input.pdf
|
|
68
70
|
tests/unit/__init__.py
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from unittest import mock
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from pdffiller.cli import cli
|
|
7
|
+
from pdffiller.cli.exit_codes import (
|
|
8
|
+
ERROR_COMMAND_NAME,
|
|
9
|
+
ERROR_UNEXPECTED,
|
|
10
|
+
SUCCESS,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_incomplete_no_action():
|
|
15
|
+
"""test empty command-line"""
|
|
16
|
+
|
|
17
|
+
with mock.patch("sys.argv", []):
|
|
18
|
+
assert cli.main() == ERROR_COMMAND_NAME
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.mark.parametrize("argv", [])
|
|
22
|
+
def test_incomplete(argv):
|
|
23
|
+
"""test empty command-line"""
|
|
24
|
+
|
|
25
|
+
# Test through direct command-line
|
|
26
|
+
with mock.patch("sys.argv", ["pdffiller", "dump_data_fields"] + argv):
|
|
27
|
+
assert cli.main() == ERROR_UNEXPECTED
|
|
28
|
+
|
|
29
|
+
# Test with direct call to main function
|
|
30
|
+
assert cli.main(["dump_data_fields"] + argv) == ERROR_UNEXPECTED
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_complete(test_data_dir, capsys):
|
|
34
|
+
"""test complete command-line"""
|
|
35
|
+
|
|
36
|
+
argv = [
|
|
37
|
+
str(test_data_dir / "input.pdf"),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
json_fields = """[
|
|
41
|
+
{
|
|
42
|
+
"FieldType": "text",
|
|
43
|
+
"FieldName": "Lastname"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"FieldType": "text",
|
|
47
|
+
"FieldName": "Firstname"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"FieldType": "checkbox",
|
|
51
|
+
"FieldOptions": [
|
|
52
|
+
"Off",
|
|
53
|
+
"Oui"
|
|
54
|
+
],
|
|
55
|
+
"FieldName": "Men"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"FieldType": "checkbox",
|
|
59
|
+
"FieldOptions": [
|
|
60
|
+
"Off",
|
|
61
|
+
"Oui"
|
|
62
|
+
],
|
|
63
|
+
"FieldName": "Women"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"FieldType": "radio",
|
|
67
|
+
"FieldOptions": [
|
|
68
|
+
"Off",
|
|
69
|
+
"Single",
|
|
70
|
+
"Married",
|
|
71
|
+
"Divorced"
|
|
72
|
+
],
|
|
73
|
+
"FieldName": "MaritalStatus",
|
|
74
|
+
"FieldValue": "Off"
|
|
75
|
+
}
|
|
76
|
+
]"""
|
|
77
|
+
# Test through direct command-line
|
|
78
|
+
with mock.patch(
|
|
79
|
+
"sys.argv",
|
|
80
|
+
["pdffiller", "dump_data_fields", "-fjson"] + argv,
|
|
81
|
+
):
|
|
82
|
+
assert cli.main() == SUCCESS
|
|
83
|
+
out, err = capsys.readouterr()
|
|
84
|
+
assert "loading file in memory" == err.strip()
|
|
85
|
+
assert json.loads(json_fields) == json.loads(out)
|
|
86
|
+
|
|
87
|
+
# Test with direct call to main function
|
|
88
|
+
assert cli.main(["dump_data_fields"] + argv) == SUCCESS
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_complete_with_invalid_file(test_data_dir):
|
|
92
|
+
"""test complete command-line"""
|
|
93
|
+
|
|
94
|
+
argv = [
|
|
95
|
+
str(test_data_dir / "empty.pdf"),
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
# Test through direct command-line
|
|
99
|
+
with mock.patch(
|
|
100
|
+
"sys.argv",
|
|
101
|
+
["pdffiller", "dump_data_fields"] + argv,
|
|
102
|
+
):
|
|
103
|
+
assert cli.main() == ERROR_UNEXPECTED
|
|
104
|
+
|
|
105
|
+
# Test with direct call to main function
|
|
106
|
+
assert cli.main(["dump_data_fields"] + argv) == ERROR_UNEXPECTED
|