thecodex.expert · The Codex Family of Knowledge
Tier 3 · Upper-Intermediate · Python Project

Recipe Manager

Store recipes with ingredients and steps, search by ingredient, and scale servings. A real app with nested data.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a recipe manager: add recipes (each with ingredients and steps), search for recipes that use an ingredient, and scale quantities up or down for different serving sizes. It teaches working with nested data — records that themselves contain lists — and the operations over them.

Where this shows up: any app with rich, nested records — e-commerce products with variants, courses with lessons, projects with tasks. Real data is rarely flat; learning to search and transform nested structures is essential.

2 How to Think About It

Think about the shape of a recipe, before any code:

The plan — in plain English
1. A recipe has a name, a serving count, a list of ingredients (each with an amount), and steps. → 2. Search looks through every recipe’s ingredient list for a match. → 3. Scaling multiplies every ingredient amount by the ratio of new servings to old. → 4. Persist to a file.

Add

Search

Scale

Recipes with nested ingredients

Action?

Store name, servings, ingredients, steps

Find recipes using an ingredient

Multiply amounts by serving ratio

Save

3 The Build — explained part by part

Here is the complete recipe manager. Each part is explained below.

Pythonrecipes.py
import json
import os

FILE = "recipes.json"

def load():
    return json.load(open(FILE)) if os.path.exists(FILE) else []

def save(recipes):
    json.dump(recipes, open(FILE, "w"), indent=2)

def search_by_ingredient(recipes, ingredient):
    ingredient = ingredient.lower()
    return [r for r in recipes
            if any(ingredient in i["name"].lower() for i in r["ingredients"])]

def scale(recipe, new_servings):
    ratio = new_servings / recipe["servings"]
    scaled = []
    for i in recipe["ingredients"]:
        scaled.append({"name": i["name"], "amount": round(i["amount"] * ratio, 2)})
    return {"name": recipe["name"], "servings": new_servings,
            "ingredients": scaled, "steps": recipe["steps"]}

if __name__ == "__main__":
    recipe = {
        "name": "Pancakes", "servings": 2,
        "ingredients": [{"name": "flour", "amount": 100},
                        {"name": "milk", "amount": 200}],
        "steps": ["Mix", "Fry"],
    }
    doubled = scale(recipe, 4)
    print(doubled["ingredients"])
What each part does — in plain words
each recipe — a dictionary with nested data: ingredients is itself a list of dictionaries (name + amount). This nesting models real recipes faithfully.

search_by_ingredient — the tricky part: any(ingredient in i["name"].lower() for i in r["ingredients"]) checks if any ingredient in a recipe matches. We keep recipes where that is true. any() stops at the first match.

scale — compute the ratio of new servings to old, then multiply every ingredient amount by it, rounding to 2 decimals. Doubling servings doubles every amount.

round(i["amount"] * ratio, 2) — keep amounts tidy (no 33.33333 grams).

scale returns a new recipe — it does not change the original, so you can scale the same recipe many ways.
Common mistakes — and how to avoid them
✗ Modifying the original recipe when scaling — then the saved recipe gets corrupted.
✓ Build and return a new recipe; never change the input.
✗ Searching only the top level — missing the nested ingredient list.
✓ Use any(... for i in r["ingredients"]) to search inside.
✗ Forgetting to round — amounts become ugly long decimals.
✓ Round scaled amounts to 2 decimals.

4 Test & Prove Each Part

We test searching nested ingredients and scaling amounts, with a known recipe.

Search finds recipes containing an ingredient
Scaling doubles amounts when servings double
Scaling leaves the original recipe unchanged
Pythontest_recipes.py
def search_by_ingredient(recipes, ingredient):
    ingredient = ingredient.lower()
    return [r for r in recipes
            if any(ingredient in i["name"].lower() for i in r["ingredients"])]


def scale(recipe, new_servings):
    ratio = new_servings / recipe["servings"]
    scaled = [{"name": i["name"], "amount": round(i["amount"] * ratio, 2)}
              for i in recipe["ingredients"]]
    return {"name": recipe["name"], "servings": new_servings, "ingredients": scaled}


RECIPE = {"name": "Pancakes", "servings": 2,
          "ingredients": [{"name": "flour", "amount": 100}]}


def test_search():
    """Search finds a recipe by ingredient."""
    assert len(search_by_ingredient([RECIPE], "flour")) == 1


def test_scale_doubles():
    """Doubling servings doubles the amount."""
    result = scale(RECIPE, 4)
    assert result["ingredients"][0]["amount"] == 200


def test_original_unchanged():
    """Scaling does not change the original recipe."""
    scale(RECIPE, 4)
    assert RECIPE["ingredients"][0]["amount"] == 100

Run with pytest -v. The original-unchanged test is important: it proves scale returns a fresh recipe rather than mutating the input — a subtle bug that this test guards against.

5 The Interface

searchby ingredientrecipes using it
Input
search_by_ingredient(recipes, "flour")
scaleto N servingsamounts adjusted
Output
[{"name": "flour", "amount": 200}]  # doubled

6 Run It & Automate It

Run it locally
python3 recipes.py
See a recipe scaled from 2 to 4 servings. Recipes save to recipes.json.

Jenkins runs the tests automatically — each line explained below.

What you should see when it works
Terminala real run
[{'name': 'flour', 'amount': 200.0}, {'name': 'milk', 'amount': 400.0}]
If it breaks — how to fix it
🚨 Scaling changed my saved recipe.
You mutated the original. Build a new dict in scale instead of editing in place.
🚨 Search misses recipes.
Make sure you search inside the ingredients list, lowercasing both sides.
GroovyJenkinsfile
// Jenkinsfile — automated tests on every change.
pipeline {
    agent any
    stages {
        stage('Get the code') { steps { checkout scm } }       // download the code
        stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } }  // test tool
        stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } }   // run every test
    }
    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed — look above.' }
    }
}
🎯 Try this next — make it yours
  1. Shopping list. Combine ingredients across several recipes. (Teaches: merging nested data.)
  2. Categories. Tag recipes and filter by meal type. (Teaches: another search axis.)
  3. Unit conversion. Convert grams to cups. (Teaches: combining with the converter project.)
What you learned
You learned to work with nested data: searching inside lists-within-records with any(), and transforming them without mutating the original. Real-world data is nested, so these skills apply everywhere. Related: Comprehensions, json.