← Back to Blog

Calories Tracker Project — Round 6/7

I'll be honest with you.

This code-along was built spontaneously, in real time, alongside the explanations and the posts. But I'm not a beginner. Military experience gives you a kind of clarity, decisiveness, and willingness to make mistakes and adapt that not everyone has. That makes it hard to simulate beginner errors in a project like this. In practice though, you will return to your code multiple times — and that's not a problem. It's part of the process. A mistake is a learning event.

What I want to highlight is the timing of persistence. This entire series of tests, prints, and functions — we've said it many times — was about clarifying what we want to collect and how we want to extract it. When that's not clear yet, worrying about the exported file, with all its own potential for error, is one problem too many. The code needs to be built, not laid out end to end from the start.

That's why, with all the inconvenience of re-entering data every time we run the project, I believe — and insist — that now is the right moment for persistence.

And what we've built so far doesn't become useless. add_food_item() moves from writing to a dictionary to writing to a file. Most of the code stays exactly the same. The work wasn't wasted.

A note on file structure

Now is also a good time to talk about file naming. We have calories_tracker_r3.py, calories_tracker_r4.py, calories_tracker_r5.py attached to previous articles. Going forward, the working version is calories_tracker.py — one file, in a dedicated folder called calories_tracker. The round files are backups. Don't delete them, but don't open them by accident instead of the working file.

The current structure of calories_tracker.py:

# food_dict = {}
# def add_food_item(): [...]
# def last_days_details(): [...]
# def print_details(): [...]
# def last_days_sum(): [...]
# def print_target(): [...]
# def general_report(): [...]
# def print_general(): [...]
# while True [...]

What changes in add_food_item()

The only moment in the function where we store data is at the end. That's what changes. Everything else — validation, input, timestamp — stays identical.

This line:

food_dict[timestamp] = [name, quantity, measure, calories]

Becomes these two:

with open("food_log.txt", "a", encoding="utf-8") as f:
    f.write(f"{timestamp};{name};{quantity};{measure};{calories}\n")

open() with "a" creates the file if it doesn't exist, and appends to it if it does. Nothing already in the file is modified, altered, or deleted.

One vulnerability worth noting: we're using ; as a separator. If someone types a semicolon in a food name or measure, the split will break. Every separator has this risk — but some characters are typed far less often than others. The pipe character | is a reasonable alternative if you want extra safety. Whatever you choose, it must be consistent between writing and reading.

The dictionary stays — but changes role

food_dict = {} remains in the file. It doesn't disappear. But it's no longer populated manually inside add_food_item(). From now on, it has one job: to be populated by load_from_txt() at the start of each session. Everything else reads from it as before.

load_from_txt()

def load_from_txt():
    try:
        with open("food_log.txt", "r", encoding="utf-8") as f:
            for line in f:
                row = line.strip().split(";")
                food_dict[row[0]] = [row[1], float(row[2]), row[3], int(row[4])]
    except FileNotFoundError:
        pass    # first run — no file yet

"r" mode — read only. Nothing inside the file is touched. split(";") must match the separator used in f.write(). If you change one, change the other.

Two things are critical here.

First — position. Python reads top to bottom. load_from_txt() must be defined and called before the while True loop. Place it after and the dictionary will be empty when the menu starts.

Second — immediacy. If we only call load_from_txt() once at startup, data added during the current session won't be available for reports until the next run. To fix this, we call load_from_txt() again at the end of add_food_item(), right after f.write(). Write to file, reload from file. The dictionary stays current.

The updated add_food_item()

def add_food_item():
    name = ""
    while not name:
        name = input("Food name: ")

    while True:
        try:
            quantity = float(input("Quantity: "))
            if quantity > 0:
                break
            print("Quantity must be greater than 0.")
        except ValueError:
            print("Invalid quantity. Enter a number.")

    measure = ""
    while not measure:
        measure = input("Measure: ")

    while True:
        try:
            calories = int(input("Calories: "))
            if calories > 0:
                break
            print("Calories must be greater than 0.")
        except ValueError:
            print("Invalid calories. Enter a whole number.")

    timestamp = datetime.datetime.now().strftime("%Y-%m-%d,%H:%M:%S")
    with open("food_log.txt", "a", encoding="utf-8") as f:
        f.write(f"{timestamp};{name};{quantity};{measure};{calories}\n")
    load_from_txt()

The current structure of calories_tracker.py

import datetime

food_dict = {}

def load_from_txt():
    try:
        with open("food_log.txt", "r", encoding="utf-8") as f:
            for line in f:
                row = line.strip().split(";")
                food_dict[row[0]] = [row[1], float(row[2]), row[3], int(row[4])]
    except FileNotFoundError:
        pass

def add_food_item():
    name = ""
    while not name:
        name = input("Food name: ")

    while True:
        try:
            quantity = float(input("Quantity: "))
            if quantity > 0:
                break
            print("Quantity must be greater than 0.")
        except ValueError:
            print("Invalid quantity. Enter a number.")

    measure = ""
    while not measure:
        measure = input("Measure: ")

    while True:
        try:
            calories = int(input("Calories: "))
            if calories > 0:
                break
            print("Calories must be greater than 0.")
        except ValueError:
            print("Invalid calories. Enter a whole number.")

    timestamp = datetime.datetime.now().strftime("%Y-%m-%d,%H:%M:%S")
    with open("food_log.txt", "a", encoding="utf-8") as f:
        f.write(f"{timestamp};{name};{quantity};{measure};{calories}\n")
    load_from_txt()

# def last_days_details(): [...]
# def print_details(): [...]
# def last_days_sum(): [...]
# def print_target(): [...]
# def general_report(): [...]
# def print_general(): [...]

load_from_txt()

while True:
    option = input("Choose option (1-Add, 2-Show food details, 3-Discipline analysis, 4-General report, q-Quit): ")
    if option == "1":
        add_food_item()
        print("Food log added successfully!")
    elif option == "2":
        if len(last_days_details()):
            print_details()
        else:
            print("No data available.")
    elif option == "3":
        if len(last_days_sum()):
            print_target()
        else:
            print("No data available.")
    elif option == "4":
        if len(general_report()):
            print_general()
        else:
            print("No data available.")
    elif option == "q":
        break
    else:
        print("Invalid option!")

The file is almost final. Round 7 brings the last piece — print_general() becomes a file export. The report stops being a print function and becomes a document.

[ login to bookmark ] // copied! 32 views · 4 min
// resources
Exercise calories_tracker_r6.py
← prev Calories Tracker Project — Round 5/7 next → Calories Tracker Project — Round 7/7
// 0 comments
// No comments yet. Be the first.
// leave a comment

// Your comment will appear after approval.