PyPDFForm 5.5.5__tar.gz → 6.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.
Files changed (85) hide show
  1. {pypdfform-5.5.5 → pypdfform-6.0.0}/PKG-INFO +3 -3
  2. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/__init__.py +1 -10
  3. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/cli/root.py +6 -17
  4. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/filler.py +11 -11
  5. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/hooks.py +8 -7
  6. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/middleware/base.py +1 -1
  7. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/utils.py +1 -1
  8. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/wrapper.py +4 -13
  9. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm.egg-info/PKG-INFO +3 -3
  10. {pypdfform-5.5.5 → pypdfform-6.0.0}/pyproject.toml +4 -4
  11. {pypdfform-5.5.5 → pypdfform-6.0.0}/tests/test_create_widget.py +6 -6
  12. {pypdfform-5.5.5 → pypdfform-6.0.0}/tests/test_dropdown.py +12 -12
  13. {pypdfform-5.5.5 → pypdfform-6.0.0}/tests/test_extract_middleware_attributes.py +2 -2
  14. {pypdfform-5.5.5 → pypdfform-6.0.0}/tests/test_fill_max_length_text_field.py +4 -3
  15. {pypdfform-5.5.5 → pypdfform-6.0.0}/tests/test_functional.py +18 -18
  16. {pypdfform-5.5.5 → pypdfform-6.0.0}/tests/test_paragraph.py +3 -3
  17. {pypdfform-5.5.5 → pypdfform-6.0.0}/LICENSE +0 -0
  18. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/cli/__init__.py +0 -0
  19. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/cli/common.py +0 -0
  20. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/cli/create.py +0 -0
  21. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/cli/entry.py +0 -0
  22. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/cli/inspect.py +0 -0
  23. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/cli/remove.py +0 -0
  24. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/cli/schemas/__init__.py +0 -0
  25. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/cli/schemas/create.py +0 -0
  26. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/cli/schemas/update.py +0 -0
  27. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/cli/update.py +0 -0
  28. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/__init__.py +0 -0
  29. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/adapter.py +0 -0
  30. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/annotations/__init__.py +0 -0
  31. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/annotations/base.py +0 -0
  32. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/annotations/link.py +0 -0
  33. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/annotations/stamp.py +0 -0
  34. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/annotations/text.py +0 -0
  35. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/annotations/text_markup.py +0 -0
  36. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/assets/__init__.py +0 -0
  37. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/assets/blank.py +0 -0
  38. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/constants.py +0 -0
  39. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/coordinate.py +0 -0
  40. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/deprecation.py +0 -0
  41. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/egress.py +0 -0
  42. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/font.py +0 -0
  43. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/image.py +0 -0
  44. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/middleware/__init__.py +0 -0
  45. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/middleware/checkbox.py +0 -0
  46. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/middleware/dropdown.py +0 -0
  47. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/middleware/image.py +0 -0
  48. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/middleware/radio.py +0 -0
  49. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/middleware/signature.py +0 -0
  50. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/middleware/text.py +0 -0
  51. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/patterns.py +0 -0
  52. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/raw/__init__.py +0 -0
  53. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/raw/circle.py +0 -0
  54. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/raw/ellipse.py +0 -0
  55. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/raw/image.py +0 -0
  56. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/raw/line.py +0 -0
  57. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/raw/rect.py +0 -0
  58. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/raw/text.py +0 -0
  59. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/template.py +0 -0
  60. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/types.py +0 -0
  61. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/watermark.py +0 -0
  62. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/widgets/__init__.py +0 -0
  63. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/widgets/base.py +0 -0
  64. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/widgets/checkbox.py +0 -0
  65. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/widgets/dropdown.py +0 -0
  66. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/widgets/image.py +0 -0
  67. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/widgets/radio.py +0 -0
  68. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/widgets/signature.py +0 -0
  69. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm/lib/widgets/text.py +0 -0
  70. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm.egg-info/SOURCES.txt +0 -0
  71. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm.egg-info/dependency_links.txt +0 -0
  72. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm.egg-info/entry_points.txt +0 -0
  73. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm.egg-info/requires.txt +0 -0
  74. {pypdfform-5.5.5 → pypdfform-6.0.0}/PyPDFForm.egg-info/top_level.txt +0 -0
  75. {pypdfform-5.5.5 → pypdfform-6.0.0}/README.md +0 -0
  76. {pypdfform-5.5.5 → pypdfform-6.0.0}/setup.cfg +0 -0
  77. {pypdfform-5.5.5 → pypdfform-6.0.0}/tests/test_bulk_create_fields.py +0 -0
  78. {pypdfform-5.5.5 → pypdfform-6.0.0}/tests/test_draw_elements.py +0 -0
  79. {pypdfform-5.5.5 → pypdfform-6.0.0}/tests/test_font_widths.py +0 -0
  80. {pypdfform-5.5.5 → pypdfform-6.0.0}/tests/test_generate_appearance_streams.py +0 -0
  81. {pypdfform-5.5.5 → pypdfform-6.0.0}/tests/test_js.py +0 -0
  82. {pypdfform-5.5.5 → pypdfform-6.0.0}/tests/test_need_appearances.py +0 -0
  83. {pypdfform-5.5.5 → pypdfform-6.0.0}/tests/test_signature.py +0 -0
  84. {pypdfform-5.5.5 → pypdfform-6.0.0}/tests/test_use_full_widget_name.py +0 -0
  85. {pypdfform-5.5.5 → pypdfform-6.0.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.5.5
3
+ Version: 6.0.0
4
4
  Summary: The Python library & CLI for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -12,16 +12,16 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: Operating System :: OS Independent
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Programming Language :: Python :: 3 :: Only
15
- Classifier: Programming Language :: Python :: 3.10
16
15
  Classifier: Programming Language :: Python :: 3.11
17
16
  Classifier: Programming Language :: Python :: 3.12
18
17
  Classifier: Programming Language :: Python :: 3.13
19
18
  Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
20
20
  Classifier: Topic :: File Formats
21
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
22
  Classifier: Topic :: Text Processing
23
23
  Classifier: Topic :: Utilities
24
- Requires-Python: >=3.10
24
+ Requires-Python: >=3.11
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
27
  Requires-Dist: cryptography<51.0.0,>=50.0.0
@@ -16,9 +16,7 @@ PyPDFForm aims to make PDF form automation straightforward whether it is used
16
16
  from Python code or from the command line.
17
17
  """
18
18
 
19
- import logging
20
-
21
- __version__ = "5.5.5"
19
+ __version__ = "6.0.0"
22
20
 
23
21
  from .lib.annotations import Annotations
24
22
  from .lib.assets.blank import BlankPage
@@ -28,13 +26,6 @@ from .lib.types import PdfArray
28
26
  from .lib.widgets import Fields
29
27
  from .lib.wrapper import PdfWrapper
30
28
 
31
- # TODO: figure out why `Annotation sizes differ:`
32
- for logger in [
33
- logging.getLogger(name) for name in getattr(logging.root.manager, "loggerDict")
34
- ]:
35
- if "pypdf" in logger.name:
36
- logger.setLevel(logging.ERROR)
37
-
38
29
  __all__ = [
39
30
  "PdfWrapper",
40
31
  "PdfArray",
@@ -108,14 +108,6 @@ def main(
108
108
  help="Generate form field appearance streams.",
109
109
  ),
110
110
  ] = False,
111
- preserve_metadata: Annotated[
112
- bool,
113
- typer.Option(
114
- "--preserve-metadata",
115
- help="Preserve input PDF metadata.",
116
- hidden=True,
117
- ),
118
- ] = False,
119
111
  use_full_widget_name: Annotated[
120
112
  bool,
121
113
  typer.Option(
@@ -138,15 +130,12 @@ def main(
138
130
  field appearances.
139
131
  generate_appearance_streams (bool): Whether to generate form field
140
132
  appearance streams while handling PDFs.
141
- preserve_metadata (bool): Deprecated compatibility option. Input PDF
142
- metadata is preserved automatically.
143
133
  use_full_widget_name (bool): Whether widget lookups should use full
144
134
  form field names.
145
135
  """
146
136
  ctx.obj = {
147
137
  "need_appearances": need_appearances,
148
138
  "generate_appearance_streams": generate_appearance_streams,
149
- "preserve_metadata": preserve_metadata,
150
139
  "use_full_widget_name": use_full_widget_name,
151
140
  }
152
141
 
@@ -163,10 +152,10 @@ def fill(
163
152
  Path | None, data_file_option("YAML or JSON file with form field values.")
164
153
  ] = None,
165
154
  output: OPTIONAL_OUTPUT_PDF = None,
166
- flatten: Annotated[
155
+ readonly: Annotated[
167
156
  bool,
168
- typer.Option("--flatten", help="Flatten form fields after filling."),
169
- ] = None,
157
+ typer.Option("--readonly", help="Make form fields read-only when filling."),
158
+ ] = False,
170
159
  ) -> None:
