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