Skip to content

Commit 127856b

Browse files
author
Mohamed Zeidan
committed
moved to exception
1 parent 04f6a8d commit 127856b

File tree

3 files changed

+29
-127
lines changed

3 files changed

+29
-127
lines changed

src/sagemaker/hyperpod/cli/commands/inference.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
_hyperpod_telemetry_emitter,
1515
)
1616
from sagemaker.hyperpod.common.telemetry.constants import Feature
17-
from sagemaker.hyperpod.common.cli_decorators import handle_cli_exceptions, smart_cli_exception_handler
17+
from sagemaker.hyperpod.common.cli_decorators import handle_cli_exceptions
1818

1919

2020
# CREATE
@@ -561,7 +561,7 @@ def custom_describe(
561561
help="Optional. The namespace of the jumpstart model endpoint to delete. Default set to 'default'.",
562562
)
563563
@_hyperpod_telemetry_emitter(Feature.HYPERPOD_CLI, "delete_js_endpoint_cli")
564-
@smart_cli_exception_handler
564+
@handle_cli_exceptions
565565
def js_delete(
566566
name: str,
567567
namespace: Optional[str],

src/sagemaker/hyperpod/common/cli_decorators.py

Lines changed: 14 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -13,63 +13,6 @@
1313
logger = logging.getLogger(__name__)
1414

1515
def handle_cli_exceptions(func):
16-
"""
17-
Decorator that provides consistent exception handling for CLI commands.
18-
19-
Replaces the repetitive pattern:
20-
except Exception as e:
21-
click.echo(str(e))
22-
import sys
23-
sys.exit(1)
24-
25-
Usage:
26-
@handle_cli_exceptions
27-
@click.command()
28-
def my_command():
29-
# Command logic here
30-
pass
31-
"""
32-
@functools.wraps(func)
33-
def wrapper(*args, **kwargs):
34-
try:
35-
return func(*args, **kwargs)
36-
except Exception as e:
37-
# Clean error output - no "Error:" prefix or "Aborted!" suffix
38-
click.echo(str(e))
39-
sys.exit(1)
40-
41-
return wrapper
42-
43-
def handle_cli_exceptions_with_debug(func):
44-
"""
45-
Enhanced decorator that provides debug information when requested.
46-
47-
This version logs the full exception details for debugging purposes
48-
while still showing clean messages to users.
49-
50-
Usage:
51-
@handle_cli_exceptions_with_debug
52-
@click.command()
53-
def my_command():
54-
# Command logic here
55-
pass
56-
"""
57-
@functools.wraps(func)
58-
def wrapper(*args, **kwargs):
59-
try:
60-
return func(*args, **kwargs)
61-
except Exception as e:
62-
# Log full exception details for debugging
63-
logger.debug(f"CLI command failed: {func.__name__}", exc_info=True)
64-
65-
# Clean error output for users
66-
click.echo(str(e))
67-
sys.exit(1)
68-
69-
return wrapper
70-
71-
72-
def smart_cli_exception_handler(func):
7316
"""
7417
Smart decorator that automatically detects resource/operation types and applies
7518
enhanced 404 handling. Eliminates repetitive exception handling across CLI commands.
@@ -81,7 +24,7 @@ def smart_cli_exception_handler(func):
8124
4. Handles all other exceptions consistently
8225
8326
Usage:
84-
@smart_cli_exception_handler
27+
@handle_cli_exceptions
8528
@click.command("hyp-jumpstart-endpoint")
8629
def js_delete(name, namespace):
8730
# Command logic here - no try/catch needed!
@@ -127,22 +70,22 @@ def _detect_resource_type(func) -> ResourceType:
12770
Returns:
12871
ResourceType enum or None if not detected
12972
"""
73+
# First try to get the Click command name from the decorator
13074
try:
131-
# First try to get the Click command name from the decorator
132-
if hasattr(func, 'name'):
75+
if hasattr(func, 'name') and func.name and isinstance(func.name, str):
13376
command_name = func.name.lower()
13477
if 'jumpstart' in command_name:
13578
return ResourceType.HYP_JUMPSTART_ENDPOINT
13679
elif 'custom' in command_name:
13780
return ResourceType.HYP_CUSTOM_ENDPOINT
13881
elif 'pytorch' in command_name:
13982
return ResourceType.HYP_PYTORCH_JOB
140-
141-
# Fallback to function name detection
142-
try:
143-
func_name = func.__name__.lower()
144-
except (AttributeError, TypeError):
145-
func_name = ""
83+
except (AttributeError, TypeError):
84+
pass
85+
86+
# Fallback to function name detection
87+
try:
88+
func_name = func.__name__.lower()
14689

14790
# Function name patterns
14891
if 'js_' in func_name or 'jumpstart' in func_name:
@@ -151,12 +94,11 @@ def _detect_resource_type(func) -> ResourceType:
15194
return ResourceType.HYP_CUSTOM_ENDPOINT
15295
elif 'pytorch' in func_name or 'training' in func_name:
15396
return ResourceType.HYP_PYTORCH_JOB
154-
155-
return None
156-
157-
except Exception:
158-
# Handle any other exceptions during detection gracefully
159-
return None
97+
98+
except (AttributeError, TypeError):
99+
pass
100+
101+
return None
160102

161103

162104
def _detect_operation_type(func) -> OperationType:

test/unit_tests/error_handling/test_cli_decorators.py

Lines changed: 13 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010

1111
from sagemaker.hyperpod.common.cli_decorators import (
1212
handle_cli_exceptions,
13-
handle_cli_exceptions_with_debug,
14-
smart_cli_exception_handler,
1513
_detect_resource_type,
1614
_detect_operation_type
1715
)
@@ -56,50 +54,12 @@ def documented_function():
5654
assert documented_function.__doc__ == "This is a test function."
5755

5856

59-
class TestHandleCliExceptionsWithDebug:
60-
"""Test handle_cli_exceptions_with_debug decorator."""
57+
class TestHandleCliExceptions404Handling:
58+
"""Test handle_cli_exceptions decorator 404 handling functionality."""
6159

6260
def test_successful_function_execution(self):
6361
"""Test decorator allows successful function execution."""
64-
@handle_cli_exceptions_with_debug
65-
def test_function():
66-
return "success"
67-
68-
result = test_function()
69-
assert result == "success"
70-
71-
@patch('sagemaker.hyperpod.common.cli_decorators.click')
72-
@patch('sagemaker.hyperpod.common.cli_decorators.sys')
73-
@patch('sagemaker.hyperpod.common.cli_decorators.logger')
74-
def test_exception_handling_with_debug(self, mock_logger, mock_sys, mock_click):
75-
"""Test decorator handles exceptions with debug logging."""
76-
@handle_cli_exceptions_with_debug
77-
def failing_function():
78-
raise Exception("Debug test error")
79-
80-
failing_function()
81-
82-
mock_logger.debug.assert_called_once()
83-
mock_click.echo.assert_called_once_with("Debug test error")
84-
mock_sys.exit.assert_called_once_with(1)
85-
86-
def test_preserves_function_metadata(self):
87-
"""Test decorator preserves original function metadata."""
88-
@handle_cli_exceptions_with_debug
89-
def documented_function():
90-
"""This is a debug test function."""
91-
pass
92-
93-
assert documented_function.__name__ == "documented_function"
94-
assert documented_function.__doc__ == "This is a debug test function."
95-
96-
97-
class TestSmartCliExceptionHandler:
98-
"""Test smart_cli_exception_handler decorator."""
99-
100-
def test_successful_function_execution(self):
101-
"""Test decorator allows successful function execution."""
102-
@smart_cli_exception_handler
62+
@handle_cli_exceptions
10363
def test_function():
10464
return "success"
10565

@@ -114,7 +74,7 @@ def test_404_exception_handling(self, mock_sys, mock_click, mock_handle_404):
11474
# Create 404 ApiException
11575
api_exception = ApiException(status=404, reason="Not Found")
11676

117-
@smart_cli_exception_handler
77+
@handle_cli_exceptions
11878
def js_delete(name, namespace):
11979
raise api_exception
12080

@@ -137,7 +97,7 @@ def js_delete(name, namespace):
13797
@patch('sagemaker.hyperpod.common.cli_decorators.sys')
13898
def test_non_404_exception_handling(self, mock_sys, mock_click):
13999
"""Test decorator handles non-404 exceptions normally."""
140-
@smart_cli_exception_handler
100+
@handle_cli_exceptions
141101
def failing_function():
142102
raise Exception("Generic error")
143103

@@ -150,7 +110,7 @@ def failing_function():
150110
@patch('sagemaker.hyperpod.common.cli_decorators.sys')
151111
def test_non_api_exception_handling(self, mock_sys, mock_click):
152112
"""Test decorator handles non-ApiException errors normally."""
153-
@smart_cli_exception_handler
113+
@handle_cli_exceptions
154114
def failing_function():
155115
raise ValueError("Value error")
156116

@@ -166,7 +126,7 @@ def test_404_detection_failure_fallback(self, mock_sys, mock_click, mock_handle_
166126
"""Test decorator falls back when resource/operation detection fails."""
167127
api_exception = ApiException(status=404, reason="Not Found")
168128

169-
@smart_cli_exception_handler
129+
@handle_cli_exceptions
170130
def unknown_function(name, namespace):
171131
raise api_exception
172132

@@ -179,7 +139,7 @@ def unknown_function(name, namespace):
179139

180140
def test_preserves_function_metadata(self):
181141
"""Test decorator preserves original function metadata."""
182-
@smart_cli_exception_handler
142+
@handle_cli_exceptions
183143
def documented_function():
184144
"""This is a smart test function."""
185145
pass
@@ -303,7 +263,7 @@ def test_complete_jumpstart_delete_flow(self, mock_sys, mock_click, mock_handle_
303263
api_exception = ApiException(status=404, reason="Not Found")
304264
mock_handle_404.side_effect = Exception("Enhanced 404 message")
305265

306-
@smart_cli_exception_handler
266+
@handle_cli_exceptions
307267
def js_delete(name, namespace="default"):
308268
raise api_exception
309269

@@ -327,7 +287,7 @@ def test_complete_custom_describe_flow(self, mock_sys, mock_click, mock_handle_4
327287
api_exception = ApiException(status=404, reason="Not Found")
328288
mock_handle_404.side_effect = Exception("Custom endpoint not found message")
329289

330-
@smart_cli_exception_handler
290+
@handle_cli_exceptions
331291
def custom_describe(name, namespace="default"):
332292
raise api_exception
333293

@@ -351,7 +311,7 @@ def test_complete_training_list_flow(self, mock_sys, mock_click, mock_handle_404
351311
api_exception = ApiException(status=404, reason="Not Found")
352312
mock_handle_404.side_effect = Exception("Training job not found message")
353313

354-
@smart_cli_exception_handler
314+
@handle_cli_exceptions
355315
def training_list(name, namespace="default"):
356316
raise api_exception
357317

@@ -371,7 +331,7 @@ def training_list(name, namespace="default"):
371331
@patch('sagemaker.hyperpod.common.cli_decorators.sys')
372332
def test_non_404_exception_passthrough(self, mock_sys, mock_click):
373333
"""Test non-404 exceptions are handled normally."""
374-
@smart_cli_exception_handler
334+
@handle_cli_exceptions
375335
def js_delete(name, namespace="default"):
376336
raise ValueError("Invalid configuration")
377337

@@ -382,7 +342,7 @@ def js_delete(name, namespace="default"):
382342

383343
def test_function_with_no_exceptions(self):
384344
"""Test function that completes successfully."""
385-
@smart_cli_exception_handler
345+
@handle_cli_exceptions
386346
def successful_function(name, namespace="default"):
387347
return f"Success: {name} in {namespace}"
388348

0 commit comments

Comments
 (0)