171
160
  """
172
161
  Fill an existing PDF form from a data file or command-line options.
@@ -187,8 +176,8 @@ def fill(
187
176
  options in ``ctx.args``. Defaults to None.
188
177
  output (Path, optional): Output PDF path. If omitted, the input PDF is
189
178
  overwritten. Defaults to None.
190
- flatten (bool, optional): Whether to flatten form fields after filling.
191
- Defaults to None.
179
+ readonly (bool): Whether to make form fields read-only when filling.
180
+ Defaults to False.
192
181
  """
193
182
  obj = PdfWrapper(str(pdf), **ctx.obj)
194
183
 
@@ -226,4 +215,4 @@ def fill(
226
215
  )
227
216
  input_data[k] = input_data[k]["path"]
228
217
 
229
- obj.fill(input_data, flatten=flatten).write(output or pdf)
218
+ obj.fill(input_data, readonly=readonly).write(output or pdf)
@@ -5,7 +5,7 @@ Module containing functions to fill PDF forms.
5
5
  This module provides the core functionality for filling PDF forms programmatically.
6
6
  It includes functions for handling various form field types, such as text fields,
7
7
  checkboxes, radio buttons, dropdowns, images, and signatures. The module also
8
- supports flattening the filled form to prevent further modifications.
8
+ supports making the filled form read-only to prevent further modifications.
9
9
  """
10
10
 
11
11
  from io import BytesIO
@@ -15,7 +15,7 @@ from pypdf import PdfWriter
15
15
  from pypdf.generic import DictionaryObject
16
16
 
17
17
  from .constants import Annots
18
- from .hooks import flatten_field
18
+ from .hooks import update_field_readonly
19
19
  from .image import get_draw_image_resolutions, get_image_dimensions
20
20
  from .middleware import WIDGET_TYPES
21
21
  from .middleware.checkbox import Checkbox
@@ -84,12 +84,12 @@ def update_widget(
84
84
  radio_button_tracker: Dict[str, int],
85
85
  images_to_draw_page: list,
86
86
  need_appearances: bool,
87
- flatten: bool,
87
+ readonly: bool,
88
88
  ) -> bool:
89
89
  """Updates a single widget's value and handles its properties.
