Skip to content

Commit 6b68036

Browse files
committed
add eror handling for image functions
1 parent b754f59 commit 6b68036

File tree

2 files changed

+276
-91
lines changed

2 files changed

+276
-91
lines changed

bigframes/blob/_functions.py

Lines changed: 141 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -120,43 +120,50 @@ def udf(self):
120120

121121

122122
def exif_func(src_obj_ref_rt: str, verbose: bool) -> str:
123-
import io
124-
import json
123+
try:
124+
import io
125+
import json
125126

126-
from PIL import ExifTags, Image
127-
import requests
128-
from requests import adapters
127+
from PIL import ExifTags, Image
128+
import requests
129+
from requests import adapters
129130

130-
result_dict = {"status": "", "content": "{}"}
131-
try:
132131
session = requests.Session()
133132
session.mount("https://", adapters.HTTPAdapter(max_retries=3))
134133

135134
src_obj_ref_rt_json = json.loads(src_obj_ref_rt)
136-
137135
src_url = src_obj_ref_rt_json["access_urls"]["read_url"]
138136

139137
response = session.get(src_url, timeout=30)
138+
response.raise_for_status()
140139
bts = response.content
141140

142141
image = Image.open(io.BytesIO(bts))
143142
exif_data = image.getexif()
144143
exif_dict = {}
144+
145145
if exif_data:
146146
for tag, value in exif_data.items():
147147
tag_name = ExifTags.TAGS.get(tag, tag)
148-
# Pillow might return bytes, which are not serializable.
149-
if isinstance(value, bytes):
150-
value = value.decode("utf-8", "replace")
151-
exif_dict[tag_name] = value
152-
result_dict["content"] = json.dumps(exif_dict)
153-
except Exception as e:
154-
result_dict["status"] = str(e)
148+
# Convert non-serializable types to strings
149+
try:
150+
json.dumps(value)
151+
exif_dict[tag_name] = value
152+
except (TypeError, ValueError):
153+
exif_dict[tag_name] = str(value)
154+
155+
if verbose:
156+
return json.dumps({"status": "", "content": json.dumps(exif_dict)})
157+
else:
158+
return json.dumps(exif_dict)
155159

156-
if verbose:
157-
return json.dumps(result_dict)
158-
else:
159-
return result_dict["content"]
160+
except Exception as e:
161+
# Return error as JSON with error field
162+
error_result = {"status": f"{type(e).__name__}: {str(e)}", "content": "{}"}
163+
if verbose:
164+
return json.dumps(error_result)
165+
else:
166+
return "{}"
160167

161168

