1
1
#!/usr/bin/env python
2
2
# Copyright (c) 2024 Oracle and/or its affiliates.
3
3
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
4
+
5
+ import re
6
+ import traceback
7
+ import uuid
4
8
from dataclasses import fields
5
9
from datetime import datetime , timedelta
10
+ from http .client import responses
6
11
from typing import Dict , Optional
7
12
8
13
from cachetools import TTLCache , cached
9
14
from tornado .web import HTTPError
10
15
11
- from ads .aqua import ODSC_MODEL_COMPARTMENT_OCID
16
+ from ads .aqua import ODSC_MODEL_COMPARTMENT_OCID , logger
12
17
from ads .aqua .common .utils import fetch_service_compartment
13
- from ads .aqua .extension .errors import Errors
18
+ from ads .aqua .constants import (
19
+ AQUA_TROUBLESHOOTING_LINK ,
20
+ OCI_OPERATION_FAILURES ,
21
+ STATUS_CODE_MESSAGES ,
22
+ )
23
+ from ads .aqua .extension .errors import Errors , ReplyDetails
14
24
15
25
16
26
def validate_function_parameters (data_class , input_data : Dict ):
@@ -32,3 +42,105 @@ def ui_compatability_check():
32
42
fetched from the configuration. The cached result is returned when multiple calls are made in quick succession
33
43
from the UI to avoid multiple config file loads."""
34
44
return ODSC_MODEL_COMPARTMENT_OCID or fetch_service_compartment ()
45
+
46
+
47
+ def get_default_error_messages (
48
+ service_payload : dict ,
49
+ status_code : str ,
50
+ default_msg : str = "Unknown HTTP Error." ,
51
+ )-> str :
52
+ """Method that maps the error messages based on the operation performed or the status codes encountered."""
53
+
54
+ if service_payload and "operation_name" in service_payload :
55
+ operation_name = service_payload .get ("operation_name" )
56
+
57
+ if operation_name and status_code in STATUS_CODE_MESSAGES :
58
+ return f"{ STATUS_CODE_MESSAGES [status_code ]} \n { service_payload .get ('message' )} \n Operation Name: { operation_name } ."
59
+
60
+ return STATUS_CODE_MESSAGES .get (status_code , default_msg )
61
+
62
+
63
+ def get_documentation_link (key : str ) -> str :
64
+ """Generates appropriate GitHub link to AQUA Troubleshooting Documentation per the user's error."""
65
+ github_header = re .sub (r"_" , "-" , key )
66
+ return f"{ AQUA_TROUBLESHOOTING_LINK } #{ github_header } "
67
+
68
+
69
+ def get_troubleshooting_tips (service_payload : dict ,
70
+ status_code : str ) -> str :
71
+ """Maps authorization errors to potential solutions on Troubleshooting Page per Aqua Documentation on oci-data-science-ai-samples"""
72
+
73
+ tip = f"For general tips on troubleshooting: { AQUA_TROUBLESHOOTING_LINK } "
74
+
75
+ if status_code in (404 , 400 ):
76
+ failed_operation = service_payload .get ('operation_name' )
77
+
78
+ if failed_operation in OCI_OPERATION_FAILURES :
79
+ link = get_documentation_link (failed_operation )
80
+ tip = OCI_OPERATION_FAILURES [failed_operation ] + link
81
+
82
+ return tip
83
+
84
+
85
+ def construct_error (status_code : int , ** kwargs ) -> ReplyDetails :
86
+ """
87
+ Formats an error response based on the provided status code and optional details.
88
+
89
+ Args:
90
+ status_code (int): The HTTP status code of the error.
91
+ **kwargs: Additional optional parameters:
92
+ - reason (str, optional): A brief reason for the error.
93
+ - service_payload (dict, optional): Contextual error data from OCI SDK methods
94
+ - message (str, optional): A custom error message, from error raised from failed AQUA methods calling OCI SDK methods
95
+ - exc_info (tuple, optional): Exception information (e.g., from `sys.exc_info()`), used for logging.
96
+
97
+ Returns:
98
+ ReplyDetails: A Pydantic object containing details about the formatted error response.
99
+ kwargs:
100
+ - "status" (int): The HTTP status code.
101
+ - "troubleshooting_tips" (str): a GitHub link to AQUA troubleshooting docs, may be linked to a specific header.
102
+ - "message" (str): error message.
103
+ - "service_payload" (Dict[str, Any], optional) : Additional context from OCI Python SDK call.
104
+ - "reason" (str): The reason for the error.
105
+ - "request_id" (str): A unique identifier for tracking the error.
106
+
107
+ Logs:
108
+ - Logs the error details with a unique request ID.
109
+ - If `exc_info` is provided and contains an `HTTPError`, updates the response message and reason accordingly.
110
+
111
+ """
112
+ reason = kwargs .get ("reason" , "Unknown Error" )
113
+ service_payload = kwargs .get ("service_payload" , {})
114
+ default_msg = responses .get (status_code , "Unknown HTTP Error" )
115
+ message = get_default_error_messages (
116
+ service_payload , str (status_code ), kwargs .get ("message" , default_msg )
117
+ )
118
+
119
+ tips = get_troubleshooting_tips (service_payload , status_code )
120
+
121
+
122
+ reply = ReplyDetails (
123
+ status = status_code ,
124
+ troubleshooting_tips = tips ,
125
+ message = message ,
126
+ service_payload = service_payload ,
127
+ reason = reason ,
128
+ request_id = str (uuid .uuid4 ()),
129
+ )
130
+
131
+ exc_info = kwargs .get ("exc_info" )
132
+ if exc_info :
133
+ logger .error (
134
+ f"Error Request ID: { reply .request_id } \n "
135
+ f"Error: { '' .join (traceback .format_exception (* exc_info ))} "
136
+ )
137
+ e = exc_info [1 ]
138
+ if isinstance (e , HTTPError ):
139
+ reply .message = e .log_message or message
140
+ reply .reason = e .reason if e .reason else reply .reason
141
+
142
+ logger .error (
143
+ f"Error Request ID: { reply .request_id } \n "
144
+ f"Error: { reply .message } { reply .reason } "
145
+ )
146
+ return reply
0 commit comments