90
90
 
91
91
  This function updates the value of a single PDF form widget based on its type. It sets
92
- the read-only flag first when flattening is requested, skips value updates when the
92
+ the read-only flag first when requested, skips value updates when the
93
93
  middleware value is ``None``, tracks radio option indices within each group, and
94
94
  prepares images or signatures for later drawing instead of writing image data directly
95
95
  into the annotation.
@@ -101,13 +101,13 @@ def update_widget(
101
101
  radio_button_tracker (Dict[str, int]): A tracker for radio button groups to manage their indices.
102
102
  images_to_draw_page (list): A list to store image data for the current page.
103
103
  need_appearances (bool): If True, skips updating appearance streams for certain fields.
104
- flatten (bool): Whether to flatten the widget to prevent further editing.
104
+ readonly (bool): Whether to make the widget read-only to prevent further editing.
105
105
 
106
106
  Returns:
107
107
  bool: True if an image or signature was prepared for drawing, False otherwise.
108
108
  """
109
- if flatten:
110
- flatten_field(annot, True)
109
+ if readonly:
110
+ update_field_readonly(annot, True)
111
111
  if widget.value is None:
112
112
  return False
113
113
 
@@ -137,7 +137,7 @@ def handle_image_drawing(
137
137
  """Merges prepared images and signatures with the filled PDF.
138
138
 
139
139
  This function takes the filled PDF and a dictionary of images to draw (from signatures
140
- or image fields), flattens that page-indexed structure into watermark drawing
140
+ or image fields), combines that page-indexed structure into watermark drawing
141
141
  instructions, and merges those image watermarks into the PDF.
142
142
 
143
143
  Args:
@@ -161,7 +161,7 @@ def fill(
161
161
  widgets: Dict[str, WIDGET_TYPES],
162
162
  need_appearances: bool,
163
163
  use_full_widget_name: bool,
164
- flatten: bool = False,
164
+ readonly: bool = False,
165
165
  ) -> tuple:
166
166
  """Fills a PDF template with the given widgets.
167
167
 
@@ -181,7 +181,7 @@ def fill(
181
181
  behavior for certain fields.
182
182
  use_full_widget_name (bool): Whether to use the full widget name when looking up widgets
183
183
  in the `widgets` dictionary.
184
- flatten (bool): Whether to flatten the filled PDF. Defaults to False.
184
+ readonly (bool): Whether to make form fields read-only. Defaults to False.
185
185
 
186
186
  Returns:
187
187
  tuple: A tuple containing the filled PDF as bytes and the image-drawn
@@ -211,7 +211,7 @@ def fill(
211
211
  radio_button_tracker,
212
212
  images_to_draw[page_num + 1],
213
213
  need_appearances,
214
- flatten,
214
+ readonly,
215
215
  )
216
216
 
217
217
  with BytesIO() as f:
@@ -4,9 +4,10 @@ This module defines widget hooks that allow for dynamic modification of PDF form
4
4
 
5
5
  It provides functions to trigger these hooks, enabling changes to text field properties
6
6
  like font, font size, color, alignment, and multiline settings, as well as the size
7
- of checkbox and radio button widgets. It also provides functions for flattening
8
- generic and radio button widgets. These hooks are triggered during the PDF form
9
- filling process, allowing for customization of the form's appearance and behavior.
7
+ of checkbox and radio button widgets. It also provides functions for changing the
8
+ editability of generic and radio button widgets. These hooks are triggered during
9
+ the PDF form filling process, allowing for customization of the form's appearance
10
+ and behavior.
10
11
  """
