PyPDFForm 4.8.4__tar.gz → 5.0.1__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.
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PKG-INFO +6 -8
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/__init__.py +1 -1
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/cli/common.py +26 -18
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/cli/inspect.py +2 -5
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/cli/schemas/create.py +1 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/cli/schemas/update.py +1 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/cli/update.py +6 -10
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/annotations/link.py +0 -1
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/raw/image.py +1 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/raw/rect.py +1 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/widgets/dropdown.py +1 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/widgets/signature.py +1 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/wrapper.py +9 -46
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm.egg-info/PKG-INFO +6 -8
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm.egg-info/SOURCES.txt +0 -8
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm.egg-info/requires.txt +5 -8
- {pypdfform-4.8.4 → pypdfform-5.0.1}/pyproject.toml +7 -9
- {pypdfform-4.8.4 → pypdfform-5.0.1}/tests/test_create_widget.py +0 -15
- {pypdfform-4.8.4 → pypdfform-5.0.1}/tests/test_functional.py +2 -0
- pypdfform-4.8.4/PyPDFForm/api/__init__.py +0 -28
- pypdfform-4.8.4/PyPDFForm/api/common.py +0 -101
- pypdfform-4.8.4/PyPDFForm/api/create.py +0 -75
- pypdfform-4.8.4/PyPDFForm/api/inspect.py +0 -127
- pypdfform-4.8.4/PyPDFForm/api/root.py +0 -20
- pypdfform-4.8.4/PyPDFForm/api/update.py +0 -56
- pypdfform-4.8.4/PyPDFForm/shared/__init__.py +0 -0
- pypdfform-4.8.4/PyPDFForm/shared/utils.py +0 -42
- {pypdfform-4.8.4 → pypdfform-5.0.1}/LICENSE +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/cli/__init__.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/cli/create.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/cli/entry.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/cli/root.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/cli/schemas/__init__.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/__init__.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/adapter.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/annotations/__init__.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/annotations/base.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/annotations/stamp.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/annotations/text.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/annotations/text_markup.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/assets/__init__.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/assets/bedrock.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/assets/blank.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/constants.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/coordinate.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/deprecation.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/egress.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/filler.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/font.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/hooks.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/image.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/middleware/__init__.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/middleware/base.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/middleware/checkbox.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/middleware/dropdown.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/middleware/image.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/middleware/radio.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/middleware/signature.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/middleware/text.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/patterns.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/raw/__init__.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/raw/circle.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/raw/ellipse.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/raw/line.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/raw/text.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/template.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/types.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/utils.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/watermark.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/widgets/__init__.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/widgets/base.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/widgets/checkbox.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/widgets/image.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/widgets/radio.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm/lib/widgets/text.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm.egg-info/dependency_links.txt +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm.egg-info/entry_points.txt +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/PyPDFForm.egg-info/top_level.txt +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/README.md +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/setup.cfg +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/tests/test_bulk_create_fields.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/tests/test_draw_elements.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/tests/test_dropdown.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/tests/test_extract_middleware_attributes.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/tests/test_fill_max_length_text_field.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/tests/test_font_widths.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/tests/test_generate_appearance_streams.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/tests/test_js.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/tests/test_need_appearances.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/tests/test_paragraph.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/tests/test_signature.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/tests/test_use_full_widget_name.py +0 -0
- {pypdfform-4.8.4 → pypdfform-5.0.1}/tests/test_widget_attr_trigger.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPDFForm
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.0.1
|
|
4
4
|
Summary: The Python library for PDF forms.
|
|
5
5
|
Author: Jinge Li
|
|
6
6
|
License-Expression: MIT
|
|
@@ -21,18 +21,16 @@ Requires-Python: >=3.10
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: cryptography<49.0.0,>=48.0.0
|
|
24
|
-
Requires-Dist: fonttools<5.0.0,>=4.
|
|
24
|
+
Requires-Dist: fonttools<5.0.0,>=4.63.0
|
|
25
25
|
Requires-Dist: pikepdf<11.0.0,>=10.5.1
|
|
26
26
|
Requires-Dist: pillow<13.0.0,>=12.2.0
|
|
27
27
|
Requires-Dist: pypdf<7.0.0,>=6.11.0
|
|
28
|
-
Requires-Dist: reportlab<5.0.0,>=4.5.
|
|
28
|
+
Requires-Dist: reportlab<5.0.0,>=4.5.1
|
|
29
29
|
Provides-Extra: cli
|
|
30
30
|
Requires-Dist: typer<1.0.0,>=0.25.1; extra == "cli"
|
|
31
31
|
Requires-Dist: jsonschema<5.0.0,>=4.26.0; extra == "cli"
|
|
32
|
-
Provides-Extra: web-api
|
|
33
|
-
Requires-Dist: fastapi[standard]<1.0.0,>=0.136.1; extra == "web-api"
|
|
34
32
|
Provides-Extra: dev
|
|
35
|
-
Requires-Dist: black<27.0.0,>=26.
|
|
33
|
+
Requires-Dist: black<27.0.0,>=26.5.1; extra == "dev"
|
|
36
34
|
Requires-Dist: coverage<8.0.0,>=7.14.0; extra == "dev"
|
|
37
35
|
Requires-Dist: isort<9.0.0,>=8.0.1; extra == "dev"
|
|
38
36
|
Requires-Dist: mike<3.0.0,>=2.2.0; extra == "dev"
|
|
@@ -42,8 +40,8 @@ Requires-Dist: pudb<2026.0.0,>=2025.1.5; extra == "dev"
|
|
|
42
40
|
Requires-Dist: pylint<5.0.0,>=4.0.5; extra == "dev"
|
|
43
41
|
Requires-Dist: pyright<2.0.0,>=1.1.409; extra == "dev"
|
|
44
42
|
Requires-Dist: pytest<10.0.0,>=9.0.3; extra == "dev"
|
|
45
|
-
Requires-Dist: requests<3.0.0,>=2.34.
|
|
46
|
-
Requires-Dist: ruff<1.0.0,>=0.15.
|
|
43
|
+
Requires-Dist: requests<3.0.0,>=2.34.2; extra == "dev"
|
|
44
|
+
Requires-Dist: ruff<1.0.0,>=0.15.13; extra == "dev"
|
|
47
45
|
Dynamic: license-file
|
|
48
46
|
|
|
49
47
|
<p align="center"><img src="https://github.com/chinapandaman/PyPDFForm/raw/master/docs/img/logo.png"></p>
|
|
@@ -15,7 +15,7 @@ import typer
|
|
|
15
15
|
from jsonschema import ValidationError, validate
|
|
16
16
|
|
|
17
17
|
from .. import PdfWrapper
|
|
18
|
-
from ..
|
|
18
|
+
from ..lib.middleware.base import Widget
|
|
19
19
|
|
|
20
20
|
INPUT_PDF = Annotated[
|
|
21
21
|
Path,
|
|
@@ -100,23 +100,6 @@ def cli_bad_parameter(
|
|
|
100
100
|
raise typer.BadParameter(message, param_hint=param_hint) from cause
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
def cli_widget_key_error(param_hint: str) -> WidgetKeyErrorHandler:
|
|
104
|
-
"""
|
|
105
|
-
Build a CLI error handler for missing form fields.
|
|
106
|
-
|
|
107
|
-
Args:
|
|
108
|
-
param_hint (str): CLI parameter associated with the field name.
|
|
109
|
-
|
|
110
|
-
Returns:
|
|
111
|
-
WidgetKeyErrorHandler: Handler that raises a Typer input error.
|
|
112
|
-
"""
|
|
113
|
-
|
|
114
|
-
def _raise_cli_bad_parameter(message: str, cause: KeyError) -> NoReturn:
|
|
115
|
-
cli_bad_parameter(message, param_hint=param_hint, cause=cause)
|
|
116
|
-
|
|
117
|
-
return _raise_cli_bad_parameter
|
|
118
|
-
|
|
119
|
-
|
|
120
103
|
def _validation_error_path(exc: ValidationError) -> str:
|
|
121
104
|
"""
|
|
122
105
|
Builds a dotted JSON path for a validation error.
|
|
@@ -170,6 +153,31 @@ def load_json_file(data: Path, schema: dict, param_hint: str) -> Any:
|
|
|
170
153
|
return input_data
|
|
171
154
|
|
|
172
155
|
|
|
156
|
+
def get_widget(wrapper: PdfWrapper, field: str, param_hint: str) -> Widget:
|
|
157
|
+
"""
|
|
158
|
+
Look up a widget and report missing names as CLI input errors.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
wrapper (PdfWrapper): PDF wrapper containing form widgets.
|
|
162
|
+
field (str): Form field name to look up.
|
|
163
|
+
param_hint (str): CLI parameter associated with the field name.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Widget: The matching widget.
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
typer.BadParameter: Raised when the widget name is not present.
|
|
170
|
+
"""
|
|
171
|
+
try:
|
|
172
|
+
return wrapper.widgets[field]
|
|
173
|
+
except KeyError as exc:
|
|
174
|
+
cli_bad_parameter(
|
|
175
|
+
f"Form field '{field}' does not exist.",
|
|
176
|
+
param_hint=param_hint,
|
|
177
|
+
cause=exc,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
173
181
|
def handle_font_registration(
|
|
174
182
|
obj: PdfWrapper, params: dict, registered_font: dict
|
|
175
183
|
) -> None:
|
|
@@ -13,8 +13,7 @@ import json
|
|
|
13
13
|
import typer
|
|
14
14
|
|
|
15
15
|
from .. import PdfWrapper
|
|
16
|
-
from
|
|
17
|
-
from .common import FIELD_NAME, INPUT_PDF, cli_widget_key_error
|
|
16
|
+
from .common import FIELD_NAME, INPUT_PDF, get_widget
|
|
18
17
|
|
|
19
18
|
inspect_cli = typer.Typer(
|
|
20
19
|
context_settings={"help_option_names": ["--help", "-h"]}, no_args_is_help=True
|
|
@@ -55,9 +54,7 @@ def location(
|
|
|
55
54
|
field: FIELD_NAME,
|
|
56
55
|
) -> None:
|
|
57
56
|
"""Print a form field's location and size as JSON."""
|
|
58
|
-
f = get_widget(
|
|
59
|
-
PdfWrapper(str(pdf), **ctx.obj), field, cli_widget_key_error("--field")
|
|
60
|
-
)
|
|
57
|
+
f = get_widget(PdfWrapper(str(pdf), **ctx.obj), field, "--field")
|
|
61
58
|
|
|
62
59
|
typer.echo(
|
|
63
60
|
json.dumps(
|
|
@@ -17,11 +17,9 @@ import typer
|
|
|
17
17
|
|
|
18
18
|
from .. import PdfWrapper
|
|
19
19
|
from ..lib.constants import PdfVersion
|
|
20
|
-
from ..shared.utils import get_widget
|
|
21
20
|
from .common import (FIELD_NAME, INPUT_PDF, OPTIONAL_OUTPUT_PDF,
|
|
22
|
-
cli_bad_parameter,
|
|
23
|
-
|
|
24
|
-
load_json_file)
|
|
21
|
+
cli_bad_parameter, get_widget, handle_font_registration,
|
|
22
|
+
json_file_option, load_json_file)
|
|
25
23
|
from .schemas.update import FIELD_SCHEMA, RENAME_SCHEMA
|
|
26
24
|
|
|
27
25
|
update_cli = typer.Typer(
|
|
@@ -109,7 +107,7 @@ def bounds(
|
|
|
109
107
|
) -> None:
|
|
110
108
|
"""Update a form field's position and size."""
|
|
111
109
|
obj = PdfWrapper(str(pdf), **ctx.obj)
|
|
112
|
-
f = get_widget(obj, widget,
|
|
110
|
+
f = get_widget(obj, widget, "--field")
|
|
113
111
|
|
|
114
112
|
f.x = x if x is not None else f.x
|
|
115
113
|
f.y = y if y is not None else f.y
|
|
@@ -139,10 +137,8 @@ def rename(
|
|
|
139
137
|
obj = PdfWrapper(str(pdf), **ctx.obj)
|
|
140
138
|
for item in input_data:
|
|
141
139
|
for k, v in item.items():
|
|
142
|
-
widget = get_widget(obj, k,
|
|
143
|
-
obj.update_widget_key(
|
|
144
|
-
widget.name, v["new_key"], index=v.get("index", 0), defer=True
|
|
145
|
-
)
|
|
140
|
+
widget = get_widget(obj, k, "--file")
|
|
141
|
+
obj.update_widget_key(widget.name, v["new_key"], index=v.get("index", 0))
|
|
146
142
|
|
|
147
143
|
obj.commit_widget_key_updates().write(output or pdf)
|
|
148
144
|
|
|
@@ -162,7 +158,7 @@ def field(
|
|
|
162
158
|
obj = PdfWrapper(str(pdf), **ctx.obj)
|
|
163
159
|
registered_font = {}
|
|
164
160
|
for k, each in input_data.items():
|
|
165
|
-
widget = get_widget(obj, k,
|
|
161
|
+
widget = get_widget(obj, k, "--file")
|
|
166
162
|
handle_font_registration(obj, each, registered_font)
|
|
167
163
|
for param, v in each.items():
|
|
168
164
|
setattr(widget, param, v)
|
|
@@ -89,6 +89,7 @@ class DropdownField(Field):
|
|
|
89
89
|
options: Optional[List[str | Tuple[str, str]]] = None
|
|
90
90
|
width: Optional[float] = None
|
|
91
91
|
height: Optional[float] = None
|
|
92
|
+
# pylint: disable=R0801
|
|
92
93
|
font: Optional[str] = None
|
|
93
94
|
font_size: Optional[float] = None
|
|
94
95
|
font_color: Optional[Tuple[float, ...]] = None
|
|
@@ -29,7 +29,6 @@ from .adapter import (fp_or_f_obj_or_f_content_to_content,
|
|
|
29
29
|
fp_or_f_obj_or_stream_to_stream)
|
|
30
30
|
from .constants import VERSION_IDENTIFIER_PREFIX, VERSION_IDENTIFIERS
|
|
31
31
|
from .coordinate import generate_coordinate_grid
|
|
32
|
-
from .deprecation import deprecation_notice
|
|
33
32
|
from .egress import appearance_streams_handler, preserve_pdf_properties
|
|
34
33
|
from .filler import fill
|
|
35
34
|
from .font import (get_all_available_fonts, register_font_acroform,
|
|
@@ -160,7 +159,7 @@ class PdfWrapper:
|
|
|
160
159
|
unique_suffix = generate_unique_suffix()
|
|
161
160
|
for k in self.widgets:
|
|
162
161
|
if k in other.widgets:
|
|
163
|
-
other.update_widget_key(k, f"{k}-{unique_suffix}"
|
|
162
|
+
other.update_widget_key(k, f"{k}-{unique_suffix}")
|
|
164
163
|
|
|
165
164
|
other.commit_widget_key_updates()
|
|
166
165
|
|
|
@@ -246,7 +245,7 @@ class PdfWrapper:
|
|
|
246
245
|
"properties": {
|
|
247
246
|
key: value.schema_definition for key, value in self.widgets.items()
|
|
248
247
|
},
|
|
249
|
-
"additionalProperties":
|
|
248
|
+
"additionalProperties": False,
|
|
250
249
|
}
|
|
251
250
|
|
|
252
251
|
@property
|
|
@@ -666,44 +665,20 @@ class PdfWrapper:
|
|
|
666
665
|
|
|
667
666
|
return self
|
|
668
667
|
|
|
669
|
-
@deprecation_notice(to_replace="bulk_create_fields")
|
|
670
|
-
def create_field(
|
|
671
|
-
self,
|
|
672
|
-
field: FieldTypes,
|
|
673
|
-
) -> PdfWrapper:
|
|
674
|
-
"""
|
|
675
|
-
Creates a new form field (widget) on the PDF using a `FieldTypes` object.
|
|
676
|
-
|
|
677
|
-
This method simplifies widget creation by taking a `FieldTypes` object
|
|
678
|
-
and delegating to the internal `_bulk_create_fields` method.
|
|
679
|
-
|
|
680
|
-
Args:
|
|
681
|
-
field (FieldTypes): An object representing the field to create.
|
|
682
|
-
This object encapsulates all necessary properties like name,
|
|
683
|
-
page number, coordinates, and type of the field.
|
|
684
|
-
|
|
685
|
-
Returns:
|
|
686
|
-
PdfWrapper: The `PdfWrapper` object, allowing for method chaining.
|
|
687
|
-
"""
|
|
688
|
-
|
|
689
|
-
return self._bulk_create_fields([field])
|
|
690
|
-
|
|
691
668
|
def update_widget_key(
|
|
692
|
-
self, old_key: str, new_key: str, index: int = 0
|
|
669
|
+
self, old_key: str, new_key: str, index: int = 0
|
|
693
670
|
) -> PdfWrapper:
|
|
694
671
|
"""
|
|
695
672
|
Updates the key (name) of a widget, allowing you to rename form fields.
|
|
696
673
|
|
|
697
|
-
This method
|
|
698
|
-
standardizing field names or resolving naming conflicts. The update
|
|
699
|
-
|
|
674
|
+
This method queues a change to the name of a form field in the PDF. This can be useful for
|
|
675
|
+
standardizing field names or resolving naming conflicts. The queued update is applied when
|
|
676
|
+
`commit_widget_key_updates` is called.
|
|
700
677
|
|
|
701
678
|
Args:
|
|
702
679
|
old_key (str): The old key of the widget that you want to rename.
|
|
703
680
|
new_key (str): The new key to assign to the widget.
|
|
704
681
|
index (int): The index of the widget if there are multiple widgets with the same name (default: 0).
|
|
705
|
-
defer (bool): Whether to defer the update. If True, the update is added to a queue and applied
|
|
706
|
-
when `commit_widget_key_updates` is called. If False, the update is applied immediately (default: False).
|
|
707
682
|
|
|
708
683
|
Returns:
|
|
709
684
|
PdfWrapper: The PdfWrapper object.
|
|
@@ -712,27 +687,15 @@ class PdfWrapper:
|
|
|
712
687
|
if getattr(self, "use_full_widget_name"):
|
|
713
688
|
raise NotImplementedError
|
|
714
689
|
|
|
715
|
-
|
|
716
|
-
deprecation_notice(to_replace="", param="defer").emit_notice(
|
|
717
|
-
self, "update_widget_key"
|
|
718
|
-
)
|
|
719
|
-
self._keys_to_update.append((old_key, new_key, index))
|
|
720
|
-
return self
|
|
721
|
-
|
|
722
|
-
self._key_update_tracker[new_key] = old_key
|
|
723
|
-
self._stream = update_widget_keys(
|
|
724
|
-
self._read(), self.widgets, [old_key], [new_key], [index]
|
|
725
|
-
)
|
|
726
|
-
self._init_helper()
|
|
727
|
-
|
|
690
|
+
self._keys_to_update.append((old_key, new_key, index))
|
|
728
691
|
return self
|
|
729
692
|
|
|
730
693
|
def commit_widget_key_updates(self) -> PdfWrapper:
|
|
731
694
|
"""
|
|
732
695
|
Commits deferred widget key updates, applying all queued key renames to the PDF.
|
|
733
696
|
|
|
734
|
-
This method applies all widget key updates
|
|
735
|
-
|
|
697
|
+
This method applies all widget key updates queued by the `update_widget_key` method. It updates
|
|
698
|
+
the underlying PDF stream with the new key names.
|
|
736
699
|
|
|
737
700
|
Returns:
|
|
738
701
|
PdfWrapper: The PdfWrapper object.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPDFForm
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.0.1
|
|
4
4
|
Summary: The Python library for PDF forms.
|
|
5
5
|
Author: Jinge Li
|
|
6
6
|
License-Expression: MIT
|
|
@@ -21,18 +21,16 @@ Requires-Python: >=3.10
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: cryptography<49.0.0,>=48.0.0
|
|
24
|
-
Requires-Dist: fonttools<5.0.0,>=4.
|
|
24
|
+
Requires-Dist: fonttools<5.0.0,>=4.63.0
|
|
25
25
|
Requires-Dist: pikepdf<11.0.0,>=10.5.1
|
|
26
26
|
Requires-Dist: pillow<13.0.0,>=12.2.0
|
|
27
27
|
Requires-Dist: pypdf<7.0.0,>=6.11.0
|
|
28
|
-
Requires-Dist: reportlab<5.0.0,>=4.5.
|
|
28
|
+
Requires-Dist: reportlab<5.0.0,>=4.5.1
|
|
29
29
|
Provides-Extra: cli
|
|
30
30
|
Requires-Dist: typer<1.0.0,>=0.25.1; extra == "cli"
|
|
31
31
|
Requires-Dist: jsonschema<5.0.0,>=4.26.0; extra == "cli"
|
|
32
|
-
Provides-Extra: web-api
|
|
33
|
-
Requires-Dist: fastapi[standard]<1.0.0,>=0.136.1; extra == "web-api"
|
|
34
32
|
Provides-Extra: dev
|
|
35
|
-
Requires-Dist: black<27.0.0,>=26.
|
|
33
|
+
Requires-Dist: black<27.0.0,>=26.5.1; extra == "dev"
|
|
36
34
|
Requires-Dist: coverage<8.0.0,>=7.14.0; extra == "dev"
|
|
37
35
|
Requires-Dist: isort<9.0.0,>=8.0.1; extra == "dev"
|
|
38
36
|
Requires-Dist: mike<3.0.0,>=2.2.0; extra == "dev"
|
|
@@ -42,8 +40,8 @@ Requires-Dist: pudb<2026.0.0,>=2025.1.5; extra == "dev"
|
|
|
42
40
|
Requires-Dist: pylint<5.0.0,>=4.0.5; extra == "dev"
|
|
43
41
|
Requires-Dist: pyright<2.0.0,>=1.1.409; extra == "dev"
|
|
44
42
|
Requires-Dist: pytest<10.0.0,>=9.0.3; extra == "dev"
|
|
45
|
-
Requires-Dist: requests<3.0.0,>=2.34.
|
|
46
|
-
Requires-Dist: ruff<1.0.0,>=0.15.
|
|
43
|
+
Requires-Dist: requests<3.0.0,>=2.34.2; extra == "dev"
|
|
44
|
+
Requires-Dist: ruff<1.0.0,>=0.15.13; extra == "dev"
|
|
47
45
|
Dynamic: license-file
|
|
48
46
|
|
|
49
47
|
<p align="center"><img src="https://github.com/chinapandaman/PyPDFForm/raw/master/docs/img/logo.png"></p>
|
|
@@ -8,12 +8,6 @@ PyPDFForm.egg-info/dependency_links.txt
|
|
|
8
8
|
PyPDFForm.egg-info/entry_points.txt
|
|
9
9
|
PyPDFForm.egg-info/requires.txt
|
|
10
10
|
PyPDFForm.egg-info/top_level.txt
|
|
11
|
-
PyPDFForm/api/__init__.py
|
|
12
|
-
PyPDFForm/api/common.py
|
|
13
|
-
PyPDFForm/api/create.py
|
|
14
|
-
PyPDFForm/api/inspect.py
|
|
15
|
-
PyPDFForm/api/root.py
|
|
16
|
-
PyPDFForm/api/update.py
|
|
17
11
|
PyPDFForm/cli/__init__.py
|
|
18
12
|
PyPDFForm/cli/common.py
|
|
19
13
|
PyPDFForm/cli/create.py
|
|
@@ -72,8 +66,6 @@ PyPDFForm/lib/widgets/image.py
|
|
|
72
66
|
PyPDFForm/lib/widgets/radio.py
|
|
73
67
|
PyPDFForm/lib/widgets/signature.py
|
|
74
68
|
PyPDFForm/lib/widgets/text.py
|
|
75
|
-
PyPDFForm/shared/__init__.py
|
|
76
|
-
PyPDFForm/shared/utils.py
|
|
77
69
|
tests/test_bulk_create_fields.py
|
|
78
70
|
tests/test_create_widget.py
|
|
79
71
|
tests/test_draw_elements.py
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
cryptography<49.0.0,>=48.0.0
|
|
2
|
-
fonttools<5.0.0,>=4.
|
|
2
|
+
fonttools<5.0.0,>=4.63.0
|
|
3
3
|
pikepdf<11.0.0,>=10.5.1
|
|
4
4
|
pillow<13.0.0,>=12.2.0
|
|
5
5
|
pypdf<7.0.0,>=6.11.0
|
|
6
|
-
reportlab<5.0.0,>=4.5.
|
|
6
|
+
reportlab<5.0.0,>=4.5.1
|
|
7
7
|
|
|
8
8
|
[cli]
|
|
9
9
|
typer<1.0.0,>=0.25.1
|
|
10
10
|
jsonschema<5.0.0,>=4.26.0
|
|
11
11
|
|
|
12
12
|
[dev]
|
|
13
|
-
black<27.0.0,>=26.
|
|
13
|
+
black<27.0.0,>=26.5.1
|
|
14
14
|
coverage<8.0.0,>=7.14.0
|
|
15
15
|
isort<9.0.0,>=8.0.1
|
|
16
16
|
mike<3.0.0,>=2.2.0
|
|
@@ -20,8 +20,5 @@ pudb<2026.0.0,>=2025.1.5
|
|
|
20
20
|
pylint<5.0.0,>=4.0.5
|
|
21
21
|
pyright<2.0.0,>=1.1.409
|
|
22
22
|
pytest<10.0.0,>=9.0.3
|
|
23
|
-
requests<3.0.0,>=2.34.
|
|
24
|
-
ruff<1.0.0,>=0.15.
|
|
25
|
-
|
|
26
|
-
[web_api]
|
|
27
|
-
fastapi[standard]<1.0.0,>=0.136.1
|
|
23
|
+
requests<3.0.0,>=2.34.2
|
|
24
|
+
ruff<1.0.0,>=0.15.13
|
|
@@ -26,11 +26,11 @@ classifiers = [
|
|
|
26
26
|
requires-python = ">=3.10"
|
|
27
27
|
dependencies = [
|
|
28
28
|
"cryptography>=48.0.0,<49.0.0",
|
|
29
|
-
"fonttools>=4.
|
|
29
|
+
"fonttools>=4.63.0,<5.0.0",
|
|
30
30
|
"pikepdf>=10.5.1,<11.0.0",
|
|
31
31
|
"pillow>=12.2.0,<13.0.0",
|
|
32
32
|
"pypdf>=6.11.0,<7.0.0",
|
|
33
|
-
"reportlab>=4.5.
|
|
33
|
+
"reportlab>=4.5.1,<5.0.0",
|
|
34
34
|
]
|
|
35
35
|
|
|
36
36
|
[project.urls]
|
|
@@ -38,10 +38,10 @@ Homepage = "https://github.com/chinapandaman/PyPDFForm"
|
|
|
38
38
|
Documentation = "https://chinapandaman.github.io/PyPDFForm/"
|
|
39
39
|
|
|
40
40
|
[project.optional-dependencies]
|
|
41
|
+
# update `CLI_DEPENDENCIES` in `PyPDFForm/cli/entry.py` when changing cli dependencies
|
|
41
42
|
cli = ["typer>=0.25.1,<1.0.0", "jsonschema>=4.26.0,<5.0.0"]
|
|
42
|
-
web_api = ["fastapi[standard]>=0.136.1,<1.0.0"]
|
|
43
43
|
dev = [
|
|
44
|
-
"black>=26.
|
|
44
|
+
"black>=26.5.1,<27.0.0",
|
|
45
45
|
"coverage>=7.14.0,<8.0.0",
|
|
46
46
|
"isort>=8.0.1,<9.0.0",
|
|
47
47
|
"mike>=2.2.0,<3.0.0",
|
|
@@ -51,8 +51,8 @@ dev = [
|
|
|
51
51
|
"pylint>=4.0.5,<5.0.0",
|
|
52
52
|
"pyright>=1.1.409,<2.0.0",
|
|
53
53
|
"pytest>=9.0.3,<10.0.0",
|
|
54
|
-
"requests>=2.34.
|
|
55
|
-
"ruff>=0.15.
|
|
54
|
+
"requests>=2.34.2,<3.0.0",
|
|
55
|
+
"ruff>=0.15.13,<1.0.0",
|
|
56
56
|
]
|
|
57
57
|
|
|
58
58
|
[project.scripts]
|
|
@@ -74,7 +74,6 @@ disable = [
|
|
|
74
74
|
"C2801",
|
|
75
75
|
"C0301",
|
|
76
76
|
"W0511",
|
|
77
|
-
"R0801",
|
|
78
77
|
]
|
|
79
78
|
|
|
80
79
|
[tool.ruff.lint]
|
|
@@ -139,7 +138,6 @@ include = ["PyPDFForm*"]
|
|
|
139
138
|
|
|
140
139
|
[tool.pytest.ini_options]
|
|
141
140
|
markers = [
|
|
142
|
-
"posix_only",
|
|
141
|
+
"posix_only", # mainly because of zlib vs zlib-ng
|
|
143
142
|
"cli_test",
|
|
144
|
-
"web_api_test",
|
|
145
143
|
]
|
|
@@ -7,21 +7,6 @@ import pytest
|
|
|
7
7
|
from PyPDFForm import Fields, PdfWrapper
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def test_create_field_deprecated(template_stream):
|
|
11
|
-
with pytest.warns(
|
|
12
|
-
DeprecationWarning,
|
|
13
|
-
match="PdfWrapper.create_field will be deprecated soon. Use PdfWrapper.bulk_create_fields instead.",
|
|
14
|
-
):
|
|
15
|
-
assert (
|
|
16
|
-
PdfWrapper(template_stream)
|
|
17
|
-
.create_field(Fields.TextField("foo", 1, 100, 100))
|
|
18
|
-
.read()
|
|
19
|
-
== PdfWrapper(template_stream)
|
|
20
|
-
.bulk_create_fields([Fields.TextField("foo", 1, 100, 100)])
|
|
21
|
-
.read()
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
10
|
@pytest.mark.posix_only
|
|
26
11
|
def test_create_checkbox_complex_fill(template_stream, pdf_samples, request):
|
|
27
12
|
expected_path = os.path.join(
|
|
@@ -645,6 +645,7 @@ def test_update_radio_key(template_with_radiobutton_stream, pdf_samples, request
|
|
|
645
645
|
with open(expected_path, "rb+") as f:
|
|
646
646
|
obj = PdfWrapper(template_with_radiobutton_stream)
|
|
647
647
|
obj.update_widget_key("radio_3", "RADIO")
|
|
648
|
+
obj.commit_widget_key_updates()
|
|
648
649
|
obj.fill({"RADIO": 0})
|
|
649
650
|
|
|
650
651
|
request.config.results["expected_path"] = expected_path
|
|
@@ -664,6 +665,7 @@ def test_update_sejda_key(sejda_template, pdf_samples, request):
|
|
|
664
665
|
obj.update_widget_key("at_future_date", "FUTURE_DATE")
|
|
665
666
|
obj.update_widget_key("purchase_option", "PURCHASE_OPTION")
|
|
666
667
|
obj.update_widget_key("buyer_signed_date", "BUYER_SIGNED_DATE")
|
|
668
|
+
obj.commit_widget_key_updates()
|
|
667
669
|
obj.fill(
|
|
668
670
|
{
|
|
669
671
|
"YEAR": "12",
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
FastAPI application for the optional PyPDFForm web API.
|
|
4
|
-
|
|
5
|
-
The web API is intended to be PyPDFForm's third user-facing interface alongside
|
|
6
|
-
the Python library and CLI. As the API surface grows, its endpoints should
|
|
7
|
-
closely mirror the CLI workflows for creating, filling, inspecting, and updating
|
|
8
|
-
PDF forms, while exposing those operations over HTTP.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from fastapi import FastAPI
|
|
12
|
-
|
|
13
|
-
from .. import __version__
|
|
14
|
-
from .create import create_router
|
|
15
|
-
from .inspect import inspect_router
|
|
16
|
-
from .root import root_router
|
|
17
|
-
from .update import update_router
|
|
18
|
-
|
|
19
|
-
app = FastAPI(
|
|
20
|
-
title="PyPDFForm Web API",
|
|
21
|
-
summary="Create, fill, inspect, and update PDF forms.",
|
|
22
|
-
version=__version__,
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
app.include_router(root_router)
|
|
26
|
-
app.include_router(create_router)
|
|
27
|
-
app.include_router(inspect_router)
|
|
28
|
-
app.include_router(update_router)
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
This module provides shared helpers for PyPDFForm web API routes.
|
|
4
|
-
|
|
5
|
-
It defines the PDF response class and common query parameter parsing used by
|
|
6
|
-
endpoint groups that construct `PdfWrapper` instances from uploaded PDF files.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from typing import NoReturn
|
|
10
|
-
|
|
11
|
-
from fastapi import HTTPException, Query, Response, status
|
|
12
|
-
from pydantic import BaseModel
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class PdfResponse(Response):
|
|
16
|
-
"""
|
|
17
|
-
FastAPI response class for PDF bytes.
|
|
18
|
-
|
|
19
|
-
Attributes:
|
|
20
|
-
media_type (str): Response media type for PDF content.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
media_type = "application/pdf"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class PdfWrapperOptions(BaseModel):
|
|
27
|
-
"""
|
|
28
|
-
Common `PdfWrapper` options accepted by web API endpoints.
|
|
29
|
-
|
|
30
|
-
These fields mirror the constructor options exposed by the Python library
|
|
31
|
-
and CLI so each route can opt into the same PDF handling behavior.
|
|
32
|
-
|
|
33
|
-
Attributes:
|
|
34
|
-
need_appearances (bool): Whether to set the PDF's NeedAppearances flag.
|
|
35
|
-
generate_appearance_streams (bool): Whether to generate appearance
|
|
36
|
-
streams for filled fields.
|
|
37
|
-
preserve_metadata (bool): Whether to preserve source PDF metadata.
|
|
38
|
-
use_full_widget_name (bool): Whether to use full widget names when
|
|
39
|
-
reading fields.
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
need_appearances: bool = False
|
|
43
|
-
generate_appearance_streams: bool = False
|
|
44
|
-
preserve_metadata: bool = False
|
|
45
|
-
use_full_widget_name: bool = False
|
|
46
|
-
|
|
47
|
-
def as_kwargs(self) -> dict:
|
|
48
|
-
"""
|
|
49
|
-
Convert options into `PdfWrapper` keyword arguments.
|
|
50
|
-
|
|
51
|
-
Returns:
|
|
52
|
-
dict: Mapping of option names to values accepted by `PdfWrapper`.
|
|
53
|
-
"""
|
|
54
|
-
return self.model_dump()
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def pdf_wrapper_options(
|
|
58
|
-
need_appearances: bool = Query(False),
|
|
59
|
-
generate_appearance_streams: bool = Query(False),
|
|
60
|
-
preserve_metadata: bool = Query(False),
|
|
61
|
-
use_full_widget_name: bool = Query(False),
|
|
62
|
-
) -> PdfWrapperOptions:
|
|
63
|
-
"""
|
|
64
|
-
Build common `PdfWrapper` options from query parameters.
|
|
65
|
-
|
|
66
|
-
FastAPI uses this function as a dependency so routes can share a
|
|
67
|
-
consistent query parameter set.
|
|
68
|
-
|
|
69
|
-
Args:
|
|
70
|
-
need_appearances (bool): Whether to set the PDF's NeedAppearances flag.
|
|
71
|
-
generate_appearance_streams (bool): Whether to generate appearance
|
|
72
|
-
streams for filled fields.
|
|
73
|
-
preserve_metadata (bool): Whether to preserve source PDF metadata.
|
|
74
|
-
use_full_widget_name (bool): Whether to use full widget names when
|
|
75
|
-
reading fields.
|
|
76
|
-
|
|
77
|
-
Returns:
|
|
78
|
-
PdfWrapperOptions: Parsed options for constructing a `PdfWrapper`.
|
|
79
|
-
"""
|
|
80
|
-
return PdfWrapperOptions(
|
|
81
|
-
need_appearances=need_appearances,
|
|
82
|
-
generate_appearance_streams=generate_appearance_streams,
|
|
83
|
-
preserve_metadata=preserve_metadata,
|
|
84
|
-
use_full_widget_name=use_full_widget_name,
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def api_widget_key_error(message: str, cause: KeyError) -> NoReturn:
|
|
89
|
-
"""
|
|
90
|
-
Raise a web API error for a missing form field.
|
|
91
|
-
|
|
92
|
-
Args:
|
|
93
|
-
message (str): Error message to return to the API client.
|
|
94
|
-
cause (KeyError): Original lookup error.
|
|
95
|
-
|
|
96
|
-
Raises:
|
|
97
|
-
HTTPException: Raised with a 404 response for the missing field.
|
|
98
|
-
"""
|
|
99
|
-
raise HTTPException(
|
|
100
|
-
status_code=status.HTTP_404_NOT_FOUND, detail=message
|
|
101
|
-
) from cause
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
This module defines web API routes for creating PDF files and PDF content.
|
|
4
|
-
|
|
5
|
-
It exposes `/create` endpoints that accept uploaded PDFs, apply matching
|
|
6
|
-
`PdfWrapper` creation operations, and return generated PDF bytes to HTTP
|
|
7
|
-
clients.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from typing import Annotated
|
|
11
|
-
|
|
12
|
-
from fastapi import APIRouter, Depends, File, Form, UploadFile
|
|
13
|
-
|
|
14
|
-
from .. import PdfWrapper
|
|
15
|
-
from .common import PdfResponse, PdfWrapperOptions, pdf_wrapper_options
|
|
16
|
-
|
|
17
|
-
create_router = APIRouter(prefix="/create", tags=["create"])
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@create_router.post(
|
|
21
|
-
"/grid",
|
|
22
|
-
summary="Add a coordinate grid to a PDF.",
|
|
23
|
-
response_class=PdfResponse,
|
|
24
|
-
responses={
|
|
25
|
-
200: {
|
|
26
|
-
"content": {
|
|
27
|
-
"application/pdf": {"schema": {"type": "string", "format": "binary"}}
|
|
28
|
-
},
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
)
|
|
32
|
-
def grid(
|
|
33
|
-
options: Annotated[PdfWrapperOptions, Depends(pdf_wrapper_options)],
|
|
34
|
-
pdf: Annotated[UploadFile, File()],
|
|
35
|
-
red: Annotated[float, Form()] = None,
|
|
36
|
-
green: Annotated[float, Form()] = None,
|
|
37
|
-
blue: Annotated[float, Form()] = None,
|
|
38
|
-
margin: Annotated[float, Form()] = None,
|
|
39
|
-
) -> PdfResponse:
|
|
40
|
-
"""
|
|
41
|
-
Upload a PDF and return a copy with a coordinate grid overlaid on each page.
|
|
42
|
-
|
|
43
|
-
Use the optional RGB components to choose the grid color and `margin` to
|
|
44
|
-
adjust the grid spacing from the page edges.
|
|
45
|
-
|
|
46
|
-
\f
|
|
47
|
-
|
|
48
|
-
Args:
|
|
49
|
-
options (PdfWrapperOptions): Common `PdfWrapper` construction options.
|
|
50
|
-
pdf (UploadFile): Uploaded PDF file to annotate with a grid.
|
|
51
|
-
red (float): Red component of the grid color, from 0 to 1.
|
|
52
|
-
green (float): Green component of the grid color, from 0 to 1.
|
|
53
|
-
blue (float): Blue component of the grid color, from 0 to 1.
|
|
54
|
-
margin (float): Grid margin in points.
|
|
55
|
-
|
|
56
|
-
Returns:
|
|
57
|
-
PdfResponse: PDF response containing the document with a coordinate grid.
|
|
58
|
-
"""
|
|
59
|
-
params = {}
|
|
60
|
-
if any(
|
|
61
|
-
[
|
|
62
|
-
red is not None,
|
|
63
|
-
green is not None,
|
|
64
|
-
blue is not None,
|
|
65
|
-
]
|
|
66
|
-
):
|
|
67
|
-
params["color"] = (red or 0, green or 0, blue or 0)
|
|
68
|
-
|
|
69
|
-
if margin is not None:
|
|
70
|
-
params["margin"] = int(margin) if margin.is_integer() else margin
|
|
71
|
-
return PdfResponse(
|
|
72
|
-
PdfWrapper(pdf.file.read(), **options.as_kwargs())
|
|
73
|
-
.generate_coordinate_grid(**params)
|
|
74
|
-
.read()
|
|
75
|
-
)
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
This module defines web API routes for inspecting PDF form information.
|
|
4
|
-
|
|
5
|
-
It exposes `/inspect` endpoints that return JSON for form schemas, current
|
|
6
|
-
form values, generated sample data, and field rectangle metadata. Each endpoint
|
|
7
|
-
wraps read-only `PdfWrapper` properties for clients calling PyPDFForm over HTTP.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from typing import Annotated
|
|
11
|
-
|
|
12
|
-
from fastapi import APIRouter, Depends, File, Form, UploadFile
|
|
13
|
-
|
|
14
|
-
from .. import PdfWrapper
|
|
15
|
-
from ..shared.utils import get_widget
|
|
16
|
-
from .common import (PdfWrapperOptions, api_widget_key_error,
|
|
17
|
-
pdf_wrapper_options)
|
|
18
|
-
|
|
19
|
-
inspect_router = APIRouter(prefix="/inspect", tags=["inspect"])
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@inspect_router.post("/schema", summary="Return the form schema as JSON.")
|
|
23
|
-
def schema(
|
|
24
|
-
options: Annotated[PdfWrapperOptions, Depends(pdf_wrapper_options)],
|
|
25
|
-
pdf: Annotated[UploadFile, File()],
|
|
26
|
-
) -> dict:
|
|
27
|
-
"""
|
|
28
|
-
Upload a PDF form and return the JSON schema PyPDFForm detects for its
|
|
29
|
-
fields.
|
|
30
|
-
|
|
31
|
-
Use this response to discover field names, value types, and validation
|
|
32
|
-
constraints before filling or updating a form.
|
|
33
|
-
|
|
34
|
-
\f
|
|
35
|
-
|
|
36
|
-
Args:
|
|
37
|
-
options (PdfWrapperOptions): Common `PdfWrapper` construction options.
|
|
38
|
-
pdf (UploadFile): Uploaded PDF file to inspect.
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
dict: JSON-serializable form schema.
|
|
42
|
-
"""
|
|
43
|
-
return PdfWrapper(pdf.file.read(), **options.as_kwargs()).schema
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
@inspect_router.post("/data", summary="Return current form data as JSON.")
|
|
47
|
-
def data(
|
|
48
|
-
options: Annotated[PdfWrapperOptions, Depends(pdf_wrapper_options)],
|
|
49
|
-
pdf: Annotated[UploadFile, File()],
|
|
50
|
-
) -> dict:
|
|
51
|
-
"""
|
|
52
|
-
Upload a PDF form and return the values currently stored in its fields.
|
|
53
|
-
|
|
54
|
-
Empty fields are included in the response so clients can distinguish
|
|
55
|
-
missing form fields from blank values.
|
|
56
|
-
|
|
57
|
-
\f
|
|
58
|
-
|
|
59
|
-
Args:
|
|
60
|
-
options (PdfWrapperOptions): Common `PdfWrapper` construction options.
|
|
61
|
-
pdf (UploadFile): Uploaded PDF file to inspect.
|
|
62
|
-
|
|
63
|
-
Returns:
|
|
64
|
-
dict: JSON-serializable current form data.
|
|
65
|
-
"""
|
|
66
|
-
return PdfWrapper(pdf.file.read(), **options.as_kwargs()).data
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@inspect_router.post("/sample", summary="Return sample fill data as JSON.")
|
|
70
|
-
def sample(
|
|
71
|
-
options: Annotated[PdfWrapperOptions, Depends(pdf_wrapper_options)],
|
|
72
|
-
pdf: Annotated[UploadFile, File()],
|
|
73
|
-
) -> dict:
|
|
74
|
-
"""
|
|
75
|
-
Upload a PDF form and return example data matching the detected schema.
|
|
76
|
-
|
|
77
|
-
Use this response as a starting payload when testing form filling or when
|
|
78
|
-
building a client-side editor for a PDF form.
|
|
79
|
-
|
|
80
|
-
\f
|
|
81
|
-
|
|
82
|
-
Args:
|
|
83
|
-
options (PdfWrapperOptions): Common `PdfWrapper` construction options.
|
|
84
|
-
pdf (UploadFile): Uploaded PDF file to inspect.
|
|
85
|
-
|
|
86
|
-
Returns:
|
|
87
|
-
dict: JSON-serializable sample fill data.
|
|
88
|
-
"""
|
|
89
|
-
return PdfWrapper(pdf.file.read(), **options.as_kwargs()).sample_data
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
@inspect_router.post(
|
|
93
|
-
"/location", summary="Return a form field's location and size as JSON."
|
|
94
|
-
)
|
|
95
|
-
def location(
|
|
96
|
-
options: Annotated[PdfWrapperOptions, Depends(pdf_wrapper_options)],
|
|
97
|
-
pdf: Annotated[UploadFile, File()],
|
|
98
|
-
field: Annotated[str, Form()],
|
|
99
|
-
) -> dict:
|
|
100
|
-
"""
|
|
101
|
-
Upload a PDF form and field name, then return that field's page number,
|
|
102
|
-
coordinates, width, and height.
|
|
103
|
-
|
|
104
|
-
Use this endpoint when placing overlays, annotations, or generated content
|
|
105
|
-
relative to an existing form field.
|
|
106
|
-
|
|
107
|
-
\f
|
|
108
|
-
|
|
109
|
-
Args:
|
|
110
|
-
options (PdfWrapperOptions): Common `PdfWrapper` construction options.
|
|
111
|
-
pdf (UploadFile): Uploaded PDF file to inspect.
|
|
112
|
-
field (str): Name of the form field to locate.
|
|
113
|
-
|
|
114
|
-
Returns:
|
|
115
|
-
dict: JSON-serializable field page number, coordinates, and dimensions.
|
|
116
|
-
"""
|
|
117
|
-
f = get_widget(
|
|
118
|
-
PdfWrapper(pdf.file.read(), **options.as_kwargs()), field, api_widget_key_error
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
"page_number": f.page_number,
|
|
123
|
-
"x": f.x,
|
|
124
|
-
"y": f.y,
|
|
125
|
-
"width": f.width,
|
|
126
|
-
"height": f.height,
|
|
127
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
Root router for the PyPDFForm web API.
|
|
4
|
-
|
|
5
|
-
This router owns the top-level API routes that make PyPDFForm available over
|
|
6
|
-
HTTP. Finished endpoint groups should stay aligned with the CLI's behavior so
|
|
7
|
-
users can choose between the Python library, command line, or web API without
|
|
8
|
-
learning a different workflow model.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from fastapi import APIRouter
|
|
12
|
-
from fastapi.responses import RedirectResponse
|
|
13
|
-
|
|
14
|
-
root_router = APIRouter()
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@root_router.get("/", include_in_schema=False)
|
|
18
|
-
def index() -> RedirectResponse:
|
|
19
|
-
"""Redirect the API root to the generated OpenAPI documentation."""
|
|
20
|
-
return RedirectResponse(url="/docs")
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
This module defines web API routes for updating existing PDF files.
|
|
4
|
-
|
|
5
|
-
It exposes `/update` endpoints that accept uploaded PDFs, apply matching
|
|
6
|
-
`PdfWrapper` operations, and return modified PDF bytes to HTTP clients.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from typing import Annotated
|
|
10
|
-
|
|
11
|
-
from fastapi import APIRouter, Depends, File, Form, UploadFile
|
|
12
|
-
|
|
13
|
-
from .. import PdfWrapper
|
|
14
|
-
from .common import PdfResponse, PdfWrapperOptions, pdf_wrapper_options
|
|
15
|
-
|
|
16
|
-
update_router = APIRouter(prefix="/update", tags=["update"])
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@update_router.post(
|
|
20
|
-
"/title",
|
|
21
|
-
summary="Set the PDF title.",
|
|
22
|
-
response_class=PdfResponse,
|
|
23
|
-
responses={
|
|
24
|
-
200: {
|
|
25
|
-
"content": {
|
|
26
|
-
"application/pdf": {"schema": {"type": "string", "format": "binary"}}
|
|
27
|
-
},
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
)
|
|
31
|
-
def title(
|
|
32
|
-
options: Annotated[PdfWrapperOptions, Depends(pdf_wrapper_options)],
|
|
33
|
-
pdf: Annotated[UploadFile, File()],
|
|
34
|
-
new_title: Annotated[str, Form()],
|
|
35
|
-
) -> PdfResponse:
|
|
36
|
-
"""
|
|
37
|
-
Upload a PDF and return a copy with its document title metadata updated.
|
|
38
|
-
|
|
39
|
-
The response body is the modified PDF file, preserving other behavior from
|
|
40
|
-
the common `PdfWrapper` options supplied as query parameters.
|
|
41
|
-
|
|
42
|
-
\f
|
|
43
|
-
|
|
44
|
-
Args:
|
|
45
|
-
options (PdfWrapperOptions): Common `PdfWrapper` construction options.
|
|
46
|
-
pdf (UploadFile): Uploaded PDF file to update.
|
|
47
|
-
new_title (str): New title to write into the PDF metadata.
|
|
48
|
-
|
|
49
|
-
Returns:
|
|
50
|
-
PdfResponse: PDF response containing the updated document bytes.
|
|
51
|
-
"""
|
|
52
|
-
return PdfResponse(
|
|
53
|
-
content=PdfWrapper(
|
|
54
|
-
pdf.file.read(), title=new_title, **options.as_kwargs()
|
|
55
|
-
).read()
|
|
56
|
-
)
|
|
File without changes
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
Shared helpers used by multiple PyPDFForm interfaces.
|
|
4
|
-
|
|
5
|
-
These utilities contain behavior that should stay consistent across the CLI,
|
|
6
|
-
web API, and any future user-facing entry points.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from collections.abc import Callable
|
|
10
|
-
from typing import NoReturn
|
|
11
|
-
|
|
12
|
-
from .. import PdfWrapper
|
|
13
|
-
from ..lib.middleware.base import Widget
|
|
14
|
-
|
|
15
|
-
WidgetKeyErrorHandler = Callable[[str, KeyError], NoReturn]
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def get_widget(
|
|
19
|
-
wrapper: PdfWrapper,
|
|
20
|
-
field: str,
|
|
21
|
-
key_error_handler: WidgetKeyErrorHandler,
|
|
22
|
-
) -> Widget:
|
|
23
|
-
"""
|
|
24
|
-
Look up a widget by field name.
|
|
25
|
-
|
|
26
|
-
Args:
|
|
27
|
-
wrapper (PdfWrapper): PDF wrapper containing form widgets.
|
|
28
|
-
field (str): Form field name to look up.
|
|
29
|
-
key_error_handler (WidgetKeyErrorHandler, optional): Interface-specific
|
|
30
|
-
error handler for missing field names. Defaults to None.
|
|
31
|
-
|
|
32
|
-
Returns:
|
|
33
|
-
Widget: The matching widget.
|
|
34
|
-
|
|
35
|
-
Raises:
|
|
36
|
-
KeyError: Raised when the widget name is not present and no handler is
|
|
37
|
-
supplied.
|
|
38
|
-
"""
|
|
39
|
-
try:
|
|
40
|
-
return wrapper.widgets[field]
|
|
41
|
-
except KeyError as exc:
|
|
42
|
-
return key_error_handler(f"Form field '{field}' does not exist.", exc)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|