Skip to content

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