11
12
 
12
13
  import sys
@@ -362,10 +363,10 @@ def update_dropdown_choices(annot: DictionaryObject, val: list) -> None:
362
363
  )
363
364
 
364
365
 
365
- def flatten_field(annot: DictionaryObject, val: bool) -> None:
366
+ def update_field_readonly(annot: DictionaryObject, val: bool) -> None:
366
367
  """
367
- Flattens a generic annotation by setting or unsetting the ReadOnly flag,
368
- making it non-editable or editable based on the `val` parameter.
368
+ Sets or unsets the ReadOnly flag on a form field annotation,
369
+ making it read-only or editable based on the `val` parameter.
369
370
 
370
371
  This function modifies the Ff (flags) entry in the annotation dictionary to
371
372
  set or unset the ReadOnly flag, preventing or allowing the user from
@@ -373,7 +374,7 @@ def flatten_field(annot: DictionaryObject, val: bool) -> None:
373
374
 
374
375
  Args:
375
376
  annot (DictionaryObject): The annotation dictionary.
376
- val (bool): True to flatten (make read-only), False to unflatten (make editable).
377
+ val (bool): True to make the field read-only, False to make it editable.
377
378
  """
378
379
  _update_field_flag(annot, READ_ONLY, val)
379
380
 
@@ -42,7 +42,7 @@ class Widget:
42
42
  "y": "update_field_y",
43
43
  "width": "update_field_width",
44
44
  "height": "update_field_height",
45
- "readonly": "flatten_field",
45
+ "readonly": "update_field_readonly",
46
46
  "required": "update_field_required",
47
47
  "hidden": "update_field_hidden",
48
48
  "tooltip": "update_field_tooltip",