162169
exif_func_def = FunctionDef(exif_func, ["pillow", "requests"])
@@ -171,11 +178,9 @@ def image_blur_func(
171178
ext: str,
172179
verbose: bool,
173180
) -> str:
174-
import json
175-
176-
result_dict = {"status": "", "content": dst_obj_ref_rt}
177-
178181
try:
182+
import json
183+
179184
import cv2 as cv # type: ignore
180185
import numpy as np
181186
import requests
@@ -193,35 +198,52 @@ def image_blur_func(
193198
dst_url = dst_obj_ref_rt_json["access_urls"]["write_url"]
194199

195200
response = session.get(src_url, timeout=30)
201+
response.raise_for_status() # Raise exception for HTTP errors
196202
bts = response.content
197203

198204
nparr = np.frombuffer(bts, np.uint8)
199205
img = cv.imdecode(nparr, cv.IMREAD_UNCHANGED)
206+
207+
if img is None:
208+
raise ValueError(
209+
"Failed to decode image - possibly corrupted or unsupported format"
210+
)
211+
200212
img_blurred = cv.blur(img, ksize=(ksize_x, ksize_y))
201213

202-
bts = cv.imencode(ext, img_blurred)[1].tobytes()
214+
success, encoded = cv.imencode(ext, img_blurred)
215+
if not success:
216+
raise ValueError(f"Failed to encode image with extension {ext}")
217+
218+
bts = encoded.tobytes()
203219

204220
ext = ext.replace(".", "")
205221
ext_mappings = {"jpg": "jpeg", "tif": "tiff"}
206222
ext = ext_mappings.get(ext, ext)
207223
content_type = "image/" + ext
208224

209-
session.put(
225+
put_response = session.put(
210226
url=dst_url,
211227
data=bts,
212-
headers={
213-
"Content-Type": content_type,
214-
},
228+
headers={"Content-Type": content_type},
215229
timeout=30,
216230
)
231+
put_response.raise_for_status()
217232

218-
except Exception as e:
219-
result_dict["status"] = str(e)
233+
if verbose:
234+
return json.dumps({"status": "", "content": dst_obj_ref_rt})
235+
else:
236+
return dst_obj_ref_rt
220237

221-
if verbose:
222-
return json.dumps(result_dict)
223-
else:
224-
return result_dict["content"]
238+
except Exception as e:
239+
# Return error in structured format
240+
error_result = {"status": f"Error: {type(e).__name__}: {str(e)}", "content": ""}
241+
if verbose:
242+
return json.dumps(error_result)
243+
else:
244+
# The calling function expects a json string that can be parsed as a blob ref
245+
# Return a valid blob ref json string with empty values.
246+
return '{"access_urls": {"read_url": "", "write_url": ""}, "authorizer": "", "generation": "", "uri": ""}'
225247

226248

227249
image_blur_def = FunctionDef(image_blur_func, ["opencv-python", "numpy", "requests"])
@@ -251,12 +273,20 @@ def image_blur_to_bytes_func(
251273
src_url = src_obj_ref_rt_json["access_urls"]["read_url"]
252274

253275
response = session.get(src_url, timeout=30)
276+
response.raise_for_status()
254277
bts = response.content
255278

256279
nparr = np.frombuffer(bts, np.uint8)
257280
img = cv.imdecode(nparr, cv.IMREAD_UNCHANGED)
281+
if img is None:
282+
raise ValueError(
283+
"Failed to decode image - possibly corrupted or unsupported format"
284+
)
258285
img_blurred = cv.blur(img, ksize=(ksize_x, ksize_y))
259-
content = cv.imencode(ext, img_blurred)[1].tobytes()
286+
success, encoded = cv.imencode(ext, img_blurred)
287+
if not success:
288+
raise ValueError(f"Failed to encode image with extension {ext}")
289+
content = encoded.tobytes()
260290

261291
except Exception as e:
262292
status = str(e)
@@ -284,11 +314,9 @@ def image_resize_func(
284314
ext: str,
285315
verbose: bool,
286316
) -> str:
287-
import json
288-
289-
result_dict = {"status": "", "content": dst_obj_ref_rt}
290-
291317
try:
318+
import json
319+
292320
import cv2 as cv # type: ignore
293321
import numpy as np
294322
import requests
@@ -306,35 +334,51 @@ def image_resize_func(
306334
dst_url = dst_obj_ref_rt_json["access_urls"]["write_url"]
307335

308336
response = session.get(src_url, timeout=30)
337+
response.raise_for_status()
309338
bts = response.content
310339

311340
nparr = np.frombuffer(bts, np.uint8)
312341
img = cv.imdecode(nparr, cv.IMREAD_UNCHANGED)
342+
if img is None:
343+
raise ValueError(
344+
"Failed to decode image - possibly corrupted or unsupported format"
345+
)
313346
img_resized = cv.resize(img, dsize=(dsize_x, dsize_y), fx=fx, fy=fy)
314347

315-
bts = cv.imencode(ext, img_resized)[1].tobytes()
348+
success, encoded = cv.imencode(ext, img_resized)
349+
if not success:
350+
raise ValueError(f"Failed to encode image with extension {ext}")
351+
bts = encoded.tobytes()
316352

317353
ext = ext.replace(".", "")
318354
ext_mappings = {"jpg": "jpeg", "tif": "tiff"}
319355
ext = ext_mappings.get(ext, ext)
320356
content_type = "image/" + ext
321357

322-
session.put(
358+
put_response = session.put(
323359
url=dst_url,
324360
data=bts,
325361
headers={
326362
"Content-Type": content_type,
327363
},
328364
timeout=30,
329365
)
366+
put_response.raise_for_status()
330367

331-
except Exception as e:
332-
result_dict["status"] = str(e)
368+
if verbose:
369+
return json.dumps({"status": "", "content": dst_obj_ref_rt})
370+
else:
371+
return dst_obj_ref_rt
333372

334-
if verbose:
335-
return json.dumps(result_dict)
336-
else:
337-
return result_dict["content"]
373+
except Exception as e:
374+
# Return error in structured format
375+
error_result = {"status": f"Error: {type(e).__name__}: {str(e)}", "content": ""}
376+
if verbose:
377+
return json.dumps(error_result)
378+
else:
379+
# The calling function expects a json string that can be parsed as a blob ref
380+
# Return a valid blob ref json string with empty values.
381+
return '{"access_urls": {"read_url": "", "write_url": ""}, "authorizer": "", "generation": "", "uri": ""}'
338382

339383

340384
image_resize_def = FunctionDef(
@@ -372,12 +416,20 @@ def image_resize_to_bytes_func(
372416
src_url = src_obj_ref_rt_json["access_urls"]["read_url"]
373417

374418
response = session.get(src_url, timeout=30)
419+
response.raise_for_status()
375420
bts = response.content
376421

377422
nparr = np.frombuffer(bts, np.uint8)
378423
img = cv.imdecode(nparr, cv.IMREAD_UNCHANGED)
424+
if img is None:
425+
raise ValueError(
426+
"Failed to decode image - possibly corrupted or unsupported format"
427+
)
379428
img_resized = cv.resize(img, dsize=(dsize_x, dsize_y), fx=fx, fy=fy)
380-
content = cv.imencode(".jpeg", img_resized)[1].tobytes()
429+
success, encoded = cv.imencode(ext, img_resized)
430+
if not success:
431+
raise ValueError(f"Failed to encode image with extension {ext}")
432+
content = encoded.tobytes()
381433

382434
except Exception as e:
383435
status = str(e)
@@ -404,11 +456,9 @@ def image_normalize_func(
404456
ext: str,
405457
verbose: bool,
406458
) -> str:
407-
import json
408-
409-
result_dict = {"status": "", "content": dst_obj_ref_rt}
410-
411459
try:
460+
import json
461+
412462
import cv2 as cv # type: ignore
413463
import numpy as np
414464
import requests
@@ -433,37 +483,53 @@ def image_normalize_func(
433483
dst_url = dst_obj_ref_rt_json["access_urls"]["write_url"]
434484

435485
response = session.get(src_url, timeout=30)
486+
response.raise_for_status()
436487
bts = response.content
437488

438489
nparr = np.frombuffer(bts, np.uint8)
439490
img = cv.imdecode(nparr, cv.IMREAD_UNCHANGED)
491+
if img is None:
492+
raise ValueError(
493+
"Failed to decode image - possibly corrupted or unsupported format"
494+
)
440495
img_normalized = cv.normalize(
441496
img, None, alpha=alpha, beta=beta, norm_type=norm_type_mapping[norm_type]
442497
)
443498

444-
bts = cv.imencode(ext, img_normalized)[1].tobytes()
499+
success, encoded = cv.imencode(ext, img_normalized)
500+
if not success:
501+
raise ValueError(f"Failed to encode image with extension {ext}")
502+
bts = encoded.tobytes()
445503

446504
ext = ext.replace(".", "")
447505
ext_mappings = {"jpg": "jpeg", "tif": "tiff"}
448506
ext = ext_mappings.get(ext, ext)
449507
content_type = "image/" + ext
450508

451-
session.put(
509+
put_response = session.put(
452510
url=dst_url,
453511
data=bts,
454512
headers={
455513
"Content-Type": content_type,
456514
},
457515
timeout=30,
458516
)
517+
put_response.raise_for_status()
459518

460-
except Exception as e:
461-
result_dict["status"] = str(e)
519+
if verbose:
520+
return json.dumps({"status": "", "content": dst_obj_ref_rt})
521+
else:
522+
return dst_obj_ref_rt
462523

463-
if verbose:
464-
return json.dumps(result_dict)
465-
else:
466-
return result_dict["content"]
524+
except Exception as e:
525+
# Return error in structured format
526+
error_result = {"status": f"Error: {type(e).__name__}: {str(e)}", "content": ""}
527+
if verbose:
528+
return json.dumps(error_result)
529+
else:
530+
# The calling function expects a json string that can be parsed as a blob ref
531+
# Return a valid blob ref json string with empty values.
532+
return '{"access_urls": {"read_url": "", "write_url": ""}, "authorizer": "", "generation": "", "uri": ""}'
467533

468534

469535
image_normalize_def = FunctionDef(
@@ -482,7 +548,8 @@ def image_normalize_to_bytes_func(
482548
import base64
483549
import json
484550

485-
result_dict = {"status": "", "content": ""}
551+
status = ""
552+
content = b""
486553

487554
try:
488555
import cv2 as cv # type: ignore
@@ -506,20 +573,28 @@ def image_normalize_to_bytes_func(
506573
src_url = src_obj_ref_rt_json["access_urls"]["read_url"]
507574

508575
response = session.get(src_url, timeout=30)
576+
response.raise_for_status()
509577
bts = response.content
510578

511579
nparr = np.frombuffer(bts, np.uint8)
512580
img = cv.imdecode(nparr, cv.IMREAD_UNCHANGED)
581+
if img is None:
582+
raise ValueError(
583+
"Failed to decode image - possibly corrupted or unsupported format"
584+
)
513585
img_normalized = cv.normalize(
514586
img, None, alpha=alpha, beta=beta, norm_type=norm_type_mapping[norm_type]
515587
)
516-
bts = cv.imencode(".jpeg", img_normalized)[1].tobytes()
517-
518-
content_b64 = base64.b64encode(bts).decode("utf-8")
519-
result_dict["content"] = content_b64
588+
success, encoded = cv.imencode(ext, img_normalized)
589+
if not success:
590+
raise ValueError(f"Failed to encode image with extension {ext}")
591+
content = encoded.tobytes()
520592

521593
except Exception as e:
522-
result_dict["status"] = str(e)
594+
status = str(e)
595+
596+
encoded_content = base64.b64encode(content).decode("utf-8")
597+
result_dict = {"status": status, "content": encoded_content}
523598

524599
if verbose:
525600
return json.dumps(result_dict)

0 commit comments

Comments
 (0)