This repository provides a guide on how to handle exceptions in a basic API that communicates with a SQLite database. It includes four versions, each with their own set of enhancements.
api_exception_handling
│
├── notebooks/
│ ├── logging_decorator.ipynb
│ └── retry_decorator.ipynb
│
├── logs/
│ ├── README.md
│ └── application.log
│
├── utils/
│ ├── __init__.py
│ ├── decorator1.py
│ ├── decorator2.py
│ └── ...
│
├── versions/
│ ├── legacy/
│ │ └── __init__.py
│ ├── v1/
│ │ ├── __init__.py
│ │ └── TO-DO.md
│ ├── v2/
│ │ ├── __init__.py
│ │ └── TO-DO.md
│ ├── v3/
│ │ ├── __init__.py
│ │ └── TO-DO.md
│ └── v4/
│ ├── __init__.py
│ └── TO-DO.md
│
├── data/
│ └── application.db
│
├── .gitignore
├── README.md
└── __init__.py
The first version of the API implements the following steps:
- Identify potential error scenarios
- Separate library-specific errors
- Handle low-level errors
- Raise errors suitable to the level above
- Add custom exception types
The second version of the API implements the following steps:
- Automate the database creation and run it as the main program
- Integrate Context Manager
class SQLite:
def __init__(self, file="application.db"):
self.file = file
def __enter__(self):
self.conn = sqlite3.connect(self.file)
return self.conn.cursor()
def __exit__(self, type, value, traceback):
self.conn.close()
The third version of the API implements the following steps:
- Create a
retry()
decorator to retry database connection in case of a failure - Create a
log_exceptions()
decorator to log errors and exceptions during data fetching.
@retry(sqlite3.OperationalError, tries=3, delay=1, backoff=2)
def create_database() -> None:
with sqlite3.connect('application.db') as conn:
cur = conn.cursor()
cur.execute('''CREATE TABLE blogs
(id text NOT NULL PRIMARY KEY,
date TEXT,
title TEXT,
content TEXT,
public INTEGER)''')
cur.execute("INSERT INTO blogs VALUES ('first-blog', '2021-03-07', 'My first blog' ,'Some content', 1)")
cur.execute("INSERT INTO blogs VALUES ('private-blog', '2021-03-07', 'Secret blog' ,'This is a secret', 0)")
conn.commit()
@app.route('/blogs/<id>')
@log_exceptions()
def get_blog(blog_id):
try:
return jsonify(fetch_blog(blog_id))
except NotFoundError:
abort(404, description="Resource not found.")
except NotAuthorizedError:
abort(403, description="Access denied.")
The fourth version refactors the code to demonstrate monadic error handling
concepts with the Result
type from the returns
library. The Result
type is a monadic data type that represents the outcome of a computation that may succeed or fail. The Success
subclass holds the result of the computation, while the Failure
subclass holds the exception.
The updated version of the @log_exceptions
decorator now logs exceptions to version4_logs.log
file.
Each version is contained within its own directory and is documented in a separate markdown file.