@@ -31,7 +31,7 @@ from .constants import (
31
31
 
32
32
  def remove_all_widgets(pdf: bytes) -> bytes:
33
33
  """
34
- Removes all widgets (form fields) from a PDF, effectively flattening the form.
34
+ Removes all widgets (form fields) from a PDF.
35
35
 
36
36
  This function takes a PDF as a bytes stream, removes all of its interactive
37
37
  form field annotations from each page, and returns the modified PDF as a
@@ -37,7 +37,6 @@ from .adapter import (
37
37
  fp_or_f_obj_or_stream_to_stream,
38
38
  )
39
39
  from .coordinate import generate_coordinate_grid
40
- from .deprecation import deprecation_notice
41
40
  from .egress import (
42
41
  appearance_streams_handler,
43
42
  rebuild_acroform_fields,
@@ -109,8 +108,6 @@ class PdfWrapper:
109
108
  - `use_full_widget_name` (bool): Whether to use the full widget name when filling the form.
110
109
  - `need_appearances` (bool): Whether to set the `NeedAppearances` flag in the PDF's AcroForm dictionary.
111
110
  - `generate_appearance_streams` (bool): Whether to explicitly generate appearance streams for all form fields using pikepdf.
112
- - `preserve_metadata` (bool): Deprecated compatibility attribute;
113
- input PDF metadata is preserved automatically.
114
111
  - `title` (str | None): The title stored in the PDF's document
115
112
  metadata. A non-None value replaces the existing title; None
116
113
  preserves it.
@@ -121,7 +118,6 @@ class PdfWrapper:
121
118
  ("use_full_widget_name", False),
122
119
  ("need_appearances", False),
123
120
  ("generate_appearance_streams", False),
124
- ("preserve_metadata", False),
125
121
  ("title", None),
126
122
  ]
127
123
 
@@ -137,9 +133,7 @@ class PdfWrapper:
137
133
  The template is normalized to bytes and existing widgets are loaded immediately.
138
134
  The title and document-open JavaScript remain in the PDF stream and are read
139
135
  lazily when their properties are accessed. A non-None `title` keyword updates
140
- the title in the PDF stream; None leaves the template's title unchanged. The
141
- deprecated `preserve_metadata` keyword is accepted for backward compatibility
142
- and emits a deprecation warning. Enabling
136
+ the title in the PDF stream; None leaves the template's title unchanged. Enabling
143
137
  `generate_appearance_streams` also enables `need_appearances`.
144
138
 
145
139
  Args:
@@ -167,10 +161,6 @@ class PdfWrapper:
167
161
 
168
162
  # sets attrs from kwargs
169
163
  for attr, default in self.USER_PARAMS:
170
- if attr == "preserve_metadata" and attr in kwargs:
171
- deprecation_notice("", "preserve_metadata").emit_notice(
172
- self, "__init__"
173
- )
174
164
  setattr(self, attr, kwargs.get(attr, default))
175
165
 
176
166
  if getattr(self, "generate_appearance_streams") is True:
@@ -676,7 +666,8 @@ class PdfWrapper:
676
666
  are form field names and values are the data to fill the fields with.
677
667
  Values can be strings, booleans, integers, file-like objects, or bytes.
678
668
  **kwargs: Additional keyword arguments:
679
- - `flatten` (bool): Whether to flatten the form after filling, making the fields read-only (default: False).
669
+ - `readonly` (bool): Whether to make form fields read-only when filling (default: False).
670
+ False does not make existing read-only fields editable.
680
671
 
681
672
  Returns:
682
673
  PdfWrapper: The `PdfWrapper` object, allowing for method chaining.
@@ -691,7 +682,7 @@ class PdfWrapper:
691
682
  self.widgets,
692
683
  need_appearances=getattr(self, "need_appearances"),
693
684
  use_full_widget_name=getattr(self, "use_full_widget_name"),
694
- flatten=kwargs.get("flatten", False),
685
+ readonly=kwargs.get("readonly", False),
695
686
  )
696
687
 
697
688
  if image_drawn_stream is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 5.5.5
3
+ Version: 6.0.0
4
4
  Summary: The Python library & CLI for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -12,16 +12,16 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: Operating System :: OS Independent
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Programming Language :: Python :: 3 :: Only
15
- Classifier: Programming Language :: Python :: 3.10
16
15
  Classifier: Programming Language :: Python :: 3.11
17
16
  Classifier: Programming Language :: Python :: 3.12
18
17
  Classifier: Programming Language :: Python :: 3.13
19
18
  Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
20
20
  Classifier: Topic :: File Formats
21
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
22
  Classifier: Topic :: Text Processing
23
23
  Classifier: Topic :: Utilities
24
- Requires-Python: >=3.10
24
+ Requires-Python: >=3.11
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
27
  Requires-Dist: cryptography<51.0.0,>=50.0.0
@@ -17,17 +17,17 @@ classifiers = [
17
17
  "Operating System :: OS Independent",
18
18
  "Programming Language :: Python :: 3",
19
19
  "Programming Language :: Python :: 3 :: Only",
20
- "Programming Language :: Python :: 3.10",
21
20
  "Programming Language :: Python :: 3.11",
22
21
  "Programming Language :: Python :: 3.12",
23
22
  "Programming Language :: Python :: 3.13",
24
23
  "Programming Language :: Python :: 3.14",
24
+ "Programming Language :: Python :: 3.15",
25
25
  "Topic :: File Formats",
26
26
  "Topic :: Software Development :: Libraries :: Python Modules",
27
27
  "Topic :: Text Processing",
28
28
  "Topic :: Utilities",
29
29
  ]
30
- requires-python = ">=3.10"
30
+ requires-python = ">=3.11"
31
31
  dependencies = [
32
32
  "cryptography>=50.0.0,<51.0.0",
33
33
  "fonttools>=4.63.0,<5.0.0",
@@ -104,14 +104,14 @@ select = [
104
104
  "PT",
105
105
  "PERF",
106
106
  "Q",
107
- "R",
107
+ "RET",
108
108
  "RSE",
109
109
  "SLF",
110
110
  "SLOT",
111
111
  "SIM",
112
112
  "T10",
113
113
  "TRY",
114
- "TCH",
114
+ "TC",
115
115
  "YTT",
116
116
  "W",
117
117
  "UP007",
@@ -311,13 +311,13 @@ def test_create_checkbox_persist_old_widgets_fill(
311
311
 
312
312
 
313
313
  @pytest.mark.requires_zlib_over_zlib_ng
314
- def test_create_widget_sejda_fill_flatten_before(sejda_template, pdf_samples, request):
314
+ def test_create_widget_sejda_fill_readonly_before(sejda_template, pdf_samples, request):
315
315
  expected_path = os.path.join(
316
- pdf_samples, "widget", "test_create_widget_sejda_fill_flatten_before.pdf"
316
+ pdf_samples, "widget", "test_create_widget_sejda_fill_readonly_before.pdf"
317
317
  )
318
318
  with open(expected_path, "rb+") as f:
319
319
  obj = PdfWrapper(sejda_template)
320
- obj.fill(obj.sample_data, flatten=True).bulk_create_fields(
320
+ obj.fill(obj.sample_data, readonly=True).bulk_create_fields(
321
321
  [
322
322
  Fields.TextField(
323
323
  name="new_text_field_widget",
@@ -345,9 +345,9 @@ def test_create_widget_sejda_fill_flatten_before(sejda_template, pdf_samples, re
345
345
 
346
346
 
347
347
  @pytest.mark.requires_zlib_over_zlib_ng
348
- def test_create_widget_sejda_fill_flatten_after(sejda_template, pdf_samples, request):
348
+ def test_create_widget_sejda_fill_readonly_after(sejda_template, pdf_samples, request):
349
349
  expected_path = os.path.join(
350
- pdf_samples, "widget", "test_create_widget_sejda_fill_flatten_after.pdf"
350
+ pdf_samples, "widget", "test_create_widget_sejda_fill_readonly_after.pdf"
351
351
  )
352
352
  with open(expected_path, "rb+") as f:
353
353
  obj = PdfWrapper(sejda_template)
@@ -366,7 +366,7 @@ def test_create_widget_sejda_fill_flatten_after(sejda_template, pdf_samples, req
366
366
  font_color=(0, 0, 1),
367
367
  )
368
368
  ]
369
- ).fill(obj.sample_data, flatten=True)
369
+ ).fill(obj.sample_data, readonly=True)
370
370
  assert obj.schema["properties"]["new_text_field_widget"]["type"] == "string"
371
371
 
372
372
  request.config.results["expected_path"] = expected_path
@@ -37,9 +37,9 @@ def test_dropdown_not_specified(sample_template_with_dropdown):
37
37
  )
38
38
 
39
39
 
40
- def test_dropdown_one_flatten(sample_template_with_dropdown, pdf_samples, request):
40
+ def test_dropdown_one_readonly(sample_template_with_dropdown, pdf_samples, request):
41
41
  expected_path = os.path.join(
42
- pdf_samples, "dropdown", "test_dropdown_one_flatten.pdf"
42
+ pdf_samples, "dropdown", "test_dropdown_one_readonly.pdf"
43
43
  )
44
44
  with open(expected_path, "rb+") as f:
45
45
  obj = PdfWrapper(sample_template_with_dropdown).fill(
@@ -53,7 +53,7 @@ def test_dropdown_one_flatten(sample_template_with_dropdown, pdf_samples, reques
53
53
  "radio_1": 1,
54
54
  "dropdown_1": 0,
55
55
  },
56
- flatten=True,
56
+ readonly=True,
57
57
  )
58
58
 
59
59
  request.config.results["expected_path"] = expected_path
@@ -87,11 +87,11 @@ def test_dropdown_alignment(dropdown_alignment, pdf_samples, request):
87
87
 
88
88
 
89
89
  @pytest.mark.requires_zlib_over_zlib_ng
90
- def test_dropdown_alignment_flatten_then_unflatten(
90
+ def test_dropdown_alignment_readonly_then_editable(
91
91
  dropdown_alignment, pdf_samples, request
92
92
  ):
93
93
  expected_path = os.path.join(
94
- pdf_samples, "dropdown", "test_dropdown_alignment_flatten_then_unflatten.pdf"
94
+ pdf_samples, "dropdown", "test_dropdown_alignment_readonly_then_editable.pdf"
95
95
  )
96
96
  with open(expected_path, "rb+") as f:
97
97
  obj = PdfWrapper(dropdown_alignment).fill(
@@ -100,7 +100,7 @@ def test_dropdown_alignment_flatten_then_unflatten(
100
100
  "dropdown_center": 1,
101
101
  "dropdown_right": 2,
102
102
  },
103
- flatten=True,
103
+ readonly=True,
104
104
  )
105
105
  obj.widgets["dropdown_center"].readonly = False
106
106
 
@@ -138,11 +138,11 @@ def test_dropdown_alignment_sejda(dropdown_alignment_sejda, pdf_samples, request
138
138
  assert obj.read() == expected
139
139
 
140
140
 
141
- def test_dropdown_alignment_sejda_flatten(
141
+ def test_dropdown_alignment_sejda_readonly(
142
142
  dropdown_alignment_sejda, pdf_samples, request
143
143
  ):
144
144
  expected_path = os.path.join(
145
- pdf_samples, "dropdown", "test_dropdown_alignment_sejda_flatten.pdf"
145
+ pdf_samples, "dropdown", "test_dropdown_alignment_sejda_readonly.pdf"
146
146
  )
147
147
  with open(
148
148
  expected_path,
@@ -154,7 +154,7 @@ def test_dropdown_alignment_sejda_flatten(
154
154
  "dropdown_center": 1,
155
155
  "dropdown_right": 2,
156
156
  },
157
- flatten=True,
157
+ readonly=True,
158
158
  )
159
159
 
160
160
  request.config.results["expected_path"] = expected_path
@@ -166,13 +166,13 @@ def test_dropdown_alignment_sejda_flatten(
166
166
  assert obj.read() == expected
167
167
 
168
168
 
169
- def test_dropdown_alignment_sejda_flatten_then_unflatten(
169
+ def test_dropdown_alignment_sejda_readonly_then_editable(
170
170
  dropdown_alignment_sejda, pdf_samples, request
171
171
  ):
172
172
  expected_path = os.path.join(
173
173
  pdf_samples,
174
174
  "dropdown",
175
- "test_dropdown_alignment_sejda_flatten_then_unflatten.pdf",
175
+ "test_dropdown_alignment_sejda_readonly_then_editable.pdf",
176
176
  )
177
177
  with open(
178
178
  expected_path,
@@ -184,7 +184,7 @@ def test_dropdown_alignment_sejda_flatten_then_unflatten(
184
184
  "dropdown_center": 1,
185
185
  "dropdown_right": 2,
186
186
  },
187
- flatten=True,
187
+ readonly=True,
188
188
  )
189
189
  obj.widgets["dropdown_center"].readonly = False
190
190
 
@@ -73,7 +73,7 @@ def test_addition_operator_3_times_values(template_stream, data_dict):
73
73
 
74
74
 
75
75
  def test_field_readonly(pdf_samples):
76
- obj = PdfWrapper(os.path.join(pdf_samples, "test_fill_flatten_then_unflatten.pdf"))
76
+ obj = PdfWrapper(os.path.join(pdf_samples, "test_fill_readonly_then_editable.pdf"))
77
77
 
78
78
  for k, v in obj.widgets.items():
79
79
  if k in ["test_2", "check_3"]:
@@ -83,7 +83,7 @@ def test_field_readonly(pdf_samples):
83
83
 
84
84
 
85
85
  def test_field_readonly_sejda(pdf_samples):
86
- obj = PdfWrapper(os.path.join(pdf_samples, "test_fill_sejda_flatten.pdf"))
86
+ obj = PdfWrapper(os.path.join(pdf_samples, "test_fill_sejda_readonly.pdf"))
87
87
 
88
88
  for v in obj.widgets.values():
89
89
  assert v.readonly
@@ -63,11 +63,12 @@ def test_fill_comb_text_field_all_chars(
63
63
  assert obj.read() == expected
64
64
 
65
65
 
66
- def test_fill_comb_text_field_all_chars_flatten(
66
+ def test_fill_comb_text_field_all_chars_readonly(
67
67
  sample_template_with_comb_text_field, max_length_expected_directory, request
68
68
  ):
69
69
  expected_path = os.path.join(
70
- max_length_expected_directory, "test_fill_comb_text_field_all_chars_flatten.pdf"
70
+ max_length_expected_directory,
71
+ "test_fill_comb_text_field_all_chars_readonly.pdf",
71
72
  )
72
73
  with open(
73
74
  expected_path,
@@ -81,7 +82,7 @@ def test_fill_comb_text_field_all_chars_flatten(
81
82
  "Awesomeness": True,
82
83
  "Gender": 0,
83
84
  },
84
- flatten=True,
85
+ readonly=True,
85
86
  )
86
87
 
87
88
  request.config.results["expected_path"] = expected_path
@@ -121,10 +121,10 @@ def test_write_io(template_stream):
121
121
  assert PdfWrapper(buff.read()).widgets.keys() == obj.widgets.keys()
122
122
 
123
123
 
124
- def test_fill_flatten_then_unflatten(template_stream, pdf_samples, data_dict, request):
125
- expected_path = os.path.join(pdf_samples, "test_fill_flatten_then_unflatten.pdf")
124
+ def test_fill_readonly_then_editable(template_stream, pdf_samples, data_dict, request):
125
+ expected_path = os.path.join(pdf_samples, "test_fill_readonly_then_editable.pdf")
126
126
  with open(expected_path, "rb+") as f:
127
- obj = PdfWrapper(template_stream).fill(data_dict, flatten=True)
127
+ obj = PdfWrapper(template_stream).fill(data_dict, readonly=True)
128
128
  obj.widgets["test_2"].readonly = False
129
129
  obj.widgets["check_3"].readonly = False
130
130
 
@@ -178,11 +178,11 @@ def test_fill_with_customized_widgets(
178
178
 
179
179
 
180
180
  @pytest.mark.requires_zlib_over_zlib_ng
181
- def test_fill_with_customized_widgets_flatten(
181
+ def test_fill_with_customized_widgets_readonly(
182
182
  template_stream, pdf_samples, sample_font_stream, data_dict, request
183
183
  ):
184
184
  expected_path = os.path.join(
185
- pdf_samples, "test_fill_with_customized_widgets_flatten.pdf"
185
+ pdf_samples, "test_fill_with_customized_widgets_readonly.pdf"
186
186
  )
187
187
  with open(
188
188
  expected_path,
@@ -198,7 +198,7 @@ def test_fill_with_customized_widgets_flatten(
198
198
  obj.widgets["test_2"].font_color = (0, 1, 0)
199
199
  obj.fill(
200
200
  data_dict,
201
- flatten=True,
201
+ readonly=True,
202
202
  )
203
203
 
204
204
  request.config.results["expected_path"] = expected_path
@@ -230,10 +230,10 @@ def test_fill_with_varied_int_values(template_stream, pdf_samples, request):
230
230
  assert obj.read() == expected
231
231
 
232
232
 
233
- def test_fill_radiobutton_flatten(
233
+ def test_fill_radiobutton_readonly(
234
234
  template_with_radiobutton_stream, pdf_samples, request
235
235
  ):
236
- expected_path = os.path.join(pdf_samples, "test_fill_radiobutton_flatten.pdf")
236
+ expected_path = os.path.join(pdf_samples, "test_fill_radiobutton_readonly.pdf")
237
237
  with open(
238
238
  expected_path,
239
239
  "rb+",
@@ -244,7 +244,7 @@ def test_fill_radiobutton_flatten(
244
244
  "radio_2": 1,
245
245
  "radio_3": 2,
246
246
  },
247
- flatten=True,
247
+ readonly=True,
248
248
  )
249
249
 
250
250
  request.config.results["expected_path"] = expected_path
@@ -256,11 +256,11 @@ def test_fill_radiobutton_flatten(
256
256
  assert obj.read() == expected
257
257
 
258
258
 
259
- def test_fill_radiobutton_flatten_then_unflatten(
259
+ def test_fill_radiobutton_readonly_then_editable(
260
260
  template_with_radiobutton_stream, pdf_samples, request
261
261
  ):
262
262
  expected_path = os.path.join(
263
- pdf_samples, "test_fill_radiobutton_flatten_then_unflatten.pdf"
263
+ pdf_samples, "test_fill_radiobutton_readonly_then_editable.pdf"
264
264
  )
265
265
  with open(
266
266
  expected_path,
@@ -272,7 +272,7 @@ def test_fill_radiobutton_flatten_then_unflatten(
272
272
  "radio_2": 1,
273
273
  "radio_3": 2,
274
274
  },
275
- flatten=True,
275
+ readonly=True,
276
276
  )
277
277
  obj.widgets["radio_2"].readonly = False
278
278
 
@@ -302,15 +302,15 @@ def test_fill_sejda(sejda_template, pdf_samples, sejda_data, request):
302
302
  assert obj.read() == expected
303
303
 
304
304
 
305
- def test_fill_sejda_flatten(sejda_template, pdf_samples, sejda_data, request):
306
- expected_path = os.path.join(pdf_samples, "test_fill_sejda_flatten.pdf")
305
+ def test_fill_sejda_readonly(sejda_template, pdf_samples, sejda_data, request):
306
+ expected_path = os.path.join(pdf_samples, "test_fill_sejda_readonly.pdf")
307
307
  with open(
308
308
  expected_path,
309
309
  "rb+",
310
310
  ) as f:
311
311
  obj = PdfWrapper(sejda_template).fill(
312
312
  sejda_data,
313
- flatten=True,
313
+ readonly=True,
314
314
  )
315
315
 
316
316
  request.config.results["expected_path"] = expected_path
@@ -322,11 +322,11 @@ def test_fill_sejda_flatten(sejda_template, pdf_samples, sejda_data, request):
322
322
  assert obj.read() == expected
323
323
 
324
324
 
325
- def test_fill_sejda_flatten_then_unflatten(
325
+ def test_fill_sejda_readonly_then_editable(
326
326
  sejda_template, pdf_samples, sejda_data, request
327
327
  ):
328
328
  expected_path = os.path.join(
329
- pdf_samples, "test_fill_sejda_flatten_then_unflatten.pdf"
329
+ pdf_samples, "test_fill_sejda_readonly_then_editable.pdf"
330
330
  )
331
331
  with open(
332
332
  expected_path,
@@ -334,7 +334,7 @@ def test_fill_sejda_flatten_then_unflatten(
334
334
  ) as f:
335
335
  obj = PdfWrapper(sejda_template).fill(
336
336
  sejda_data,
337
- flatten=True,
337
+ readonly=True,
338
338
  )
339
339
  obj.widgets["buyer_name"].readonly = False
340
340
  obj.widgets["at_future_date"].readonly = False
@@ -25,16 +25,16 @@ def test_fill_sejda_complex(
25
25
  assert obj.read() == expected
26
26
 
27
27
 
28
- def test_fill_sejda_complex_flatten(
28
+ def test_fill_sejda_complex_readonly(
29
29
  sejda_template_complex, sejda_complex_data, pdf_samples, request
30
30
  ):
31
31
  expected_path = os.path.join(
32
- pdf_samples, "paragraph", "test_fill_sejda_complex_flatten.pdf"
32
+ pdf_samples, "paragraph", "test_fill_sejda_complex_readonly.pdf"
33
33
  )
34
34
  with open(expected_path, "rb+") as f:
35
35
  obj = PdfWrapper(sejda_template_complex).fill(
36
36
  sejda_complex_data,
37
- flatten=True,
37
+ readonly=True,
38
38
  )
39
39
 
40
40
  request.config.results["expected_path"] = expected_path
File without changes
File without changes
File without changes
File without changes