<jupyter_start><jupyter_text>Project 6 - Solution ```1. Supplier exceptions    a. Not manufactured anymore    b. Production delayed    c. Shipping delayed    2. Checkout exceptions    a. Inventory type exceptions        - out of stock    b. Pricing exceptions        - invalid coupon code        - cannot stack coupons```<jupyter_code>from datetime import datetime 

class WidgetException(Exception):
    message = 'Generic Widget exception.'
    
    def __init__(self, *args, customer_message=None):
        super().__init__(args)
        if args:
            self.message = args[0]
        self.customer_message = customer_message if customer_message is not None else self.message
        
    def log_exception(self):
        exception = {
            "type": type(self).__name__,
            "message": self.message,
            "args": self.args[1:]
        }
        print(f'EXCEPTION: {datetime.utcnow().isoformat()}: {exception}')
ex1 = WidgetException('some custom message', 10, 100)
ex2 = WidgetException(customer_message='A custom user message.')
ex1.log_exception()
ex2.log_exception()<jupyter_output>EXCEPTION: 2019-08-15T05:25:05.732242: {'type': 'WidgetException', 'message': 'Generic Widget exception.', 'args': ()}<jupyter_text>Now we can create our hierarchy, and override the appropriate values for `message` to make it more specific:<jupyter_code>class SupplierException(WidgetException):
    message = 'Supplier exception.'

class NotManufacturedException(SupplierException):
    message = 'Widget is no longer manufactured by supplier.'
    
class ProductionDelayedException(SupplierException):
    message = 'Widget production has been delayed by supplier.'
    
class ShippingDelayedException(SupplierException):
    message = 'Widget shipping has been delayed by supplier.'
    
class CheckoutException(WidgetException):
    message = 'Checkout exception.'
    
class InventoryException(CheckoutException):
    message = 'Checkout inventory exception.'
    
class OutOfStockException(InventoryException):
    message = 'Inventory out of stock'
    
class PricingException(CheckoutException):
    message = 'Checkout pricing exception.'
    
class InvalidCouponCodeException(PricingException):
    message = 'Invalid checkout coupon code.'
    
class CannotStackCouponException(PricingException):
    message = 'Cannot stack checkout coupon codes.'<jupyter_output><empty_output><jupyter_text>And now we can use any of these exceptions in our code, and use the defined "logger" we implemented:<jupyter_code>try:
    raise CannotStackCouponException()
except WidgetException as ex:
    ex.log_exception()
    raise<jupyter_output>EXCEPTION: 2019-08-15T05:25:05.748971: {'type': 'CannotStackCouponException', 'message': 'Cannot stack checkout coupon codes.', 'args': ()}<jupyter_text>Next let's add the http status codes we want to assign to each exception type.<jupyter_code>from http import HTTPStatus
class WidgetException(Exception):
    message = 'Generic Widget exception.'
    http_status = HTTPStatus.INTERNAL_SERVER_ERROR
    
    def __init__(self, *args, customer_message=None):
        super().__init__(*args)
        if args:
            self.message = args[0]
        self.customer_message = customer_message if customer_message is not None else self.message
        
    def log_exception(self):
        exception = {
            "type": type(self).__name__,
            "message": self.message,
            "args": self.args[1:]
        }
        print(f'EXCEPTION: {datetime.utcnow().isoformat()}: {exception}')<jupyter_output><empty_output><jupyter_text>Before we redefine our child classes, let's also implement the `to_json` function that we can use to send back to our users:<jupyter_code>import json
class WidgetException(Exception):
    message = 'Generic Widget exception.'
    http_status = HTTPStatus.INTERNAL_SERVER_ERROR
    
    def __init__(self, *args, customer_message=None):
        super().__init__(*args)
        if args:
            self.message = args[0]
        self.customer_message = customer_message if customer_message is not None else self.message
        
    def log_exception(self):
        exception = {
            "type": type(self).__name__,
            "message": self.message,
            "args": self.args[1:]
        }
        print(f'EXCEPTION: {datetime.utcnow().isoformat()}: {exception}')
        
    def to_json(self):
        response = {
            'code': self.http_status.value,
            'message': '{}: {}'.format(self.http_status.phrase, self.customer_message),
            'category': type(self).__name__,
            'time_utc': datetime.utcnow().isoformat()            
        }
        return json.dumps(response)
