← Back to Blog

Safe Paws Project — Round 2/5

No arguments about persistence this time. What we said at the Calories Tracker holds here too — persistence comes when the data is mature. We're there. So the dictionary becomes a file: safe_paws.txt.

paws_dict = {}

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

def get_next_id():
    if not paws_dict:
        return 1
    all_ids = []
    for k in paws_dict.keys():
        all_ids.append(int(k))
    return max(all_ids) + 1

def add_paw():
    name = ""
    while not name:
        name = input("Paw name: ")

    specie = ""
    while not specie:
        specie = input("Paw specie: ")

    size = ""
    while not size or size.lower() not in ["small", "medium", "big"]:
        size = input("Paw size (small/medium/big only): ")
    size = size.lower()

    health = ""
    while not health or health.lower() not in ["healthy", "ill"]:
        health = input("Paw health status (healthy/ill only): ")
    health = health.lower()

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

    id = get_next_id()

    with open("safe_paws.txt", "a", encoding="utf-8") as f:
        f.write(f"{id};{name};{specie};{size};{health};{age};available\n")

    load_from_txt()

load_from_txt()

while True:
    print("\nSafe Paws Menu")
    option = input("Choose your option (1-Add paw, q-Quit): ")
    if option == "1":
        add_paw()
        print("Paw added successfully!")
    elif option == "q":
        break
    else:
        print("Invalid option!")

Quick recap of the structure. paws_dict starts empty — it gets populated from the file. load_from_txt() does the loading — it exists but doesn't run until called. get_next_id() assigns each animal the next available number. add_paw() writes to the file and reloads the dictionary immediately after. load_from_txt() is called before the menu — so the dictionary is ready when the program starts. The menu gives us two options for now: add and quit.

Now — filtering. We want an advanced search that doesn't lock the user into a single specific animal. Someone looking for a dog doesn't always care about size. Someone who didn't find what they wanted the first time might want to broaden the search.

The criteria: species, size, max age. Each one optional — entering 0 skips that filter. Status is always filtered — we only show available animals.

We start by collecting the inputs. The code is copied almost directly from add_paw() — only the prompt changes.

def adv_search():
    specie = input("Searched specie (0 for all): ").strip()

    size = ""
    while not size or size.lower() not in ["small", "medium", "big", "0"]:
        size = input("Searched size (small/medium/big, 0 for all): ")
    size = size.lower()

    while True:
        try:
            age = int(input("Searched max age (0 for all): "))
            if age >= 0:
                break
            print("Age must be 0 or greater.")
        except ValueError:
            print("Invalid age. Enter a number.")

Now the filter itself.

    results = {}
    for key in paws_dict:
        if paws_dict[key][5] == "available":
            if specie == "0" or paws_dict[key][1] == specie:
                if size == "0" or paws_dict[key][2] == size:
                    if age == 0 or paws_dict[key][4] <= age:
                        results[key] = paws_dict[key]

    return results

Four levels. Each one is a checkpoint. An animal passes the first — it's available. It passes the second — species matches, or the user doesn't care about species. It passes the third — size matches, or skipped. It passes the fourth — age is within range, or skipped. Only animals that clear every checkpoint make it into results.

0 disables the checkpoint entirely — everyone passes. It's not a workaround. It's a design decision. The user controls how wide or narrow the search is.

One note about age and health — these fields matter, and we include age here as an exercise. In practice, filtering by age or health status risks excluding animals that deserve a chance. Someone always picks the young, healthy one and ignores the rest. That's a conversation for another day. For now, the filter works.

Now a print function.

def print_adv_search():
    results = adv_search()
    for key in results:
        print(f"ID: {key} - {results[key][0]} is a {results[key][4]} years old, {results[key][2]} sized {results[key][1]}. Health status: {results[key][3]}.")

Assuming the shelter has these animals registered:

{
    1: ['Lassie', 'dog', 'medium', 'healthy', 4, 'available'],
    2: ['Whiskers', 'cat', 'small', 'healthy', 2, 'available'],
    3: ['Rex', 'dog', 'big', 'healthy', 6, 'available']
}

A search for dogs, any size, max age 5:

Searched specie (0 for all): dog
Searched size (small/medium/big, 0 for all): 0
Searched max age (0 for all): 5

Output:

ID: 1 - Lassie is a 4 years old, medium sized dog. Health status: healthy.

Rex doesn't make it — age 6 is over the limit. Whiskers doesn't make it — wrong species. Lassie passes all checkpoints.

And the updated menu. We check if the search returns anything before calling the print function — if nothing matches, we say so.

load_from_txt()

while True:
    print("\nSafe Paws Menu")
    option = input("Choose your option (1-Add paw, 2-Adv search, q-Quit): ")
    if option == "1":
        add_paw()
        print("Paw added successfully!")
    elif option == "2":
        if len(adv_search()) > 0:
            print_adv_search()
        else:
            print("No data match your search.")
    elif option == "q":
        break
    else:
        print("Invalid option!")

Quantitatively — a lot. We couldn't sit down and write this end to end from scratch. But the logic is simple, and broken into steps, this is exactly what we built. One brick at a time.

We have persistence. We have advanced filtering. The fort is taking shape.

Next — edit, adoption and more. More bricks a stronger fort. Same method.

[ login to bookmark ] // copied! 27 views · 4 min
← prev Safe Paws Project — Round 1/5 next → Safe Paws Project — Round 3/5
// 0 comments
// No comments yet. Be the first.
// leave a comment

// Your comment will appear after approval.