PyPDFForm 5.2.5__tar.gz → 5.4.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.
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PKG-INFO +2 -13
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/__init__.py +1 -1
- pypdfform-5.4.0/PyPDFForm/cli/common.py +373 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/cli/create.py +75 -30
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/cli/entry.py +1 -1
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/cli/root.py +25 -9
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/cli/update.py +97 -28
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm.egg-info/PKG-INFO +2 -13
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm.egg-info/requires.txt +1 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/README.md +0 -12
- {pypdfform-5.2.5 → pypdfform-5.4.0}/pyproject.toml +5 -1
- pypdfform-5.2.5/PyPDFForm/cli/common.py +0 -259
- {pypdfform-5.2.5 → pypdfform-5.4.0}/LICENSE +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/cli/__init__.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/cli/inspect.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/cli/remove.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/cli/schemas/__init__.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/cli/schemas/create.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/cli/schemas/update.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/__init__.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/adapter.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/annotations/__init__.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/annotations/base.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/annotations/link.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/annotations/stamp.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/annotations/text.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/annotations/text_markup.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/assets/__init__.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/assets/bedrock.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/assets/blank.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/constants.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/coordinate.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/deprecation.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/egress.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/filler.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/font.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/hooks.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/image.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/middleware/__init__.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/middleware/base.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/middleware/checkbox.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/middleware/dropdown.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/middleware/image.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/middleware/radio.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/middleware/signature.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/middleware/text.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/patterns.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/raw/__init__.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/raw/circle.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/raw/ellipse.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/raw/image.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/raw/line.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/raw/rect.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/raw/text.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/template.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/types.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/utils.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/watermark.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/widgets/__init__.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/widgets/base.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/widgets/checkbox.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/widgets/dropdown.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/widgets/image.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/widgets/radio.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/widgets/signature.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/widgets/text.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm/lib/wrapper.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm.egg-info/SOURCES.txt +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm.egg-info/dependency_links.txt +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm.egg-info/entry_points.txt +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/PyPDFForm.egg-info/top_level.txt +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/setup.cfg +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/tests/test_bulk_create_fields.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/tests/test_create_widget.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/tests/test_draw_elements.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/tests/test_dropdown.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/tests/test_extract_middleware_attributes.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/tests/test_fill_max_length_text_field.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/tests/test_font_widths.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/tests/test_functional.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/tests/test_generate_appearance_streams.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/tests/test_js.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/tests/test_need_appearances.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/tests/test_paragraph.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/tests/test_signature.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/tests/test_use_full_widget_name.py +0 -0
- {pypdfform-5.2.5 → pypdfform-5.4.0}/tests/test_widget_attr_trigger.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPDFForm
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.4.0
|
|
4
4
|
Summary: The Python library & CLI for PDF forms.
|
|
5
5
|
Author: Jinge Li
|
|
6
6
|
License-Expression: MIT
|
|
@@ -33,6 +33,7 @@ Requires-Dist: reportlab<6.0.0,>=4.5.1
|
|
|
33
33
|
Provides-Extra: cli
|
|
34
34
|
Requires-Dist: typer<1.0.0,>=0.26.1; extra == "cli"
|
|
35
35
|
Requires-Dist: jsonschema<5.0.0,>=4.26.0; extra == "cli"
|
|
36
|
+
Requires-Dist: PyYAML<7.0.0,>=6.0.3; extra == "cli"
|
|
36
37
|
Provides-Extra: dev
|
|
37
38
|
Requires-Dist: coverage<8.0.0,>=7.14.1; extra == "dev"
|
|
38
39
|
Requires-Dist: mike<3.0.0,>=2.2.0; extra == "dev"
|
|
@@ -120,15 +121,3 @@ The official documentation can be found on [the GitHub page](https://chinapandam
|
|
|
120
121
|
<!-- TODO: remove WIP when finish recording -->
|
|
121
122
|
* [(WIP) Video Tutorials](https://youtube.com/playlist?list=PLNz_PBu1QA-gzYg5BvyOO98q15u5Xve1L&si=8MWasKEckBzY-NRQ)
|
|
122
123
|
* [Chicago Python User Group - Dec 14, 2023](https://youtu.be/8t1RdAKwr9w?si=TLgumBNXv9H8szSn)
|
|
123
|
-
|
|
124
|
-
## Star History
|
|
125
|
-
|
|
126
|
-
This project is maintained entirely in my spare time. If you like the project please consider starring the GitHub repository. It is the best way to keep me motivated and continue making the project better.
|
|
127
|
-
|
|
128
|
-
<a href="https://www.star-history.com/?repos=chinapandaman%2FPyPDFForm&type=date&logscale=&legend=top-left">
|
|
129
|
-
<picture>
|
|
130
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=chinapandaman/PyPDFForm&type=date&theme=dark&legend=top-left" />
|
|
131
|
-
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=chinapandaman/PyPDFForm&type=date&legend=top-left" />
|
|
132
|
-
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=chinapandaman/PyPDFForm&type=date&legend=top-left" />
|
|
133
|
-
</picture>
|
|
134
|
-
</a>
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
This module provides shared helpers for PyPDFForm CLI commands.
|
|
4
|
+
|
|
5
|
+
It contains utilities for loading structured command input, registering custom
|
|
6
|
+
fonts once per command invocation, and converting grouped element definitions
|
|
7
|
+
into the objects expected by `PdfWrapper` methods.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Annotated, Any, NoReturn
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
import yaml
|
|
16
|
+
from jsonschema import ValidationError, validate
|
|
17
|
+
|
|
18
|
+
from .. import PdfWrapper
|
|
19
|
+
from ..lib.middleware.base import Widget
|
|
20
|
+
|
|
21
|
+
INPUT_PDF = Annotated[
|
|
22
|
+
Path,
|
|
23
|
+
typer.Argument(
|
|
24
|
+
exists=True,
|
|
25
|
+
file_okay=True,
|
|
26
|
+
dir_okay=False,
|
|
27
|
+
readable=True,
|
|
28
|
+
resolve_path=True,
|
|
29
|
+
help="Input PDF path.",
|
|
30
|
+
),
|
|
31
|
+
]
|
|
32
|
+
REQUIRED_OUTPUT_PDF = Annotated[
|
|
33
|
+
Path,
|
|
34
|
+
typer.Option(
|
|
35
|
+
"--output",
|
|
36
|
+
"-o",
|
|
37
|
+
file_okay=True,
|
|
38
|
+
dir_okay=False,
|
|
39
|
+
writable=True,
|
|
40
|
+
resolve_path=True,
|
|
41
|
+
help="Output PDF path.",
|
|
42
|
+
),
|
|
43
|
+
]
|
|
44
|
+
OPTIONAL_OUTPUT_PDF = Annotated[
|
|
45
|
+
Path | None,
|
|
46
|
+
typer.Option(
|
|
47
|
+
"--output",
|
|
48
|
+
"-o",
|
|
49
|
+
file_okay=True,
|
|
50
|
+
dir_okay=False,
|
|
51
|
+
writable=True,
|
|
52
|
+
resolve_path=True,
|
|
53
|
+
help="Output PDF path. Overwrites the input when omitted.",
|
|
54
|
+
),
|
|
55
|
+
]
|
|
56
|
+
FIELD_NAME = Annotated[str, typer.Option("--field", help="Form field name.")]
|
|
57
|
+
FIELD_NAMES = Annotated[
|
|
58
|
+
list[str],
|
|
59
|
+
typer.Option(
|
|
60
|
+
"--field",
|
|
61
|
+
help="Form field name. Repeat this option to select multiple fields.",
|
|
62
|
+
),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def data_file_option(help_text: str):
|
|
67
|
+
"""
|
|
68
|
+
Creates the common validated structured data file option.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
help_text (str): Help text to display for the option.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
typer.Option: A configured `--file` / `-f` option for file input.
|
|
75
|
+
"""
|
|
76
|
+
return typer.Option(
|
|
77
|
+
"--file",
|
|
78
|
+
"-f",
|
|
79
|
+
exists=True,
|
|
80
|
+
file_okay=True,
|
|
81
|
+
dir_okay=False,
|
|
82
|
+
readable=True,
|
|
83
|
+
resolve_path=True,
|
|
84
|
+
help=help_text,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def cli_bad_parameter(
|
|
89
|
+
message: str,
|
|
90
|
+
param_hint: str,
|
|
91
|
+
cause: BaseException | None = None,
|
|
92
|
+
) -> NoReturn:
|
|
93
|
+
"""
|
|
94
|
+
Raises a Typer input error with a stable CLI message.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
message (str): Error message to display to the CLI user.
|
|
98
|
+
param_hint (str): CLI parameter associated with the error.
|
|
99
|
+
cause (BaseException, optional): Original exception that caused the CLI
|
|
100
|
+
error. Defaults to None.
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
typer.BadParameter: Raised with the provided message and parameter hint.
|
|
104
|
+
"""
|
|
105
|
+
if cause is None:
|
|
106
|
+
raise typer.BadParameter(message, param_hint=param_hint)
|
|
107
|
+
|
|
108
|
+
raise typer.BadParameter(message, param_hint=param_hint) from cause
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _validation_error_path(exc: ValidationError) -> str:
|
|
112
|
+
"""
|
|
113
|
+
Builds a dotted input data path for a validation error.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
exc (ValidationError): The JSON schema validation error.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
str: Dotted path for the failing instance location.
|
|
120
|
+
"""
|
|
121
|
+
return ".".join(str(each) for each in exc.absolute_path)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _input_file_kind(data: Path) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Gets the structured data format to use for a CLI input file.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
data (Path): Input file path.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
str: The upper-case format name for user-facing messages.
|
|
133
|
+
"""
|
|
134
|
+
if data.suffix.lower() in {".yaml", ".yml"}:
|
|
135
|
+
return "YAML"
|
|
136
|
+
|
|
137
|
+
return "JSON"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _validate_input_data(
|
|
141
|
+
input_data: Any,
|
|
142
|
+
schema: dict,
|
|
143
|
+
source: str,
|
|
144
|
+
param_hint: str,
|
|
145
|
+
) -> Any:
|
|
146
|
+
"""
|
|
147
|
+
Validates parsed CLI input against a JSON schema.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
input_data (Any): Parsed input data to validate.
|
|
151
|
+
schema (dict): JSON schema to validate against.
|
|
152
|
+
source (str): User-facing description of the input source.
|
|
153
|
+
param_hint (str): CLI parameter associated with the input.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Any: The validated input data.
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
typer.BadParameter: Raised when validation fails.
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
validate(instance=input_data, schema=schema)
|
|
163
|
+
except ValidationError as exc:
|
|
164
|
+
error_path = _validation_error_path(exc)
|
|
165
|
+
location = f" at {error_path}" if error_path else ""
|
|
166
|
+
cli_bad_parameter(
|
|
167
|
+
f"Invalid {source}{location}: {exc.message}",
|
|
168
|
+
param_hint=param_hint,
|
|
169
|
+
cause=exc,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
return input_data
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def load_data_file(data: Path, schema: dict, param_hint: str) -> Any:
|
|
176
|
+
"""
|
|
177
|
+
Loads a YAML or JSON CLI input file and validates it against a schema.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
data (Path): YAML or JSON file path.
|
|
181
|
+
schema (dict): JSON schema to validate against.
|
|
182
|
+
param_hint (str): CLI parameter associated with the input file.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Any: Parsed and validated input data.
|
|
186
|
+
|
|
187
|
+
Raises:
|
|
188
|
+
typer.BadParameter: Raised when the file cannot be loaded or validation
|
|
189
|
+
fails.
|
|
190
|
+
"""
|
|
191
|
+
input_kind = _input_file_kind(data)
|
|
192
|
+
try:
|
|
193
|
+
with open(data, "r", encoding="utf-8") as f:
|
|
194
|
+
input_data = yaml.safe_load(f) if input_kind == "YAML" else json.load(f)
|
|
195
|
+
except (OSError, json.JSONDecodeError, yaml.YAMLError) as exc:
|
|
196
|
+
cli_bad_parameter(
|
|
197
|
+
f"Invalid {input_kind} file: {exc}",
|
|
198
|
+
param_hint=param_hint,
|
|
199
|
+
cause=exc,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return _validate_input_data(
|
|
203
|
+
input_data,
|
|
204
|
+
schema,
|
|
205
|
+
f"{input_kind} file",
|
|
206
|
+
param_hint,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def load_data_options(options: list[str], schema: dict, param_hint: str) -> Any:
|
|
211
|
+
"""
|
|
212
|
+
Parses command-line data options and validates them against a schema.
|
|
213
|
+
|
|
214
|
+
Options use ``--name value`` syntax. Each value is parsed with
|
|
215
|
+
:func:`yaml.safe_load`.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
options (list[str]): Unrecognized command arguments captured by Typer.
|
|
219
|
+
schema (dict): JSON schema to validate the resulting option mapping
|
|
220
|
+
against.
|
|
221
|
+
param_hint (str): CLI parameter description used in error messages.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Any: Parsed and validated option data.
|
|
225
|
+
|
|
226
|
+
Raises:
|
|
227
|
+
typer.BadParameter: Raised when the options cannot be parsed or the
|
|
228
|
+
resulting mapping fails validation.
|
|
229
|
+
"""
|
|
230
|
+
if any(not option.startswith("--") for option in options[::2]):
|
|
231
|
+
cli_bad_parameter(
|
|
232
|
+
"Use '--name value' pairs with valid values.",
|
|
233
|
+
param_hint=param_hint,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
input_data = {
|
|
238
|
+
option[2:]: yaml.safe_load(value)
|
|
239
|
+
for option, value in zip(options[::2], options[1::2], strict=True)
|
|
240
|
+
}
|
|
241
|
+
except (ValueError, yaml.YAMLError) as exc:
|
|
242
|
+
cli_bad_parameter(
|
|
243
|
+
"Use '--name value' pairs with valid values.",
|
|
244
|
+
param_hint=param_hint,
|
|
245
|
+
cause=exc,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
return _validate_input_data(input_data, schema, "CLI options", param_hint)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def get_widget(wrapper: PdfWrapper, field: str, param_hint: str) -> Widget:
|
|
252
|
+
"""
|
|
253
|
+
Look up a widget and report missing names as CLI input errors.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
wrapper (PdfWrapper): PDF wrapper containing form widgets.
|
|
257
|
+
field (str): Form field name to look up.
|
|
258
|
+
param_hint (str): CLI parameter associated with the field name.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Widget: The matching widget.
|
|
262
|
+
|
|
263
|
+
Raises:
|
|
264
|
+
typer.BadParameter: Raised when the widget name is not present.
|
|
265
|
+
"""
|
|
266
|
+
try:
|
|
267
|
+
return wrapper.widgets[field]
|
|
268
|
+
except KeyError as exc:
|
|
269
|
+
cli_bad_parameter(
|
|
270
|
+
f"Form field '{field}' does not exist.",
|
|
271
|
+
param_hint=param_hint,
|
|
272
|
+
cause=exc,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def handle_font_registration(
|
|
277
|
+
obj: PdfWrapper, params: dict, registered_font: dict
|
|
278
|
+
) -> None:
|
|
279
|
+
"""
|
|
280
|
+
Registers a custom font referenced by CLI input.
|
|
281
|
+
|
|
282
|
+
CLI input may provide a file path in a `font` parameter. This helper
|
|
283
|
+
registers each unique font path on the supplied `PdfWrapper` once, assigns
|
|
284
|
+
it a generated internal font name, and mutates `params["font"]` to that
|
|
285
|
+
registered name so downstream field or element constructors can use it.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
obj (PdfWrapper): The wrapper for the PDF currently being modified.
|
|
289
|
+
params (dict): The element or widget parameters loaded from CLI input.
|
|
290
|
+
This dictionary is mutated when it contains a `font` key.
|
|
291
|
+
registered_font (dict): Mapping of resolved source font paths to
|
|
292
|
+
generated `PdfWrapper` font names for the current command
|
|
293
|
+
invocation.
|
|
294
|
+
"""
|
|
295
|
+
if "font" in params:
|
|
296
|
+
font_path = str(Path(params["font"]).resolve())
|
|
297
|
+
if font_path not in registered_font:
|
|
298
|
+
font_name = f"new_font_{len(registered_font)}"
|
|
299
|
+
obj.register_font(font_name, font_path)
|
|
300
|
+
registered_font[font_path] = font_name
|
|
301
|
+
params["font"] = registered_font[font_path]
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def create_elements_from_file(
|
|
305
|
+
pdf: Path,
|
|
306
|
+
data: Path | None,
|
|
307
|
+
element_map: dict,
|
|
308
|
+
schema: dict,
|
|
309
|
+
method_name: str,
|
|
310
|
+
ctx: typer.Context,
|
|
311
|
+
file_param_hint: str,
|
|
312
|
+
options_param_hint: str,
|
|
313
|
+
output: Path | None = None,
|
|
314
|
+
element_type: str | None = None,
|
|
315
|
+
) -> None:
|
|
316
|
+
"""
|
|
317
|
+
Creates PDF elements from a data file or command-line options.
|
|
318
|
+
|
|
319
|
+
A data file groups element definitions by type, such as `text`, `image`, or
|
|
320
|
+
`highlight`. Without a data file, one element of `element_type` is created
|
|
321
|
+
from the additional command-line options stored in ``ctx.args``. The data
|
|
322
|
+
file takes precedence when both input forms are supplied. Each definition
|
|
323
|
+
is constructed after optional font registration and passed to `method_name`
|
|
324
|
+
on `PdfWrapper`.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
pdf (Path): The path to the input PDF file.
|
|
328
|
+
data (Path, optional): The path to the input file containing grouped
|
|
329
|
+
element definitions. Defaults to None.
|
|
330
|
+
element_map (dict): Mapping from input group names to element classes
|
|
331
|
+
or callables used to construct each object.
|
|
332
|
+
schema (dict): JSON schema used to validate file or command-line input.
|
|
333
|
+
method_name (str): Name of the `PdfWrapper` method that accepts the
|
|
334
|
+
constructed elements, such as `bulk_create_fields`, `draw`, or
|
|
335
|
+
`annotate`.
|
|
336
|
+
ctx (typer.Context): Typer context containing global wrapper options in
|
|
337
|
+
`ctx.obj`.
|
|
338
|
+
file_param_hint (str): CLI parameter associated with the input file.
|
|
339
|
+
options_param_hint (str): CLI parameter description associated with
|
|
340
|
+
command-line options.
|
|
341
|
+
output (Path, optional): Path where the modified PDF should be saved. If
|
|
342
|
+
omitted, the input PDF is overwritten. Defaults to None.
|
|
343
|
+
element_type (str, optional): Element type selected for command-line
|
|
344
|
+
input. Defaults to None.
|
|
345
|
+
"""
|
|
346
|
+
if data is not None:
|
|
347
|
+
input_data = load_data_file(data, schema, file_param_hint)
|
|
348
|
+
else:
|
|
349
|
+
if element_type not in element_map:
|
|
350
|
+
choices = ", ".join(element_map)
|
|
351
|
+
cli_bad_parameter(
|
|
352
|
+
f"Choose one of: {choices}.",
|
|
353
|
+
param_hint="--type",
|
|
354
|
+
)
|
|
355
|
+
input_data = {
|
|
356
|
+
element_type: [
|
|
357
|
+
load_data_options(
|
|
358
|
+
ctx.args,
|
|
359
|
+
schema["properties"][element_type]["items"],
|
|
360
|
+
options_param_hint,
|
|
361
|
+
)
|
|
362
|
+
]
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
obj = PdfWrapper(str(pdf), **ctx.obj)
|
|
366
|
+
ungrouped_input = []
|
|
367
|
+
registered_font = {}
|
|
368
|
+
for k, v in input_data.items():
|
|
369
|
+
for each in v:
|
|
370
|
+
handle_font_registration(obj, each, registered_font)
|
|
371
|
+
ungrouped_input.append(element_map[k](**each))
|
|
372
|
+
|
|
373
|
+
getattr(obj, method_name)(ungrouped_input).write(output or pdf)
|
|
@@ -4,8 +4,8 @@ This module defines CLI commands for creating PDF files and PDF content.
|
|
|
4
4
|
|
|
5
5
|
It exposes the `create` command group for blank PDFs, extracted page ranges,
|
|
6
6
|
merged PDFs, form fields, raw drawn elements, annotations, and coordinate grid
|
|
7
|
-
views. Commands in this module translate command-line arguments or grouped
|
|
8
|
-
input into `PdfWrapper`, `BlankPage`, `Fields`, `RawElements`, and
|
|
7
|
+
views. Commands in this module translate command-line arguments or grouped
|
|
8
|
+
YAML/JSON input into `PdfWrapper`, `BlankPage`, `Fields`, `RawElements`, and
|
|
9
9
|
`Annotations` operations.
|
|
10
10
|
"""
|
|
11
11
|
|
|
@@ -21,7 +21,7 @@ from .common import (
|
|
|
21
21
|
REQUIRED_OUTPUT_PDF,
|
|
22
22
|
cli_bad_parameter,
|
|
23
23
|
create_elements_from_file,
|
|
24
|
-
|
|
24
|
+
data_file_option,
|
|
25
25
|
)
|
|
26
26
|
from .schemas.create import ANNOTATION_SCHEMA, FIELD_SCHEMA, RAW_SCHEMA
|
|
27
27
|
|
|
@@ -188,30 +188,43 @@ def merge(
|
|
|
188
188
|
|
|
189
189
|
|
|
190
190
|
@create_cli.command(
|
|
191
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
191
192
|
no_args_is_help=True,
|
|
192
|
-
help="Add form fields
|
|
193
|
+
help="Add form fields from a YAML or JSON file or command-line options.",
|
|
193
194
|
)
|
|
194
195
|
def field(
|
|
195
196
|
ctx: typer.Context,
|
|
196
197
|
pdf: INPUT_PDF,
|
|
197
|
-
data: Annotated[
|
|
198
|
+
data: Annotated[
|
|
199
|
+
Path | None, data_file_option("YAML or JSON file with form field definitions.")
|
|
200
|
+
] = None,
|
|
198
201
|
output: OPTIONAL_OUTPUT_PDF = None,
|
|
202
|
+
element_type: Annotated[
|
|
203
|
+
str | None,
|
|
204
|
+
typer.Option("--type", help="Type of form field to create without --file."),
|
|
205
|
+
] = None,
|
|
199
206
|
) -> None:
|
|
200
207
|
"""
|
|
201
|
-
Add form fields
|
|
208
|
+
Add form fields from a data file or command-line options.
|
|
202
209
|
|
|
203
|
-
The command maps
|
|
204
|
-
PyPDFForm field classes, validates the input
|
|
205
|
-
|
|
206
|
-
`PdfWrapper.bulk_create_fields` before writing the modified PDF.
|
|
210
|
+
The command maps input groups such as `text`, `check`, and `signature` to
|
|
211
|
+
PyPDFForm field classes, validates the input against the CLI field schema,
|
|
212
|
+
creates the corresponding field objects, and calls
|
|
213
|
+
`PdfWrapper.bulk_create_fields` before writing the modified PDF. A data file
|
|
214
|
+
can define multiple fields grouped by type. Without `--file`, `--type` and
|
|
215
|
+
the field options define one field. The data file takes precedence when
|
|
216
|
+
both input forms are supplied.
|
|
207
217
|
|
|
208
218
|
Args:
|
|
209
219
|
ctx (typer.Context): Typer context containing global `PdfWrapper`
|
|
210
220
|
options in `ctx.obj`.
|
|
211
221
|
pdf (Path): Input PDF path.
|
|
212
|
-
data (Path): JSON file containing grouped form field
|
|
222
|
+
data (Path, optional): YAML or JSON file containing grouped form field
|
|
223
|
+
definitions. Defaults to None.
|
|
213
224
|
output (Path, optional): Output PDF path. If omitted, the input PDF is
|
|
214
225
|
overwritten. Defaults to None.
|
|
226
|
+
element_type (str, optional): Form field type used when `data` is
|
|
227
|
+
omitted. Defaults to None.
|
|
215
228
|
"""
|
|
216
229
|
field_map = {
|
|
217
230
|
"text": Fields.TextField,
|
|
@@ -228,36 +241,51 @@ def field(
|
|
|
228
241
|
schema=FIELD_SCHEMA,
|
|
229
242
|
method_name="bulk_create_fields",
|
|
230
243
|
ctx=ctx,
|
|
231
|
-
|
|
244
|
+
file_param_hint="--file",
|
|
245
|
+
options_param_hint="field options",
|
|
232
246
|
output=output,
|
|
247
|
+
element_type=element_type,
|
|
233
248
|
)
|
|
234
249
|
|
|
235
250
|
|
|
236
251
|
@create_cli.command(
|
|
252
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
237
253
|
no_args_is_help=True,
|
|
238
|
-
help="Draw
|
|
254
|
+
help="Draw elements from a YAML or JSON file or command-line options.",
|
|
239
255
|
)
|
|
240
256
|
def raw(
|
|
241
257
|
ctx: typer.Context,
|
|
242
258
|
pdf: INPUT_PDF,
|
|
243
|
-
data: Annotated[
|
|
259
|
+
data: Annotated[
|
|
260
|
+
Path | None, data_file_option("YAML or JSON file with raw element definitions.")
|
|
261
|
+
] = None,
|
|
244
262
|
output: OPTIONAL_OUTPUT_PDF = None,
|
|
263
|
+
element_type: Annotated[
|
|
264
|
+
str | None,
|
|
265
|
+
typer.Option("--type", help="Type of element to draw without --file."),
|
|
266
|
+
] = None,
|
|
245
267
|
) -> None:
|
|
246
268
|
"""
|
|
247
|
-
Draw
|
|
269
|
+
Draw elements from a data file or command-line options.
|
|
248
270
|
|
|
249
|
-
The command maps
|
|
250
|
-
raw element classes, validates the input
|
|
251
|
-
|
|
252
|
-
|
|
271
|
+
The command maps input groups such as `text`, `image`, and `rectangle` to
|
|
272
|
+
raw element classes, validates the input against the CLI raw element schema,
|
|
273
|
+
creates the corresponding drawable objects, and calls `PdfWrapper.draw`
|
|
274
|
+
before writing the modified PDF. A data file can define multiple elements
|
|
275
|
+
grouped by type. Without `--file`, `--type` and the element options define
|
|
276
|
+
one element. The data file takes precedence when both input forms are
|
|
277
|
+
supplied.
|
|
253
278
|
|
|
254
279
|
Args:
|
|
255
280
|
ctx (typer.Context): Typer context containing global `PdfWrapper`
|
|
256
281
|
options in `ctx.obj`.
|
|
257
282
|
pdf (Path): Input PDF path.
|
|
258
|
-
data (Path): JSON file containing grouped raw element
|
|
283
|
+
data (Path, optional): YAML or JSON file containing grouped raw element
|
|
284
|
+
definitions. Defaults to None.
|
|
259
285
|
output (Path, optional): Output PDF path. If omitted, the input PDF is
|
|
260
286
|
overwritten. Defaults to None.
|
|
287
|
+
element_type (str, optional): Element type used when `data` is omitted.
|
|
288
|
+
Defaults to None.
|
|
261
289
|
"""
|
|
262
290
|
raw_element_map = {
|
|
263
291
|
"text": RawElements.RawText,
|
|
@@ -274,36 +302,51 @@ def raw(
|
|
|
274
302
|
schema=RAW_SCHEMA,
|
|
275
303
|
method_name="draw",
|
|
276
304
|
ctx=ctx,
|
|
277
|
-
|
|
305
|
+
file_param_hint="--file",
|
|
306
|
+
options_param_hint="element options",
|
|
278
307
|
output=output,
|
|
308
|
+
element_type=element_type,
|
|
279
309
|
)
|
|
280
310
|
|
|
281
311
|
|
|
282
312
|
@create_cli.command(
|
|
313
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
283
314
|
no_args_is_help=True,
|
|
284
|
-
help="Add annotations
|
|
315
|
+
help="Add annotations from a YAML or JSON file or command-line options.",
|
|
285
316
|
)
|
|
286
317
|
def annotation(
|
|
287
318
|
ctx: typer.Context,
|
|
288
319
|
pdf: INPUT_PDF,
|
|
289
|
-
data: Annotated[
|
|
320
|
+
data: Annotated[
|
|
321
|
+
Path | None, data_file_option("YAML or JSON file with annotation definitions.")
|
|
322
|
+
] = None,
|
|
290
323
|
output: OPTIONAL_OUTPUT_PDF = None,
|
|
324
|
+
element_type: Annotated[
|
|
325
|
+
str | None,
|
|
326
|
+
typer.Option("--type", help="Type of annotation to add without --file."),
|
|
327
|
+
] = None,
|
|
291
328
|
) -> None:
|
|
292
329
|
"""
|
|
293
|
-
Add annotations
|
|
330
|
+
Add annotations from a data file or command-line options.
|
|
294
331
|
|
|
295
|
-
The command maps
|
|
296
|
-
annotation classes, validates the input
|
|
297
|
-
|
|
298
|
-
`PdfWrapper.annotate` before writing the modified PDF.
|
|
332
|
+
The command maps input groups such as `text`, `link`, and `highlight` to
|
|
333
|
+
annotation classes, validates the input against the CLI annotation schema,
|
|
334
|
+
creates the corresponding annotation objects, and calls
|
|
335
|
+
`PdfWrapper.annotate` before writing the modified PDF. A data file can
|
|
336
|
+
define multiple annotations grouped by type. Without `--file`, `--type` and
|
|
337
|
+
the annotation options define one annotation. The data file takes
|
|
338
|
+
precedence when both input forms are supplied.
|
|
299
339
|
|
|
300
340
|
Args:
|
|
301
341
|
ctx (typer.Context): Typer context containing global `PdfWrapper`
|
|
302
342
|
options in `ctx.obj`.
|
|
303
343
|
pdf (Path): Input PDF path.
|
|
304
|
-
data (Path): JSON file containing grouped annotation
|
|
344
|
+
data (Path, optional): YAML or JSON file containing grouped annotation
|
|
345
|
+
definitions. Defaults to None.
|
|
305
346
|
output (Path, optional): Output PDF path. If omitted, the input PDF is
|
|
306
347
|
overwritten. Defaults to None.
|
|
348
|
+
element_type (str, optional): Annotation type used when `data` is
|
|
349
|
+
omitted. Defaults to None.
|
|
307
350
|
"""
|
|
308
351
|
annotation_map = {
|
|
309
352
|
"text": Annotations.TextAnnotation,
|
|
@@ -321,8 +364,10 @@ def annotation(
|
|
|
321
364
|
schema=ANNOTATION_SCHEMA,
|
|
322
365
|
method_name="annotate",
|
|
323
366
|
ctx=ctx,
|
|
324
|
-
|
|
367
|
+
file_param_hint="--file",
|
|
368
|
+
options_param_hint="annotation options",
|
|
325
369
|
output=output,
|
|
370
|
+
element_type=element_type,
|
|
326
371
|
)
|
|
327
372
|
|
|
328
373
|
|
|
@@ -10,7 +10,7 @@ of an import traceback when those optional dependencies are absent.
|
|
|
10
10
|
import importlib
|
|
11
11
|
import sys
|
|
12
12
|
|
|
13
|
-
CLI_DEPENDENCIES = {"jsonschema", "typer"}
|
|
13
|
+
CLI_DEPENDENCIES = {"jsonschema", "typer", "yaml"}
|
|
14
14
|
CLI_INSTALL_HINT = 'pipx install "PyPDFForm[cli]"'
|
|
15
15
|
CLI_ENV_INSTALL_HINT = "pip install 'PyPDFForm[cli]'"
|
|
16
16
|
|