Skip to content

Commit f947803

Browse files
authored
Add exception chaining (#37423)
* Add exception chaining to preserve error context - Add 'from e' to exception re-raises in CloudSQLEnrichmentHandler - Add exception chaining in processes.py for OSError and CalledProcessError - Improve logging in core.py to preserve traceback context This improves debuggability by preserving the full exception chain, following Python PEP 3134 best practices. Fixes #37422 * Fix yapf formatting for logging.warning statement * Fix yapf formatting: put logging arguments on single line
1 parent 18493cf commit f947803

4 files changed

Lines changed: 17 additions & 15 deletions

File tree

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868

6969
## New Features / Improvements
7070

71+
* (Python) Added exception chaining to preserve error context in CloudSQLEnrichmentHandler, processes utilities, and core transforms ([#37422](https://github.com/apache/beam/issues/37422)).
7172
* X feature added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)).
7273

7374
## Breaking Changes

sdks/python/apache_beam/transforms/core.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2444,7 +2444,8 @@ def exception_handling_wrapper_do_fn_custom_process(self, *args, **kwargs):
24442444
try:
24452445
self._on_failure_callback(exn, args[0])
24462446
except Exception as e:
2447-
logging.warning('on_failure_callback failed with error: %s', e)
2447+
logging.warning(
2448+
'on_failure_callback failed with error: %s', e, exc_info=True)
24482449
yield pvalue.TaggedOutput(
24492450
self._dead_letter_tag,
24502451
(

sdks/python/apache_beam/transforms/enrichment_handlers/cloudsql.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,11 +395,11 @@ def _execute_query(
395395
return data
396396
except Exception as e:
397397
transaction.rollback()
398-
raise RuntimeError(f"Database operation failed: {e}")
398+
raise RuntimeError(f"Database operation failed: {e}") from e
399399
except Exception as e:
400400
raise Exception(
401401
f'Could not execute the query. Please check if the query is properly '
402-
f'formatted and the table exists. {e}')
402+
f'formatted and the table exists. {e}') from e
403403
finally:
404404
if connection:
405405
connection.close()

sdks/python/apache_beam/utils/processes.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,56 +49,56 @@ def call(*args, **kwargs):
4949
kwargs['shell'] = True
5050
try:
5151
out = subprocess.call(*args, **kwargs)
52-
except OSError:
53-
raise RuntimeError("Executable {} not found".format(args[0]))
52+
except OSError as e:
53+
raise RuntimeError("Executable {} not found".format(args[0])) from e
5454
except subprocess.CalledProcessError as error:
5555
if isinstance(args, tuple) and (args[0][2] == "pip"):
5656
raise RuntimeError( \
5757
"Full traceback: {}\n Pip install failed for package: {} \
5858
\n Output from execution of subprocess: {}" \
59-
.format(traceback.format_exc(), args[0][6], error. output))
59+
.format(traceback.format_exc(), args[0][6], error. output)) from error
6060
else:
6161
raise RuntimeError("Full trace: {}\
6262
\n Output of the failed child process: {} " \
63-
.format(traceback.format_exc(), error.output))
63+
.format(traceback.format_exc(), error.output)) from error
6464
return out
6565

6666
def check_call(*args, **kwargs):
6767
if force_shell:
6868
kwargs['shell'] = True
6969
try:
7070
out = subprocess.check_call(*args, **kwargs)
71-
except OSError:
72-
raise RuntimeError("Executable {} not found".format(args[0]))
71+
except OSError as e:
72+
raise RuntimeError("Executable {} not found".format(args[0])) from e
7373
except subprocess.CalledProcessError as error:
7474
if isinstance(args, tuple) and (args[0][2] == "pip"):
7575
raise RuntimeError( \
7676
"Full traceback: {} \n Pip install failed for package: {} \
7777
\n Output from execution of subprocess: {}" \
78-
.format(traceback.format_exc(), args[0][6], error.output))
78+
.format(traceback.format_exc(), args[0][6], error.output)) from error
7979
else:
8080
raise RuntimeError("Full trace: {} \
8181
\n Output of the failed child process: {}" \
82-
.format(traceback.format_exc(), error.output))
82+
.format(traceback.format_exc(), error.output)) from error
8383
return out
8484

8585
def check_output(*args, **kwargs):
8686
if force_shell:
8787
kwargs['shell'] = True
8888
try:
8989
out = subprocess.check_output(*args, **kwargs)
90-
except OSError:
91-
raise RuntimeError("Executable {} not found".format(args[0]))
90+
except OSError as e:
91+
raise RuntimeError("Executable {} not found".format(args[0])) from e
9292
except subprocess.CalledProcessError as error:
9393
if isinstance(args, tuple) and (args[0][2] == "pip"):
9494
raise RuntimeError( \
9595
"Full traceback: {} \n Pip install failed for package: {} \
9696
\n Output from execution of subprocess: {}" \
97-
.format(traceback.format_exc(), args[0][6], error.output))
97+
.format(traceback.format_exc(), args[0][6], error.output)) from error
9898
else:
9999
raise RuntimeError("Full trace: {}, \
100100
output of the failed child process {} "\
101-
.format(traceback.format_exc(), error.output))
101+
.format(traceback.format_exc(), error.output)) from error
102102
return out
103103

104104
def Popen(*args, **kwargs):

0 commit comments

Comments
 (0)