Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,116 @@ Run the app
```console
$ flask run
```
---

## Assignment: Error Handling and Logging Improvements

This repository was forked from the original project:
https://github.com/patrickloeber/flask-todo

The purpose of this fork was to analyze weaknesses in the project's error handling and logging strategy, and implement improvements as part of a software engineering practical assignment.

### Problems Identified in the Original Code

The original implementation had several issues related to robustness and debugging:

1. **No input validation**
- The `/add` route accepted empty todo titles.

2. **Missing exception handling**
- Database operations (`add`, `update`, `delete`) were executed without `try/except` blocks.

3. **No rollback mechanism**
- If a database operation failed, the transaction could leave the database in an inconsistent state.

4. **Unsafe update/delete operations**
- The `update()` and `delete()` routes assumed that a todo item always exists.
- If an invalid ID was used, the application could crash.

5. **No logging**
- The application did not record important events such as errors, updates, or deletions.

---

### Improvements Implemented

The following improvements were added to make the application more robust and easier to debug:

#### 1. Input Validation
The `/add` route now checks that the todo title is not empty before saving it to the database.

#### 2. Exception Handling
All database operations are wrapped in `try/except` blocks to prevent application crashes.

#### 3. Transaction Rollback
`db.session.rollback()` is now used whenever a database error occurs to maintain database integrity.

#### 4. Safe Record Handling
The application now verifies that a todo item exists before performing update or delete operations.

#### 5. Meaningful Logging
Logging has been added using Python’s `logging` module. The application now records:

- INFO logs for successful operations
- WARNING logs for suspicious user actions
- ERROR logs for database failures

Example log messages:

```
INFO - Added new todo: 'Finish assignment'
WARNING - Update failed: todo with id=9999 not found
ERROR - Database error while deleting todo
```

Logs are written to the file:

```
app.log
```
---

### AI-Generated Logging vs Human Reasoning

AI-generated logging suggestions are often generic, such as:

```
logging.error("An error occurred")
```

Human reasoning improves logging by adding **context and useful debugging information**, for example:

```
logging.error("Database error while deleting todo id=%d: %s", todo_id, e)
```

This approach makes it much easier for developers to diagnose issues in production systems.

---

### Outcome

These improvements increase the **reliability**, **maintainability**, and **debuggability** of the application by introducing proper exception handling and meaningful logging.


---

### Running the Improved Version

After installing the required dependencies, the application can be started with:

```bash
python app.py
```

Then open the browser and navigate to:

```
http://127.0.0.1:5000
```

The application will now run with improved exception handling and logging enabled. Log entries will be written to:

```
app.log
```
88 changes: 69 additions & 19 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.exc import SQLAlchemyError
import logging

app = Flask(__name__)

# Configure logging
logging.basicConfig(
filename="app.log",
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)

# /// = relative path, //// = absolute path
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
Expand All @@ -11,40 +20,81 @@

class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100))
complete = db.Column(db.Boolean)
title = db.Column(db.String(100), nullable=False)
complete = db.Column(db.Boolean, default=False)


@app.route("/")
def home():
todo_list = Todo.query.all()
return render_template("base.html", todo_list=todo_list)
try:
todo_list = Todo.query.all()
logging.info("Loaded %d todo items", len(todo_list))
return render_template("base.html", todo_list=todo_list)
except SQLAlchemyError as e:
logging.error("Database error while loading todo list: %s", e)
return "An error occurred while loading tasks.", 500


@app.route("/add", methods=["POST"])
def add():
title = request.form.get("title")
new_todo = Todo(title=title, complete=False)
db.session.add(new_todo)
db.session.commit()
return redirect(url_for("home"))
title = request.form.get("title", "").strip()

if not title:
logging.warning("User attempted to add an empty todo title")
return "Todo title cannot be empty.", 400

try:
new_todo = Todo(title=title, complete=False)
db.session.add(new_todo)
db.session.commit()
logging.info("Added new todo: '%s'", title)
return redirect(url_for("home"))
except SQLAlchemyError as e:
db.session.rollback()
logging.error("Database error while adding todo '%s': %s", title, e)
return "An error occurred while adding the task.", 500


@app.route("/update/<int:todo_id>")
def update(todo_id):
todo = Todo.query.filter_by(id=todo_id).first()
todo.complete = not todo.complete
db.session.commit()
return redirect(url_for("home"))
try:
todo = Todo.query.filter_by(id=todo_id).first()

if todo is None:
logging.warning("Update failed: todo with id=%d not found", todo_id)
return "Task not found.", 404

todo.complete = not todo.complete
db.session.commit()
logging.info("Updated todo id=%d, complete=%s", todo_id, todo.complete)
return redirect(url_for("home"))
except SQLAlchemyError as e:
db.session.rollback()
logging.error("Database error while updating todo id=%d: %s", todo_id, e)
return "An error occurred while updating the task.", 500


@app.route("/delete/<int:todo_id>")
def delete(todo_id):
todo = Todo.query.filter_by(id=todo_id).first()
db.session.delete(todo)
db.session.commit()
return redirect(url_for("home"))
try:
todo = Todo.query.filter_by(id=todo_id).first()

if todo is None:
logging.warning("Delete failed: todo with id=%d not found", todo_id)
return "Task not found.", 404

db.session.delete(todo)
db.session.commit()
logging.info("Deleted todo id=%d", todo_id)
return redirect(url_for("home"))
except SQLAlchemyError as e:
db.session.rollback()
logging.error("Database error while deleting todo id=%d: %s", todo_id, e)
return "An error occurred while deleting the task.", 500


if __name__ == "__main__":
db.create_all()
app.run(debug=True)
with app.app_context():
db.create_all()
logging.info("Database initialized successfully")
app.run(debug=True)