Skip to content

Commit 4644e78

Browse files
author
Cyrus Radfar
committed
Added handling for 'files' and 'file'.
1 parent 0bed8fd commit 4644e78

File tree

3 files changed

+174
-13
lines changed

3 files changed

+174
-13
lines changed

python_anvil/api_resources/payload.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ class CreateEtchFilePayload(BaseModel):
197197
payloads: Union[str, Dict[str, FillPDFPayload]]
198198

199199

200-
class CreateEtchPacketPayload(BaseModel):
200+
class CreateEtchPacketPayload(FileCompatibleBaseModel):
201201
"""
202202
Payload for createEtchPacket.
203203

python_anvil/models.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from typing import Any
2-
import warnings
3-
from pydantic import BaseModel, ConfigDict
4-
from io import IOBase, BytesIO, BufferedReader
2+
from io import BytesIO, BufferedReader
53

6-
try:
4+
try:
5+
from pydantic import BaseModel
76
from pydantic.version import VERSION as PYDANTIC_VERSION
87
IS_V2 = not PYDANTIC_VERSION.startswith('1')
98
except ImportError:
9+
from pydantic.v1 import BaseModel
1010
IS_V2 = False
1111

1212
if IS_V2:
@@ -15,17 +15,41 @@ class FileCompatibleBaseModel(BaseModel):
1515
Patched model_dump to extract file objects from SerializationIterator in V2
1616
and return as BufferedReader
1717
"""
18+
19+
def _iterator_to_buffered_reader(self, value):
20+
content = bytearray()
21+
try:
22+
while True:
23+
content.extend(next(value))
24+
except StopIteration:
25+
# Create a BytesIO with the content
26+
bio = BytesIO(bytes(content))
27+
# Get the total length
28+
bio.seek(0, 2) # Seek to end
29+
total_length = bio.tell()
30+
bio.seek(0) # Reset to start
31+
32+
# Create a BufferedReader with the content
33+
reader = BufferedReader(bio)
34+
# Add a length attribute that requests_toolbelt can use
35+
reader.len = total_length
36+
return reader
37+
38+
def _check_if_serialization_iterator(self, value):
39+
return str(type(value).__name__) == 'SerializationIterator' and hasattr(value, '__next__')
40+
1841
def model_dump(self, **kwargs):
19-
2042
data = super().model_dump(**kwargs)
2143
for key, value in data.items():
22-
if str(type(value).__name__) == 'SerializationIterator' and hasattr(value, '__next__'):
23-
content = bytearray()
24-
try:
25-
while True:
26-
content.extend(next(value))
27-
except StopIteration:
28-
data[key] = BufferedReader(BytesIO(bytes(content)))
44+
if key == 'file' and self._check_if_serialization_iterator(value):
45+
# Direct file case
46+
data[key] = self._iterator_to_buffered_reader(value)
47+
elif key == 'files' and isinstance(value, list):
48+
# List of objects case
49+
for index, item in enumerate(value):
50+
if isinstance(item, dict) and 'file' in item:
51+
if self._check_if_serialization_iterator(item['file']):
52+
data[key][index]['file'] = self._iterator_to_buffered_reader(item['file'])
2953
return data
3054

3155
else:

