What to Return at an Unexpected Error from API
TL; DR: API returns something crazy. What should this API calling component return? None
? -1
? ""
? None of the above. Raise an exception.
Problem
There is a function that gets a response from an API and returns it to another function in a specific data type. This post is about what to return when the API returns an error.
A few (quite common in reality) characteristics of this error:
- The error message from the API does not contain much detail to help developers to understand, or debug the problem.
- The product behind the API is still under development; hence, it is likely that the error is something unexpected at all.
- The client calling the API system requires high accuracy. It would rather break the pipeline and goes down than receive any unreliable values.
With these given, it is more reliable to fail the program when the target API returns something unexpected, instead of dealing with it based on the wrong assumptions.
Extra Requirements
To be a little more graceful, it would be great if
- the function logs the error message, so that the developers could have a bit more hint.
- the program instantly stops, without making more error messages due to the cascading errors after the unexpected response.
- how the function handles the error is written in scalable architecture
Solution
- Create an exception class (api_exception.py) away from the one calling the API
- Add an exception message in the
__init__
so that the mainException
class can handle the logging
# api_exception.pyfrom typing import Listclass ApiException(Exception):
def __init__(self, school: str, class_num: int, attrs=List[str]):
self.school = school
self.class_num = class_num
self.attrs = attrsmsg = f"API call to class_num({class_num}) at school({school}) to obtain {attrs} failed"
super().__init__(msg)
3. Raise the Exception in the component calling the API (client.py) so that the program fails instantly when given an error
# client.pyfrom typing import Any
from typing import cast
from typing import Dict
from typing import List
class APIClient:
"""
calls the third-party API with the given parameters
"""
def call_api(school: str, class_num: int, attrs: List[str]) -> List[Dict[str, Any]]:
# API is expcected to return something like this...
dummy_res = [{"name": "Jane Doe", "age": 7},
{"name": "John Doe", "age": 8}] return dummy_res def load_from_api(
self, school: str, class_num: int, attrs: List[str]
) -> List[Dict[str, Any]]:
try:
data = call_api(school, class_nu, attrs)
return cast(List[Dict[str, str]], data) except Exception as err:
raise ApiException(
school=school, class_num=class_num, attrs=attrs
) from err
Don’ts
- Leave the log simply in the API-calling component and return something unexpected
...
import logginglogger = logging.getLogger(__name__)
...def load_from_api(
self, school: str, class_num: int, attrs: List[str]
) -> List[Dict[str, Any]]:
try:
data = call_api(school, class_nu, attrs)
return cast(List[Dict[str, str]], data)
except Exception as err:
logger.error(f"API call to class_num({class_num}) at school({school}) to obtain {attrs} failed")
return -1 # None, or empty string
This code does not conform to the expected data type to return.
2. Okay, then what about we return something that conforms to the expected data type?
try:
data = call_api(school, class_nu, attrs)
return cast(List[Dict[str, str]], data)except Exception as err:
return cast(List[Dict[str, str]], None)
This could be misleading because the API may have returned None
. We cannot distinguish if the error returned it or the API did.
Takeaways
- Avoid returning something arbitrary (i.e.
-1
,None
,""
), but raise an exception, because returning such values can be misleading - Raise an exception that is broad enough, when a third-party API specification is not clear. It should be ready to log whatever API returns.