Skip to content

Commit 9620f9c

Browse files
Merge pull request #192 from chinapandaman/PPF-191
PPF-191: allowing setting element value to file path or file object
2 parents e86d9ba + 19671ff commit 9620f9c

File tree

4 files changed

+108
-5
lines changed

4 files changed

+108
-5
lines changed

PyPDFForm/middleware/element.py

+31-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
"""Contains element middleware."""
33

44
from enum import Enum
5-
from typing import Union
5+
from typing import BinaryIO, Union
66

77
from ..core.font import Font as FontCore
88
from ..core.image import Image as ImageCore
9+
from .adapter import FileAdapter
910
from .exceptions.element import (InvalidElementNameError,
1011
InvalidElementTypeError,
1112
InvalidElementValueError,
@@ -30,13 +31,21 @@ def __init__(
3031
self,
3132
element_name: str,
3233
element_type: "ElementType",
33-
element_value: Union[str, bool, bytes, int] = None,
34+
element_value: Union[str, bool, bytes, int, BinaryIO] = None,
3435
) -> None:
3536
"""Constructs all attributes for the Element object."""
3637

38+
if element_type == ElementType.image and (
39+
isinstance(element_value, (bytes, str))
40+
or FileAdapter().readable(element_value)
41+
):
42+
adapted = FileAdapter().fp_or_f_obj_or_stream_to_stream(element_value)
43+
if adapted is not None:
44+
element_value = adapted
45+
3746
self._name = element_name
3847
self._type = element_type
39-
self.value = element_value
48+
self._value = element_value
4049

4150
if element_type == ElementType.text:
4251
self.font = None
@@ -46,6 +55,25 @@ def __init__(
4655
self.text_y_offset = None
4756
self.text_wrap_length = None
4857

58+
@property
59+
def value(self) -> Union[str, bool, bytes, int, BinaryIO]:
60+
"""Value of the element."""
61+
62+
return self._value
63+
64+
@value.setter
65+
def value(self, v) -> None:
66+
"""Insures the value gets converted to bytes when set to fp or f object."""
67+
68+
if self._type == ElementType.image and (
69+
isinstance(v, (bytes, str)) or FileAdapter().readable(v)
70+
):
71+
adapted = FileAdapter().fp_or_f_obj_or_stream_to_stream(v)
72+
if adapted is not None:
73+
v = adapted
74+
75+
self._value = v
76+
4977
@property
5078
def name(self) -> str:
5179
"""Name of the element."""

docs/v2/api_reference.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ It currently supports the following based on the type of the element:
171171
1) A `string`, if the element is a `text`.
172172
2) A `boolean`, if the element is a `checkbox`.
173173
3) An `integer`, if the element is a `radio`.
174-
4) A valid image `bytes` stream, if the element is an `image`.
174+
4) A valid image `bytes` stream, if the element is an `image`. When setting the value of an `image` element explicitly,
175+
a file path or file object of the image is also accepted. In either case however the attribute will still be converted to a `bytes` stream.
175176

176177
### **name**
177178

@@ -190,7 +191,8 @@ It currently supports the following based on the type of the element:
190191
1) A `string`, if the element is a `text`.
191192
2) A `boolean`, if the element is a `checkbox`.
192193
3) An `integer`, if the element is a `radio`.
193-
4) A valid image `bytes` stream, if the element is an `image`.
194+
4) A valid image `bytes` stream, if the element is an `image`. When setting the value of an `image` element explicitly,
195+
a file path or file object of the image is also accepted. In either case however the attribute will still be converted to a `bytes` stream.
194196

195197
### **font** = *None*
196198

tests/functional/test_fill.py

+53
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,59 @@ def test_fill_images_f_obj_params(pdf_samples, image_samples):
469469
assert obj.stream[:32767] == expected[:32767]
470470

471471

472+
def test_fill_images_fp_params_explicitly_setting_elements(pdf_samples, image_samples):
473+
with open(os.path.join(pdf_samples, "sample_filled_images.pdf"), "rb+") as f:
474+
expected = f.read()
475+
476+
obj = PyPDFForm(
477+
os.path.join(pdf_samples, "sample_template_with_image_field.pdf"),
478+
simple_mode=False,
479+
)
480+
481+
obj.elements["image_1"].value = os.path.join(image_samples, "sample_image.jpg")
482+
obj.elements["image_2"].value = os.path.join(image_samples, "sample_image_2.jpg")
483+
obj.elements["image_3"].value = os.path.join(image_samples, "sample_image_3.jpg")
484+
485+
obj.fill({})
486+
487+
if os.name == "nt":
488+
assert len(obj.stream) == len(expected)
489+
assert obj.stream == expected
490+
else:
491+
assert obj.stream[:32767] == expected[:32767]
492+
493+
494+
def test_fill_images_f_obj_params_explicitly_setting_elements(
495+
pdf_samples, image_samples
496+
):
497+
with open(os.path.join(pdf_samples, "sample_filled_images.pdf"), "rb+") as f:
498+
expected = f.read()
499+
500+
with open(
501+
os.path.join(pdf_samples, "sample_template_with_image_field.pdf"), "rb+"
502+
) as template:
503+
with open(os.path.join(image_samples, "sample_image.jpg"), "rb+") as image:
504+
with open(
505+
os.path.join(image_samples, "sample_image_2.jpg"), "rb+"
506+
) as image_2:
507+
with open(
508+
os.path.join(image_samples, "sample_image_3.jpg"), "rb+"
509+
) as image_3:
510+
obj = PyPDFForm(template, simple_mode=False)
511+
512+
obj.elements["image_1"].value = image
513+
obj.elements["image_2"].value = image_2
514+
obj.elements["image_3"].value = image_3
515+
516+
obj.fill({})
517+
518+
if os.name == "nt":
519+
assert len(obj.stream) == len(expected)
520+
assert obj.stream == expected
521+
else:
522+
assert obj.stream[:32767] == expected[:32767]
523+
524+
472525
def test_simple_fill_radiobutton(pdf_samples, template_with_radiobutton_stream):
473526
with open(
474527
os.path.join(pdf_samples, "sample_filled_radiobutton_simple.pdf"), "rb+"

tests/unit/test_element.py

+20
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,23 @@ def test_invalid_constants(text_element):
253253
assert False
254254
except InvalidElementTypeError:
255255
assert True
256+
257+
258+
def test_value_set_and_get_with_fp_and_f_obj(image_samples, image_stream):
259+
path = os.path.join(image_samples, "sample_image.jpg")
260+
261+
foo = Element("foo", ElementType.image, path)
262+
assert foo.value == image_stream
263+
264+
with open(path, "rb+") as f:
265+
bar = Element("bar", ElementType.image, f)
266+
assert bar.value == image_stream
267+
268+
foo_bar = Element("foo_bar", ElementType.image)
269+
foo_bar.value = path
270+
assert foo_bar.value == image_stream
271+
272+
bar_foo = Element("bar_foo", ElementType.image)
273+
with open(path, "rb+") as i:
274+
bar_foo.value = i
275+
assert bar_foo.value == image_stream

0 commit comments

Comments
 (0)