! pip install typeguard rollbar returns tenacity icontract > /dev/null 2>&1
import contextlib
import json
import icontract
import logging
import pathlib
import os
from typing import Union
import requests
from typeguard import typechecked
def get_relevant_restaurants(user):
base_url = "https://en.wikipedia.org/wiki"
return requests.get(f"{base_url}/{user}").content
def get_config(path):
with open(path, 'r') as json_file:
return json.load(json_file)
def pick_best_restaurants(restaurants):
pass
def get_restaurant_recommendation(path):
config = get_config(path)
user = config["user"]
candidates = get_relevant_restaurants(user)
pick_best_restaurants(candidates)
def get_restaurant_recommendation(path):
try:
config = get_config(path)
user = config["user"]
candidates = get_relevant_restaurants(user)
pick_best_restaurants(candidates)
except BaseException:
logging.error("VERY UNINFORMATIVE INFORMATION")
raise BaseException
generaly it’s better for a program to fail fast and crash than to silence the error and continue running the program.
The bugs that inevitably happen later on will be harder to debug since they are far removed from the original cause.
Just because programmers often ignore error messages doesn’t mean the program should stop emitting them.
def get_restaurant_recommendation(path):
try:
config = get_config(path)
user = config["user"]
except FileNotFoundException:
logging.error("VERY UNINFORMATIVE INFORMATION")
raise
except JSONDecodeError:
logging.error("VERY UNINFORMATIVE INFORMATION")
raise
except KeyError:
user = "default_user"
candidates = get_relevant_restaurants(user)
pick_best_restaurants(candidates)
def get_restaurant_recommendation(path):
try:
config = get_config(path)
user = config["user"]
except FileNotFoundException:
logging.error("VERY UNINFORMATIVE INFORMATION")
raise
except JSONDecodeError:
logging.error("VERY UNINFORMATIVE INFORMATION")
raise
except KeyError:
user = "default_user"
candidates = get_relevant_restaurants(user)
pick_best_restaurants(candidates)
Secondly we can use else clause which occur when the try block executed and did not raise an exception.
Thirdly, we use dictionary builtin function get which allow us to define default values.
def run_unstopable_animation():
pass
try:
os.remove('somefile.pyc')
except FileNotFoundError:
pass
try:
run_unstopable_animation()
except KeyboardInterrupt:
pass
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove('somefile.pyc')
from contextlib import suppress
with suppress(KeyboardInterrupt):
run_unstopable_animation()
The "perfect" code:
def get_restaurant_recommendation(path):
config = get_config(path)
user = get_config.get("user", "default_user")
candidates = get_relevant_restaurants(user)
pick_best_restaurants(candidates)
def get_config(path):
with open(path, 'r') as json_file:
config = json.load(json_file)
return config
def get_restaurant_recommendation(path):
try:
config = get_config(path)
except (FileNotFoundException, JSONDecodeError):
logging.error("VERY UNINFORMATIVE INFORMATION")
raise
else:
user = config.get("user", "default_user")
candidates = get_relevant_restaurants(user)
pick_best_restaurants(candidates)
def get_user(path):
if isinstance(path, (str, pathlib.PurePath)):
raise TypeError(f"path has invalid type: {type(path).__name__}")
with open(path, 'r') as json_file:
try:
config = json.load(json_file)
except (FileNotFoundException, JSONDecodeError):
logging.error("VERY INFORMATIVE INFORMATION")
raise
else:
user = config.get("user","default_user")
if isinstance(user, str):
raise TypeError(f"user has invalid type: {type(user).__name__}")
return user
@typechecked
def get_user(path: Union[str, pathlib.PurePath]) -> str:
with open(path, 'r') as json_file:
try:
data = json.load(json_file)
except (FileNotFoundException, JSONDecodeError):
logging.error("VERY INFORMATIVE INFORMATION")
raise
else:
user = data.get("user","default_user")
return user
@icontract.require(lambda path: path.startswith("s3://"), "path must be valid s3 path")
def get_user(path):
with open(path, 'r') as json_file:
try:
data = json.load(json_file)
except (FileNotFoundException, JSONDecodeError):
logging.error("VERY INFORMATIVE INFORMATION")
raise
else:
user = data.get("user","default_user")
return user
def get_relevant_restaurants(user):
base_url = "cool_restaurants.com"
resp = requests.get(f"{base_url}/{user}")
resp.raise_for_status()
return resp.json()
def get_relevant_restaurants(user):
base_url = "cool_restaurants.com"
allowed_retries = 5
for i in range(allowed_retries):
try:
resp = requests.get(f"{base_url}/{user}")
resp.raise_for_status()
except (requests.ConnectionError):
if i == allowed_retries:
raise
else:
return resp.json()
from functools import wraps
def retry(exceptions, allowed_retries=5):
def callable(func):
@wraps(func)
def wrapped(*args, **kwargs):
for i in range(allowed_retries):
try:
res = func()
except exceptions:
continue
else:
return res
return wrapped
return callable
@retry(exceptions=requests.ConnectionError)
def get_relevant_restaurants(country):
base_url = "cool_restaurants.com"
resp = requests.get(f"{base_url}/{user}")
resp.raise_for_status()
return resp.json()
import tenacity
@tenacity.retry(retry=tenacity.retry_if_exception_type(ConnectionError))
def get_relevant_restaurants(user):
base_url = "cool_restaurants.com"
resp = requests.get(f"{base_url}/{user}")
resp.raise_for_status()
return resp.json()
Lets say we have ValueError and we want to recover in diffrent way between TooBig/TooSmall.
def login(user):
pass
import requests
def get_restaurant_recommendation(path):
# ...
try:
candidates = get_relevant_restaurants(user)
except requests.exceptions.ReadTimeout:
login.user()
# ...
try:
1/0
except ZeroDivisionError:
# Some amazing recovery mechanism
raise
through logging, reporting, and monitoring software.
In a world where regulation around personal data is constantly getting stricter,
def login(user):
raise CommonPasswordException(f"password: {password} is too common")
import sys
import rollbar
rollbar.init("Super Secret Token")
def rollbar_except_hook(exc_type, exc_value, traceback):
rollbar.report_exc_info((exc_type, exc_value, traceback))
sys.__excepthook__(exc_type, exc_value, traceback)
sys.excepthook = rollbar_except_hook
try:
raise ValueError
except Exception:
result = "Exception"
except ValueError:
result = "ValueError"
result
raise NotImplementedError
raise NotImplemented
def supprising_result():
try:
return "Expected"
finally:
return "Supprising"
supprising_result()
That being said, errors, whether in code form or simple error response, are a bit like getting a shot — unpleasant, but incredibly useful. Error codes are probably the most useful diagnostic element in the API space, and this is surprising, given how little attention we often pay them.
In general, the goal with error responses is to create a source of information to not only inform the user of a problem, but of the solution to that problem as well. Simply stating a problem does nothing to fix it – and the same is true of API failures.
How do i recover
what is recoverable:
Bugs Aren’t Recoverable Errors! A critical distinction we made early on is the difference between recoverable errors and bugs:
A recoverable error is usually the result of programmatic data validation. Some code has examined the state of the world and deemed the situation unacceptable for progress. Maybe it’s some markup text being parsed, user input from a website, or a transient network connection failure. In these cases, programs are expected to recover. The developer who wrote this code must think about what to do in the event of failure because it will happen in well-constructed programs no matter what you do. The response might be to communicate the situation to an end-user, retry, or abandon the operation entirely, however it is a predictable and, frequently, planned situation, despite being called an “error.”
A bug is a kind of error the programmer didn’t expect. Inputs weren’t validated correctly, logic was written wrong, or any host of problems have arisen. Such problems often aren’t even detected promptly; it takes a while until “secondary effects” are observed indirectly, at which point significant damage to the program’s state might have occurred. Because the developer didn’t expect this to happen, all bets are off. All data structures reachable by this code are now suspect. And because these problems aren’t necessarily detected promptly, in fact, a whole lot more is suspect. Depending on the isolation guarantees of your language, perhaps the entire process is tainted.