# Resiliens
A small Python library of decorators to help make your application more resilient. Currently, there are two decorators provided:
1. Retryable
2. CircuitBreaker

## 1. Retryable
You can use the `@Retryable` decorator on any function to automatically retry calling it in case it throws an exception. One use-case would be if you make a remote call to an external API which returns the HTTP status code 500. Maybe it's just a very temporary issue, in which case you might as well try a few more times.

```python
# Use default configuration
@Retryable
def get_github():
    res = requests.get('https://api.github.com')
    if res.status == '500':
        raise ThisFailedError()

# Configure your own max retries, backoff time
# between attempts, and more
@Retryable(max_retries=5, backoff=1000)
def get_github():
    requests.get('https://api.github.com')
    if res.status == '500':
            raise ThisFailedError()
```

## 2. CircuitBreaker
If you make a remote call, and it keeps failing, you may want to stop making this call to save your API usage quota or lower the response time of something that would be failing anyway. In that case, a circuit breaker comes handy.

Currently, this decorator stops calling the decorated function after a configured number of failures in a row (the circuit breaker "opens") and just returns the most recent exception instead. After a wait period, the circuit breaker allows new calls to the decorated function.
```python
@CircuitBreaker
def get_github():
    res = requests.get('https://api.github.com')
    if res.status == '500':
        raise ThisFailedError()

# Configure your own max failed attempts, 
# reset_timeout and more
@CircuitBreaker(max_attempts=5, reset_timeout=1000)
def get_github():
    requests.get('https://api.github.com')
    if res.status == '500':
            raise ThisFailedError()
```
# Expected exceptions
Both decorators have the parameter `expected_exception`. This is the exception they should consider as an expected failure, say that an API is unreachable. If that exception, or a subclass of it, gets raised in the decorated function, Retryable will retry as intended, and CircuitBreaker will count it as a failure and eventually open if it keeps getting raised. If, however, an exception gets raised that is not of that exception type, or a subclass of it, Retryably will not retry and CircuitBreaker will not count it as a failure. By default, they consider all exceptions as expected, but ideally you should set this in a more fine-grained way - e.g. ConnectionError, RequestException.

# Fallback functions
It may be the case that a function decorated with @Retryable never succeeds despite retrying a bunch of times. By default, it will just raise the last exception. However, you can set a fallback function that gets called after all retries are exhausted, for example to provide a fallback return value or do something else.
Same goes for @CircuitBreaker - by default, if the circuit breaker is open it just raises the most recent exception again. You can however supply a fallback function here as well.