python_anvil/tests/test_models.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,140 @@ def test_document_upload_handles_file_objects():
120120
assert data['title'] == "Test Document"
121121
assert len(data['fields']) == 1
122122
assert data['fields'][0]['id'] == "sig1"
123+
124+
def test_create_etch_packet_payload_handles_nested_file_objects():
125+
from python_anvil.api_resources.payload import (
126+
CreateEtchPacketPayload,
127+
DocumentUpload,
128+
SignatureField,
129+
EtchSigner,
130+
)
131+
132+
# Create a sample signature field
133+
field = SignatureField(
134+
id="sig1",
135+
type="signature",
136+
page_num=1,
137+
rect={"x": 100.0, "y": 100.0, "width": 100.0}
138+
)
139+
140+
# Create a signer
141+
signer = EtchSigner(
142+
name="Test Signer",
143+
144+
fields=[{"file_id": "doc1", "field_id": "sig1"}]
145+
)
146+
147+
# Test with a file object
148+
with open(__file__, 'rb') as test_file:
149+
# Create a DocumentUpload instance
150+
doc = DocumentUpload(
151+
id="doc1",
152+
title="Test Document",
153+
file=test_file,
154+
fields=[field]
155+
)
156+
157+
# Create the packet payload
158+
packet = CreateEtchPacketPayload(
159+
name="Test Packet",
160+
signers=[signer],
161+
files=[doc],
162+
is_test=True
163+
)
164+
165+
# Dump the model
166+
data = packet.model_dump()
167+
168+
# Verify the structure
169+
assert data['name'] == "Test Packet"
170+
assert len(data['files']) == 1
171+
assert len(data['signers']) == 1
172+
173+
# Verify file handling in the nested DocumentUpload
174+
file_data = data['files'][0]
175+
assert file_data['id'] == "doc1"
176+
assert file_data['title'] == "Test Document"
177+
assert isinstance(file_data['file'], BufferedReader), \
178+
f"Expected BufferedReader but got {type(file_data['file'])}"
179+
180+
# Verify file is still readable
181+
file_data['file'].seek(0)
182+
content = file_data['file'].read()
183+
assert len(content) > 0, "File should be readable"
184+
185+
# Verify the content matches the original file
186+
with open(__file__, 'rb') as original_file:
187+
assert content == original_file.read(), "File content should match original"
188+
189+
def test_create_etch_packet_payload_handles_multiple_files():
190+
from python_anvil.api_resources.payload import (
191+
CreateEtchPacketPayload,
192+
DocumentUpload,
193+
SignatureField,
194+
EtchSigner,
195+
)
196+
197+
# Create signature fields
198+
field1 = SignatureField(
199+
id="sig1",
200+
type="signature",
201+
page_num=1,
202+
rect={"x": 100.0, "y": 100.0, "width": 100.0}
203+
)
204+
205+
field2 = SignatureField(
206+
id="sig2",
207+
type="signature",
208+
page_num=1,
209+
rect={"x": 200.0, "y": 200.0, "width": 100.0}
210+
)
211+
212+
signer = EtchSigner(
213+
name="Test Signer",
214+
215+
fields=[
216+
{"file_id": "doc1", "field_id": "sig1"},
217+
{"file_id": "doc2", "field_id": "sig2"}
218+
]
219+
)
220+
221+
# Test with multiple file objects
222+
with open(__file__, 'rb') as test_file1, open(__file__, 'rb') as test_file2:
223+
doc1 = DocumentUpload(
224+
id="doc1",
225+
title="Test Document 1",
226+
file=test_file1,
227+
fields=[field1]
228+
)
229+
230+
doc2 = DocumentUpload(
231+
id="doc2",
232+
title="Test Document 2",
233+
file=test_file2,
234+
fields=[field2]
235+
)
236+
237+
packet = CreateEtchPacketPayload(
238+
name="Test Packet",
239+
signers=[signer],
240+
files=[doc1, doc2],
241+
is_test=True
242+
)
243+
244+
data = packet.model_dump()
245+
246+
# Verify structure
247+
assert len(data['files']) == 2
248+
249+
# Verify both files are properly handled
250+
for i, file_data in enumerate(data['files'], 1):
251+
assert file_data['id'] == f"doc{i}"
252+
assert file_data['title'] == f"Test Document {i}"
253+
assert isinstance(file_data['file'], BufferedReader), \
254+
f"File {i}: Expected BufferedReader but got {type(file_data['file'])}"
255+
256+
# Verify file is readable
257+
file_data['file'].seek(0)
258+
content = file_data['file'].read()
259+
assert len(content) > 0, f"File {i} should be readable"

0 commit comments

Comments
 (0)