e = WidgetException('same custom message for log and user')
e.log_exception()
json.loads(e.to_json())
e = WidgetException('custom internal message', customer_message='custom user message')
e.log_exception()
e.to_json()<jupyter_output><empty_output><jupyter_text>Now for the bonus exercise - I asked you to try and log the stack trace as well. To do that we could cannot simply use the `str` or `repr` of the  `__traceback__` property of the exception:<jupyter_code>try:
    raise WidgetException('custom error message')
except WidgetException as ex:
    print(repr(ex.__traceback__))<jupyter_output><traceback object at 0x7fecb03b1f88><jupyter_text>Instead we can use the `traceback` module:<jupyter_code>import traceback
try:
    raise ValueError
except ValueError:
    try:
        raise WidgetException('custom error message')
    except WidgetException as ex:
        print(list(traceback.TracebackException.from_exception(ex).format()))<jupyter_output>['Traceback (most recent call last):\n', '  File "<ipython-input-19-2a9225338511>", line 2, in <module>\n    raise ValueError\n', 'ValueError\n', '\nDuring handling of the above exception, another exception occurred:\n\n', 'Traceback (most recent call last):\n', '  File "<ipython-input-19-2a9225338511>", line 5, in <module>\n    raise WidgetException(\'custom error message\')\n', 'WidgetException: custom error message\n']<jupyter_text>So we can use that to implement logging the traceback. What would be nice too would be to expose the formatted traceback in our exception class while we're at it.Since tracebacks can be huge, we're not going to materialize the traceback generator in that property (we'll still have to when we log the exception):<jupyter_code>class WidgetException(Exception):
    message = 'Generic Widget exception.'
    http_status = HTTPStatus.INTERNAL_SERVER_ERROR
    
    def __init__(self, *args, customer_message=None):
        super().__init__(*args)
        if args:
            self.message = args[0]
        self.customer_message = customer_message if customer_message is not None else self.message
        
    @property
    def traceback(self):
        return traceback.TracebackException.from_exception(self).format()
    
    def log_exception(self):
        exception = {
            "type": type(self).__name__,
            "message": self.message,
            "args": self.args[1:],
            "traceback": list(self.traceback)
        }
        print(f'EXCEPTION: {datetime.utcnow().isoformat()}: {exception}')
        
    def to_json(self):
        response = {
            'code': self.http_status.value,
            'message': '{}: {}'.format(self.http_status.phrase, self.customer_message),
            'category': type(self).__name__,
            'time_utc': datetime.utcnow().isoformat()            
        }
        return json.dumps(response)
try:
    raise WidgetException('custom internal message', customer_message='custom user message')
except WidgetException as ex:
    ex.log_exception()
    print('------------')
    print(ex.to_json())<jupyter_output>EXCEPTION: 2019-08-15T05:25:15.569467: {'type': 'WidgetException', 'message': 'custom internal message', 'args': (), 'traceback': ['Traceback (most recent call last):\n', '  File "<ipython-input-21-472686457160>", line 2, in <module>\n    raise WidgetException(\'custom internal message\', customer_message=\'custom user message\')\n', 'WidgetException: custom internal message\n']}
------------
{"code": 500, "message": "Internal Server Error: custom user message", "category": "WidgetException", "time_utc": "2019-08-15T05:25:15.569634"}<jupyter_text>What's nice now, is that we could just print the traceback wihout logging the exception:<jupyter_code>try:
    a = 1 / 0
except ZeroDivisionError:
    try:
        raise WidgetException()
    except WidgetException as ex:
        print(''.join(ex.traceback))<jupyter_output>Traceback (most recent call last):
  File "<ipython-input-22-2212fef7bb30>", line 2, in <module>
    a = 1 / 0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<ipython-input-22-2212fef7bb30>", line 5, in <module>
    raise WidgetException()
WidgetException<jupyter_text>Now we can define our exception sub types, including the http status for each:<jupyter_code>class SupplierException(WidgetException):
    message = 'Supplier exception.'
    http_status = HTTPStatus.INTERNAL_SERVER_ERROR

class NotManufacturedException(SupplierException):
    message = 'Widget is no longer manufactured by supplier.'
    http_status = HTTPStatus.INTERNAL_SERVER_ERROR
    
class ProductionDelayedException(SupplierException):
    message = 'Widget production has been delayed by supplier.'
    http_status = HTTPStatus.INTERNAL_SERVER_ERROR
    
class ShippingDelayedException(SupplierException):
    message = 'Widget shipping has been delayed by supplier.'
    http_status = HTTPStatus.INTERNAL_SERVER_ERROR
    
class CheckoutException(WidgetException):
    message = 'Checkout exception.'
    http_status = HTTPStatus.INTERNAL_SERVER_ERROR
    
class InventoryException(CheckoutException):
    message = 'Checkout inventory exception.'
    http_status = HTTPStatus.INTERNAL_SERVER_ERROR
    
class OutOfStockException(InventoryException):
    message = 'Inventory out of stock'
    http_status = HTTPStatus.INTERNAL_SERVER_ERROR
    
class PricingException(CheckoutException):
    message = 'Checkout pricing exception.'
    http_status = HTTPStatus.INTERNAL_SERVER_ERROR
    
class InvalidCouponCodeException(PricingException):
    message = 'Invalid checkout coupon code.'
    http_status = HTTPStatus.BAD_REQUEST
    
class CannotStackCouponException(PricingException):
    message = 'Cannot stack checkout coupon codes.'
    http_status = HTTPStatus.BAD_REQUEST
e = InvalidCouponCodeException('User tried to use an old coupon', customer_message='Sorry. This coupon has expired.')
e.log_exception()
e.to_json()<jupyter_output><empty_output><jupyter_text>As you can see our traceback was empty above (the exception is present, but there is no call stack) - because we did not actually raise the exception!<jupyter_code>try:
    raise ValueError
except ValueError:
    try:
        raise InvalidCouponCodeException(
            'User tried to use an old coupon', customer_message='Sorry. This coupon has expired.'
        )
    except InvalidCouponCodeException as ex:
        ex.log_exception()
        print('------------')
        print(ex.to_json())
        print('------------')
        print(''.join(ex.traceback))<jupyter_output>EXCEPTION: 2019-08-15T05:25:17.441852: {'type': 'InvalidCouponCodeException', 'message': 'User tried to use an old coupon', 'args': (), 'traceback': ['Traceback (most recent call last):\n', '  File "<ipython-input-27-775351168ae0>", line 2, in <module>\n    raise ValueError\n', 'ValueError\n', '\nDuring handling of the above exception, another exception occurred:\n\n', 'Traceback (most recent call last):\n', '  File "<ipython-input-27-775351168ae0>", line 6, in <module>\n    \'User tried to use an old coupon\', customer_message=\'Sorry. This coupon has expired.\'\n', 'InvalidCouponCodeException: User tried to use an old coupon\n']}
------------
{"code": 400, "message": "Bad Request: Sorry. This coupon has expired.", "category": "InvalidCouponCodeException", "time_utc": "2019-08-15T05:25:17.442103"}
------------
Traceback (most recent call last):
  File "<ipython-input-27-775351168ae0>", line 2, in <module>
    raise ValueError
ValueError

During handling of the above exception, another[...]
{'fraction_non_alphanumeric': 0.11042696265089524, 'fraction_numerical': 0.020983553431094547, 'mean_word_length': 3.803112840466926, 'pattern_counts': {'":': 21, '<': 55, '<?xml version=': 0, '>': 55, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '357564', 'license': 'Apache-2.0', 'n_tokens_mistral': 3681, 'n_tokens_neox': 3502, 'n_words': 1000, 'path': 'dd_1/Part 4/Section 13 - Project 6/Project 6 - Exceptions - Solution.ipynb', 'repo_name': 'rebekka-halal/bg', 'size': '12343'}