SmartException¶
The SmartException
automatically detects if running from a CLI or from a Web/API Route.
If running from the web/api, then it uses the standard HTTPException
If running from the CLI, then it uses Pythons built in Exception.
Note
The parameters are slightly different than the HTTPException in that the detail
and status_code
parameters are reversed.
Why Use a SmartException?¶
Think about the Job concept. Where the CLI and API are simply entrypoints into the actual work. The work itself lives in a Job, not in a controller or command. If the Job is dispatched from an API and errors, the error should morph into a proper HTTP error. But if the Job is dispatched from a CLI, then it should throw a basic non HTTP based exception.
Naturally you could catch the Jobs exception at either end of the entrypoint and handle properly, but using a SmartException
does all of this for you.
Throw From Controller¶
import uvicore
from uvicore.http import status
from uvicore.exceptions import SmartException
from uvicore.http.routing import ApiRouter, Controller
@uvicore.controller()
class Test(Controller):
def register(self, route: ApiRouter):
try:
# This is caught by the except Exception as e: below
a = b
# This is something we threw, but is also passed to the
# except Exception as e: below as well
if 1 != 2:
# Simple
raise SmartException(message='Not Found', 404, detail='/tmp/foo not found')
# Or Full Params
raise SmartException(
detail='/tmp/foo not found',
status_code=status.HTTP_404_NOT_FOUND,
message='Not Found',
exception='super detail, will be hidden if debug=true',
extra={'foo': 'bar'},
headers={'optional': 'dict of headers'}
)
except Exception as e:
raise SmartException(exception=e, 500)
API Response Example¶
Actual exception
{
"status_code": 500,
"message": "name 'b' is not defined",
"detail": "name 'b' is not defined",
"exception": "Traceback (most recent call last):\n File \"Code\train.py\", line 36, in get_complete_model\n a = b\nNameError: name 'b' is not defined\n",
"extra": null
}
Manually raised exception
{
"status_code": 404,
"message": "Not Found",
"detail": "/tmp/foo not found",
"exception": null,
"extra": null
}
Source Code¶
See the Source Code on Github
class HTTPException(_HTTPException):
"""Main Base HTTP Exception"""
# Message is optional and will default the the HTTP status codes TEXT as outlined in the python http module
# Detail is a more detailed text description of the issue
# Extra lets you pass in a dict of options or extra information that some handlers may want to use
def __init__(self,
status_code: int,
detail: Optional[str] = None,
*,
message: Optional[str] = None,
exception: Optional[str] = None,
extra: Optional[Dict] = None,
headers: Optional[Dict[str, Any]] = None
) -> None:
# Detect if we were raised from another HTTPException with a status_code
# If so, grab values from that first exception
if exception is not None and haskey(exception, 'status_code'):
status_code = exception.status_code
message = exception.message
detail = exception.detail
extra = exception.extra
headers = exception.headers
exception = None
else:
# Standard try catch that raises HTTPException
st = traceback.format_exc()
message = message or str(exception)
detail = detail or message
exception = st
# Call starlette exception where their detail is my message
super().__init__(status_code=status_code, detail=message)
# Swap starlette detail to my message
self.message = self.detail
self.detail = detail
self.exception = exception if uvicore.config.app.debug else None # Hidden unless in debug mode
self.extra = extra
self.headers = headers