<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Daily Python Projects]]></title><description><![CDATA[Learn Python by practicing every day with a new project.]]></description><link>https://dailypythonprojects.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!gfXP!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e0e0918-4155-47ff-ba5d-c061609cbc83_1000x1000.png</url><title>Daily Python Projects</title><link>https://dailypythonprojects.substack.com</link></image><generator>Substack</generator><lastBuildDate>Fri, 22 May 2026 08:52:58 GMT</lastBuildDate><atom:link href="https://dailypythonprojects.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Ardit Sulce]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[dailypythonprojects@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[dailypythonprojects@substack.com]]></itunes:email><itunes:name><![CDATA[Ardit Sulce]]></itunes:name></itunes:owner><itunes:author><![CDATA[Ardit Sulce]]></itunes:author><googleplay:owner><![CDATA[dailypythonprojects@substack.com]]></googleplay:owner><googleplay:email><![CDATA[dailypythonprojects@substack.com]]></googleplay:email><googleplay:author><![CDATA[Ardit Sulce]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Build a Workout Tracker Suite: Day 3- Complete Workout Dashboard ]]></title><description><![CDATA[Today we&#8217;re creating a complete training dashboard with templates, 1RM calculator, strength standards, and session timers!]]></description><link>https://dailypythonprojects.substack.com/p/build-a-workout-tracker-suite-day-c58</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/build-a-workout-tracker-suite-day-c58</guid><pubDate>Thu, 21 May 2026 11:03:49 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/4e76f4bd-9fc1-48be-8771-061c0f2b3b41_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build a <strong>Workout Tracker Suite</strong> that helps you log lifts, visualize progress, and optimize your training &#8212; perfect for anyone serious about strength training!</p><ul><li><p><strong>Day 1:</strong> Barbell Plate Calculator (Tkinter GUI)</p></li><li><p><strong>Day 2:</strong> Workout Logger with Live Charts</p></li><li><p><strong>Day 3:</strong> Complete Workout Dashboard <strong>(Today)</strong></p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-18">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p><strong>Welcome to the finale!</strong> We&#8217;ve built a plate calculator and workout logger. Today we&#8217;re creating a <strong>complete training dashboard</strong> with templates, 1RM calculator, strength standards, and session timers!</p><p>You&#8217;ll have a professional-grade workout tracking system that rivals commercial fitness apps!</p><h2>Project Task</h2><p>Create a complete workout dashboard with:</p><ul><li><p>Workout templates (Push/Pull/Legs, Upper/Lower, Full Body)</p></li><li><p>1RM (one-rep max) calculator with multiple formulas</p></li><li><p>Strength standards comparison (beginner/intermediate/advanced)</p></li><li><p>Session timer with rest period alerts</p></li><li><p>Progressive overload recommendations</p></li><li><p>Volume tracking with periodization suggestions</p></li><li><p>Export workout reports as text files</p></li><li><p>Enhanced charts with volume and intensity trends</p></li><li><p>All Day 1 &amp; 2 features integrated</p></li></ul><p>This project gives you hands-on practice with advanced GUI design, mathematical formulas, timer functionality, template systems, data analysis, and building production-ready fitness applications &#8212; essential skills for complete software solutions!</p><h2>Expected Output</h2><p>We have gone one step further in this version of the GUI and added tabs. This is a major milestone because it demonstrates one of the most popular techniques to organize a GUI when we are adding more features in the app. With tabs we can organize the various functionalities of the apps as you can see below:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!chk2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d9639f-c236-4921-888f-5f4cc0e99fea_2496x1664.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!chk2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d9639f-c236-4921-888f-5f4cc0e99fea_2496x1664.png 424w, https://substackcdn.com/image/fetch/$s_!chk2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d9639f-c236-4921-888f-5f4cc0e99fea_2496x1664.png 848w, https://substackcdn.com/image/fetch/$s_!chk2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d9639f-c236-4921-888f-5f4cc0e99fea_2496x1664.png 1272w, https://substackcdn.com/image/fetch/$s_!chk2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d9639f-c236-4921-888f-5f4cc0e99fea_2496x1664.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!chk2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d9639f-c236-4921-888f-5f4cc0e99fea_2496x1664.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/12d9639f-c236-4921-888f-5f4cc0e99fea_2496x1664.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:870408,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/198272856?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d9639f-c236-4921-888f-5f4cc0e99fea_2496x1664.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!chk2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d9639f-c236-4921-888f-5f4cc0e99fea_2496x1664.png 424w, https://substackcdn.com/image/fetch/$s_!chk2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d9639f-c236-4921-888f-5f4cc0e99fea_2496x1664.png 848w, https://substackcdn.com/image/fetch/$s_!chk2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d9639f-c236-4921-888f-5f4cc0e99fea_2496x1664.png 1272w, https://substackcdn.com/image/fetch/$s_!chk2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d9639f-c236-4921-888f-5f4cc0e99fea_2496x1664.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>For example, we have now moved the weight progress to a new &#8220;Analytics&#8221; tab:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aH4b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4f244f7-a00f-473c-9807-8cd15948e6a6_2496x1664.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aH4b!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4f244f7-a00f-473c-9807-8cd15948e6a6_2496x1664.png 424w, https://substackcdn.com/image/fetch/$s_!aH4b!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4f244f7-a00f-473c-9807-8cd15948e6a6_2496x1664.png 848w, https://substackcdn.com/image/fetch/$s_!aH4b!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4f244f7-a00f-473c-9807-8cd15948e6a6_2496x1664.png 1272w, https://substackcdn.com/image/fetch/$s_!aH4b!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4f244f7-a00f-473c-9807-8cd15948e6a6_2496x1664.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aH4b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4f244f7-a00f-473c-9807-8cd15948e6a6_2496x1664.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4f244f7-a00f-473c-9807-8cd15948e6a6_2496x1664.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1065833,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/198272856?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4f244f7-a00f-473c-9807-8cd15948e6a6_2496x1664.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!aH4b!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4f244f7-a00f-473c-9807-8cd15948e6a6_2496x1664.png 424w, https://substackcdn.com/image/fetch/$s_!aH4b!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4f244f7-a00f-473c-9807-8cd15948e6a6_2496x1664.png 848w, https://substackcdn.com/image/fetch/$s_!aH4b!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4f244f7-a00f-473c-9807-8cd15948e6a6_2496x1664.png 1272w, https://substackcdn.com/image/fetch/$s_!aH4b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4f244f7-a00f-473c-9807-8cd15948e6a6_2496x1664.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And we have also added other tabs as it can be seen in the screenshots. <br></p><h2>Setup Instructions</h2><p><strong>Install Required Packages:</strong></p><pre><code><code>pip install matplotlib pandas
</code></code></pre><p><strong>Run the dashboard:</strong></p><pre><code><code>python workout_dashboard.py
</code></code></pre><p><strong>All features ready to use:</strong></p><ul><li><p>No additional setup needed</p></li><li><p>Data persists in CSV files</p></li><li><p>Templates saved as JSON</p></li><li><p>Reports export as TXT files</p></li></ul><h2>Understanding 1RM Calculations</h2><p><strong>What is 1RM?</strong></p><p>Your <strong>one-rep max</strong> - the maximum weight you can lift for exactly one rep.</p><p><strong>Why calculate it?</strong></p><ul><li><p>Design training programs (% of 1RM)</p></li><li><p>Track true strength gains</p></li><li><p>Compare against standards</p></li><li><p>Set realistic goals</p></li></ul><p><strong>Common formulas:</strong></p><p><strong>1. Epley Formula:</strong></p><pre><code><code>1RM = weight &#215; (1 + reps/30)

# Example: 225 lbs &#215; 8 reps
1RM = 225 &#215; (1 + 8/30)
1RM = 225 &#215; 1.267
1RM = 285 lbs
</code></code></pre><p><strong>2. Brzycki Formula:</strong></p><pre><code><code>1RM = weight &#215; (36 / (37 - reps))

# Example: 225 lbs &#215; 8 reps
1RM = 225 &#215; (36 / (37 - 8))
1RM = 225 &#215; (36 / 29)
1RM = 279 lbs
</code></code></pre><p><strong>3. Lander Formula:</strong></p><pre><code><code>1RM = (100 &#215; weight) / (101.3 - 2.67123 &#215; reps)

# Example: 225 lbs &#215; 8 reps
1RM = (100 &#215; 225) / (101.3 - 2.67123 &#215; 8)
1RM = 22500 / 79.93
1RM = 281 lbs
</code></code></pre><p><strong>Our implementation:</strong></p><pre><code><code>def calculate_1rm(weight, reps):
    # Epley
    epley = weight * (1 + reps/30)
    
    # Brzycki
    brzycki = weight * (36 / (37 - reps))
    
    # Lander
    lander = (100 * weight) / (101.3 - 2.67123 * reps)
    
    # Lombardi
    lombardi = weight * (reps ** 0.10)
    
    # Average
    average = (epley + brzycki + lander + lombardi) / 4
    
    return {
        'epley': round(epley, 1),
        'brzycki': round(brzycki, 1),
        'lander': round(lander, 1),
        'lombardi': round(lombardi, 1),
        'average': round(average, 1)
    }
</code></code></pre><h2>Understanding Strength Standards</h2><p><strong>What are strength standards?</strong></p><p>Benchmarks for comparing your lifts to typical progression levels.</p><p><strong>Standard categories:</strong></p><pre><code><code>STRENGTH_STANDARDS = {
    'Bench Press': {
        'Beginner': 0.75,      # 0.75&#215; bodyweight
        'Novice': 1.0,         # 1.0&#215; bodyweight
        'Intermediate': 1.25,  # 1.25&#215; bodyweight
        'Advanced': 1.5,       # 1.5&#215; bodyweight
        'Elite': 1.75,         # 1.75&#215; bodyweight
    },
    'Squat': {
        'Beginner': 1.0,
        'Novice': 1.5,
        'Intermediate': 2.0,
        'Advanced': 2.5,
        'Elite': 3.0,
    },
    'Deadlift': {
        'Beginner': 1.25,
        'Novice': 1.75,
        'Intermediate': 2.25,
        'Advanced': 2.75,
        'Elite': 3.25,
    }
}
</code></code></pre><p></p><h2>Understanding Workout Templates</h2><p><strong>What are templates?</strong></p><p>Pre-designed workout programs with exercises, sets, reps, and rest times.</p><p><strong>Common splits:</strong></p><p><strong>Push/Pull/Legs (PPL):</strong></p><pre><code><code>TEMPLATES = {
    'Push': [
        {'exercise': 'Bench Press', 'sets': 4, 'reps': 8, 'rest': 180},
        {'exercise': 'Overhead Press', 'sets': 3, 'reps': 10, 'rest': 120},
        {'exercise': 'Incline DB Press', 'sets': 3, 'reps': 12, 'rest': 90},
        {'exercise': 'Lateral Raises', 'sets': 3, 'reps': 15, 'rest': 60},
        {'exercise': 'Tricep Extensions', 'sets': 3, 'reps': 12, 'rest': 60},
    ],
    'Pull': [
        {'exercise': 'Deadlift', 'sets': 3, 'reps': 5, 'rest': 180},
        {'exercise': 'Pull-ups', 'sets': 3, 'reps': 10, 'rest': 120},
        {'exercise': 'Barbell Row', 'sets': 4, 'reps': 8, 'rest': 120},
        {'exercise': 'Face Pulls', 'sets': 3, 'reps': 15, 'rest': 60},
        {'exercise': 'Bicep Curls', 'sets': 3, 'reps': 12, 'rest': 60},
    ],
    'Legs': [
        {'exercise': 'Squat', 'sets': 4, 'reps': 6, 'rest': 180},
        {'exercise': 'Romanian Deadlift', 'sets': 3, 'reps': 10, 'rest': 120},
        {'exercise': 'Leg Press', 'sets': 3, 'reps': 12, 'rest': 90},
        {'exercise': 'Leg Curls', 'sets': 3, 'reps': 15, 'rest': 60},
        {'exercise': 'Calf Raises', 'sets': 4, 'reps': 20, 'rest': 60},
    ]
}
</code></code></pre><p><strong>Loading a template:</strong></p><pre><code><code>def load_template(template_name):
    exercises = TEMPLATES[template_name]
    
    print(f"\n{template_name.upper()} DAY:")
    for i, ex in enumerate(exercises, 1):
        print(f"{i}. {ex['exercise']}")
        print(f"   {ex['sets']}&#215;{ex['reps']}")
        print(f"   Rest: {ex['rest']}s")
</code></code></pre><p>We also have other features such as the timer which the user can use to time your workout sessions.</p><h2>What You&#8217;ve Accomplished This Week</h2><p>&#127881; <strong>Congratulations!</strong> You&#8217;ve built a <strong>complete workout tracking system</strong>!</p><ul><li><p><strong>Day 1:</strong> Visual plate calculator</p></li><li><p><strong>Day 2:</strong> Workout logger with charts</p></li><li><p><strong>Day 3:</strong> Complete training dashboard</p></li></ul><p><strong>You now have:</strong></p><p>&#9989; <strong>Plate calculator</strong> - Visual barbell loading<br>&#9989; <strong>Workout logging</strong> - Track all exercises<br>&#9989; <strong>Progress charts</strong> - See gains visualized<br>&#9989; <strong>1RM calculator</strong> - Multiple formulas<br>&#9989; <strong>Strength standards</strong> - Compare your lifts<br>&#9989; <strong>Workout templates</strong> - Pre-built programs<br>&#9989; <strong>Session timers</strong> - Track workout &amp; rest<br>&#9989; <strong>Analytics</strong> - Volume &amp; intensity trends<br>&#9989; <strong>Export reports</strong> - Share your progress<br>&#9989; <strong>Professional GUI</strong> - Tabbed interface</p><p><strong>Next steps:</strong></p><ul><li><p>Add exercise video library</p></li><li><p>Integrate with fitness trackers</p></li><li><p>Build mobile version</p></li><li><p>Add nutrition tracking</p></li><li><p>Social features (share workouts)</p></li><li><p>Online coaching integration</p></li></ul><p>You&#8217;ve built the foundation for a <strong>complete fitness platform</strong>! &#128640;</p><h2>View Code Evolution</h2><p>Compare today&#8217;s complete dashboard with earlier versions and see the full progression from simple calculator &#8594; logger &#8594; complete system!</p>
      <p>
          <a href="https://dailypythonprojects.substack.com/p/build-a-workout-tracker-suite-day-c58">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Build a Workout Tracker Suite: Day 2- Workout Logger with Live Charts]]></title><description><![CDATA[Learn Python by practicing every day with a new project.]]></description><link>https://dailypythonprojects.substack.com/p/build-a-workout-tracker-suite-day-057</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/build-a-workout-tracker-suite-day-057</guid><pubDate>Wed, 20 May 2026 11:51:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/54bbc298-9a93-4227-bce7-d27357716868_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build a <strong>Workout Tracker Suite</strong> that helps you log lifts, visualize progress, and optimize your training &#8212; perfect for anyone serious about strength training!</p><ul><li><p><strong>Day 1:</strong> Barbell Plate Calculator (Tkinter GUI)</p></li><li><p><strong>Day 2:</strong> Workout Logger with Live Charts <strong>(Today)</strong></p></li><li><p><strong>Day 3:</strong> Complete Workout Dashboard</p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-18">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p>Yesterday we built a plate calculator. Today we&#8217;re adding <strong>workout logging with live progress charts</strong>! Log your sets, track your PRs, and see your strength gains visualized in real-time!</p><p>You&#8217;ll enhance the app into a complete training tracker with embedded matplotlib charts!</p><h2>Project Task</h2><p>Enhance the plate calculator with workout logging:</p><ul><li><p>Log exercises with sets, reps, weight, and RPE</p></li><li><p>Save workouts to CSV with timestamps</p></li><li><p>Live progress chart showing weight over time</p></li><li><p>Personal records (PR) tracker</p></li><li><p>Exercise history viewer</p></li><li><p>Volume calculations (sets &#215; reps &#215; weight)</p></li><li><p>Filter by exercise and date range</p></li><li><p>Embedded matplotlib charts in Tkinter</p></li><li><p>All Day 1 features still work</p></li></ul><p>This project gives you hands-on practice with CSV data storage, pandas data analysis, matplotlib integration in Tkinter, data visualization, progressive overload tracking, and building data-driven fitness tools &#8212; essential skills for analytics applications!</p><h2>Expected Output</h2><p><strong>Running the enhanced workout logger:</strong></p><pre><code><code>python workout_logger.py
</code></code></pre><p><strong>Log a workout:</strong></p><p>In this version the user can log in a workout such as bench press, squat, etc. These can be chosen from the drop down list:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NYRB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bc14bea-732b-4222-a34d-e8c229c7aea2_2502x1668.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NYRB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bc14bea-732b-4222-a34d-e8c229c7aea2_2502x1668.png 424w, https://substackcdn.com/image/fetch/$s_!NYRB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bc14bea-732b-4222-a34d-e8c229c7aea2_2502x1668.png 848w, https://substackcdn.com/image/fetch/$s_!NYRB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bc14bea-732b-4222-a34d-e8c229c7aea2_2502x1668.png 1272w, https://substackcdn.com/image/fetch/$s_!NYRB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bc14bea-732b-4222-a34d-e8c229c7aea2_2502x1668.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NYRB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bc14bea-732b-4222-a34d-e8c229c7aea2_2502x1668.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2bc14bea-732b-4222-a34d-e8c229c7aea2_2502x1668.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:986242,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/198247786?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bc14bea-732b-4222-a34d-e8c229c7aea2_2502x1668.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NYRB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bc14bea-732b-4222-a34d-e8c229c7aea2_2502x1668.png 424w, https://substackcdn.com/image/fetch/$s_!NYRB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bc14bea-732b-4222-a34d-e8c229c7aea2_2502x1668.png 848w, https://substackcdn.com/image/fetch/$s_!NYRB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bc14bea-732b-4222-a34d-e8c229c7aea2_2502x1668.png 1272w, https://substackcdn.com/image/fetch/$s_!NYRB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bc14bea-732b-4222-a34d-e8c229c7aea2_2502x1668.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>After some days, you will see a weight progress at the bottom part of the GUI showing how your weights have changed over time:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-YbK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47066338-87db-4984-809d-dbf57089964b_2796x1864.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-YbK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47066338-87db-4984-809d-dbf57089964b_2796x1864.png 424w, https://substackcdn.com/image/fetch/$s_!-YbK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47066338-87db-4984-809d-dbf57089964b_2796x1864.png 848w, https://substackcdn.com/image/fetch/$s_!-YbK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47066338-87db-4984-809d-dbf57089964b_2796x1864.png 1272w, https://substackcdn.com/image/fetch/$s_!-YbK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47066338-87db-4984-809d-dbf57089964b_2796x1864.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-YbK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47066338-87db-4984-809d-dbf57089964b_2796x1864.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/47066338-87db-4984-809d-dbf57089964b_2796x1864.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1226399,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/198247786?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47066338-87db-4984-809d-dbf57089964b_2796x1864.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-YbK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47066338-87db-4984-809d-dbf57089964b_2796x1864.png 424w, https://substackcdn.com/image/fetch/$s_!-YbK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47066338-87db-4984-809d-dbf57089964b_2796x1864.png 848w, https://substackcdn.com/image/fetch/$s_!-YbK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47066338-87db-4984-809d-dbf57089964b_2796x1864.png 1272w, https://substackcdn.com/image/fetch/$s_!-YbK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47066338-87db-4984-809d-dbf57089964b_2796x1864.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>CSV file generated: </strong></p><p>Every time you log in a workout, the data are saved in a local <em>workouts.csv</em> <code>file. </code>For the example demonstrated above, here is how the file looks like:</p><pre><code><code>date,time,exercise,sets,reps,weight,rpe,volume
2026-05-13,14:30:00,Bench Press,3,8,225,8,5400
2026-05-13,14:45:00,Squat,4,6,315,9,7560
2026-05-13,15:00:00,Deadlift,3,5,405,9,6075
2026-05-10,15:15:00,Bench Press,4,6,215,7,5160
2026-05-10,15:30:00,Squat,3,8,295,8,7080

</code></code></pre><h2>Setup Instructions</h2><p><strong>Install Required Package:</strong></p><p></p><pre><code><code>pip install matplotlib pandas</code></code></pre><p><strong>Run the logger:</strong></p><pre><code><code>python workout_logger.py
</code></code></pre><p><strong>Data storage:</strong></p><ul><li><p>Workouts saved to <code>workouts.csv</code> automatically</p></li><li><p>PRs tracked automatically</p></li><li><p>Charts update in real-time</p></li></ul><h2>Understanding CSV Workout Storage</h2><p><strong>Why CSV for workouts?</strong></p>
      <p>
          <a href="https://dailypythonprojects.substack.com/p/build-a-workout-tracker-suite-day-057">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Build a Workout Tracker Suite: Day 1 - Barbell Plate Calculator ]]></title><description><![CDATA[Today we will build a visual barbell plate calculator with Python that shows you exactly how to load your barbell!]]></description><link>https://dailypythonprojects.substack.com/p/build-a-workout-tracker-suite-day</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/build-a-workout-tracker-suite-day</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Tue, 19 May 2026 11:23:47 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/74fed1f8-f653-40b5-a49a-8210983d9762_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build a <strong>Workout Tracker Suite</strong> that helps you log lifts, visualize progress, and optimize your training &#8212; perfect for anyone serious about strength training!</p><p><strong>Why build this?</strong> Because tracking workouts manually is tedious, and most apps are overcomplicated. You&#8217;ll create tools that solve real problems: calculating plates, logging progress, and seeing gains visualized!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://dailypythonprojects.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Daily Python Projects is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><strong>What you&#8217;ll learn:</strong> This series teaches you GUI development, data visualization, mathematical calculations, user interface design, and building practical fitness tools &#8212; essential skills for desktop applications!</p><p><strong>Why users love this:</strong> A plate calculator that shows exactly what your barbell looks like? Instant dopamine! By Day 3, you&#8217;ll have a complete training dashboard that tracks every lift and shows your progress charts in real-time!</p><ul><li><p><strong>Day 1:</strong> Barbell Plate Calculator (Tkinter GUI) <strong>(Today)</strong></p></li><li><p><strong>Day 2:</strong> Workout Logger with Live Charts</p></li><li><p><strong>Day 3:</strong> Complete Workout Dashboard</p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-18">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p>We&#8217;re starting with something <strong>immediately satisfying</strong>: a visual plate calculator that shows you exactly how to load your barbell! No more mental math at the gym!</p><p>You&#8217;ll build a beautiful GUI that calculates the optimal plate combination and displays your loaded barbell visually!</p><h2>Project Task</h2><p>Create a barbell plate calculator with:</p><ul><li><p>Target weight input (kg or lbs)</p></li><li><p>Visual barbell display showing loaded plates</p></li><li><p>Optimal plate combination calculator</p></li><li><p>Common lift presets (45/135, 60/185, 100/225, etc.)</p></li><li><p>Support for standard plate sets (45, 35, 25, 10, 5, 2.5)</p></li><li><p>Bar weight selection (20kg/45lbs standard, women&#8217;s bar, specialty bars)</p></li><li><p>Save favorite lifts</p></li><li><p>Clean, intuitive Tkinter interface</p></li><li><p>Color-coded plates by weight</p></li><li><p>Real-time updates as you type</p></li></ul><p>This project gives you hands-on practice with Tkinter GUI, canvas drawing, mathematical optimization, event handling, and building visual tools &#8212; essential skills for desktop applications!</p><h2>Expected Output</h2><p>The app runs on the desktop. It lets the user to set the target weight. Based on that, the visual is instantly updated with the correct number of plates and their weights for the target weight:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-jt_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ed5b9d5-8827-4d79-9918-4378d6111605_2496x1664.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-jt_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ed5b9d5-8827-4d79-9918-4378d6111605_2496x1664.png 424w, https://substackcdn.com/image/fetch/$s_!-jt_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ed5b9d5-8827-4d79-9918-4378d6111605_2496x1664.png 848w, https://substackcdn.com/image/fetch/$s_!-jt_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ed5b9d5-8827-4d79-9918-4378d6111605_2496x1664.png 1272w, https://substackcdn.com/image/fetch/$s_!-jt_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ed5b9d5-8827-4d79-9918-4378d6111605_2496x1664.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-jt_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ed5b9d5-8827-4d79-9918-4378d6111605_2496x1664.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3ed5b9d5-8827-4d79-9918-4378d6111605_2496x1664.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:659797,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/198241916?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ed5b9d5-8827-4d79-9918-4378d6111605_2496x1664.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-jt_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ed5b9d5-8827-4d79-9918-4378d6111605_2496x1664.png 424w, https://substackcdn.com/image/fetch/$s_!-jt_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ed5b9d5-8827-4d79-9918-4378d6111605_2496x1664.png 848w, https://substackcdn.com/image/fetch/$s_!-jt_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ed5b9d5-8827-4d79-9918-4378d6111605_2496x1664.png 1272w, https://substackcdn.com/image/fetch/$s_!-jt_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ed5b9d5-8827-4d79-9918-4378d6111605_2496x1664.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The user can also press the &#8220;Save Current&#8221; button to save a configuration and load it later.<br></p><h2>Setup Instructions</h2><p><strong>No installations needed!</strong></p><p>Uses Python standard library only (Tkinter is built-in):</p><pre><code><code>python plate_calculator.py</code></code></pre><p></p><h2>Understanding Plate Calculation Algorithm</h2><p><strong>The Challenge:</strong></p><p>Given a target weight, find the <strong>minimum number of plates</strong> to load on each side.</p><p><strong>Standard plate sets:</strong></p><p><strong>Pounds (lbs):</strong></p><ul><li><p>45 lbs (red) - biggest</p></li><li><p>35 lbs (yellow)</p></li><li><p>25 lbs (green)</p></li><li><p>10 lbs (white)</p></li><li><p>5 lbs (blue)</p></li><li><p>2.5 lbs (small red)</p></li></ul><p><strong>Kilograms (kg):</strong></p><ul><li><p>25 kg (red)</p></li><li><p>20 kg (blue)</p></li><li><p>15 kg (yellow)</p></li><li><p>10 kg (green)</p></li><li><p>5 kg (white)</p></li><li><p>2.5 kg (small red)</p></li><li><p>1.25 kg (small blue)</p></li></ul><p><strong>Algorithm (Greedy approach):</strong></p><pre><code><code>def calculate_plates(target_weight, bar_weight, plates):
    # Weight needed on plates (both sides)
    plates_weight = target_weight - bar_weight
    
    # Weight per side
    weight_per_side = plates_weight / 2
    
    # Greedy algorithm: largest plates first
    plates_needed = []
    remaining = weight_per_side
    
    for plate in sorted(plates, reverse=True):
        count = int(remaining / plate)
        if count &gt; 0:
            plates_needed.append((plate, count))
            remaining -= plate * count
    
    return plates_needed

# Example
target = 225  # lbs
bar = 45      # lbs
plates = [45, 35, 25, 10, 5, 2.5]

result = calculate_plates(target, bar, plates)
# [(45, 1), (25, 1), (10, 1), (5, 1)]
# = 1&#215;45 + 1&#215;25 + 1&#215;10 + 1&#215;5 = 85 lbs per side
# = 85 &#215; 2 + 45 bar = 215 lbs... wait, that's not 225!
</code></code></pre><p><strong>Problem: Rounding!</strong></p><p>225 - 45 = 180 lbs plates / 2 = 90 lbs per side</p><p>But 1&#215;45 + 1&#215;25 + 1&#215;10 + 1&#215;5 = 85 lbs per side (10 lbs short!)</p><p><strong>Solution: Round to nearest 5 lbs (smallest plate &#215; 2)</strong></p><pre><code><code>def calculate_plates(target, bar, plates):
    # Round to nearest achievable weight
    smallest_increment = min(plates) * 2  # 2.5 &#215; 2 = 5 lbs
    target = round(target / smallest_increment) * smallest_increment
    
    weight_per_side = (target - bar) / 2
    
    # ... rest of algorithm
</code></code></pre><h2>Understanding Tkinter Canvas Drawing</h2><p><strong>Why Canvas?</strong></p><p>Tkinter Canvas lets us draw custom graphics:</p><pre><code><code>import tkinter as tk

root = tk.Tk()
canvas = tk.Canvas(root, width=800, height=200, bg='white')
canvas.pack()

# Draw rectangle (plate)
canvas.create_rectangle(100, 50, 150, 150, fill='red', outline='black')

# Draw circle
canvas.create_oval(300, 75, 350, 125, fill='blue')

# Draw line (barbell)
canvas.create_line(50, 100, 750, 100, fill='gray', width=10)

# Draw text
canvas.create_text(400, 50, text="225 lbs", font=('Arial', 16, 'bold'))

root.mainloop()
</code></code></pre><p><strong>Our barbell drawing:</strong></p><pre><code><code>def draw_barbell(canvas, plates_per_side):
    canvas.delete('all')  # Clear previous
    
    # Bar dimensions
    bar_length = 600
    bar_x_start = 100
    bar_y = 100
    
    # Draw bar
    canvas.create_line(
        bar_x_start, bar_y,
        bar_x_start + bar_length, bar_y,
        fill='gray', width=15
    )
    
    # Draw plates on left side
    x_offset = bar_x_start - 10
    for plate_weight, count in plates_per_side:
        for _ in range(count):
            color = get_plate_color(plate_weight)
            width = get_plate_width(plate_weight)
            height = get_plate_height(plate_weight)
            
            # Draw plate
            canvas.create_rectangle(
                x_offset - width, bar_y - height/2,
                x_offset, bar_y + height/2,
                fill=color, outline='black', width=2
            )
            
            # Label
            canvas.create_text(
                x_offset - width/2, bar_y - height/2 - 15,
                text=str(plate_weight), font=('Arial', 10, 'bold')
            )
            
            x_offset -= width + 5
    
    # Mirror for right side
    # ...
</code></code></pre><h2>Understanding Saved Lifts</h2><p><strong>Persistence with JSON:</strong></p><pre><code><code>import json
from pathlib import Path

SAVED_LIFTS_FILE = 'saved_lifts.json'

def save_lift(name, weight, unit):
    # Load existing
    if Path(SAVED_LIFTS_FILE).exists():
        with open(SAVED_LIFTS_FILE, 'r') as f:
            lifts = json.load(f)
    else:
        lifts = {}
    
    # Add new
    lifts[name] = {'weight': weight, 'unit': unit}
    
    # Save
    with open(SAVED_LIFTS_FILE, 'w') as f:
        json.dump(lifts, f, indent=2)

def load_lifts():
    if Path(SAVED_LIFTS_FILE).exists():
        with open(SAVED_LIFTS_FILE, 'r') as f:
            return json.load(f)
    return {}

# Usage
save_lift('Squat', 315, 'lbs')
save_lift('Bench', 225, 'lbs')

lifts = load_lifts()
# {'Squat': {'weight': 315, 'unit': 'lbs'}, ...}
</code></code></pre><h2>Practical Use Cases</h2><p><strong>1. Pre-planning workouts:</strong></p><pre><code><code>Before gym: Check what plates you need
At gym: Load bar exactly right, no confusion
</code></code></pre><p><strong>2. Progressive overload:</strong></p><pre><code><code>Current: 225 lbs
Next week: 230 lbs
Calculator: Shows you need 2.5 lb plates!
</code></code></pre><p><strong>3. Home gym setup:</strong></p><pre><code><code>See which plates you use most
Buy optimal plate set
Avoid buying unnecessary weights
</code></code></pre><p><strong>4. Teaching form:</strong></p><pre><code><code>Show beginners what "135" looks like
Visual reference for proper loading
Safety check before lifting
</code></code></pre><p><strong>5. Multiple lifters:</strong></p><pre><code><code>Save each person's working weights
Quick load for different people
No mental math during workout
</code></code></pre><h2>Coming Tomorrow</h2><p>Tomorrow we&#8217;re adding <strong>workout logging</strong> with live progress charts! Log your sets/reps/weight and see your strength gains visualized in real-time. The plate calculator becomes part of a complete training tool!</p><h2>Skeleton and Solution</h2><p>Below you will find both a downloadable skeleton.py file to help you code the project with comment guides and the downloadable solution.py file containing the correct solution.</p><p>Get the code skeleton here:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://share.pythonanywhere.com/view/j9o5i5jp-IWRwzewAnRJlQ&quot;,&quot;text&quot;:&quot;View Code Skeleton&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://share.pythonanywhere.com/view/j9o5i5jp-IWRwzewAnRJlQ"><span>View Code Skeleton</span></a></p><p></p><p>Get the code solution here:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://share.pythonanywhere.com/evolution/cybvuP1g3cKY8t7vki6L5A&quot;,&quot;text&quot;:&quot;View Code Solution&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://share.pythonanywhere.com/evolution/cybvuP1g3cKY8t7vki6L5A"><span>View Code Solution</span></a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://dailypythonprojects.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Daily Python Projects is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[AI-Powered Daily Journal: Day 3 - Insights Dashboard]]></title><description><![CDATA[Learn Python by practicing every day with a new project.]]></description><link>https://dailypythonprojects.substack.com/p/ai-powered-daily-journal-day-3-insights</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/ai-powered-daily-journal-day-3-insights</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Thu, 14 May 2026 18:33:28 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/959c0a0f-9a48-4dd7-aa20-f83c6ab5d0d3_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build an <strong>AI-Powered Daily Journal</strong> that writes markdown entries, analyzes your mood with AI, and visualizes your emotional patterns over time!</p><ul><li><p><strong>Day 1:</strong> Markdown Journal Writer</p></li><li><p><strong>Day 2:</strong> AI Mood &amp; Theme Analyzer</p></li><li><p><strong>Day 3:</strong> Insights Dashboard <strong>(Today)</strong></p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-17">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p><strong>Welcome to the finale!</strong> We&#8217;ve built a journal with AI analysis. Today we&#8217;re creating a <strong>beautiful Streamlit dashboard</strong> with mood charts, word clouds, and AI-generated monthly summaries!</p><p>You&#8217;ll visualize your emotional journey and discover patterns you never noticed!</p><h2>Project Task</h2><p>Create a Streamlit insights dashboard with:</p><ul><li><p>Mood-over-time line chart (interactive with Plotly)</p></li><li><p>Word cloud from all journal entries</p></li><li><p>Monthly summary cards (AI-generated)</p></li><li><p>Theme frequency chart</p></li><li><p>Entry statistics and streaks</p></li><li><p>Filter by date range</p></li><li><p>Export insights as PDF</p></li><li><p>Beautiful, intuitive interface</p></li></ul><p>This project gives you hands-on practice with Streamlit, data visualization, Plotly charts, word clouds, dashboard design, and building data-driven applications &#8212; essential skills for analytics and insight tools!</p><h2>Expected Output</h2><p><strong>Running the dashboard:</strong></p><pre><code><code>streamlit run dashboard.py</code></code></pre><p><strong>Browser opens with dashboard showing your mood in a graph:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sQdM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff73ecf67-c077-4297-967e-b5891d5d23aa_2694x1796.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sQdM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff73ecf67-c077-4297-967e-b5891d5d23aa_2694x1796.png 424w, https://substackcdn.com/image/fetch/$s_!sQdM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff73ecf67-c077-4297-967e-b5891d5d23aa_2694x1796.png 848w, https://substackcdn.com/image/fetch/$s_!sQdM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff73ecf67-c077-4297-967e-b5891d5d23aa_2694x1796.png 1272w, https://substackcdn.com/image/fetch/$s_!sQdM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff73ecf67-c077-4297-967e-b5891d5d23aa_2694x1796.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sQdM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff73ecf67-c077-4297-967e-b5891d5d23aa_2694x1796.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f73ecf67-c077-4297-967e-b5891d5d23aa_2694x1796.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:519663,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/197734871?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff73ecf67-c077-4297-967e-b5891d5d23aa_2694x1796.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sQdM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff73ecf67-c077-4297-967e-b5891d5d23aa_2694x1796.png 424w, https://substackcdn.com/image/fetch/$s_!sQdM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff73ecf67-c077-4297-967e-b5891d5d23aa_2694x1796.png 848w, https://substackcdn.com/image/fetch/$s_!sQdM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff73ecf67-c077-4297-967e-b5891d5d23aa_2694x1796.png 1272w, https://substackcdn.com/image/fetch/$s_!sQdM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff73ecf67-c077-4297-967e-b5891d5d23aa_2694x1796.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Scrolling down the webpage you will find the score for the average mood, a word cloud showing the most used words in your journal, and the top themes you have written about:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!OfrT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7c7e6c9-8ce8-43e6-b2f7-d9c62ddfdb98_2742x1828.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!OfrT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7c7e6c9-8ce8-43e6-b2f7-d9c62ddfdb98_2742x1828.png 424w, https://substackcdn.com/image/fetch/$s_!OfrT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7c7e6c9-8ce8-43e6-b2f7-d9c62ddfdb98_2742x1828.png 848w, https://substackcdn.com/image/fetch/$s_!OfrT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7c7e6c9-8ce8-43e6-b2f7-d9c62ddfdb98_2742x1828.png 1272w, https://substackcdn.com/image/fetch/$s_!OfrT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7c7e6c9-8ce8-43e6-b2f7-d9c62ddfdb98_2742x1828.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!OfrT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7c7e6c9-8ce8-43e6-b2f7-d9c62ddfdb98_2742x1828.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a7c7e6c9-8ce8-43e6-b2f7-d9c62ddfdb98_2742x1828.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:940675,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/197734871?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7c7e6c9-8ce8-43e6-b2f7-d9c62ddfdb98_2742x1828.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!OfrT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7c7e6c9-8ce8-43e6-b2f7-d9c62ddfdb98_2742x1828.png 424w, https://substackcdn.com/image/fetch/$s_!OfrT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7c7e6c9-8ce8-43e6-b2f7-d9c62ddfdb98_2742x1828.png 848w, https://substackcdn.com/image/fetch/$s_!OfrT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7c7e6c9-8ce8-43e6-b2f7-d9c62ddfdb98_2742x1828.png 1272w, https://substackcdn.com/image/fetch/$s_!OfrT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7c7e6c9-8ce8-43e6-b2f7-d9c62ddfdb98_2742x1828.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p></p><h2>Setup Instructions</h2><p><strong>Install Required Packages:</strong></p><pre><code><code>pip install streamlit plotly wordcloud langchain-google-genai</code></code></pre><p><strong>Set Google API Key:</strong></p><pre><code><code>export GOOGLE_API_KEY="your-key-here"</code></code></pre><p><strong>Run the dashboard:</strong></p><pre><code><code>streamlit run dashboard.py</code></code></pre><p>Browser automatically opens to </p><p>http://localhost:8501</p><p>Make sure you have some jornal entries in the same directory with the dashboard.py file. Here are some sample files you can use:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://drive.google.com/file/d/18oULtiYyBSIZaxZ6fGiiN1Xdm08iqALv/view?usp=sharing&quot;,&quot;text&quot;:&quot;Download Data&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://drive.google.com/file/d/18oULtiYyBSIZaxZ6fGiiN1Xdm08iqALv/view?usp=sharing"><span>Download Data</span></a></p><p></p><h2>Understanding Streamlit Layout</h2><p><strong>Dashboard structure:</strong></p>
      <p>
          <a href="https://dailypythonprojects.substack.com/p/ai-powered-daily-journal-day-3-insights">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[AI-Powered Daily Journal: Day 2 - AI Mood & Theme Analyzer]]></title><description><![CDATA[Learn Python by practicing every day with a new project.]]></description><link>https://dailypythonprojects.substack.com/p/ai-powered-daily-journal-day-2-ai</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/ai-powered-daily-journal-day-2-ai</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Wed, 13 May 2026 17:15:48 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/fa54f694-4fc1-4b4a-891b-d0c86120397d_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build an <strong>AI-Powered Daily Journal</strong> that writes markdown entries, analyzes your mood with AI, and visualizes your emotional patterns over time!</p><ul><li><p><strong>Day 1:</strong> Markdown Journal Writer</p></li><li><p><strong>Day 2:</strong> AI Mood &amp; Theme Analyzer <strong>(Today)</strong></p></li><li><p><strong>Day 3:</strong> Insights Dashboard</p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-17">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p>Yesterday we built a markdown journal. Today we&#8217;re adding <strong>AI-powered analysis</strong> &#8212; Gemini reads each entry, scores your mood from -1 (negative) to +1 (positive), and extracts 3-5 recurring themes!</p><p>All analysis is cached as JSON metadata, so re-running is instant and free!</p><h2>Project Task</h2><p>Enhance the journal with AI analysis that:</p><ul><li><p>New <code>review</code> command analyzes entries</p></li><li><p>Uses Gemini to score mood (-1 to 1 scale)</p></li><li><p>Extracts 3-5 recurring themes per entry</p></li><li><p>Caches analysis as JSON metadata files</p></li><li><p>Re-runs are instant (reads from cache)</p></li><li><p>Shows mood trends over time</p></li><li><p>Lists most common themes across all entries</p></li><li><p>Clean output with mood emoji indicators</p></li><li><p>All Day 1 features still work</p></li></ul><p>This project gives you hands-on practice with AI analysis, sentiment analysis, theme extraction, JSON caching, metadata management, and building intelligent tools &#8212; essential skills for AI-powered applications!</p><h2>Expected Output</h2><p><strong>Analyzing entries for the first time:</strong></p><pre><code><code>python journal.py review</code></code></pre><p><strong>Console Output:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_yca!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6930018e-badd-4e48-a9f9-b18506280c42_1372x2048.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_yca!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6930018e-badd-4e48-a9f9-b18506280c42_1372x2048.png 424w, https://substackcdn.com/image/fetch/$s_!_yca!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6930018e-badd-4e48-a9f9-b18506280c42_1372x2048.png 848w, https://substackcdn.com/image/fetch/$s_!_yca!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6930018e-badd-4e48-a9f9-b18506280c42_1372x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!_yca!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6930018e-badd-4e48-a9f9-b18506280c42_1372x2048.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_yca!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6930018e-badd-4e48-a9f9-b18506280c42_1372x2048.png" width="1372" height="2048" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6930018e-badd-4e48-a9f9-b18506280c42_1372x2048.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2048,&quot;width&quot;:1372,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:459571,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/197544274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6930018e-badd-4e48-a9f9-b18506280c42_1372x2048.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_yca!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6930018e-badd-4e48-a9f9-b18506280c42_1372x2048.png 424w, https://substackcdn.com/image/fetch/$s_!_yca!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6930018e-badd-4e48-a9f9-b18506280c42_1372x2048.png 848w, https://substackcdn.com/image/fetch/$s_!_yca!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6930018e-badd-4e48-a9f9-b18506280c42_1372x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!_yca!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6930018e-badd-4e48-a9f9-b18506280c42_1372x2048.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZNmL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2980761-725f-419b-a7d7-e1169836fa70_1372x1714.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZNmL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2980761-725f-419b-a7d7-e1169836fa70_1372x1714.png 424w, https://substackcdn.com/image/fetch/$s_!ZNmL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2980761-725f-419b-a7d7-e1169836fa70_1372x1714.png 848w, https://substackcdn.com/image/fetch/$s_!ZNmL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2980761-725f-419b-a7d7-e1169836fa70_1372x1714.png 1272w, https://substackcdn.com/image/fetch/$s_!ZNmL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2980761-725f-419b-a7d7-e1169836fa70_1372x1714.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZNmL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2980761-725f-419b-a7d7-e1169836fa70_1372x1714.png" width="1372" height="1714" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f2980761-725f-419b-a7d7-e1169836fa70_1372x1714.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1714,&quot;width&quot;:1372,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:366598,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/197544274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2980761-725f-419b-a7d7-e1169836fa70_1372x1714.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZNmL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2980761-725f-419b-a7d7-e1169836fa70_1372x1714.png 424w, https://substackcdn.com/image/fetch/$s_!ZNmL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2980761-725f-419b-a7d7-e1169836fa70_1372x1714.png 848w, https://substackcdn.com/image/fetch/$s_!ZNmL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2980761-725f-419b-a7d7-e1169836fa70_1372x1714.png 1272w, https://substackcdn.com/image/fetch/$s_!ZNmL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2980761-725f-419b-a7d7-e1169836fa70_1372x1714.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>Setup Instructions</h2><p><strong>Install Required Package:</strong></p><pre><code><code>pip install langchain-google-genai</code></code></pre><p><strong>Get Your Google API Key:</strong></p><ol><li><p>Go to <a href="https://aistudio.google.com/app/apikey">Google AI Studio</a></p></li><li><p>Click &#8220;Create API Key&#8221;</p></li><li><p>Copy your key</p></li><li><p>Set environment variable:</p></li></ol><pre><code><code># Mac/Linux
export GOOGLE_API_KEY="your-key-here"

# Windows (PowerShell)
$env:GOOGLE_API_KEY="your-key-here"
</code></code></pre><p><strong>Or edit the script:</strong></p><p>This is a simpler method to provide the API key, but less secure.</p><pre><code><code>GOOGLE_API_KEY = "your-key-here"  # Paste your key</code></code></pre><p><strong>Run analysis:</strong></p><pre><code><code>python journal.py review
</code></code></pre><h2>Understanding AI Mood Analysis</h2><p><strong>What is mood scoring?</strong></p><p>The AI reads your entry and assigns a score from -1 to 1:</p><p></p><pre><code><code> 1.0  Very positive   &#128516;  Joyful, excited, grateful, accomplished
 0.5  Positive        &#128578;  Happy, content, optimistic
 0.0  Neutral         &#128528;  Balanced, matter-of-fact
-0.5  Negative        &#128532;  Sad, frustrated, disappointed
-1.0  Very negative   &#128546;  Devastated, hopeless, extremely upset
</code></code></pre><p><strong>How it works:</strong></p>
      <p>
          <a href="https://dailypythonprojects.substack.com/p/ai-powered-daily-journal-day-2-ai">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[AI-Powered Daily Journal: Day 1 - Markdown Journal Writer]]></title><description><![CDATA[Date-based journal that greets you with a randomized reflection prompt and saves each entry to a YYYY-MM-DD.md file under a journal/ folder. Includes commands to list, search, and open past entries.]]></description><link>https://dailypythonprojects.substack.com/p/ai-powered-daily-journal-day-1-markdown</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/ai-powered-daily-journal-day-1-markdown</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Tue, 12 May 2026 16:14:04 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/62664104-68d9-43f7-a15a-5055774b1d78_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build an <strong>AI-Powered Daily Journal</strong> that writes markdown entries, analyzes your mood with AI, and visualizes your emotional patterns over time!</p><p><strong>Why build this?</strong> Because journaling is powerful for mental clarity and self-reflection. You&#8217;ll create a tool that not only stores your thoughts but also uses AI to understand them and show you patterns you might miss!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://dailypythonprojects.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Daily Python Projects is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><strong>What you&#8217;ll learn:</strong> This series teaches you file-based data storage, markdown formatting, CLI tool development, AI sentiment analysis, data visualization, and building applications that provide real insights &#8212; essential skills for personal productivity tools!</p><p><strong>Why users love this:</strong> A journal that understands you! By Day 3, you&#8217;ll have mood charts, word clouds, and AI-generated summaries showing your emotional journey and recurring themes. It&#8217;s like having a therapist and data analyst in one!</p><ul><li><p><strong>Day 1:</strong> Markdown Journal Writer <strong>(Today)</strong></p></li><li><p><strong>Day 2:</strong> AI Mood &amp; Theme Analyzer</p></li><li><p><strong>Day 3:</strong> Insights Dashboard</p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-17">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p>We&#8217;re starting with the foundation: a command-line journal that greets you with randomized reflection prompts, saves entries as dated markdown files, and lets you search through your past thoughts! <strong>We will not use AI today yet.</strong></p><p>You&#8217;ll learn file-based storage, markdown formatting, and building a clean CLI for daily use!</p><h2>Project Task</h2><p>Create a markdown journal system that:</p><ul><li><p>Date-based entries (one file per day: <code>YYYY-MM-DD.md</code>)</p></li><li><p>Stores entries in <code>journal/</code> folder</p></li><li><p>Randomized reflection prompts</p></li><li><p>Commands: <code>new</code>, <code>list</code>, <code>search</code>, <code>open</code></p></li><li><p>Markdown formatting for entries</p></li><li><p>Timestamp each entry</p></li><li><p>Clean CLI interface with argparse</p></li><li><p>Search through past entries</p></li><li><p>Open entries in your editor</p></li></ul><p>This project gives you hands-on practice with file organization, markdown formatting, command-line tools, argparse, pathlib, datetime handling, and building daily-use tools &#8212; essential skills for productivity applications!</p><h2>Expected Output</h2><p><strong>Creating a new journal entry:</strong></p><pre><code><code>python journal.py new</code></code></pre><p><strong>Console Output:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!x10D!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3ac634f-8d68-48dc-ba13-c5a6d47355b6_1372x1228.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!x10D!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3ac634f-8d68-48dc-ba13-c5a6d47355b6_1372x1228.png 424w, https://substackcdn.com/image/fetch/$s_!x10D!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3ac634f-8d68-48dc-ba13-c5a6d47355b6_1372x1228.png 848w, https://substackcdn.com/image/fetch/$s_!x10D!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3ac634f-8d68-48dc-ba13-c5a6d47355b6_1372x1228.png 1272w, https://substackcdn.com/image/fetch/$s_!x10D!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3ac634f-8d68-48dc-ba13-c5a6d47355b6_1372x1228.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!x10D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3ac634f-8d68-48dc-ba13-c5a6d47355b6_1372x1228.png" width="1372" height="1228" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d3ac634f-8d68-48dc-ba13-c5a6d47355b6_1372x1228.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1228,&quot;width&quot;:1372,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:267231,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/197370213?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3ac634f-8d68-48dc-ba13-c5a6d47355b6_1372x1228.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!x10D!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3ac634f-8d68-48dc-ba13-c5a6d47355b6_1372x1228.png 424w, https://substackcdn.com/image/fetch/$s_!x10D!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3ac634f-8d68-48dc-ba13-c5a6d47355b6_1372x1228.png 848w, https://substackcdn.com/image/fetch/$s_!x10D!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3ac634f-8d68-48dc-ba13-c5a6d47355b6_1372x1228.png 1272w, https://substackcdn.com/image/fetch/$s_!x10D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3ac634f-8d68-48dc-ba13-c5a6d47355b6_1372x1228.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><br></p><p><strong>Generated file: </strong><code>journal/2026-05-12.md</code></p><pre><code><code># Friday, April 26, 2026

## &#128221; Entry 1 - 10:30 AM

**Prompt:** What's one small thing that brought you joy today?

Had a great coffee this morning at the new cafe downtown.
The barista remembered my order - made me feel like a regular.
Spent the afternoon coding a new project. Made real progress!
Feeling accomplished and energized.

---
</code></code></pre><p><strong>Listing all entries:</strong></p><pre><code><code>python journal.py list</code></code></pre><p><strong>Output:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Mcni!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f0c4d2d-b1cc-406f-a8e7-fdde7ed014a3_1372x1116.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Mcni!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f0c4d2d-b1cc-406f-a8e7-fdde7ed014a3_1372x1116.png 424w, https://substackcdn.com/image/fetch/$s_!Mcni!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f0c4d2d-b1cc-406f-a8e7-fdde7ed014a3_1372x1116.png 848w, https://substackcdn.com/image/fetch/$s_!Mcni!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f0c4d2d-b1cc-406f-a8e7-fdde7ed014a3_1372x1116.png 1272w, https://substackcdn.com/image/fetch/$s_!Mcni!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f0c4d2d-b1cc-406f-a8e7-fdde7ed014a3_1372x1116.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Mcni!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f0c4d2d-b1cc-406f-a8e7-fdde7ed014a3_1372x1116.png" width="1372" height="1116" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2f0c4d2d-b1cc-406f-a8e7-fdde7ed014a3_1372x1116.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1116,&quot;width&quot;:1372,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:256462,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/197370213?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f0c4d2d-b1cc-406f-a8e7-fdde7ed014a3_1372x1116.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Mcni!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f0c4d2d-b1cc-406f-a8e7-fdde7ed014a3_1372x1116.png 424w, https://substackcdn.com/image/fetch/$s_!Mcni!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f0c4d2d-b1cc-406f-a8e7-fdde7ed014a3_1372x1116.png 848w, https://substackcdn.com/image/fetch/$s_!Mcni!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f0c4d2d-b1cc-406f-a8e7-fdde7ed014a3_1372x1116.png 1272w, https://substackcdn.com/image/fetch/$s_!Mcni!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f0c4d2d-b1cc-406f-a8e7-fdde7ed014a3_1372x1116.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>Searching entries:</strong></p><pre><code><code>python journal.py search "coffee"
</code></code></pre><p><strong>Output:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IDK_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd465b507-dbb0-45be-a40e-21c9f67915a7_1372x1378.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IDK_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd465b507-dbb0-45be-a40e-21c9f67915a7_1372x1378.png 424w, https://substackcdn.com/image/fetch/$s_!IDK_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd465b507-dbb0-45be-a40e-21c9f67915a7_1372x1378.png 848w, https://substackcdn.com/image/fetch/$s_!IDK_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd465b507-dbb0-45be-a40e-21c9f67915a7_1372x1378.png 1272w, https://substackcdn.com/image/fetch/$s_!IDK_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd465b507-dbb0-45be-a40e-21c9f67915a7_1372x1378.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IDK_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd465b507-dbb0-45be-a40e-21c9f67915a7_1372x1378.png" width="1372" height="1378" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d465b507-dbb0-45be-a40e-21c9f67915a7_1372x1378.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1378,&quot;width&quot;:1372,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:253601,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/197370213?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd465b507-dbb0-45be-a40e-21c9f67915a7_1372x1378.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!IDK_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd465b507-dbb0-45be-a40e-21c9f67915a7_1372x1378.png 424w, https://substackcdn.com/image/fetch/$s_!IDK_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd465b507-dbb0-45be-a40e-21c9f67915a7_1372x1378.png 848w, https://substackcdn.com/image/fetch/$s_!IDK_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd465b507-dbb0-45be-a40e-21c9f67915a7_1372x1378.png 1272w, https://substackcdn.com/image/fetch/$s_!IDK_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd465b507-dbb0-45be-a40e-21c9f67915a7_1372x1378.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><br></p><p><strong>Opening a specific entry:</strong></p><pre><code><code>python journal.py open 2026-04-26</code></code></pre><p><strong>Output:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9uil!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F381ec866-597b-4ab7-a053-a8cae944b73e_1372x1116.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9uil!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F381ec866-597b-4ab7-a053-a8cae944b73e_1372x1116.png 424w, https://substackcdn.com/image/fetch/$s_!9uil!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F381ec866-597b-4ab7-a053-a8cae944b73e_1372x1116.png 848w, https://substackcdn.com/image/fetch/$s_!9uil!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F381ec866-597b-4ab7-a053-a8cae944b73e_1372x1116.png 1272w, https://substackcdn.com/image/fetch/$s_!9uil!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F381ec866-597b-4ab7-a053-a8cae944b73e_1372x1116.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9uil!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F381ec866-597b-4ab7-a053-a8cae944b73e_1372x1116.png" width="1372" height="1116" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/381ec866-597b-4ab7-a053-a8cae944b73e_1372x1116.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1116,&quot;width&quot;:1372,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:232723,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/197370213?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F381ec866-597b-4ab7-a053-a8cae944b73e_1372x1116.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9uil!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F381ec866-597b-4ab7-a053-a8cae944b73e_1372x1116.png 424w, https://substackcdn.com/image/fetch/$s_!9uil!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F381ec866-597b-4ab7-a053-a8cae944b73e_1372x1116.png 848w, https://substackcdn.com/image/fetch/$s_!9uil!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F381ec866-597b-4ab7-a053-a8cae944b73e_1372x1116.png 1272w, https://substackcdn.com/image/fetch/$s_!9uil!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F381ec866-597b-4ab7-a053-a8cae944b73e_1372x1116.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>Setup Instructions</h2><p><strong>No installations needed!</strong></p><p>Uses Python standard library only:</p><pre><code><code>python journal.py new</code></code></pre><p><strong>The journal creates:</strong></p><ul><li><p><code>journal/</code> folder (auto-created on first use)</p></li><li><p>One <code>.md</code> file per day (e.g., <code>2026-05-12.md</code>)</p></li><li><p>Each file contains all entries for that day</p></li></ul><h2>Understanding File-Based Journaling</h2><p><strong>Why file-based?</strong></p><p>&#9989; <strong>Simple</strong> - Just markdown files, no database<br>&#9989; <strong>Portable</strong> - Copy your <code>journal/</code> folder anywhere<br>&#9989; <strong>Human-readable</strong> - Open files in any text editor<br>&#9989; <strong>Git-friendly</strong> - Version control your thoughts<br>&#9989; <strong>Privacy</strong> - Files stay on your computer<br>&#9989; <strong>Future-proof</strong> - Markdown will always be readable</p><p><strong>File structure:</strong></p><pre><code><code>your-project/
&#9500;&#9472;&#9472; journal.py
&#9492;&#9472;&#9472; journal/
    &#9500;&#9472;&#9472; 2026-04-26.md
    &#9500;&#9472;&#9472; 2026-04-25.md
    &#9500;&#9472;&#9472; 2026-04-24.md
    &#9492;&#9472;&#9472; ...
</code></code></pre><p><strong>Why YYYY-MM-DD format?</strong></p><p>Putting the year before is usually better in programming and working with files:</p><pre><code><code>2026-04-26.md  &#9989; Sorts chronologically
26-04-2026.md  &#10060; Sorts incorrectly
april-26.md    &#10060; No year, doesn't sort
</code></code></pre><h2>Understanding pathlib</h2><p><strong>Modern file handling with pathlib:</strong></p><pre><code><code>from pathlib import Path

# Create journal directory
journal_dir = Path("journal")
journal_dir.mkdir(exist_ok=True)  # Create if doesn't exist

# Build file path
date_str = "2026-04-26"
file_path = journal_dir / f"{date_str}.md"
# Result: journal/2026-04-26.md

# Check if file exists
if file_path.exists():
    print("Entry already exists!")

# Read file
content = file_path.read_text()

# Write file
file_path.write_text(content)

# List all journal files
entries = sorted(journal_dir.glob("*.md"))
</code></code></pre><p><strong>Why pathlib over os.path?</strong></p><pre><code><code># OLD way (os.path)
import os
path = os.path.join("journal", date_str + ".md")

# NEW way (pathlib)
from pathlib import Path
path = Path("journal") / f"{date_str}.md"
</code></code></pre><p>&#9989; <strong>Cleaner syntax</strong> - <code>/</code> operator joins paths<br>&#9989; <strong>Object-oriented</strong> - Methods like <code>.exists()</code>, <code>.read_text()</code><br>&#9989; <strong>Cross-platform</strong> - Handles Windows/Mac/Linux differences</p><h2>Understanding argparse for CLI</h2><p><strong>Building a professional CLI:</strong></p><pre><code><code>import argparse

parser = argparse.ArgumentParser(description="AI Daily Journal")

# Add subcommands
subparsers = parser.add_subparsers(dest='command')

# 'new' command
new_parser = subparsers.add_parser('new', help='Create new entry')

# 'list' command
list_parser = subparsers.add_parser('list', help='List all entries')
list_parser.add_argument('--all', action='store_true', help='Show all entries')

# 'search' command
search_parser = subparsers.add_parser('search', help='Search entries')
search_parser.add_argument('query', help='Search term')

# 'open' command
open_parser = subparsers.add_parser('open', help='Open entry')
open_parser.add_argument('date', help='Date (YYYY-MM-DD)')

# Parse arguments
args = parser.parse_args()

if args.command == 'new':
    create_entry()
elif args.command == 'list':
    list_entries(show_all=args.all)
elif args.command == 'search':
    search_entries(args.query)
elif args.command == 'open':
    open_entry(args.date)
</code></code></pre><p><strong>Result:</strong></p><pre><code><code>python journal.py new              # Create entry
python journal.py list             # List recent
python journal.py list --all       # List all
python journal.py search "coffee"  # Search
python journal.py open 2026-04-26  # Open
</code></code></pre><h2>Understanding Reflection Prompts</h2><p><strong>Why prompts?</strong></p><p>Random prompts help you:</p><ul><li><p>Break through writer&#8217;s block</p></li><li><p>Explore different aspects of your day</p></li><li><p>Maintain variety in entries</p></li><li><p>Discover new insights</p></li></ul><p><strong>Our prompt bank:</strong></p><pre><code><code>REFLECTION_PROMPTS = [
    "What's one small thing that brought you joy today?",
    "What challenged you today, and what did you learn from it?",
    "What are you grateful for right now?",
    "What's on your mind that you need to get out?",
    "What progress did you make today, no matter how small?",
    "What would you do differently if you could redo today?",
    "Who or what inspired you today?",
    "What's something you're looking forward to?",
    "What did you do today that aligned with your values?",
    "What's one thing you want to remember about today?"
]
</code></code></pre><p><strong>Selecting a random prompt:</strong></p><pre><code><code>import random

prompt = random.choice(REFLECTION_PROMPTS)
print(f"&#128173; Reflection Prompt:\n\"{prompt}\"")
</code></code></pre><h2>Understanding Markdown Formatting</h2><p><strong>Why markdown for journals?</strong></p><p>&#9989; <strong>Readable</strong> - Plain text with simple formatting<br>&#9989; <strong>Portable</strong> - Works everywhere<br>&#9989; <strong>Structured</strong> - Headers, lists, emphasis<br>&#9989; <strong>Future-proof</strong> - Never becomes obsolete</p><p><strong>Our entry structure:</strong></p><pre><code><code># Friday, April 26, 2026          &#8592; Day header

## &#128221; Entry 1 - 10:30 AM          &#8592; Entry header

**Prompt:** What brought you joy? &#8592; Prompt (bold)

Entry text goes here...           &#8592; User's writing
Multiple paragraphs supported.

---                               &#8592; Separator
</code></code></pre><p><strong>Markdown elements we use:</strong></p><pre><code><code># H1 Header          &#8594; Day title
## H2 Header         &#8594; Entry number
**bold**            &#8594; Prompt label
---                 &#8594; Horizontal rule (separator)
</code></code></pre><h2>Understanding Entry Metadata</h2><p><strong>What we track:</strong></p><pre><code><code>entry_data = {
    'date': '2026-04-26',
    'day_name': 'Friday',
    'entry_number': 1,
    'timestamp': '10:30 AM',
    'prompt': 'What brought you joy today?',
    'content': '...',
    'word_count': 32,
    'char_count': 189
}
</code></code></pre><p><strong>Why track this:</strong></p><ul><li><p>Know how many entries per day</p></li><li><p>See when you journal (morning/evening patterns)</p></li><li><p>Track writing volume over time</p></li><li><p>Tomorrow: AI will analyze this data!</p></li></ul><h2>Practical Use Cases</h2><p><strong>1. Daily reflection:</strong></p><pre><code><code># Every morning
python journal.py new
# Write about yesterday, plan for today
</code></code></pre><p><strong>2. Gratitude practice:</strong></p><pre><code><code># Focus on positive moments
python journal.py new
# Prompt guides you to find joy
</code></code></pre><p><strong>3. Problem processing:</strong></p><pre><code><code># When stressed or confused
python journal.py new
# Writing clarifies thoughts
</code></code></pre><p><strong>4. Review patterns:</strong></p><pre><code><code># Look back at your month
python journal.py list
# See frequency and themes
</code></code></pre><p><strong>5. Find specific memories:</strong></p><pre><code><code># Remember that conversation?
python journal.py search "conversation"
# Find when you wrote about it
</code></code></pre><h2>Coming Tomorrow</h2><p>Tomorrow we&#8217;re adding <strong>AI mood and theme analysis</strong> with Gemini! The AI will read each entry, score your mood from -1 (negative) to +1 (positive), and extract 3-5 recurring themes. All cached as JSON so re-runs are free!</p><h2>Skeleton and Solution</h2><p>Below you will find both a downloadable skeleton.py file to help you code the project with comment guides and the downloadable solution.py file containing the correct solution.</p><p>Get the code skeleton here:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://share.pythonanywhere.com/view/SwLEaQ2yRXbCUfJ3v_N7sQ&quot;,&quot;text&quot;:&quot;View Skeleton&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://share.pythonanywhere.com/view/SwLEaQ2yRXbCUfJ3v_N7sQ"><span>View Skeleton</span></a></p><p></p><p>Get the code solution here:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://share.pythonanywhere.com/evolution/YlEP5nvpeInO18ujOP1uCQ&quot;,&quot;text&quot;:&quot;View Code Solution&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://share.pythonanywhere.com/evolution/YlEP5nvpeInO18ujOP1uCQ"><span>View Code Solution</span></a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://dailypythonprojects.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Daily Python Projects is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Desktop Notification System: Day 3 - Smart Notification Manager with Persistence]]></title><description><![CDATA[Learn Python by practicing every day with a new project.]]></description><link>https://dailypythonprojects.substack.com/p/desktop-notification-system-day-3</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/desktop-notification-system-day-3</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Thu, 07 May 2026 14:52:55 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/22ec9163-4fb1-42fb-96c7-7f845a0ab0b9_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build a <strong>Desktop Notification System</strong> that displays toast notifications, schedules reminders, and manages alerts &#8212; perfect for building productivity tools!</p><ul><li><p><strong>Day 1:</strong> Simple Desktop Notifications</p></li><li><p><strong>Day 2:</strong> Scheduled Notifications &amp; Timers</p></li><li><p><strong>Day 3:</strong> Smart Notification Manager with Persistence <strong>(Today)</strong></p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-16">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p><strong>Welcome to the finale!</strong> We&#8217;ve built notifications and timers. Today we&#8217;re creating a <strong>complete notification management system</strong> with saved reminders, templates, and a persistent scheduler that remembers everything!</p><p>You&#8217;ll build a manager that saves your notifications, loads them on startup, and runs multiple scheduled reminders simultaneously!</p><h2>Project Task</h2><p>Create a complete notification management system with:</p><ul><li><p>Save notifications to JSON file</p></li><li><p>Load saved notifications on startup</p></li><li><p>Manage multiple scheduled notifications</p></li><li><p>Create reusable notification templates</p></li><li><p>List all active/scheduled notifications</p></li><li><p>Cancel specific notifications</p></li><li><p>Edit and update notifications</p></li><li><p>Persistent storage between sessions</p></li><li><p>Clean command-line interface</p></li></ul><p>This project gives you hands-on practice with JSON persistence, data management, state tracking, CRUD operations, and building production-ready tools &#8212; essential skills for real-world applications!</p><h2>Expected Output</h2><p><strong>Running the notification manager:</strong></p><pre><code><code>python solution.py</code></code></pre><p><strong>Console Output:</strong></p><p>First you need to set up a scheduled notification in the terminal. Notice that we are also creating a template out of it.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GjSG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1715bb4f-ac76-4468-a8fc-437b542e714a_2508x1672.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GjSG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1715bb4f-ac76-4468-a8fc-437b542e714a_2508x1672.png 424w, https://substackcdn.com/image/fetch/$s_!GjSG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1715bb4f-ac76-4468-a8fc-437b542e714a_2508x1672.png 848w, https://substackcdn.com/image/fetch/$s_!GjSG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1715bb4f-ac76-4468-a8fc-437b542e714a_2508x1672.png 1272w, https://substackcdn.com/image/fetch/$s_!GjSG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1715bb4f-ac76-4468-a8fc-437b542e714a_2508x1672.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GjSG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1715bb4f-ac76-4468-a8fc-437b542e714a_2508x1672.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1715bb4f-ac76-4468-a8fc-437b542e714a_2508x1672.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1354771,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/196787062?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1715bb4f-ac76-4468-a8fc-437b542e714a_2508x1672.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GjSG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1715bb4f-ac76-4468-a8fc-437b542e714a_2508x1672.png 424w, https://substackcdn.com/image/fetch/$s_!GjSG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1715bb4f-ac76-4468-a8fc-437b542e714a_2508x1672.png 848w, https://substackcdn.com/image/fetch/$s_!GjSG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1715bb4f-ac76-4468-a8fc-437b542e714a_2508x1672.png 1272w, https://substackcdn.com/image/fetch/$s_!GjSG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1715bb4f-ac76-4468-a8fc-437b542e714a_2508x1672.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Then, the notification will trigger as a native desktop notification:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bfIo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a8aeec7-1652-458e-93b6-0870d0c4eafe_1854x1236.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bfIo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a8aeec7-1652-458e-93b6-0870d0c4eafe_1854x1236.png 424w, https://substackcdn.com/image/fetch/$s_!bfIo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a8aeec7-1652-458e-93b6-0870d0c4eafe_1854x1236.png 848w, https://substackcdn.com/image/fetch/$s_!bfIo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a8aeec7-1652-458e-93b6-0870d0c4eafe_1854x1236.png 1272w, https://substackcdn.com/image/fetch/$s_!bfIo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a8aeec7-1652-458e-93b6-0870d0c4eafe_1854x1236.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bfIo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a8aeec7-1652-458e-93b6-0870d0c4eafe_1854x1236.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6a8aeec7-1652-458e-93b6-0870d0c4eafe_1854x1236.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:902618,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/196787062?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a8aeec7-1652-458e-93b6-0870d0c4eafe_1854x1236.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bfIo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a8aeec7-1652-458e-93b6-0870d0c4eafe_1854x1236.png 424w, https://substackcdn.com/image/fetch/$s_!bfIo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a8aeec7-1652-458e-93b6-0870d0c4eafe_1854x1236.png 848w, https://substackcdn.com/image/fetch/$s_!bfIo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a8aeec7-1652-458e-93b6-0870d0c4eafe_1854x1236.png 1272w, https://substackcdn.com/image/fetch/$s_!bfIo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a8aeec7-1652-458e-93b6-0870d0c4eafe_1854x1236.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p></p><p><strong>Saved JSON file: </strong><code>notifications_data.json</code></p><pre><code><code>{
  "notifications": [
    {
      "id": "notif_001",
      "title": "Team Meeting",
      "message": "Weekly sync starts now",
      "type": "scheduled",
      "time": "14:00",
      "status": "active",
      "created_at": "2026-04-26T10:15:00",
      "template_name": "Weekly Team Meeting"
    },
    {
      "id": "notif_002",
      "title": "Hydration Reminder",
      "message": "Time to drink water!",
      "type": "recurring",
      "interval_minutes": 60,
      "status": "active",
      "created_at": "2026-04-26T10:20:00",
      "template_name": "Hydration Reminder"
    }
  ],
  "templates": [
    {
      "name": "Daily Standup",
      "type": "scheduled",
      "time": "09:30",
      "title": "Daily Standup",
      "message": "Morning team sync",
      "use_count": 15
    },
    {
      "name": "Hydration Reminder",
      "type": "recurring",
      "interval_minutes": 60,
      "title": "Hydration Reminder",
      "message": "Time to drink water!",
      "use_count": 5
    }
  ]
}
</code></code></pre><p></p><h2>Setup Instructions</h2><p><strong>No new installations needed!</strong></p><p>Same setup as Days 1 &amp; 2:</p><p><strong>macOS:</strong></p><pre><code><code>python solution.py
</code></code></pre><p><strong>Windows:</strong></p><pre><code><code>pip install win10toast  # If not already installed
python solution.py
</code></code></pre><p><strong>Linux:</strong></p><pre><code><code>python solution.py
</code></code></pre><p><strong>Data file:</strong></p><ul><li><p>Creates <code>notifications_data.json</code> automatically</p></li><li><p>Loads on startup</p></li><li><p>Saves on exit or after changes</p></li></ul><h2>Understanding Data Persistence</h2><p><strong>Why persist data?</strong></p><p>Without persistence:</p><ul><li><p>&#10060; All notifications lost when program closes</p></li><li><p>&#10060; Must recreate reminders every time</p></li><li><p>&#10060; Can&#8217;t track notification history</p></li></ul><p>With persistence:</p><ul><li><p>&#9989; Notifications survive program restarts</p></li><li><p>&#9989; Templates saved for reuse</p></li><li><p>&#9989; History of completed notifications</p></li><li><p>&#9989; Resume where you left off</p></li></ul><p><strong>Our approach:</strong></p><pre><code><code># On startup
def load():
    with open('notifications_data.json', 'r') as f:
        data = json.load(f)
    return data

# On exit or after changes
def save():
    with open('notifications_data.json', 'w') as f:
        json.dump(data, f, indent=2)
</code></code></pre><h2>Understanding the Data Structure</h2><p><strong>notifications_data.json structure:</strong></p><pre><code><code>{
  "notifications": [
    {
      "id": "notif_001",           // Unique identifier
      "title": "Meeting",          // Notification title
      "message": "Team sync",      // Notification message
      "type": "scheduled",         // Type: scheduled/recurring/instant
      "time": "14:00",            // For scheduled notifications
      "interval_minutes": 60,      // For recurring notifications
      "status": "active",          // Status: active/completed/cancelled
      "created_at": "2026-04-26T10:15:00",
      "template_name": "Weekly Meeting"  // Optional
    }
  ],
  "templates": [
    {
      "name": "Daily Standup",     // Template name
      "type": "scheduled",         // Template type
      "time": "09:30",            // Template time
      "title": "Daily Standup",    // Default title
      "message": "Morning sync",   // Default message
      "use_count": 15             // Usage tracking
    }
  ]
}
</code></code></pre><p><strong>Key fields:</strong></p><ul><li><p><strong>id</strong> - Unique identifier (e.g., <code>notif_001</code>)</p></li><li><p><strong>type</strong> - <code>scheduled</code>, <code>recurring</code>, <code>instant</code>, <code>countdown</code></p></li><li><p><strong>status</strong> - <code>active</code>, <code>completed</code>, <code>cancelled</code></p></li><li><p><strong>created_at</strong> - When notification was created</p></li><li><p><strong>template_name</strong> - Links to template (optional)</p></li></ul><h2>Understanding Notification States</h2><p><strong>Notification lifecycle:</strong></p><pre><code><code>Created &#8594; Active &#8594; Completed
           &#8595;
       Cancelled
</code></code></pre><p><strong>State definitions:</strong></p><p><strong>Active:</strong></p><ul><li><p>Scheduled in the future</p></li><li><p>Currently recurring</p></li><li><p>Waiting to be sent</p></li></ul><p><strong>Completed:</strong></p><ul><li><p>Already sent</p></li><li><p>Countdown finished</p></li><li><p>One-time notification done</p></li></ul><p><strong>Cancelled:</strong></p><ul><li><p>User manually cancelled</p></li><li><p>Removed before sending</p></li></ul><p><strong>State transitions:</strong></p><pre><code><code># Create notification
notification['status'] = 'active'

# Send notification
notification['status'] = 'completed'
notification['completed_at'] = datetime.now().isoformat()

# Cancel notification
notification['status'] = 'cancelled'
notification['cancelled_at'] = datetime.now().isoformat()
</code></code></pre><h2>Understanding Templates</h2><p><strong>What are templates?</strong></p><p>Templates are <strong>saved notification configurations</strong> you can reuse:</p><pre><code><code># Template
{
  "name": "Daily Standup",
  "type": "scheduled",
  "time": "09:30",
  "title": "Daily Standup",
  "message": "Morning team sync"
}

# Create from template
notification = create_from_template("Daily Standup")
# Automatically fills in all fields
</code></code></pre><p><strong>Template benefits:</strong></p><p>&#9989; <strong>Consistency</strong> - Same notification every time<br>&#9989; <strong>Speed</strong> - One click instead of typing<br>&#9989; <strong>Tracking</strong> - See which templates are most used<br>&#9989; <strong>Reusability</strong> - Use same reminder daily/weekly</p><p><strong>Common templates:</strong></p><ul><li><p>Daily standup (9:30 AM)</p></li><li><p>Lunch break (12:00 PM)</p></li><li><p>End of workday (5:00 PM)</p></li><li><p>Hydration reminder (every 1 hour)</p></li><li><p>Pomodoro session (25 minute timer)</p></li></ul><h2>Understanding Notification IDs</h2><p><strong>Why unique IDs?</strong></p><p>Need a way to identify specific notifications:</p><pre><code><code># Without IDs - ambiguous!
cancel_notification("Team Meeting")  # Which one?

# With IDs - precise!
cancel_notification("notif_001")  # Exact notification
</code></code></pre><p><strong>ID generation:</strong></p><pre><code><code>def generate_id(self):
    # Get highest existing ID number
    existing_ids = [n['id'] for n in self.notifications]
    
    if not existing_ids:
        return "notif_001"
    
    # Extract numbers and increment
    numbers = [int(id.split('_')[1]) for id in existing_ids]
    next_num = max(numbers) + 1
    
    return f"notif_{next_num:03d}"
</code></code></pre><h2>Understanding Multi-Notification Management</h2><p><strong>Managing multiple scheduled notifications:</strong></p><pre><code><code># Track all active threads
active_threads = []

# Start notification 1
thread1 = threading.Thread(target=send_later, args=("notif_001",))
thread1.start()
active_threads.append(thread1)

# Start notification 2
thread2 = threading.Thread(target=send_later, args=("notif_002",))
thread2.start()
active_threads.append(thread2)

# Both running simultaneously!
</code></code></pre><p><strong>Cancelling specific notifications:</strong></p><pre><code><code># Each thread has reference to notification ID
# Store stop flags in dictionary

stop_flags = {}

def send_later(notif_id):
    while not stop_flags.get(notif_id, False):
        # Wait and check if cancelled
        time.sleep(1)
    
    # Send notification
    send_notification(...)

# To cancel
stop_flags[notif_id] = True
</code></code></pre><h2>Understanding Auto-Reload on Startup</h2><p><strong>Resume notifications on startup:</strong></p><pre><code><code>def load_and_schedule(self):
    # Load from JSON
    data = self.load_data()
    
    # Find active scheduled notifications
    for notif in data['notifications']:
        if notif['status'] == 'active':
            if notif['type'] == 'scheduled':
                # Reschedule if time hasn't passed
                self._reschedule_notification(notif)
            elif notif['type'] == 'recurring':
                # Restart recurring reminder
                self._restart_recurring(notif)
</code></code></pre><p><strong>What gets rescheduled:</strong></p><p>&#9989; <strong>Scheduled notifications</strong> - If time is in future<br>&#9989; <strong>Recurring reminders</strong> - Always restart<br>&#10060; <strong>Completed notifications</strong> - Already sent<br>&#10060; <strong>Past scheduled times</strong> - Mark as missed</p><h2>What You&#8217;ve Accomplished This Week</h2><ul><li><p><strong>Day 1:</strong> Desktop notifications with cross-platform support</p></li><li><p><strong>Day 2:</strong> Scheduling, timers, and Pomodoro</p></li><li><p><strong>Day 3:</strong> Persistence, templates, and full management</p></li></ul><p><strong>You now have:</strong></p><p>&#9989; <strong>Cross-platform notifications</strong> - Works on Mac, Windows, Linux<br>&#9989; <strong>Flexible scheduling</strong> - Instant, scheduled, recurring, countdown<br>&#9989; <strong>Persistent storage</strong> - Survives program restarts<br>&#9989; <strong>Template system</strong> - Reusable notification configs<br>&#9989; <strong>Full management</strong> - Create, list, cancel, edit<br>&#9989; <strong>Production-ready</strong> - Clean code, error handling, JSON storage</p><p></p><h2>View Code Evolution</h2><p>Compare today&#8217;s full management system with earlier versions and see how we evolved from simple notifications &#8594; scheduling &#8594; complete persistence.</p>
      <p>
          <a href="https://dailypythonprojects.substack.com/p/desktop-notification-system-day-3">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Desktop Notification System: Day 2 - Scheduled Notifications & Timers]]></title><description><![CDATA[Learn Python by practicing every day with a new project.]]></description><link>https://dailypythonprojects.substack.com/p/desktop-notification-system-day-2</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/desktop-notification-system-day-2</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Wed, 06 May 2026 17:05:07 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/858994ac-a29b-4ec2-83ab-ca67c2b77fa4_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build a <strong>Desktop Notification System</strong> that displays toast notifications, schedules reminders, and manages alerts &#8212; perfect for building productivity tools!</p><ul><li><p><strong>Day 1:</strong> Simple Desktop Notifications</p></li><li><p><strong>Day 2:</strong> Scheduled Notifications &amp; Timers <strong>(Today)</strong></p></li><li><p><strong>Day 3:</strong> Smart Notification Manager with Persistence</p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-16">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p>Yesterday we built basic notifications. Today we&#8217;re adding <strong>time-based features</strong> &#8212; schedule notifications for specific times, create countdown timers, and build a Pomodoro work timer!</p><p>You&#8217;ll learn scheduling, time calculations, threading, and building productivity tools that notify you at the right moment!</p><h2>Project Task</h2><p>Enhance the notification system with time-based features:</p><ul><li><p>Schedule notifications for specific times</p></li><li><p>Countdown timers with notifications</p></li><li><p>Recurring reminders (every X minutes)</p></li><li><p>Pomodoro timer (25 min work + 5 min break)</p></li><li><p>Time parsing (natural language like &#8220;5pm&#8221;, &#8220;30 minutes&#8221;)</p></li><li><p>Background task scheduling</p></li><li><p>Visual countdown display</p></li><li><p>All Day 1 features still work</p></li></ul><p>This project gives you hands-on practice with scheduling, datetime parsing, threading, timers, background tasks, and building productivity tools &#8212; essential skills for automation and time-based applications!</p><h2>Expected Output</h2><p><strong>Running the enhanced notification system:</strong></p><pre><code><code>python solution.py</code></code></pre><p><strong>Console Output:</strong></p><p>Here is what the user sees in the console when running the program:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0r82!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c14bfd0-0947-4dc8-809e-8612172f7e7e_2142x1428.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0r82!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c14bfd0-0947-4dc8-809e-8612172f7e7e_2142x1428.png 424w, https://substackcdn.com/image/fetch/$s_!0r82!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c14bfd0-0947-4dc8-809e-8612172f7e7e_2142x1428.png 848w, https://substackcdn.com/image/fetch/$s_!0r82!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c14bfd0-0947-4dc8-809e-8612172f7e7e_2142x1428.png 1272w, https://substackcdn.com/image/fetch/$s_!0r82!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c14bfd0-0947-4dc8-809e-8612172f7e7e_2142x1428.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0r82!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c14bfd0-0947-4dc8-809e-8612172f7e7e_2142x1428.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1c14bfd0-0947-4dc8-809e-8612172f7e7e_2142x1428.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:877874,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/196681939?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c14bfd0-0947-4dc8-809e-8612172f7e7e_2142x1428.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0r82!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c14bfd0-0947-4dc8-809e-8612172f7e7e_2142x1428.png 424w, https://substackcdn.com/image/fetch/$s_!0r82!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c14bfd0-0947-4dc8-809e-8612172f7e7e_2142x1428.png 848w, https://substackcdn.com/image/fetch/$s_!0r82!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c14bfd0-0947-4dc8-809e-8612172f7e7e_2142x1428.png 1272w, https://substackcdn.com/image/fetch/$s_!0r82!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c14bfd0-0947-4dc8-809e-8612172f7e7e_2142x1428.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In the interaction above the user has chosen to use the Pomodoro feature. After 25 minutes have elapsed, the user will get a native desktop notification:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8vf3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f58e305-e004-472e-a72a-3f3a7705a6bd_1818x1212.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8vf3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f58e305-e004-472e-a72a-3f3a7705a6bd_1818x1212.png 424w, https://substackcdn.com/image/fetch/$s_!8vf3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f58e305-e004-472e-a72a-3f3a7705a6bd_1818x1212.png 848w, https://substackcdn.com/image/fetch/$s_!8vf3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f58e305-e004-472e-a72a-3f3a7705a6bd_1818x1212.png 1272w, https://substackcdn.com/image/fetch/$s_!8vf3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f58e305-e004-472e-a72a-3f3a7705a6bd_1818x1212.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8vf3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f58e305-e004-472e-a72a-3f3a7705a6bd_1818x1212.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4f58e305-e004-472e-a72a-3f3a7705a6bd_1818x1212.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:358725,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/196681939?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f58e305-e004-472e-a72a-3f3a7705a6bd_1818x1212.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8vf3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f58e305-e004-472e-a72a-3f3a7705a6bd_1818x1212.png 424w, https://substackcdn.com/image/fetch/$s_!8vf3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f58e305-e004-472e-a72a-3f3a7705a6bd_1818x1212.png 848w, https://substackcdn.com/image/fetch/$s_!8vf3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f58e305-e004-472e-a72a-3f3a7705a6bd_1818x1212.png 1272w, https://substackcdn.com/image/fetch/$s_!8vf3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f58e305-e004-472e-a72a-3f3a7705a6bd_1818x1212.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>After some minutes, another focus session will start.</p><h2>Setup Instructions</h2><p><strong>No new installations needed!</strong></p><p>Same setup as Day 1:</p><p><strong>macOS:</strong></p><pre><code><code>python solution.py</code></code></pre><p><strong>Windows:</strong></p><pre><code><code>pip install win10toast  # If not already installed
python solution.py</code></code></pre><p><strong>Linux:</strong></p><pre><code><code>python solution.py</code></code></pre><h2>Understanding Time-Based Scheduling</h2><p><strong>Three types of scheduling:</strong></p><p><strong>1. Absolute time (schedule_at):</strong></p><pre><code><code># Notify at 3:00 PM today
scheduler.schedule_at("15:00", "Meeting", "Daily standup")
</code></code></pre><p><strong>2. Relative time (countdown):</strong></p><pre><code><code># Notify in 25 minutes
scheduler.countdown_timer(25, "Timer Done", "Break time!")
</code></code></pre><p><strong>3. Recurring (repeating):</strong></p><pre><code><code># Notify every 30 minutes
scheduler.recurring_reminder(30, "Reminder", "Drink water")
</code></code></pre><h2>Understanding Datetime Parsing</h2><p><strong>Converting user input to datetime objects:</strong></p><pre><code><code>from datetime import datetime, timedelta

# Parse "15:00" to today at 3 PM
def parse_time(time_str):
    # Split "15:00" into hours and minutes
    hours, minutes = map(int, time_str.split(':'))
    
    # Get today's date
    now = datetime.now()
    
    # Combine with specified time
    scheduled_time = now.replace(hour=hours, minute=minutes, second=0)
    
    # If time already passed today, schedule for tomorrow
    if scheduled_time &lt; now:
        scheduled_time += timedelta(days=1)
    
    return scheduled_time
</code></code></pre><p><strong>Calculating time until notification:</strong></p><pre><code><code># How long until 3 PM?
now = datetime.now()
scheduled_time = parse_time("15:00")

time_diff = scheduled_time - now

# Convert to readable format
hours = time_diff.seconds // 3600
minutes = (time_diff.seconds % 3600) // 60

print(f"Time until notification: {hours} hours {minutes} minutes")
</code></code></pre><h2>Understanding Threading for Background Tasks</h2><p><strong>Why threading?</strong></p><p>Without threading, your program <strong>blocks</strong> while waiting:</p><pre><code><code># BAD - Program freezes for 25 minutes!
time.sleep(25 * 60)
send_notification("Timer Done", "Break time!")
# User can't do anything else during wait
</code></code></pre><p><strong>With threading, program stays responsive:</strong></p><pre><code><code>import threading

def timer_thread(minutes):
    time.sleep(minutes * 60)
    send_notification("Timer Done", "Break time!")

# Start timer in background
thread = threading.Thread(target=timer_thread, args=(25,))
thread.start()

# Program continues - user can start another timer!
</code></code></pre><p><strong>Our approach:</strong></p><pre><code><code>def countdown_timer(self, minutes, title, message):
    # Create background thread
    thread = threading.Thread(
        target=self._run_countdown,
        args=(minutes, title, message)
    )
    thread.daemon = True  # Thread dies when main program exits
    thread.start()
</code></code></pre><h2>Understanding the Pomodoro Technique</h2><p><strong>What is Pomodoro?</strong></p><p>A productivity method that uses timed work sessions with breaks:</p><ol><li><p><strong>Work</strong> for 25 minutes (focused)</p></li><li><p><strong>Break</strong> for 5 minutes (short)</p></li><li><p><strong>Repeat</strong> 4 times</p></li><li><p><strong>Long break</strong> 15-30 minutes</p></li></ol><p><strong>Our implementation:</strong></p><pre><code><code>def start_pomodoro(self, sessions=4):
    for i in range(sessions):
        # Work session
        print(f"&#127813; Work Session {i+1}/{sessions}")
        self.countdown_timer(25, "Work Complete", "Time for a break!")
        
        # Short break (except after last session)
        if i &lt; sessions - 1:
            print("&#9749; Break Time")
            self.countdown_timer(5, "Break Over", "Back to work!")
</code></code></pre><p><strong>Why it works:</strong></p><ul><li><p>&#9989; Maintains focus with time limits</p></li><li><p>&#9989; Prevents burnout with regular breaks</p></li><li><p>&#9989; Creates rhythm and routine</p></li><li><p>&#9989; Notifications keep you on track</p></li></ul><h2>Understanding Countdown Display</h2><p><strong>Live countdown in terminal:</strong></p><pre><code><code>import time

def show_countdown(minutes):
    total_seconds = minutes * 60
    
    for remaining in range(total_seconds, 0, -60):
        mins = remaining // 60
        secs = remaining % 60
        
        # Display with carriage return (overwrites line)
        print(f"\r&#9201;&#65039;  Time remaining: {mins}:{secs:02d}", end='', flush=True)
        
        time.sleep(60)  # Wait 1 minute
    
    print("\n&#128276; Timer complete!")
</code></code></pre><p><strong>Key techniques:</strong></p><ul><li><p><code>\r</code> - Carriage return (moves cursor to start of line)</p></li><li><p><code>end=''</code> - Don&#8217;t print newline</p></li><li><p><code>flush=True</code> - Force immediate output</p></li><li><p>Result: Counter updates in place instead of printing new lines</p></li></ul><h2>Understanding Recurring Reminders</h2><p><strong>How recurring reminders work:</strong></p><pre><code><code>def recurring_reminder(self, interval_minutes, title, message):
    while True:
        # Wait for interval
        time.sleep(interval_minutes * 60)
        
        # Send reminder
        self.send_notification(title, message)
        
        # Calculate next reminder time
        next_time = datetime.now() + timedelta(minutes=interval_minutes)
        print(f"Next reminder: {next_time.strftime('%I:%M %p')}")
</code></code></pre><p><strong>Stopping recurring reminders:</strong></p><pre><code><code># Use Ctrl+C to stop
try:
    recurring_reminder(30, "Break", "Take a break!")
except KeyboardInterrupt:
    print("\nRecurring reminder stopped.")
</code></code></pre><h2>Practical Use Cases</h2><p><strong>1. Meeting reminders:</strong></p><pre><code><code># Remind 15 minutes before 2 PM meeting
scheduler.schedule_at("13:45", "Meeting Soon", "Team sync at 2 PM")
</code></code></pre><p><strong>2. Pomodoro work sessions:</strong></p><pre><code><code># Deep work with automatic breaks
scheduler.start_pomodoro(sessions=4)
</code></code></pre><p><strong>3. Hydration reminders:</strong></p><pre><code><code># Drink water every hour
scheduler.recurring_reminder(60, "Hydration", "Drink some water!")
</code></code></pre><p><strong>4. Cooking timers:</strong></p><pre><code><code># Pasta cooking timer
scheduler.countdown_timer(10, "Pasta Ready", "Time to drain the pasta!")
</code></code></pre><p><strong>5. Long-running script notifications:</strong></p><pre><code><code># Start training ML model
train_model()  # Takes 3 hours

# Schedule notification for when it should be done
scheduler.countdown_timer(180, "Training Complete", "Model training finished!")
</code></code></pre><h2>Coming Tomorrow</h2><p>Tomorrow we&#8217;re building a <strong>Smart Notification Manager</strong> with persistence &#8212; save your scheduled notifications, create notification templates, manage multiple reminders, and run everything from the system tray!</p><h2>View Code Evolution</h2><p>Compare today&#8217;s scheduler-enhanced system with yesterday&#8217;s basic notifications and see how we added time-based features.</p>
      <p>
          <a href="https://dailypythonprojects.substack.com/p/desktop-notification-system-day-2">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Desktop Notification System: Day 1 - Simple Desktop Notifications]]></title><description><![CDATA[Learn how to make a Python program that triggers native notifications on your computer.]]></description><link>https://dailypythonprojects.substack.com/p/desktop-notification-system-day-1</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/desktop-notification-system-day-1</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Tue, 05 May 2026 21:18:47 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ecd14843-c76e-4737-b6cf-c1ad09478555_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build a <strong>Desktop Notification System</strong> that displays toast notifications, schedules reminders, and manages alerts &#8212; perfect for building productivity tools!</p><p><strong>Why build this?</strong> Because notifications are everywhere &#8212; apps, websites, productivity tools. You&#8217;ll learn how to create professional desktop alerts that grab attention at the right moment!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://dailypythonprojects.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Daily Python Projects is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><strong>What you&#8217;ll learn:</strong> This series teaches you desktop notifications, system tray integration, scheduling, time-based triggers, persistence, and building tools that run in the background &#8212; essential skills for automation and productivity apps!</p><p><strong>Why users love this:</strong> Create reminders that actually work! No more forgetting tasks or missing deadlines. By Day 3, you&#8217;ll have a smart notification manager running silently in your system tray!</p><ul><li><p><strong>Day 1:</strong> Simple Desktop Notifications <strong>(Today)</strong></p></li><li><p><strong>Day 2:</strong> Scheduled Notifications &amp; Timers</p></li><li><p><strong>Day 3:</strong> Smart Notification Manager with Persistence</p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-16">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p>We&#8217;re starting with the foundation: a simple notification system that displays desktop toast notifications with custom messages, titles, and durations!</p><p>You&#8217;ll learn how to trigger system notifications from Python using native OS capabilities &#8212; no complex dependencies!</p><h2>Project Task</h2><p>Create a desktop notification system that:</p><ul><li><p>Displays toast notifications on Windows/Mac/Linux</p></li><li><p>Custom title and message</p></li><li><p>Adjustable duration (how long it shows)</p></li><li><p>Multiple notification types (info, warning, success, error)</p></li><li><p>Simple command-line interface</p></li><li><p>Cross-platform compatibility</p></li><li><p>Uses native OS notification systems</p></li><li><p>No complex dependencies</p></li></ul><p>This project gives you hands-on practice with desktop notifications, system integration, cross-platform development, user alerts, and building tools that interact with the operating system &#8212; essential for automation and productivity apps!</p><h2>Expected Output</h2><p><strong>Running the notification tool:</strong></p><pre><code><code>python solution.py</code></code></pre><p><strong>Console Output:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Xmxe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bf2d6bf-726f-45e1-9daf-d8a5f4c1e777_1668x1112.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Xmxe!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bf2d6bf-726f-45e1-9daf-d8a5f4c1e777_1668x1112.png 424w, https://substackcdn.com/image/fetch/$s_!Xmxe!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bf2d6bf-726f-45e1-9daf-d8a5f4c1e777_1668x1112.png 848w, https://substackcdn.com/image/fetch/$s_!Xmxe!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bf2d6bf-726f-45e1-9daf-d8a5f4c1e777_1668x1112.png 1272w, https://substackcdn.com/image/fetch/$s_!Xmxe!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bf2d6bf-726f-45e1-9daf-d8a5f4c1e777_1668x1112.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Xmxe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bf2d6bf-726f-45e1-9daf-d8a5f4c1e777_1668x1112.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6bf2d6bf-726f-45e1-9daf-d8a5f4c1e777_1668x1112.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:640732,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/196590756?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bf2d6bf-726f-45e1-9daf-d8a5f4c1e777_1668x1112.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Xmxe!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bf2d6bf-726f-45e1-9daf-d8a5f4c1e777_1668x1112.png 424w, https://substackcdn.com/image/fetch/$s_!Xmxe!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bf2d6bf-726f-45e1-9daf-d8a5f4c1e777_1668x1112.png 848w, https://substackcdn.com/image/fetch/$s_!Xmxe!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bf2d6bf-726f-45e1-9daf-d8a5f4c1e777_1668x1112.png 1272w, https://substackcdn.com/image/fetch/$s_!Xmxe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bf2d6bf-726f-45e1-9daf-d8a5f4c1e777_1668x1112.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>That would trigger a notification on your computer:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!slJU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ff503f-178d-45f0-9bb5-858fee7e2fb9_1344x896.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!slJU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ff503f-178d-45f0-9bb5-858fee7e2fb9_1344x896.png 424w, https://substackcdn.com/image/fetch/$s_!slJU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ff503f-178d-45f0-9bb5-858fee7e2fb9_1344x896.png 848w, https://substackcdn.com/image/fetch/$s_!slJU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ff503f-178d-45f0-9bb5-858fee7e2fb9_1344x896.png 1272w, https://substackcdn.com/image/fetch/$s_!slJU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ff503f-178d-45f0-9bb5-858fee7e2fb9_1344x896.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!slJU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ff503f-178d-45f0-9bb5-858fee7e2fb9_1344x896.png" width="1344" height="896" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f9ff503f-178d-45f0-9bb5-858fee7e2fb9_1344x896.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:896,&quot;width&quot;:1344,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:540494,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/196590756?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ff503f-178d-45f0-9bb5-858fee7e2fb9_1344x896.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!slJU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ff503f-178d-45f0-9bb5-858fee7e2fb9_1344x896.png 424w, https://substackcdn.com/image/fetch/$s_!slJU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ff503f-178d-45f0-9bb5-858fee7e2fb9_1344x896.png 848w, https://substackcdn.com/image/fetch/$s_!slJU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ff503f-178d-45f0-9bb5-858fee7e2fb9_1344x896.png 1272w, https://substackcdn.com/image/fetch/$s_!slJU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ff503f-178d-45f0-9bb5-858fee7e2fb9_1344x896.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>Setup Instructions</h2><p><strong>Install Required Packages:</strong></p><p><strong>macOS and Linux (no installation needed!)</strong></p><p><strong>Windows:</strong></p><pre><code><code>pip install win10toast

</code></code></pre><p><strong>Platform support:</strong></p><ul><li><p>&#9989; <strong>macOS</strong> - Native notification center via <code>osascript</code></p></li><li><p>&#9989; <strong>Windows 10/11</strong> - Native toast notifications via <code>win10toast</code></p></li><li><p>&#9989; <strong>Linux</strong> - Native notifications via <code>notify-send</code></p></li></ul><h2>Understanding Desktop Notifications</h2><p><strong>What are desktop notifications?</strong></p><p>Desktop notifications (also called &#8220;toast notifications&#8221;) are small popup messages that appear on your screen to alert you about events, updates, or reminders.</p><p><strong>Common uses:</strong></p><ul><li><p>Email notifications</p></li><li><p>Chat messages</p></li><li><p>Reminders and alarms</p></li><li><p>Task completions</p></li><li><p>System alerts</p></li><li><p>Download completions</p></li></ul><p><strong>How they work:</strong></p><pre><code><code>Your Python Script &#8594; Native OS API &#8594; Desktop Notification</code></code></pre><p><strong>Example flow:</strong></p><pre><code><code># 1. Your code
notifier.send_notification("Download Complete", "file.zip is ready!")

# 2. Platform detection
# macOS: Uses osascript
# Windows: Uses win10toast
# Linux: Uses notify-send

# 3. OS displays notification
# User sees popup on screen
</code></code></pre><h2>Understanding Cross-Platform Notifications</h2><p><strong>Why we detect the platform:</strong></p><p>Different operating systems have different notification systems. Our code automatically detects your OS and uses the right method!</p><p><strong>macOS approach:</strong></p><pre><code><code># Uses AppleScript via osascript command
osascript -e 'display notification "message" with title "title"'
</code></code></pre><p><strong>Windows approach:</strong></p><pre><code><code># Uses Windows Toast Notification API
from win10toast import ToastNotifier
toaster = ToastNotifier()
toaster.show_toast("Title", "Message")
</code></code></pre><p><strong>Linux approach:</strong></p><pre><code><code># Uses notify-send (part of libnotify)
notify-send "Title" "Message" -t 5000
</code></code></pre><p><strong>Our unified approach:</strong></p><pre><code><code>def send_notification(self, title, message, duration=5):
    if self.platform == "Darwin":  # macOS
        self._send_macos_notification(title, message)
    elif self.platform == "Windows":
        self._send_windows_notification(title, message, duration)
    elif self.platform == "Linux":
        self._send_linux_notification(title, message, duration)
</code></code></pre><p><strong>Benefits:</strong></p><ul><li><p>&#9989; Same code works everywhere</p></li><li><p>&#9989; No complex dependencies</p></li><li><p>&#9989; Uses native OS features</p></li><li><p>&#9989; Looks native on each platform</p></li></ul><h2>Understanding Notification Parameters</h2><p><strong>Title:</strong></p><ul><li><p>Short, attention-grabbing text</p></li><li><p>Appears in bold at the top</p></li><li><p>Keep under 50 characters</p></li><li><p>Example: &#8220;Download Complete&#8221;, &#8220;New Message&#8221;, &#8220;Reminder&#8221;</p></li></ul><p><strong>Message:</strong></p><ul><li><p>Main notification content</p></li><li><p>Can be longer (up to 200 characters recommended)</p></li><li><p>Should be clear and actionable</p></li><li><p>Example: &#8220;Your file download.zip is ready to use&#8221;</p></li></ul><p><strong>Duration:</strong></p><ul><li><p>How long notification stays on screen (seconds)</p></li><li><p>Default: 5-10 seconds</p></li><li><p>Short messages: 5 seconds</p></li><li><p>Important messages: 10+ seconds</p></li><li><p><strong>Note:</strong> macOS ignores duration and uses system default</p></li></ul><p><strong>App Name:</strong></p><ul><li><p>Identifies which app sent the notification</p></li><li><p>Appears at bottom of notification</p></li><li><p>Example: &#8220;Python Notifier&#8221;, &#8220;My Reminder App&#8221;</p></li></ul><h2>Understanding Notification Types</h2><p><strong>We implement different notification types with emoji indicators:</strong></p><p><strong>1. Info Notification (&#8505;&#65039;):</strong></p><pre><code><code>notifier.send_info(
    title="Information",
    message="This is an informational message"
)
# Displays: &#8505;&#65039; Information
</code></code></pre><p><strong>2. Success Notification (&#9989;):</strong></p><pre><code><code>notifier.send_success(
    title="Success!",
    message="Operation completed successfully"
)
# Displays: &#9989; Success!
</code></code></pre><p><strong>3. Urgent Notification (&#9888;&#65039;):</strong></p><pre><code><code>notifier.send_urgent(
    title="Warning",
    message="Action required!"
)
# Displays: &#9888;&#65039; Warning
</code></code></pre><p><strong>4. Error Notification (&#10060;):</strong></p><pre><code><code>notifier.send_error(
    title="Error",
    message="Something went wrong"
)
# Displays: &#10060; Error
</code></code></pre><p><strong>Why emojis?</strong></p><ul><li><p>&#9989; Universal visual indicators</p></li><li><p>&#9989; Work on all platforms</p></li><li><p>&#9989; No icon files needed</p></li><li><p>&#9989; Instant recognition</p></li></ul><h2>Practical Use Cases</h2><p><strong>1. Long-running scripts:</strong></p><pre><code><code># Scraping script
scraper.run()  # Takes 30 minutes

# Notify when done
notifier.send_success(
    "Scraping Complete",
    "Collected 1,500 quotes successfully!"
)
</code></code></pre><p><strong>2. File downloads:</strong></p><pre><code><code>download_file(url)

notifier.send_notification(
    "Download Complete",
    f"{filename} is ready!"
)
</code></code></pre><p><strong>3. Task reminders:</strong></p><pre><code><code>notifier.send_notification(
    "Break Time!",
    "You've been working for 1 hour. Take a 5-minute break."
)
</code></code></pre><p><strong>4. System monitoring:</strong></p><pre><code><code>if disk_space &lt; 10:
    notifier.send_urgent(
        "Low Disk Space",
        f"Only {disk_space}% remaining!"
    )
</code></code></pre><p><strong>5. Build/test completions:</strong></p><pre><code><code>run_tests()

if all_passed:
    notifier.send_success("Tests Passed", "All 47 tests passed!")
else:
    notifier.send_error("Tests Failed", f"{failed_count} tests failed")
</code></code></pre><h2>Coming Tomorrow</h2><p>Tomorrow we&#8217;re adding <strong>scheduled notifications and timers</strong> &#8212; set reminders for specific times, create countdown timers, and build a Pomodoro-style work timer with automatic notifications!</p><h2>Skeleton and Solution</h2><p>Below you will find both a downloadable skeleton.py file to help you code the project with comment guides and the downloadable solution.py file containing the correct solution.</p><p>Get the code skeleton here:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://share.pythonanywhere.com/view/ZkSgSLoa8GnkZbeONH4y9A&quot;,&quot;text&quot;:&quot;View Code Skeleton&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://share.pythonanywhere.com/view/ZkSgSLoa8GnkZbeONH4y9A"><span>View Code Skeleton</span></a></p><p></p><p>Get the code solution here: </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://share.pythonanywhere.com/evolution/bGbd5o7Y3Bfzqr4cGhkV-w&quot;,&quot;text&quot;:&quot;View Code Solution&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://share.pythonanywhere.com/evolution/bGbd5o7Y3Bfzqr4cGhkV-w"><span>View Code Solution</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Build a Quote Scraper: Day 3 - Adding Inheritance & Multiple Scrapers ]]></title><description><![CDATA[Today, we go more advanced with classes and use class inheritance in the code. We also add multiple scrapers.]]></description><link>https://dailypythonprojects.substack.com/p/build-a-quote-scraper-day-3-adding</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/build-a-quote-scraper-day-3-adding</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Thu, 30 Apr 2026 13:03:21 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/c6cc07ea-906e-4051-b98a-87ca9c6a707f_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we learn <strong>Object-Oriented Programming (OOP)</strong> by building a web scraper that evolves from functions to classes to inheritance.</p><ul><li><p><strong>Day 1:</strong> Quote Scraper with Functions</p></li><li><p><strong>Day 2:</strong> Refactoring to Classes</p></li><li><p><strong>Day 3:</strong> Adding Inheritance &amp; Multiple Scrapers <strong>(Today)</strong></p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-15">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p><strong>Welcome to the finale!</strong> Yesterday we refactored to classes. Today we&#8217;re taking it further with <strong>inheritance</strong> &#8212; creating a base <code>Scraper</code> class and two specialized scrapers that inherit from it!</p><p>You&#8217;ll build <code>QuoteScraper</code> and <code>AuthorScraper</code> &#8212; both share common scraping logic but extract different data. This is how professional code reuses functionality!</p><h2>Project Task</h2><p>Create a scraping system with inheritance that:</p><ul><li><p>Base <code>Scraper</code> class with common scraping logic</p></li><li><p><code>QuoteScraper</code> inherits from <code>Scraper</code> (extracts quotes)</p></li><li><p><code>AuthorScraper</code> inherits from <code>Scraper</code> (extracts authors)</p></li><li><p>Shared methods in base class (fetch, scrape, save)</p></li><li><p>Specialized parsing in each subclass</p></li><li><p>Both scrapers work independently</p></li><li><p>Demonstrates clean code reuse</p></li></ul><p>This project gives you hands-on practice with inheritance, base classes, method overriding, code reuse, and OOP design &#8212; essential skills for building maintainable applications!</p><h2>Expected Output</h2><p><strong>Running the scraper:</strong></p><pre><code><code>python solution.py</code></code></pre><p><strong>Console Output:</strong></p><p>The scraper scrapes quotes like in Day 2 but in addition, it also scrapes other data such as authors:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yEmw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2456fefd-abaf-415d-9665-e1d23557f747_2640x1760.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yEmw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2456fefd-abaf-415d-9665-e1d23557f747_2640x1760.png 424w, https://substackcdn.com/image/fetch/$s_!yEmw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2456fefd-abaf-415d-9665-e1d23557f747_2640x1760.png 848w, https://substackcdn.com/image/fetch/$s_!yEmw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2456fefd-abaf-415d-9665-e1d23557f747_2640x1760.png 1272w, https://substackcdn.com/image/fetch/$s_!yEmw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2456fefd-abaf-415d-9665-e1d23557f747_2640x1760.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yEmw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2456fefd-abaf-415d-9665-e1d23557f747_2640x1760.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2456fefd-abaf-415d-9665-e1d23557f747_2640x1760.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1278239,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/195742235?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2456fefd-abaf-415d-9665-e1d23557f747_2640x1760.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yEmw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2456fefd-abaf-415d-9665-e1d23557f747_2640x1760.png 424w, https://substackcdn.com/image/fetch/$s_!yEmw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2456fefd-abaf-415d-9665-e1d23557f747_2640x1760.png 848w, https://substackcdn.com/image/fetch/$s_!yEmw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2456fefd-abaf-415d-9665-e1d23557f747_2640x1760.png 1272w, https://substackcdn.com/image/fetch/$s_!yEmw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2456fefd-abaf-415d-9665-e1d23557f747_2640x1760.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>Setup Instructions</h2><p><strong>Install Required Packages:</strong></p><pre><code><code>pip install requests beautifulsoup4</code></code></pre><p><strong>Run the scrit:</strong></p><pre><code><code>python solution.py</code></code></pre><h2>Understanding Inheritance</h2><p><strong>What is inheritance?</strong></p><p>Inheritance lets you create a <strong>base class</strong> with common functionality, then create <strong>child classes</strong> that inherit and extend that functionality.</p><p><strong>Our hierarchy:</strong></p><pre><code><code>        Scraper (Base Class)
           &#8593;
           |
    &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
    |             |
QuoteScraper  AuthorScraper
</code></code></pre><p><strong>Before inheritance (repetitive):</strong></p><pre><code><code>class QuoteScraper:
    def fetch_page(self, url):
        # Fetching logic
        pass
    
    def scrape(self):
        # Pagination logic
        pass
    
    def parse_quotes(self, html):
        # Different!
        pass

class AuthorScraper:
    def fetch_page(self, url):
        # DUPLICATE CODE!
        pass
    
    def scrape(self):
        # DUPLICATE CODE!
        pass
    
    def parse_authors(self, html):
        # Different!
        pass
</code></code></pre><p><strong>After inheritance (clean):</strong></p><pre><code><code>class Scraper:
    """Base class - write once, use everywhere"""
    def fetch_page(self, url):
        # Common code
        pass
    
    def scrape(self):
        # Common code
        pass

class QuoteScraper(Scraper):
    """Inherits fetch_page and scrape"""
    def parse_data(self, html):
        # Only write the unique part
        pass

class AuthorScraper(Scraper):
    """Also inherits fetch_page and scrape"""
    def parse_data(self, html):
        # Only write the unique part
        pass
</code></code></pre><h2>Understanding the Base Scraper Class</h2><p><strong>The base class contains what&#8217;s shared:</strong></p><pre><code><code>class Scraper:
    """Base scraper with common functionality"""
    
    def __init__(self, base_url, max_pages=3):
        self.base_url = base_url
        self.max_pages = max_pages
        self.data = []
    
    def fetch_page(self, url):
        """Fetch HTML - same for all scrapers"""
        response = requests.get(url)
        return response.text
    
    def scrape(self):
        """Scrape pages - same logic for all scrapers"""
        for page_num in range(1, self.max_pages + 1):
            url = f"{self.base_url}/page/{page_num}/"
            html = self.fetch_page(url)
            
            # Call parse_data (implemented by child)
            data = self.parse_data(html)
            
            self.data.extend(data)
    
    def save_to_json(self, filename):
        """Save data - same for all scrapers"""
        with open(filename, 'w') as f:
            json.dump(self.data, f)
    
    def parse_data(self, html):
        """Must be implemented by child classes"""
        raise NotImplementedError("Child must implement parse_data()")
</code></code></pre><p><strong>Key points:</strong></p><p>&#9989; <code>fetch_page()</code> - All scrapers fetch HTML the same way<br>&#9989; <code>scrape()</code> - All scrapers loop through pages the same way<br>&#9989; <code>save_to_json()</code> - All scrapers save files the same way<br>&#9989; <code>parse_data()</code> - Each child implements differently</p><h2>Understanding Child Classes</h2><p><strong>QuoteScraper - extracts quotes:</strong></p><pre><code><code>class QuoteScraper(Scraper):
    """Inherits from Scraper"""
    
    def __init__(self, base_url, tags_filter, max_pages=3):
        super().__init__(base_url, max_pages)  # Call parent init
        self.tags_filter = tags_filter
    
    def parse_data(self, html):
        """Parse quotes - unique to this scraper"""
        soup = BeautifulSoup(html, 'html.parser')
        quotes = soup.find_all('div', class_='quote')
        
        results = []
        for quote in quotes:
            text = quote.find('span', class_='text').text
            author = quote.find('small', class_='author').text
            tags = [tag.text for tag in quote.find_all('a', class_='tag')]
            
            # Filter by tags
            if any(tag in self.tags_filter for tag in tags):
                results.append({
                    'text': text,
                    'author': author,
                    'tags': tags
                })
        
        return results
</code></code></pre><p><strong>AuthorScraper - counts authors:</strong></p><pre><code><code>class AuthorScraper(Scraper):
    """Also inherits from Scraper"""
    
    def parse_data(self, html):
        """Parse authors - unique to this scraper"""
        soup = BeautifulSoup(html, 'html.parser')
        authors = soup.find_all('small', class_='author')
        
        # Just return author names
        return [author.text for author in authors]
</code></code></pre><p><strong>What they inherit:</strong></p><ul><li><p>&#9989; <code>fetch_page()</code> from <code>Scraper</code></p></li><li><p>&#9989; <code>scrape()</code> from <code>Scraper</code></p></li><li><p>&#9989; <code>save_to_json()</code> from <code>Scraper</code></p></li></ul><p><strong>What they implement:</strong></p><ul><li><p>&#127381; <code>parse_data()</code> - unique to each scraper</p></li></ul><h2>Understanding <code>super()</code></h2><p><code>super()</code><strong> calls the parent class:</strong></p><pre><code><code>class QuoteScraper(Scraper):
    def __init__(self, base_url, tags_filter, max_pages=3):
        # Call parent's __init__ first
        super().__init__(base_url, max_pages)
        
        # Then add child-specific attributes
        self.tags_filter = tags_filter
</code></code></pre><p><strong>What happens:</strong></p><pre><code><code>scraper = QuoteScraper("http://example.com", ['life'], max_pages=3)

# Step 1: super().__init__(base_url, max_pages)
#   &#8594; self.base_url = "http://example.com"
#   &#8594; self.max_pages = 3
#   &#8594; self.data = []

# Step 2: Child __init__ continues
#   &#8594; self.tags_filter = ['life']

# Result: All attributes set!
</code></code></pre><h2>Why Inheritance Matters</h2><p><strong>What we gain:</strong></p><p><strong>1. Write once, use everywhere:</strong></p><pre><code><code># fetch_page() written ONCE in Scraper
# Both QuoteScraper and AuthorScraper use it
</code></code></pre><p><strong>2. Easy to add new scrapers:</strong></p><pre><code><code>class TagScraper(Scraper):
    def parse_data(self, html):
        # Only implement the unique part!
        # Everything else inherited
        pass
</code></code></pre><p><strong>3. Fix bugs in one place:</strong></p><pre><code><code># Bug in fetch_page()?
# Fix it in Scraper class
# All children benefit automatically!
</code></code></pre><p><strong>4. Consistent behavior:</strong></p><pre><code><code># All scrapers fetch pages the same way
# All scrapers handle pagination the same way
# All scrapers save files the same way
</code></code></pre><h2>Code Comparison: Day 2 vs Day 3</h2><p><strong>Day 2 (Single QuoteScraper class):</strong></p><ul><li><p>&#9989; Better than functions</p></li><li><p>&#10060; Can&#8217;t reuse for other scrapers</p></li><li><p>&#10060; Would duplicate code for AuthorScraper</p></li></ul><p><strong>Day 3 (Inheritance):</strong></p><ul><li><p>&#9989; Base class holds common code</p></li><li><p>&#9989; Easy to add new scrapers</p></li><li><p>&#9989; No code duplication</p></li><li><p>&#9989; Fix once, benefit everywhere</p></li></ul><h2>What You&#8217;ve Accomplished This Week</h2><p>&#127881; <strong>Congratulations!</strong> You&#8217;ve completed the <strong>OOP journey</strong>!</p><ul><li><p><strong>Day 1:</strong> Functions - Simple and procedural</p></li><li><p><strong>Day 2:</strong> Classes - Organized with encapsulation</p></li><li><p><strong>Day 3:</strong> Inheritance - Reusable and scalable</p></li></ul><p><strong>You now understand:</strong></p><p>&#9989; <strong>Functions</strong> &#8594; Small, independent tasks<br>&#9989; <strong>Classes</strong> &#8594; Grouping related data and behavior<br>&#9989; <strong>Inheritance</strong> &#8594; Sharing code across similar classes</p><p><strong>This is how professional Python is written!</strong></p><p><strong>Real-world examples of inheritance:</strong></p><ul><li><p><strong>Games:</strong> <code>Character</code> &#8594; <code>Player</code>, <code>Enemy</code>, <code>NPC</code></p></li><li><p><strong>Web frameworks:</strong> <code>View</code> &#8594; <code>ListView</code>, <code>DetailView</code>, <code>CreateView</code></p></li><li><p><strong>ML models:</strong> <code>Model</code> &#8594; <code>LinearRegression</code>, <code>DecisionTree</code>, <code>NeuralNetwork</code></p></li><li><p><strong>Data processing:</strong> <code>FileReader</code> &#8594; <code>CSVReader</code>, <code>JSONReader</code>, <code>XMLReader</code></p></li></ul><p><strong>You&#8217;ve mastered the fundamentals of OOP!</strong> &#128640;</p><h2>View Code Evolution</h2><p>Compare today&#8217;s inheritance-based system with earlier versions and see how we evolved from functions &#8594; classes &#8594; inheritance for maximum code reuse.</p><p></p>
      <p>
          <a href="https://dailypythonprojects.substack.com/p/build-a-quote-scraper-day-3-adding">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Build a Quote Scraper: Day 2 - Refactoring to Classes]]></title><description><![CDATA[Today, our program behavior does not change, but we refactor the code making it more organized with classes instead of functions.]]></description><link>https://dailypythonprojects.substack.com/p/build-a-quote-scraper-day-2-refactoring</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/build-a-quote-scraper-day-2-refactoring</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Wed, 29 Apr 2026 12:17:53 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/91f71156-9441-4338-aacc-4771bf53fb4a_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we learn <strong>Object-Oriented Programming (OOP)</strong> by building a web scraper that evolves from functions to classes to inheritance.</p><ul><li><p><strong>Day 1:</strong> Quote Scraper with Functions</p></li><li><p><strong>Day 2:</strong> Refactoring to Classes <strong>(Today)</strong></p></li><li><p><strong>Day 3:</strong> Adding Inheritance &amp; Multiple Scrapers</p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-15">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p>Yesterday we built a quote scraper using <strong>only functions</strong>. Today we&#8217;re <strong>refactoring the exact same scraper using classes</strong>!</p><p>You&#8217;ll see the same functionality, but organized as a <code>QuoteScraper</code> class. This is your &#8220;aha moment&#8221; for understanding <em>why</em> and <em>when</em> to use classes instead of functions!</p><h2>Project Task</h2><p>Refactor yesterday&#8217;s scraper into a class-based design that:</p><ul><li><p>Same functionality as Day 1 (scrape, filter, save)</p></li><li><p>Organized as a <code>QuoteScraper</code> class</p></li><li><p>Instance variables store configuration and state</p></li><li><p>Methods replace standalone functions</p></li><li><p>Cleaner data flow with <code>self</code></p></li><li><p>Easier to extend and maintain</p></li><li><p>Same output as Day 1</p></li></ul><p>This project gives you hands-on practice with class design, instance variables, methods, <code>self</code> keyword, refactoring, and object-oriented thinking &#8212; the foundation of professional Python code!</p><h2>Expected Output</h2><p><strong>Running the scraper:</strong></p><pre><code><code>python solution.py
</code></code></pre><p><strong>Console Output:</strong></p><p>The output is the same as the output of <a href="https://dailypythonprojects.substack.com/p/build-a-job-listing-scraper-day-1">Day 1</a>. We get the quotes displayed. So, the behavior of the program does not change. It is only the code that changes:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ARjo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f5dcc89-0503-461f-a322-7bcadc941d1f_2442x1628.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ARjo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f5dcc89-0503-461f-a322-7bcadc941d1f_2442x1628.png 424w, https://substackcdn.com/image/fetch/$s_!ARjo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f5dcc89-0503-461f-a322-7bcadc941d1f_2442x1628.png 848w, https://substackcdn.com/image/fetch/$s_!ARjo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f5dcc89-0503-461f-a322-7bcadc941d1f_2442x1628.png 1272w, https://substackcdn.com/image/fetch/$s_!ARjo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f5dcc89-0503-461f-a322-7bcadc941d1f_2442x1628.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ARjo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f5dcc89-0503-461f-a322-7bcadc941d1f_2442x1628.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0f5dcc89-0503-461f-a322-7bcadc941d1f_2442x1628.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1372961,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/195741311?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f5dcc89-0503-461f-a322-7bcadc941d1f_2442x1628.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ARjo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f5dcc89-0503-461f-a322-7bcadc941d1f_2442x1628.png 424w, https://substackcdn.com/image/fetch/$s_!ARjo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f5dcc89-0503-461f-a322-7bcadc941d1f_2442x1628.png 848w, https://substackcdn.com/image/fetch/$s_!ARjo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f5dcc89-0503-461f-a322-7bcadc941d1f_2442x1628.png 1272w, https://substackcdn.com/image/fetch/$s_!ARjo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f5dcc89-0503-461f-a322-7bcadc941d1f_2442x1628.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><br></p><h2>Setup Instructions</h2><p><strong>Install Required Packages:</strong></p><pre><code><code>pip install requests beautifulsoup4
</code></code></pre><p><strong>Same packages as Day 1!</strong></p><p><strong>Run the scraper:</strong></p><pre><code><code>python solution.py
</code></code></pre><p><strong>Same output as Day 1</strong>, but now it&#8217;s organized as a class!</p><h2>Understanding Classes vs Functions</h2><p><strong>Yesterday (Functions):</strong></p><pre><code><code># Pass data between functions
html = fetch_page(url)
quotes = parse_quotes(html)
filtered = filter_by_tags(quotes, tags_filter)
save_to_json(filtered, tags_filter)
</code></code></pre><p><strong>Problems:</strong></p><ul><li><p>&#10060; Pass <code>tags_filter</code> to every function</p></li><li><p>&#10060; No shared state</p></li><li><p>&#10060; Hard to track what data belongs together</p></li><li><p>&#10060; Difficult to extend</p></li></ul><p><strong>Today (Classes):</strong></p><pre><code><code># Create instance with configuration
scraper = QuoteScraper(base_url=url, tags_filter=tags)

# Call methods - they share state via self
scraper.scrape()  # Uses self.base_url and self.tags_filter
scraper.save_to_json()  # Uses self.quotes
</code></code></pre><p><strong>Benefits:</strong></p><ul><li><p>&#9989; Configuration stored once in <code>self</code></p></li><li><p>&#9989; Shared state across methods</p></li><li><p>&#9989; Related data grouped together</p></li><li><p>&#9989; Easy to extend with new methods</p></li></ul><h2>Understanding the QuoteScraper Class</h2><p><strong>Class structure:</strong></p><pre><code><code>class QuoteScraper:
    def __init__(self, base_url, tags_filter):
        """Initialize the scraper with configuration"""
        self.base_url = base_url
        self.tags_filter = tags_filter
        self.quotes = []  # Store results here
    
    def fetch_page(self, url):
        """Fetch HTML from URL"""
        # Same logic as Day 1 function
        return html
    
    def parse_quotes(self, html):
        """Parse quotes from HTML"""
        # Same logic as Day 1 function
        return quotes
    
    def filter_by_tags(self, quotes):
        """Filter quotes by self.tags_filter"""
        # Uses self.tags_filter instead of parameter
        return filtered
    
    def scrape(self):
        """Main scraping method"""
        page = 1
        while True:
            url = f"{self.base_url}/page/{page}/"
            html = self.fetch_page(url)
            quotes = self.parse_quotes(html)
            filtered = self.filter_by_tags(quotes)
            self.quotes.extend(filtered)  # Store in self.quotes
            page += 1
    
    def save_to_json(self):
        """Save self.quotes to JSON"""
        # Uses self.quotes and self.tags_filter
        pass
</code></code></pre><p></p><h2>Understanding <code>self</code></h2><p><strong>What is </strong><code>self</code><strong>?</strong></p><p><code>self</code> refers to the <strong>instance</strong> of the class. It&#8217;s how methods access the instance&#8217;s data.</p><pre><code><code># Create an instance
scraper = QuoteScraper(
    base_url="http://quotes.toscrape.com",
    tags_filter=['life']
)

# When you call a method:
scraper.fetch_page(url)

# Python automatically passes the instance as 'self':
QuoteScraper.fetch_page(scraper, url)
#                       ^^^^^^
#                       This is 'self'!
</code></code></pre><p><strong>Using self to share data:</strong></p><pre><code><code>class QuoteScraper:
    def __init__(self, tags_filter):
        self.tags_filter = tags_filter  # Store in instance
        self.quotes = []  # Shared across methods
    
    def scrape(self):
        # Access instance variables with self
        print(f"Filtering by: {self.tags_filter}")
        
        quotes = self.parse_quotes(html)
        filtered = self.filter_by_tags(quotes)
        
        # Store in instance variable
        self.quotes.extend(filtered)
    
    def filter_by_tags(self, quotes):
        # Use self.tags_filter instead of parameter!
        return [q for q in quotes if any(tag in self.tags_filter for tag in q['tags'])]
    
    def save_to_json(self):
        # Access self.quotes that scrape() filled
        data = {'quotes': self.quotes}
        # ...
</code></code></pre><p><strong>Without </strong><code>self</code><strong> (doesn&#8217;t work):</strong></p><pre><code><code>class QuoteScraper:
    def __init__(self, tags_filter):
        tags_filter = tags_filter  # &#10060; Local variable, lost after __init__
    
    def scrape(self):
        print(tags_filter)  # &#10060; ERROR: tags_filter not defined!
</code></code></pre><h2>Understanding Instance Variables</h2><p><strong>Instance variables</strong> = data that belongs to a specific instance.</p><p><strong>Example:</strong></p><pre><code><code># Create two different scrapers
scraper1 = QuoteScraper(tags_filter=['life'])
scraper2 = QuoteScraper(tags_filter=['humor'])

# Each has its own data!
print(scraper1.tags_filter)  # ['life']
print(scraper2.tags_filter)  # ['humor']

# Run both
scraper1.scrape()
scraper2.scrape()

# Each stores different quotes
print(len(scraper1.quotes))  # 20 life quotes
print(len(scraper2.quotes))  # 15 humor quotes
</code></code></pre><p><strong>Common instance variables in our scraper:</strong></p><pre><code><code>def __init__(self, base_url, tags_filter):
    # Configuration (set once, used everywhere)
    self.base_url = base_url
    self.tags_filter = tags_filter
    
    # State (changes during execution)
    self.quotes = []  # Filled by scrape()
    self.current_page = 1  # Tracks pagination
</code></code></pre><h2>Understanding Methods</h2><p><strong>Methods</strong> = functions that belong to a class.</p><p><strong>Two types of methods in our scraper:</strong></p><p><strong>1. Public methods</strong> (meant to be called from outside):</p><pre><code><code>class QuoteScraper:
    def scrape(self):
        """Main method - users call this"""
        # ...
    
    def save_to_json(self):
        """Save results - users call this"""
        # ...
</code></code></pre><p><strong>2. Helper methods</strong> (used internally):</p><pre><code><code>class QuoteScraper:
    def _fetch_page(self, url):
        """Internal helper - users don't call this directly"""
        # Underscore prefix = "private" by convention
        # ...
    
    def _parse_quotes(self, html):
        """Internal helper"""
        # ...
</code></code></pre><p><strong>Calling methods:</strong></p><pre><code><code># From outside the class
scraper = QuoteScraper(...)
scraper.scrape()  # Public method

# From inside the class (in another method)
class QuoteScraper:
    def scrape(self):
        html = self._fetch_page(url)  # Call helper method
        quotes = self._parse_quotes(html)  # Call another helper
</code></code></pre><h2>Why Classes Are Better Here</h2><p><strong>Day 1 approach (functions):</strong></p><pre><code><code># Problem: Pass everything everywhere
def main():
    tags = ['life', 'love']
    
    quotes = scrape_all_pages(BASE_URL, tags)
    filtered = filter_by_tags(quotes, tags)
    save_to_json(filtered, tags)
    
    # Want to scrape again with different tags?
    # Start over, pass everything again
    quotes2 = scrape_all_pages(BASE_URL, ['humor'])
    filtered2 = filter_by_tags(quotes2, ['humor'])
    save_to_json(filtered2, ['humor'])
</code></code></pre><p><strong>Day 2 approach (classes):</strong></p><pre><code><code># Solution: Create reusable objects
scraper1 = QuoteScraper(BASE_URL, ['life', 'love'])
scraper1.scrape()
scraper1.save_to_json()

scraper2 = QuoteScraper(BASE_URL, ['humor'])
scraper2.scrape()
scraper2.save_to_json()

# Easy to compare results
print(f"Life quotes: {len(scraper1.quotes)}")
print(f"Humor quotes: {len(scraper2.quotes)}")
</code></code></pre><p><strong>Other benefits:</strong></p><p>&#9989; <strong>Encapsulation</strong> - Related data and functions grouped together<br>&#9989; <strong>State management</strong> - <code>self.quotes</code> persists across method calls<br>&#9989; <strong>Reusability</strong> - Create multiple scraper instances<br>&#9989; <strong>Extensibility</strong> - Easy to add new methods tomorrow<br>&#9989; <strong>Clarity</strong> - Clear what data belongs to what</p><h2>Coming Tomorrow</h2><p>Tomorrow we&#8217;re adding <strong>inheritance</strong>! We&#8217;ll create a base <code>Scraper</code> class and inherit from it to create different scrapers (quotes, authors, tags). You&#8217;ll see how OOP makes it easy to reuse code across similar classes!</p><h2>View Code Evolution</h2><p>Compare today&#8217;s class-based solution with yesterday&#8217;s function-based version and see the differences in organization and structure.</p>
      <p>
          <a href="https://dailypythonprojects.substack.com/p/build-a-quote-scraper-day-2-refactoring">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Build a Quote Scraper: Day 1 - Quote Scraper with Functions ]]></title><description><![CDATA[We start this project today by using Python functions and then in day 2 we refactor the code using classes for better code organization.]]></description><link>https://dailypythonprojects.substack.com/p/build-a-job-listing-scraper-day-1</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/build-a-job-listing-scraper-day-1</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Tue, 28 Apr 2026 12:03:15 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f3a1ef24-8214-4ad9-ac13-4f659a8d0701_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we learn <strong>Object-Oriented Programming (OOP)</strong> by building a web scraper that evolves from functions to classes to inheritance.</p><p><strong>Why build this?</strong> Because OOP is how professional code is structured. You&#8217;ll see the same scraper written three ways &#8212; first with functions (today), then refactored to classes (Day 2), then enhanced with inheritance (Day 3). By the end, you&#8217;ll understand <em>why</em> classes matter!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://dailypythonprojects.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Daily Python Projects is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><strong>What you&#8217;ll learn:</strong> This series teaches you function-based programming, class structures, object-oriented design, inheritance, code organization, and refactoring &#8212; essential skills for writing maintainable, professional Python code.</p><p><strong>Why users love this:</strong> You&#8217;ll see the same project evolve from simple functions to elegant OOP. Perfect for understanding when and why to use classes instead of functions!</p><ul><li><p><strong>Day 1:</strong> Quote Scraper with Functions <strong>(Today)</strong></p></li><li><p><strong>Day 2:</strong> Refactoring to Classes</p></li><li><p><strong>Day 3:</strong> Adding Inheritance &amp; Multiple Scrapers</p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-15">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p>We&#8217;re building a <strong>quote scraper</strong> that fetches quotes from <a href="http://quotes.toscrape.com">quotes.toscrape.com</a>, filters them by tag, and saves them to a JSON file. Today&#8217;s version uses <strong>pure functions</strong> &#8212; clean, simple, and procedural!</p><p>You&#8217;ll learn web scraping basics, HTML parsing, data filtering, and JSON storage!</p><h2>Project Task</h2><p>Create a quote scraper that:</p><ul><li><p>Scrapes quotes from quotes.toscrape.com</p></li><li><p>Extracts quote text, author, and tags</p></li><li><p>Filters quotes by specific tags (motivational, life, inspirational)</p></li><li><p>Handles multiple pages of quotes</p></li><li><p>Saves results to JSON file</p></li><li><p>Clean console output showing progress</p></li><li><p>Error handling for failed requests</p></li><li><p>Uses only functions (no classes yet!)</p></li></ul><p>This project gives you hands-on practice with web scraping, BeautifulSoup, requests library, data parsing, filtering, file I/O, and functional programming &#8212; all the foundations you need before learning OOP!</p><h2>Expected Output</h2><p><strong>Running the scraper:</strong></p><pre><code><code>python solution.py</code></code></pre><p><strong>Console Output:</strong></p><p>Running the program will print out the progress of the scraper as below:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!25q6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c414750-4cdc-471e-a64f-ab7d77e9b8be_2616x1744.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!25q6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c414750-4cdc-471e-a64f-ab7d77e9b8be_2616x1744.png 424w, https://substackcdn.com/image/fetch/$s_!25q6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c414750-4cdc-471e-a64f-ab7d77e9b8be_2616x1744.png 848w, https://substackcdn.com/image/fetch/$s_!25q6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c414750-4cdc-471e-a64f-ab7d77e9b8be_2616x1744.png 1272w, https://substackcdn.com/image/fetch/$s_!25q6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c414750-4cdc-471e-a64f-ab7d77e9b8be_2616x1744.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!25q6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c414750-4cdc-471e-a64f-ab7d77e9b8be_2616x1744.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2c414750-4cdc-471e-a64f-ab7d77e9b8be_2616x1744.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1340697,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/195739925?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c414750-4cdc-471e-a64f-ab7d77e9b8be_2616x1744.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!25q6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c414750-4cdc-471e-a64f-ab7d77e9b8be_2616x1744.png 424w, https://substackcdn.com/image/fetch/$s_!25q6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c414750-4cdc-471e-a64f-ab7d77e9b8be_2616x1744.png 848w, https://substackcdn.com/image/fetch/$s_!25q6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c414750-4cdc-471e-a64f-ab7d77e9b8be_2616x1744.png 1272w, https://substackcdn.com/image/fetch/$s_!25q6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c414750-4cdc-471e-a64f-ab7d77e9b8be_2616x1744.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Below that you will see the quotes scraped and printed out:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tTHL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb870b06b-df11-4d0e-91f2-9a055d4f3246_2502x1668.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tTHL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb870b06b-df11-4d0e-91f2-9a055d4f3246_2502x1668.png 424w, https://substackcdn.com/image/fetch/$s_!tTHL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb870b06b-df11-4d0e-91f2-9a055d4f3246_2502x1668.png 848w, https://substackcdn.com/image/fetch/$s_!tTHL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb870b06b-df11-4d0e-91f2-9a055d4f3246_2502x1668.png 1272w, https://substackcdn.com/image/fetch/$s_!tTHL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb870b06b-df11-4d0e-91f2-9a055d4f3246_2502x1668.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tTHL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb870b06b-df11-4d0e-91f2-9a055d4f3246_2502x1668.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b870b06b-df11-4d0e-91f2-9a055d4f3246_2502x1668.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1459572,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/195739925?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb870b06b-df11-4d0e-91f2-9a055d4f3246_2502x1668.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!tTHL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb870b06b-df11-4d0e-91f2-9a055d4f3246_2502x1668.png 424w, https://substackcdn.com/image/fetch/$s_!tTHL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb870b06b-df11-4d0e-91f2-9a055d4f3246_2502x1668.png 848w, https://substackcdn.com/image/fetch/$s_!tTHL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb870b06b-df11-4d0e-91f2-9a055d4f3246_2502x1668.png 1272w, https://substackcdn.com/image/fetch/$s_!tTHL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb870b06b-df11-4d0e-91f2-9a055d4f3246_2502x1668.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>Generated JSON file: </strong><code>quotes_motivational_life_inspirational.json</code></p><p><code>In addition to printing out the scraped content, the program will also save the data in a JSON file:</code></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xc6d!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffaebe6ec-a27f-4dd7-9226-5d5c39a3f848_2268x1512.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xc6d!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffaebe6ec-a27f-4dd7-9226-5d5c39a3f848_2268x1512.png 424w, https://substackcdn.com/image/fetch/$s_!xc6d!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffaebe6ec-a27f-4dd7-9226-5d5c39a3f848_2268x1512.png 848w, https://substackcdn.com/image/fetch/$s_!xc6d!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffaebe6ec-a27f-4dd7-9226-5d5c39a3f848_2268x1512.png 1272w, https://substackcdn.com/image/fetch/$s_!xc6d!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffaebe6ec-a27f-4dd7-9226-5d5c39a3f848_2268x1512.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xc6d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffaebe6ec-a27f-4dd7-9226-5d5c39a3f848_2268x1512.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/faebe6ec-a27f-4dd7-9226-5d5c39a3f848_2268x1512.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:994805,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/195739925?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffaebe6ec-a27f-4dd7-9226-5d5c39a3f848_2268x1512.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xc6d!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffaebe6ec-a27f-4dd7-9226-5d5c39a3f848_2268x1512.png 424w, https://substackcdn.com/image/fetch/$s_!xc6d!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffaebe6ec-a27f-4dd7-9226-5d5c39a3f848_2268x1512.png 848w, https://substackcdn.com/image/fetch/$s_!xc6d!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffaebe6ec-a27f-4dd7-9226-5d5c39a3f848_2268x1512.png 1272w, https://substackcdn.com/image/fetch/$s_!xc6d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffaebe6ec-a27f-4dd7-9226-5d5c39a3f848_2268x1512.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>Setup Instructions</h2><p><strong>Install Required Packages:</strong></p><pre><code><code>pip install requests beautifulsoup4
</code></code></pre><p><strong>What each package does:</strong></p><ul><li><p><code>requests</code> - Makes HTTP requests to fetch web pages</p></li><li><p><code>beautifulsoup4</code> - Parses HTML and extracts data</p></li></ul><p><strong>Run the scraper:</strong></p><pre><code><code>python solution.py
</code></code></pre><p><strong>The scraper will:</strong></p><ol><li><p>Fetch quotes from quotes.toscrape.com</p></li><li><p>Filter by tags: motivational, life, inspirational</p></li><li><p>Save results to JSON file</p></li><li><p>Show progress in console</p></li></ol><p><strong>You can modify the tags</strong> in the script to scrape different quotes!</p><h2>Understanding Web Scraping Basics</h2><p><strong>What is web scraping?</strong></p><p>Web scraping is extracting data from websites by:</p><ol><li><p>Sending HTTP request to get the HTML</p></li><li><p>Parsing the HTML to find specific elements</p></li><li><p>Extracting the data you need</p></li><li><p>Storing it in a structured format</p></li></ol><p><strong>The scraping workflow:</strong></p><pre><code><code>URL &#8594; HTTP Request &#8594; HTML Response &#8594; Parse HTML &#8594; Extract Data &#8594; Save to File
</code></code></pre><p><strong>Example HTML structure from quotes.toscrape.com:</strong></p><pre><code><code>&lt;div class="quote"&gt;
  &lt;span class="text"&gt;"The world as we have created it..."&lt;/span&gt;
  &lt;small class="author"&gt;Albert Einstein&lt;/small&gt;
  &lt;div class="tags"&gt;
    &lt;a class="tag"&gt;change&lt;/a&gt;
    &lt;a class="tag"&gt;inspirational&lt;/a&gt;
    &lt;a class="tag"&gt;life&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></code></pre><p><strong>How we extract it:</strong></p><pre><code><code>import requests
from bs4 import BeautifulSoup

# 1. Fetch the page
response = requests.get('http://quotes.toscrape.com/page/1/')
html = response.text

# 2. Parse HTML
soup = BeautifulSoup(html, 'html.parser')

# 3. Find all quote divs
quotes = soup.find_all('div', class_='quote')

# 4. Extract data from each quote
for quote in quotes:
    text = quote.find('span', class_='text').text
    author = quote.find('small', class_='author').text
    tags = [tag.text for tag in quote.find_all('a', class_='tag')]
</code></code></pre><p><strong>Why quotes.toscrape.com?</strong></p><p>This website is specifically designed for learning web scraping:</p><ul><li><p>&#9989; Simple, clean HTML structure</p></li><li><p>&#9989; No anti-scraping measures</p></li><li><p>&#9989; Paginated data (multiple pages)</p></li><li><p>&#9989; Different data types (text, authors, tags)</p></li><li><p>&#9989; Legal and ethical to scrape</p></li></ul><h2>Understanding BeautifulSoup</h2><p><strong>BeautifulSoup</strong> is a Python library that makes HTML parsing easy.</p><p><strong>Key methods we use:</strong></p><pre><code><code>from bs4 import BeautifulSoup

soup = BeautifulSoup(html, 'html.parser')

# Find first matching element
quote = soup.find('div', class_='quote')

# Find all matching elements
quotes = soup.find_all('div', class_='quote')

# Get text content
text = element.text

# Get attribute value
href = element.get('href')
</code></code></pre><p><strong>CSS class selector:</strong></p><pre><code><code># Find by class name
soup.find('div', class_='quote')

# Find by ID
soup.find('div', id='my-id')

# Find by tag name
soup.find('span')
</code></code></pre><p><strong>Navigating the HTML tree:</strong></p><pre><code><code># Find nested elements
quote = soup.find('div', class_='quote')
author = quote.find('small', class_='author')

# Find multiple nested elements
tags = quote.find_all('a', class_='tag')
</code></code></pre><h2>Understanding Pagination</h2><p><strong>What is pagination?</strong></p><p>Websites split data across multiple pages:</p><ul><li><p>Page 1: quotes.toscrape.com/page/1/</p></li><li><p>Page 2: quotes.toscrape.com/page/2/</p></li><li><p>Page 3: quotes.toscrape.com/page/3/</p></li><li><p>...</p></li></ul><p><strong>How we handle it:</strong></p><pre><code><code>page_num = 1
has_more_pages = True

while has_more_pages:
    url = f'http://quotes.toscrape.com/page/{page_num}/'
    response = requests.get(url)
    
    # Check if page exists
    if response.status_code == 404:
        has_more_pages = False
        break
    
    # Scrape this page
    quotes = scrape_page(url)
    
    # Move to next page
    page_num += 1
</code></code></pre><p><strong>Detecting the last page:</strong></p><p>We know we&#8217;re on the last page when:</p><ul><li><p>HTTP status code is 404 (page not found)</p></li><li><p>Or there&#8217;s no &#8220;Next&#8221; button in the HTML</p></li><li><p>Or we find 0 quotes on the page</p></li></ul><h2>Understanding Function-Based Design</h2><p><strong>Today&#8217;s architecture uses only functions:</strong></p><pre><code><code># Each function does ONE thing

def fetch_page(url):
    """Fetch HTML from URL"""
    return html

def parse_quotes(html):
    """Extract quotes from HTML"""
    return quotes

def filter_by_tags(quotes, tags):
    """Filter quotes by tags"""
    return filtered_quotes

def save_to_json(quotes, filename):
    """Save quotes to JSON file"""
    # write to file
</code></code></pre><p><strong>Why functions first?</strong></p><p>&#9989; Simple to understand<br>&#9989; Easy to test each piece<br>&#9989; Clear data flow<br>&#9989; Good for small projects</p><p><strong>Limitations we&#8217;ll see tomorrow:</strong></p><p>&#10060; No shared state (passing data everywhere)<br>&#10060; Difficult to extend functionality<br>&#10060; Hard to manage related data<br>&#10060; No encapsulation</p><p><strong>Tomorrow we&#8217;ll refactor to classes</strong> and see how OOP solves these problems!</p><h2>Coming Tomorrow</h2><p>Tomorrow we&#8217;re <strong>refactoring this exact code to use classes</strong>! You&#8217;ll see how the same scraper becomes cleaner, more organized, and easier to extend when we introduce OOP concepts like encapsulation and methods!</p><h2>Skeleton and Solution</h2><p>Below you will find both a downloadable skeleton.py file to help you code the project with comment guides and the downloadable solution.py file containing the correct solution.</p><p>Get the code skeleton here:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://share.pythonanywhere.com/view/w7twDp4p_DbcMJ1oRAAYiQ&quot;,&quot;text&quot;:&quot;View Code Skeleton&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://share.pythonanywhere.com/view/w7twDp4p_DbcMJ1oRAAYiQ"><span>View Code Skeleton</span></a></p><p></p><p>Get the code solution here:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://share.pythonanywhere.com/evolution/W-4QunaloRCXxtjSfLjWDw&quot;,&quot;text&quot;:&quot;View Code Solution&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://share.pythonanywhere.com/evolution/W-4QunaloRCXxtjSfLjWDw"><span>View Code Solution</span></a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://dailypythonprojects.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Daily Python Projects is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Build a Weather Data API: Day 3 - API Key Authentication]]></title><description><![CDATA[Finally, we enable API key authentication so app that connect to the API will need an API key.]]></description><link>https://dailypythonprojects.substack.com/p/build-a-weather-data-api-day-3-api</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/build-a-weather-data-api-day-3-api</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Fri, 24 Apr 2026 19:48:45 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f821b793-7c6f-4e2d-876a-57a4f263bace_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build a real-world Weather Data API from scratch using Flask, teaching you how APIs work by building one yourself.</p><ul><li><p><strong>Day 1:</strong> Basic Weather API with Flask</p></li><li><p><strong>Day 2:</strong> POST Requests &amp; Data Storage</p></li><li><p><strong>Day 3:</strong> API Key Authentication <strong>(Today)</strong></p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-14">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p><strong>Welcome to the finale!</strong> We&#8217;ve built a working API with GET and POST endpoints. Today we&#8217;re adding <strong>API key authentication</strong> &#8212; the security layer that protects your API and controls who can access it!</p><p>You&#8217;ll learn how real-world APIs like Stripe, OpenAI, and Google Maps protect their endpoints and track usage!</p><h2>Project Task</h2><p>Create a production-ready Weather API with authentication that:</p><ul><li><p>Generates unique API keys for users</p></li><li><p>Protects endpoints with API key validation</p></li><li><p>Tracks API usage per key</p></li><li><p>Implements rate limiting (requests per hour)</p></li><li><p>Stores API keys securely</p></li><li><p>Returns proper authentication errors</p></li><li><p>Allows key generation via endpoint</p></li><li><p>Shows usage statistics</p></li><li><p>Production-ready security</p></li></ul><p>This project gives you hands-on practice with API authentication, security best practices, rate limiting, usage tracking, middleware patterns, and building professional APIs &#8212; essential skills for real-world backend development!</p><h2>Expected Output</h2><p><strong>RUNNING THE API:</strong></p><p><strong>As the developer of the API you can run it with:</strong></p><pre><code><code>python solution.py</code></code></pre><p><strong>USING THE API</strong></p><p>Users who want to use your API, first need to generate an API key. They can do so by running this <code>generate_key.py</code>: file:</p><pre><code><code>import requests

# Generate a new API key
response = requests.post(
    'http://127.0.0.1:5000/auth/generate-key',
    json={'name': 'My Weather App'}
)

result = response.json()
print("\n" + "="*50)
print("API KEY GENERATED!")
print("="*50)
print(f"\nYour API Key: {result['api_key']}")
print(f"\nApp Name: {result['name']}")
print(f"Rate Limit: {result['rate_limit']} requests/hour")
print(f"Created: {result['created_at']}")
print("\n&#9888;&#65039;  SAVE THIS KEY - You'll need it for all requests!")
print("="*50 + "\n")
</code></code></pre><p><strong>Run it:</strong></p><pre><code><code>python generate_key.py
</code></code></pre><p><strong>Output:</strong></p><pre><code><code>==================================================
API KEY GENERATED!
==================================================

Your API Key: wapi_a8f3c2e1b9d4f6a7c3e8b2d9f1a4c6e3

App Name: My Weather App
Rate Limit: 100 requests/hour
Created: 2026-04-24T10:30:00

&#9888;&#65039;  SAVE THIS KEY - You'll need it for all requests!
==================================================
</code></code></pre><p><strong>Clients using the API key to get weather </strong></p><p>Once users have the API key, they can use it in a program (this program is known as the client). For example, here is a script that consumes the API and provides the API key in the script to be able to access the API data:</p><pre><code><code>import requests

# Your API key from Step 1
API_KEY = "wapi_a8f3c2e1b9d4f6a7c3e8b2d9f1a4c6e3"

# Set up headers with your API key
headers = {
    "X-API-Key": API_KEY
}

# Get weather for New York
print("\nGetting weather for New York...")
response = requests.get(
    'http://127.0.0.1:5000/weather/newyork',
    headers=headers
)

weather = response.json()
print(f"\nCity: {weather['city']}")
print(f"Temperature: {weather['temperature']}&#176;{weather['unit'][0].upper()}")
print(f"Conditions: {weather['conditions']}")
print(f"Humidity: {weather['humidity']}%")
print(f"Wind Speed: {weather['wind_speed']} mph")
</code></code></pre><p><strong>Output:</strong></p><pre><code><code>Getting weather for New York...

City: New York
Temperature: 72&#176;F
Conditions: Partly Cloudy
Humidity: 65%
Wind Speed: 12 mph
</code></code></pre><p><strong>Submit weather data</strong></p><p><strong>Users can also submit data. Here is a script that does that:</strong></p><pre><code><code># Submit new weather report
print("\n&#128228; Submitting weather report for Seattle...")

weather_data = {
    "city": "Seattle",
    "temperature": 60,
    "unit": "fahrenheit",
    "conditions": "Rainy",
    "humidity": 85,
    "wind_speed": 15
}

response = requests.post(
    'http://127.0.0.1:5000/weather/submit',
    headers=headers,
    json=weather_data
)

result = response.json()
print(f"\n&#9989; {result['message']}")
print(f"Report ID: {result['data']['report_id']}")
print(f"Timestamp: {result['data']['timestamp']}")
</code></code></pre><p><strong>Output:</strong></p><pre><code><code>&#128228; Submitting weather report for Seattle...

&#9989; Weather report submitted successfully
Report ID: seattle_20260424_103700
Timestamp: 2026-04-24T10:37:00
</code></code></pre><p><strong>Check your API usage</strong></p><p>Users can also check their usage statistics of the API from their end:</p><pre><code><code># Check usage statistics
print("\n&#128202; Checking API usage...")

response = requests.get(
    'http://127.0.0.1:5000/auth/usage',
    headers=headers
)

usage = response.json()
print(f"\nApp: {usage['name']}")
print(f"Total Requests: {usage['usage']['total_requests']}")
print(f"Last Hour: {usage['usage']['requests_last_hour']}")
print(f"Remaining: {usage['usage']['remaining']}/{usage['usage']['rate_limit']}")
print(f"Last Used: {usage['last_used']}")
</code></code></pre><p><strong>Output:</strong></p><pre><code><code>&#128202; Checking API usage...

App: My Weather App
Total Requests: 15
Last Hour: 3
Remaining: 97/100
Last Used: 2026-04-24T10:37:00
</code></code></pre><p><strong>What happens without an API key?</strong></p><p>If a user accesses the API without the header:</p><pre><code><code># No API key - this will fail!
response = requests.get('http://127.0.0.1:5000/weather/london')
print(response.json())</code></code></pre><p><strong>the output will be an error message inside the JSON output:</strong></p><pre><code><code>{
  "error": "Unauthorized",
  "message": "API key is required. Include 'X-API-Key' header in your request.",
  "example": "X-API-Key: your_api_key_here"
}
</code></code></pre><p><strong>What happens when you exceed the rate limit?</strong></p><p>After 100 requests in one hour:</p><pre><code><code># 101st request
response = requests.get(
    'http://127.0.0.1:5000/weather/paris',
    headers=headers
)
print(response.json())
</code></code></pre><p><strong>Output:</strong></p><pre><code><code>{
  "error": "Rate limit exceeded",
  "message": "You have exceeded 100 requests per hour",
  "retry_after": "52 minutes",
  "limit": 100
}
</code></code></pre><h2></h2><p></p><h2>Understanding API Key Authentication</h2><p><strong>What is an API key?</strong></p><p>An API key is a unique identifier that:</p><ul><li><p>&#9989; Identifies who is making the request</p></li><li><p>&#9989; Controls access to your API</p></li><li><p>&#9989; Tracks usage per user/app</p></li><li><p>&#9989; Enables rate limiting</p></li><li><p>&#9989; Can be revoked if abused</p></li></ul><p><strong>Real-world examples:</strong></p><pre><code><code>OpenAI API: sk-proj-abc123...
Google Maps: AIzaSyD...
Stripe: sk_live_51H...
</code></code></pre><p><strong>Our format:</strong> <code>wapi_</code> prefix + 32 random hex characters</p><p><strong>How it works:</strong></p><pre><code><code>Your Python Script:
  headers = {"X-API-Key": "wapi_abc123..."}
  requests.get(url, headers=headers)
           &#8595;
Your Flask API:
  1. Check if X-API-Key header exists &#8594; No? Return 401
  2. Validate key in api_keys.json &#8594; Invalid? Return 401
  3. Check rate limit &#8594; Exceeded? Return 429
  4. Log usage
  5. Process request
  6. Return data
</code></code></pre><h2>Understanding Rate Limiting</h2><p><strong>Why rate limit?</strong></p><p>Without rate limiting, someone could:</p><ul><li><p>Overload your server with thousands of requests</p></li><li><p>Cost you money (if you pay per request)</p></li><li><p>Abuse your API</p></li><li><p>Create denial of service</p></li></ul><p><strong>Our rate limit:</strong> 100 requests per hour per API key</p><p><strong>How it works:</strong></p><p>Every time a request comes in, we:</p><ol><li><p>Count how many requests this API key made in the last hour</p></li><li><p>If it&#8217;s 100 or more &#8594; reject with 429 error</p></li><li><p>If under 100 &#8594; allow and log the request</p></li></ol><p><strong>Example:</strong></p><pre><code><code>10:00 AM - Request 1 &#9989;
10:15 AM - Request 2 &#9989;
...
10:50 AM - Request 100 &#9989;
10:55 AM - Request 101 &#10060; (Rate limit exceeded, retry after 11:00 AM)
11:01 AM - Request 102 &#9989; (Request 1 is now older than 1 hour)
</code></code></pre><h2>Understanding Middleware &amp; Decorators</h2><p><strong>What is middleware?</strong></p><p>Code that runs <em>before</em> your endpoint function. Perfect for:</p><ul><li><p>Authentication</p></li><li><p>Rate limiting</p></li><li><p>Logging</p></li><li><p>Request validation</p></li></ul><p><strong>Without middleware (repetitive):</strong></p><pre><code><code>@app.route('/weather/&lt;city&gt;')
def get_weather(city):
    # Check API key
    api_key = request.headers.get('X-API-Key')
    if not api_key:
        return jsonify({'error': 'No key'}), 401
    
    # Validate key
    if not is_valid_key(api_key):
        return jsonify({'error': 'Invalid key'}), 401
    
    # Check rate limit
    if is_rate_limited(api_key):
        return jsonify({'error': 'Rate limit'}), 429
    
    # Finally, actual logic
    return jsonify(weather_data)
</code></code></pre><p><strong>With middleware (clean):</strong></p><pre><code><code>@app.route('/weather/&lt;city&gt;')
@require_api_key  # &#8592; This handles all authentication!
def get_weather(city):
    # Just the actual logic!
    return jsonify(weather_data)
</code></code></pre><p><strong>The </strong><code>@require_api_key</code><strong> decorator does all the work:</strong></p><ul><li><p>Checks for API key header</p></li><li><p>Validates the key</p></li><li><p>Checks rate limit</p></li><li><p>Logs the request</p></li><li><p>Only calls your function if everything is valid</p></li></ul><h2>Security Best Practices</h2><p><strong>1. Never share your API key publicly</strong></p><pre><code><code># &#10060; BAD - Don't commit this to GitHub
API_KEY = "wapi_a8f3c2e1b9d4f6a7c3e8b2d9f1a4c6e3"

# &#9989; GOOD - Use environment variables
import os
API_KEY = os.getenv('WEATHER_API_KEY')
</code></code></pre><p><strong>2. Use HTTPS in production</strong></p><ul><li><p>API keys in headers are visible on HTTP</p></li><li><p>Always use HTTPS to encrypt traffic</p></li><li><p>Free with services like Heroku, Railway</p></li></ul><p><strong>3. Store keys securely</strong></p><ul><li><p>Add <code>api_keys.json</code> to <code>.gitignore</code></p></li><li><p>Never commit keys to version control</p></li><li><p>Use environment variables for sensitive data</p></li></ul><p><strong>4. Implement rate limiting</strong></p><ul><li><p>Prevents abuse</p></li><li><p>Protects your infrastructure</p></li><li><p>Fair usage for all users</p></li></ul><p><strong>5. Monitor usage</strong></p><ul><li><p>Track which keys are used most</p></li><li><p>Detect suspicious patterns</p></li><li><p>Revoke abusive keys</p></li></ul><h2>What You&#8217;ve Accomplished This Week</h2><p>&#127881; <strong>Congratulations!</strong> You&#8217;ve built a <strong>complete, production-ready API</strong> and learned:</p><ul><li><p><strong>Day 1:</strong> API fundamentals, Flask routes, GET requests, JSON responses</p></li><li><p><strong>Day 2:</strong> POST requests, data validation, JSON file storage</p></li><li><p><strong>Day 3:</strong> API key authentication, rate limiting, usage tracking</p></li></ul><p>You now have a <strong>professional Weather API</strong> that: &#9989; Handles GET and POST requests<br>&#9989; Validates all incoming data<br>&#9989; Stores data persistently<br>&#9989; Requires authentication<br>&#9989; Tracks usage per API key<br>&#9989; Implements rate limiting<br>&#9989; Returns proper HTTP status codes<br>&#9989; Production-ready security</p><p><strong>This is how real APIs work!</strong></p><p>You&#8217;ve learned the fundamentals that power <strong>every web service on the internet</strong>! &#128640;</p><h2>View Code Evolution</h2><p>Compare today&#8217;s solution with earlier versions and see how we evolved from a basic API to a production-ready authenticated service.</p>
      <p>
          <a href="https://dailypythonprojects.substack.com/p/build-a-weather-data-api-day-3-api">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Build a Weather Data API: Day 2 - POST Requests & Data Storage]]></title><description><![CDATA[Learn Python by practicing every day with a new project.]]></description><link>https://dailypythonprojects.substack.com/p/build-a-weather-data-api-day-2-post</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/build-a-weather-data-api-day-2-post</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Thu, 23 Apr 2026 18:39:03 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/55a3205c-8b76-40d4-8667-fd95da6b9f9b_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build a real-world Weather Data API from scratch using Flask, teaching you how APIs work by building one yourself.</p><ul><li><p><strong>Day 1:</strong> Basic Weather API with Flask</p></li><li><p><strong>Day 2:</strong> POST Requests &amp; Data Storage <strong>(Today)</strong></p></li><li><p><strong>Day 3:</strong> API Key Authentication</p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-14">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p>Yesterday we built a basic API that <em>retrieves</em> data (GET requests). Today we&#8217;re adding <strong>POST requests</strong> so users can <em>submit</em> their own weather reports, and we&#8217;ll <strong>store that data</strong> in a JSON file!</p><p>You&#8217;ll learn the difference between GET and POST, how to accept user data, validate it, and persist it to storage!</p><h2>Project Task</h2><p>Create an enhanced Weather API that:</p><ul><li><p>Accepts POST requests to submit weather data</p></li><li><p>Validates incoming data (required fields, valid values)</p></li><li><p>Stores submitted weather reports in JSON file</p></li><li><p>Returns newly submitted data</p></li><li><p>Retrieves stored weather reports</p></li><li><p>Handles both GET and POST on same endpoint</p></li><li><p>Persists data between server restarts</p></li><li><p>Provides helpful error messages for invalid data</p></li></ul><p>This project gives you hands-on practice with POST requests, request data handling, JSON file storage, data validation, CRUD operations, and building interactive APIs &#8212; essential skills for backend development!</p><h2>Expected Output</h2><p><strong>Running the API:</strong></p><pre><code><code>python solution.py
</code></code></pre><p><strong>Console Output:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!C-OG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F251f3af9-4f4d-4da1-b4d0-e2d748b3795b_1632x1088.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!C-OG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F251f3af9-4f4d-4da1-b4d0-e2d748b3795b_1632x1088.png 424w, https://substackcdn.com/image/fetch/$s_!C-OG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F251f3af9-4f4d-4da1-b4d0-e2d748b3795b_1632x1088.png 848w, https://substackcdn.com/image/fetch/$s_!C-OG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F251f3af9-4f4d-4da1-b4d0-e2d748b3795b_1632x1088.png 1272w, https://substackcdn.com/image/fetch/$s_!C-OG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F251f3af9-4f4d-4da1-b4d0-e2d748b3795b_1632x1088.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!C-OG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F251f3af9-4f4d-4da1-b4d0-e2d748b3795b_1632x1088.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/251f3af9-4f4d-4da1-b4d0-e2d748b3795b_1632x1088.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:656592,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/195269633?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F251f3af9-4f4d-4da1-b4d0-e2d748b3795b_1632x1088.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!C-OG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F251f3af9-4f4d-4da1-b4d0-e2d748b3795b_1632x1088.png 424w, https://substackcdn.com/image/fetch/$s_!C-OG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F251f3af9-4f4d-4da1-b4d0-e2d748b3795b_1632x1088.png 848w, https://substackcdn.com/image/fetch/$s_!C-OG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F251f3af9-4f4d-4da1-b4d0-e2d748b3795b_1632x1088.png 1272w, https://substackcdn.com/image/fetch/$s_!C-OG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F251f3af9-4f4d-4da1-b4d0-e2d748b3795b_1632x1088.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>Test GET (retrieve): </strong><code>http://127.0.0.1:5000/weather/newyork</code></p><p><code>You can send the GET request to the API either by visiting the above URL in your browser:</code></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EPy9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ed5a19-dadd-4a27-81b1-8f94952c600d_1668x1112.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EPy9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ed5a19-dadd-4a27-81b1-8f94952c600d_1668x1112.png 424w, https://substackcdn.com/image/fetch/$s_!EPy9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ed5a19-dadd-4a27-81b1-8f94952c600d_1668x1112.png 848w, https://substackcdn.com/image/fetch/$s_!EPy9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ed5a19-dadd-4a27-81b1-8f94952c600d_1668x1112.png 1272w, https://substackcdn.com/image/fetch/$s_!EPy9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ed5a19-dadd-4a27-81b1-8f94952c600d_1668x1112.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EPy9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ed5a19-dadd-4a27-81b1-8f94952c600d_1668x1112.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a9ed5a19-dadd-4a27-81b1-8f94952c600d_1668x1112.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:382125,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/195269633?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ed5a19-dadd-4a27-81b1-8f94952c600d_1668x1112.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!EPy9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ed5a19-dadd-4a27-81b1-8f94952c600d_1668x1112.png 424w, https://substackcdn.com/image/fetch/$s_!EPy9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ed5a19-dadd-4a27-81b1-8f94952c600d_1668x1112.png 848w, https://substackcdn.com/image/fetch/$s_!EPy9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ed5a19-dadd-4a27-81b1-8f94952c600d_1668x1112.png 1272w, https://substackcdn.com/image/fetch/$s_!EPy9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ed5a19-dadd-4a27-81b1-8f94952c600d_1668x1112.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Or through a Python script such as this one below. In this case the Python script is the app that is consuming your API:</p><p></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;d3fb5398-1c1f-4bfb-bdc5-8741af242944&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import requests

response = requests.get("http://127.0.0.1:5000/weather/newyork")

print(response.json())
</code></pre></div><p><strong>Test POST (submit new weather report):</strong></p><p>Similarly, Python programs such as this one can send data to the API. These are known as POST requests:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;daf07f13-9325-423d-b9b6-386651f7fb01&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import requests

response = requests.post(
    "http://127.0.0.1:5000/weather/submit",
    json={
        "city": "San Francisco",
        "temperature": 65,
        "unit": "fahrenheit",
        "conditions": "Foggy",
        "humidity": 75,
        "wind_speed": 10
    }
)

print(response.json())</code></pre></div><p>The data that the Python script sends are received by the API. Then, as the developer of the API you can do whatever you want with the data. So, at this point the two apps are communicating with each other sending and receiving data.</p><h2>Setup Instructions</h2><p><strong>Install Required Package:</strong></p><pre><code><code>pip install flask</code></code></pre><p><strong>Same as Day 1</strong> - no new packages needed!</p><p><strong>Run the API:</strong></p><pre><code><code>python solution.py</code></code></pre><p><strong>The API creates </strong><code>weather_data.json</code><strong> automatically</strong> to store submitted reports.</p><p></p><h2>Understanding GET vs POST</h2><p><strong>GET = Retrieve data</strong></p><ul><li><p>Read-only operation</p></li><li><p>Parameters in URL (<code>?city=london</code>)</p></li><li><p>Browser can do GET requests</p></li><li><p>Idempotent (same result every time)</p></li><li><p>Example: Getting weather for a city</p></li></ul><p><strong>POST = Submit data</strong></p><ul><li><p>Sends data to server</p></li><li><p>Data in request body (JSON)</p></li><li><p>Cannot be done from browser URL bar</p></li><li><p>Creates or modifies data</p></li><li><p>Example: Submitting a weather report</p></li></ul><p><strong>Visual comparison:</strong></p><pre><code><code>GET Request:
  Client &#8594; "Give me weather for London" &#8594; Server
  Client &#8592; Temperature: 15&#176;C, Rainy &#8592; Server

POST Request:
  Client &#8594; "Here's weather data for Austin" + JSON &#8594; Server
  Client &#8592; "Saved! Report ID: austin_123" &#8592; Server
</code></code></pre><h2>Understanding Request Data in Flask</h2><p><strong>Accessing POST data:</strong></p><pre><code><code>from flask import request

@app.route('/weather/submit', methods=['POST'])
def submit_weather():
    # Get JSON data from request body
    data = request.get_json()
    
    # Access fields
    city = data.get('city')
    temperature = data.get('temperature')
    
    # Validate
    if not city:
        return jsonify({'error': 'City is required'}), 400
    
    # Process and return
    return jsonify({'message': 'Success', 'data': data})
</code></code></pre><p><strong>Key Flask concepts:</strong></p><pre><code><code># Specify HTTP methods
@app.route('/endpoint', methods=['GET', 'POST'])

# Get JSON from request
data = request.get_json()

# Get specific field with default
city = data.get('city', 'Unknown')

# Return error with status code
return jsonify({'error': 'Invalid data'}), 400
</code></code></pre><h2>Understanding JSON File Storage</h2><p><strong>Why JSON files?</strong></p><ul><li><p>Simple for small projects</p></li><li><p>Human-readable</p></li><li><p>No database setup needed</p></li><li><p>Perfect for learning</p></li></ul><p></p><h2>Understanding Data Validation</h2><p><strong>Why validate?</strong></p><ul><li><p>Prevent bad data from being stored</p></li><li><p>Give users helpful error messages</p></li><li><p>Ensure data consistency</p></li><li><p>Avoid crashes</p></li></ul><p><strong>Validation checks we implement:</strong></p><ol><li><p><strong>Required fields</strong> - All fields must be present</p></li><li><p><strong>Temperature range</strong> - Between -100 and 150</p></li><li><p><strong>Humidity range</strong> - Between 0 and 100</p></li><li><p><strong>Wind speed range</strong> - Between 0 and 200</p></li><li><p><strong>Valid units</strong> - Only &#8216;celsius&#8217; or &#8216;fahrenheit&#8217;</p></li></ol><p><strong>Validation example:</strong></p><pre><code><code># Check required fields
required = ['city', 'temperature', 'unit', 'conditions', 'humidity', 'wind_speed']
missing = [field for field in required if field not in data]

if missing:
    return jsonify({
        'error': 'Missing required fields',
        'missing': missing
    }), 400

# Validate temperature
if not -100 &lt;= data['temperature'] &lt;= 150:
    return jsonify({
        'error': 'Invalid temperature',
        'message': 'Temperature must be between -100 and 150'
    }), 400
</code></code></pre><h2>Coming Tomorrow</h2><p>Tomorrow we&#8217;re adding <strong>API key authentication</strong> &#8212; generate API keys, protect endpoints, implement rate limiting, and make your API production-ready with security!</p><h2>View Code Evolution</h2><p>Compare today&#8217;s solution with yesterday&#8217;s version and see how we added POST requests and data storage.</p>
      <p>
          <a href="https://dailypythonprojects.substack.com/p/build-a-weather-data-api-day-2-post">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Build a Weather Data API: Day 1 - Basic Weather API with Flask ]]></title><description><![CDATA[Build an API that serves weather data to users using Python and Flask.]]></description><link>https://dailypythonprojects.substack.com/p/build-a-weather-data-api-basic-weather</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/build-a-weather-data-api-basic-weather</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Wed, 22 Apr 2026 16:21:45 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f8ab188c-b103-4afa-9345-021e2ee0f568_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build a real-world Weather Data API from scratch using Flask, teaching you how APIs work by building one yourself.</p><p><strong>Why build this?</strong> Because APIs are everywhere &#8212; every app, website, and service uses them. Instead of just <em>consuming</em> APIs, you&#8217;ll learn how to <em>create</em> them. By the end of this week, you&#8217;ll have built a production-ready API with authentication!</p><p><strong>What you&#8217;ll learn:</strong> This series teaches you API fundamentals, Flask web framework, HTTP methods (GET/POST), JSON responses, route handling, and API authentication. These skills apply to any web service you&#8217;ll ever build.</p><p><strong>Why users love this:</strong> Build something real! Your API will actually work &#8212; you can test it in the browser, use it in other projects, or even deploy it for others to use. This is how Stripe, Twitter, and OpenAI APIs work!</p><ul><li><p><strong>Day 1:</strong> Basic Weather API with Flask <strong>(Today)</strong></p></li><li><p><strong>Day 2:</strong> POST Requests &amp; Data Storage</p></li><li><p><strong>Day 3:</strong> API Key Authentication</p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-14">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p>We&#8217;re starting with the foundation: a simple Flask API that returns weather data. You&#8217;ll learn what an API is, how to create endpoints, return JSON data, and test your API!</p><p>You&#8217;ll understand routes, HTTP GET requests, JSON responses, and how to structure an API!</p><h2>Project Task</h2><p>Create a basic Weather API that:</p><ul><li><p>Returns current weather for different cities</p></li><li><p>Multiple API endpoints (routes)</p></li><li><p>Returns data in JSON format</p></li><li><p>Handles multiple cities (New York, London, Tokyo, etc.)</p></li><li><p>Provides temperature, conditions, humidity</p></li><li><p>Works in the browser</p></li><li><p>Can be tested with curl or Postman</p></li><li><p>Clean, simple Flask structure</p></li></ul><p>This project gives you hands-on practice with Flask, API endpoints, JSON responses, route parameters, HTTP methods, and building web services &#8212; essential skills for backend development!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://dailypythonprojects.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Daily Python Projects is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h2>Expected Output</h2><p><strong>Running the API:</strong></p><pre><code><code>python solution.py</code></code></pre><p><strong>Console Output:</strong></p><pre><code><code> * Running on http://127.0.0.1:5000
 * Debug mode: on</code></code></pre><p><strong>Test in browser and go to </strong>http://127.0.0.1:5000/</p><p>You will see this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!07lJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff47a0ef9-bcad-4511-b6e4-5dc1097755d1_2232x1488.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!07lJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff47a0ef9-bcad-4511-b6e4-5dc1097755d1_2232x1488.png 424w, https://substackcdn.com/image/fetch/$s_!07lJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff47a0ef9-bcad-4511-b6e4-5dc1097755d1_2232x1488.png 848w, https://substackcdn.com/image/fetch/$s_!07lJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff47a0ef9-bcad-4511-b6e4-5dc1097755d1_2232x1488.png 1272w, https://substackcdn.com/image/fetch/$s_!07lJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff47a0ef9-bcad-4511-b6e4-5dc1097755d1_2232x1488.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!07lJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff47a0ef9-bcad-4511-b6e4-5dc1097755d1_2232x1488.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f47a0ef9-bcad-4511-b6e4-5dc1097755d1_2232x1488.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:492912,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/195051399?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff47a0ef9-bcad-4511-b6e4-5dc1097755d1_2232x1488.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!07lJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff47a0ef9-bcad-4511-b6e4-5dc1097755d1_2232x1488.png 424w, https://substackcdn.com/image/fetch/$s_!07lJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff47a0ef9-bcad-4511-b6e4-5dc1097755d1_2232x1488.png 848w, https://substackcdn.com/image/fetch/$s_!07lJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff47a0ef9-bcad-4511-b6e4-5dc1097755d1_2232x1488.png 1272w, https://substackcdn.com/image/fetch/$s_!07lJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff47a0ef9-bcad-4511-b6e4-5dc1097755d1_2232x1488.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>Test endpoint: </strong><code>http://127.0.0.1:5000/weather/newyork</code></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!u4Lc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fe94095-db7f-471b-86b9-b1a34116bde0_2034x1356.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!u4Lc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fe94095-db7f-471b-86b9-b1a34116bde0_2034x1356.png 424w, https://substackcdn.com/image/fetch/$s_!u4Lc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fe94095-db7f-471b-86b9-b1a34116bde0_2034x1356.png 848w, https://substackcdn.com/image/fetch/$s_!u4Lc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fe94095-db7f-471b-86b9-b1a34116bde0_2034x1356.png 1272w, https://substackcdn.com/image/fetch/$s_!u4Lc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fe94095-db7f-471b-86b9-b1a34116bde0_2034x1356.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!u4Lc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fe94095-db7f-471b-86b9-b1a34116bde0_2034x1356.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5fe94095-db7f-471b-86b9-b1a34116bde0_2034x1356.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:407315,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/195051399?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fe94095-db7f-471b-86b9-b1a34116bde0_2034x1356.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!u4Lc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fe94095-db7f-471b-86b9-b1a34116bde0_2034x1356.png 424w, https://substackcdn.com/image/fetch/$s_!u4Lc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fe94095-db7f-471b-86b9-b1a34116bde0_2034x1356.png 848w, https://substackcdn.com/image/fetch/$s_!u4Lc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fe94095-db7f-471b-86b9-b1a34116bde0_2034x1356.png 1272w, https://substackcdn.com/image/fetch/$s_!u4Lc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fe94095-db7f-471b-86b9-b1a34116bde0_2034x1356.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>Let&#8217;s try London now: </strong><code>http://127.0.0.1:5000/weather/london</code></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KwFI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeb07e55-4eea-478e-b11f-9dd893992b0f_2094x1396.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KwFI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeb07e55-4eea-478e-b11f-9dd893992b0f_2094x1396.png 424w, https://substackcdn.com/image/fetch/$s_!KwFI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeb07e55-4eea-478e-b11f-9dd893992b0f_2094x1396.png 848w, https://substackcdn.com/image/fetch/$s_!KwFI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeb07e55-4eea-478e-b11f-9dd893992b0f_2094x1396.png 1272w, https://substackcdn.com/image/fetch/$s_!KwFI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeb07e55-4eea-478e-b11f-9dd893992b0f_2094x1396.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KwFI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeb07e55-4eea-478e-b11f-9dd893992b0f_2094x1396.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aeb07e55-4eea-478e-b11f-9dd893992b0f_2094x1396.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:538950,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/195051399?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeb07e55-4eea-478e-b11f-9dd893992b0f_2094x1396.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KwFI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeb07e55-4eea-478e-b11f-9dd893992b0f_2094x1396.png 424w, https://substackcdn.com/image/fetch/$s_!KwFI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeb07e55-4eea-478e-b11f-9dd893992b0f_2094x1396.png 848w, https://substackcdn.com/image/fetch/$s_!KwFI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeb07e55-4eea-478e-b11f-9dd893992b0f_2094x1396.png 1272w, https://substackcdn.com/image/fetch/$s_!KwFI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeb07e55-4eea-478e-b11f-9dd893992b0f_2094x1396.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>Or get all available cities: </strong><code>http://127.0.0.1:5000/weather/all</code></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Yldt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80be5bbc-f443-4f2d-b476-7b00d40e6ba5_2994x1996.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Yldt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80be5bbc-f443-4f2d-b476-7b00d40e6ba5_2994x1996.png 424w, https://substackcdn.com/image/fetch/$s_!Yldt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80be5bbc-f443-4f2d-b476-7b00d40e6ba5_2994x1996.png 848w, https://substackcdn.com/image/fetch/$s_!Yldt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80be5bbc-f443-4f2d-b476-7b00d40e6ba5_2994x1996.png 1272w, https://substackcdn.com/image/fetch/$s_!Yldt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80be5bbc-f443-4f2d-b476-7b00d40e6ba5_2994x1996.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Yldt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80be5bbc-f443-4f2d-b476-7b00d40e6ba5_2994x1996.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/80be5bbc-f443-4f2d-b476-7b00d40e6ba5_2994x1996.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:919732,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/195051399?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80be5bbc-f443-4f2d-b476-7b00d40e6ba5_2994x1996.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Yldt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80be5bbc-f443-4f2d-b476-7b00d40e6ba5_2994x1996.png 424w, https://substackcdn.com/image/fetch/$s_!Yldt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80be5bbc-f443-4f2d-b476-7b00d40e6ba5_2994x1996.png 848w, https://substackcdn.com/image/fetch/$s_!Yldt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80be5bbc-f443-4f2d-b476-7b00d40e6ba5_2994x1996.png 1272w, https://substackcdn.com/image/fetch/$s_!Yldt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80be5bbc-f443-4f2d-b476-7b00d40e6ba5_2994x1996.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p></p><p><strong>If the city does not exist, the API gracefully returns a message: </strong><code>http://127.0.0.1:5000/weather/fakecity</code></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4dZJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faffc5823-1b61-4943-af4c-c6c8803d786c_2022x1348.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4dZJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faffc5823-1b61-4943-af4c-c6c8803d786c_2022x1348.png 424w, https://substackcdn.com/image/fetch/$s_!4dZJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faffc5823-1b61-4943-af4c-c6c8803d786c_2022x1348.png 848w, https://substackcdn.com/image/fetch/$s_!4dZJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faffc5823-1b61-4943-af4c-c6c8803d786c_2022x1348.png 1272w, https://substackcdn.com/image/fetch/$s_!4dZJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faffc5823-1b61-4943-af4c-c6c8803d786c_2022x1348.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4dZJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faffc5823-1b61-4943-af4c-c6c8803d786c_2022x1348.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/affc5823-1b61-4943-af4c-c6c8803d786c_2022x1348.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:439340,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/195051399?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faffc5823-1b61-4943-af4c-c6c8803d786c_2022x1348.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4dZJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faffc5823-1b61-4943-af4c-c6c8803d786c_2022x1348.png 424w, https://substackcdn.com/image/fetch/$s_!4dZJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faffc5823-1b61-4943-af4c-c6c8803d786c_2022x1348.png 848w, https://substackcdn.com/image/fetch/$s_!4dZJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faffc5823-1b61-4943-af4c-c6c8803d786c_2022x1348.png 1272w, https://substackcdn.com/image/fetch/$s_!4dZJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faffc5823-1b61-4943-af4c-c6c8803d786c_2022x1348.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>Setup Instructions</h2><p><strong>Install Required Package:</strong></p><pre><code><code>pip install flask</code></code></pre><p><strong>That&#8217;s it!</strong> Flask is all you need for Day 1.</p><p><strong>Run the API:</strong></p><pre><code><code>python solution.py
</code></code></pre><p>The API will start on </p><p>http://127.0.0.1:5000</p><p></p><h2>Understanding APIs</h2><p><strong>What is an API?</strong></p><p>API = <strong>Application Programming Interface</strong></p><p>Think of it as a restaurant menu:</p><ul><li><p><strong>Menu</strong> = API documentation (what you can order)</p></li><li><p><strong>Order</strong> = API request (asking for something)</p></li><li><p><strong>Food</strong> = API response (getting what you asked for)</p></li></ul><p><strong>How APIs work:</strong></p><pre><code><code>Client (Browser/App) &#8594; HTTP Request &#8594; Your API (Flask)
                                          &#8595;
                                    Process Request
                                          &#8595;
Client &#8592; HTTP Response (JSON) &#8592; Your API
</code></code></pre><p><strong>Endpoints = URLs that do different things:</strong></p><pre><code><code>http://127.0.0.1:5000/              &#8594; Home (API info)
http://127.0.0.1:5000/weather/tokyo &#8594; Get Tokyo weather
http://127.0.0.1:5000/weather/all   &#8594; Get all cities
</code></code></pre><p><strong>JSON = Data format for APIs:</strong></p><p>APIs use JSON (JavaScript Object Notation) because:</p><ul><li><p>Easy for computers to read</p></li><li><p>Easy for humans to read</p></li><li><p>Works in any programming language</p></li><li><p>Lightweight and fast</p></li></ul><p><strong>HTTP Methods (for now, just GET):</strong></p><ul><li><p><strong>GET</strong> = Retrieve data (&#8221;Get me the weather for London&#8221;)</p></li><li><p>POST = Send data (tomorrow!)</p></li><li><p>PUT = Update data</p></li><li><p>DELETE = Remove data</p></li></ul><h2>Understanding Flask</h2><p><strong>What is Flask?</strong></p><p>Flask is a lightweight Python web framework that makes it easy to build APIs and web apps.</p><p><strong>Basic Flask structure:</strong></p><pre><code><code>from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/')
def home():
    return jsonify({"message": "Hello, World!"})

if __name__ == '__main__':
    app.run(debug=True)
</code></code></pre><p><strong>Key concepts:</strong></p><p><strong>1. Routes (Endpoints):</strong></p><pre><code><code>@app.route('/weather/tokyo')
def tokyo_weather():
    return jsonify({"city": "Tokyo", "temp": 22})
</code></code></pre><p><strong>2. Dynamic routes (Path parameters):</strong></p><pre><code><code>@app.route('/weather/&lt;city&gt;')
def get_weather(city):
    # city is a variable from the URL
    return jsonify({"city": city})
</code></code></pre><p><strong>3. Returning JSON:</strong></p><pre><code><code>from flask import jsonify

data = {"city": "London", "temp": 15}
return jsonify(data)
</code></code></pre><p><strong>4. Error handling:</strong></p><pre><code><code>return jsonify({"error": "Not found"}), 404</code></code></pre><h2>Coming Tomorrow</h2><p>Tomorrow we&#8217;ll add <strong>POST requests</strong> so users can submit their own weather reports, and we&#8217;ll store data in a JSON file. You&#8217;ll learn the difference between GET (retrieve) and POST (submit)!</p><h2>Skeleton and Solution</h2><p>Below you will find both a downloadable skeleton.py file to help you code the project with comment guides and the downloadable solution.py file containing the correct solution.</p><p>Get the code skeleton here:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://share.pythonanywhere.com/view/Us1glBRvvN8HxL8zq-b5uw&quot;,&quot;text&quot;:&quot;View Code Skeleton&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://share.pythonanywhere.com/view/Us1glBRvvN8HxL8zq-b5uw"><span>View Code Skeleton</span></a></p><p></p><p>Get the code solution here:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://share.pythonanywhere.com/evolution/F8Y8XLUKpXVmXR3tSRF-tA&quot;,&quot;text&quot;:&quot;View Code Solution&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://share.pythonanywhere.com/evolution/F8Y8XLUKpXVmXR3tSRF-tA"><span>View Code Solution</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Build a YouTube Video Summarizer - Day 3: YouTube Summarizer Web App]]></title><description><![CDATA[Learn Python by practicing every day with a new project.]]></description><link>https://dailypythonprojects.substack.com/p/build-a-youtube-video-summarizer-f9f</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/build-a-youtube-video-summarizer-f9f</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Thu, 16 Apr 2026 18:57:43 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/28628f77-6288-4b87-bf95-8fa56a6c2341_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build an AI-powered YouTube video summarizer that extracts transcripts and generates intelligent summaries using LangChain and Google&#8217;s Gemini AI.</p><ul><li><p><strong>Day 1:</strong> YouTube Video Summarizer</p></li><li><p><strong>Day 2:</strong> Timestamped Chapters &amp; Key Insights</p></li><li><p><strong>Day 3:</strong> YouTube Summarizer Web App <strong>(Today)</strong></p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-13">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p><strong>Welcome to the finale!</strong> We&#8217;ve built command-line tools for YouTube summarization. Today we&#8217;re creating a <strong>beautiful web app</strong> that anyone can use &#8212; no terminal, no Python knowledge required!</p><p>Paste a YouTube URL, click a button, and get instant summaries with chapters, insights, and action items. A production-ready app you can deploy and share!</p><h2>Project Task</h2><p>Create a YouTube summarizer web app that:</p><ul><li><p>Beautiful web interface built with Streamlit</p></li><li><p>Simple URL input field</p></li><li><p>One-click summarization</p></li><li><p>Displays chapters with clickable timestamps</p></li><li><p>Shows key insights and quotes</p></li><li><p>Lists action items</p></li><li><p>Clean, modern design</p></li><li><p>Real-time AI processing</p></li><li><p>Shareable results</p></li><li><p>Deployment ready</p></li></ul><p>This project gives you hands-on practice with Streamlit, web app development, user interface design, real-time processing, and building production-ready AI tools that anyone can use!</p><h2>Expected Output</h2><p>Here is how the web app looks like. The user simply pastes the YouTube video URL and get a video summary right away:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qH73!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa034d98-0ebf-48af-910a-98de3fc244d0_2556x1704.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qH73!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa034d98-0ebf-48af-910a-98de3fc244d0_2556x1704.png 424w, https://substackcdn.com/image/fetch/$s_!qH73!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa034d98-0ebf-48af-910a-98de3fc244d0_2556x1704.png 848w, https://substackcdn.com/image/fetch/$s_!qH73!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa034d98-0ebf-48af-910a-98de3fc244d0_2556x1704.png 1272w, https://substackcdn.com/image/fetch/$s_!qH73!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa034d98-0ebf-48af-910a-98de3fc244d0_2556x1704.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qH73!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa034d98-0ebf-48af-910a-98de3fc244d0_2556x1704.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fa034d98-0ebf-48af-910a-98de3fc244d0_2556x1704.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:517806,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/194437589?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa034d98-0ebf-48af-910a-98de3fc244d0_2556x1704.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qH73!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa034d98-0ebf-48af-910a-98de3fc244d0_2556x1704.png 424w, https://substackcdn.com/image/fetch/$s_!qH73!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa034d98-0ebf-48af-910a-98de3fc244d0_2556x1704.png 848w, https://substackcdn.com/image/fetch/$s_!qH73!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa034d98-0ebf-48af-910a-98de3fc244d0_2556x1704.png 1272w, https://substackcdn.com/image/fetch/$s_!qH73!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa034d98-0ebf-48af-910a-98de3fc244d0_2556x1704.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!OOdE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39c4bc8f-64a6-482d-be49-0b7e76bed6b9_2622x1748.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!OOdE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39c4bc8f-64a6-482d-be49-0b7e76bed6b9_2622x1748.png 424w, https://substackcdn.com/image/fetch/$s_!OOdE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39c4bc8f-64a6-482d-be49-0b7e76bed6b9_2622x1748.png 848w, https://substackcdn.com/image/fetch/$s_!OOdE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39c4bc8f-64a6-482d-be49-0b7e76bed6b9_2622x1748.png 1272w, https://substackcdn.com/image/fetch/$s_!OOdE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39c4bc8f-64a6-482d-be49-0b7e76bed6b9_2622x1748.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!OOdE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39c4bc8f-64a6-482d-be49-0b7e76bed6b9_2622x1748.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/39c4bc8f-64a6-482d-be49-0b7e76bed6b9_2622x1748.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:608525,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/194437589?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39c4bc8f-64a6-482d-be49-0b7e76bed6b9_2622x1748.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!OOdE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39c4bc8f-64a6-482d-be49-0b7e76bed6b9_2622x1748.png 424w, https://substackcdn.com/image/fetch/$s_!OOdE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39c4bc8f-64a6-482d-be49-0b7e76bed6b9_2622x1748.png 848w, https://substackcdn.com/image/fetch/$s_!OOdE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39c4bc8f-64a6-482d-be49-0b7e76bed6b9_2622x1748.png 1272w, https://substackcdn.com/image/fetch/$s_!OOdE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39c4bc8f-64a6-482d-be49-0b7e76bed6b9_2622x1748.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p></p><h2>Setup Instructions</h2><p><strong>Install Required Packages:</strong></p><pre><code><code>pip install streamlit youtube-transcript-api langchain-google-genai</code></code></pre><p><strong>Get Your Google API Key:</strong></p><ol><li><p>Go to <a href="https://aistudio.google.com/app/apikey">Google AI Studio</a></p></li><li><p>Click &#8220;Create API Key&#8221;</p></li><li><p>Copy your key</p></li><li><p>Paste it in the script where it says <code>YOUR_GOOGLE_API_KEY</code></p></li></ol><p><strong>Run the web app:</strong></p><pre><code><code>streamlit run solution.py
</code></code></pre><p>Your browser will automatically open to </p><p>http://localhost:8501</p><p><strong>Deploy to Streamlit Cloud (Optional):</strong></p><ol><li><p>Push your code to GitHub</p></li><li><p>Go to <a href="https://share.streamlit.io/">share.streamlit.io</a></p></li><li><p>Connect your repo</p></li><li><p>Add your Google API key in Secrets: </p></li></ol><pre><code><code>GOOGLE_API_KEY = "your-key-here"
</code></code></pre><ol><li><p>Deploy! Your app is now live and shareable</p></li></ol><h2>Understanding Streamlit for This App</h2><p><strong>Key Streamlit features we use:</strong></p><pre><code><code>import streamlit as st

# Page config
st.set_page_config(page_title="YouTube Summarizer", page_icon="&#127909;")

# Title
st.title("&#127909; YouTube Video Summarizer")

# Text input
url = st.text_input("Enter YouTube URL")

# Button
if st.button("&#128269; Summarize Video"):
    # Process video
    
# Status messages
st.success("&#9989; Video Loaded")
st.info("&#128250; Generating summary...")

# Expanders for sections
with st.expander("&#9201;&#65039; CHAPTERS", expanded=True):
    st.markdown(chapters)

# Markdown for formatting
st.markdown("**Bold text**")
</code></code></pre><p><strong>Session state for this app:</strong></p><pre><code><code># Store summary results
if 'summary' not in st.session_state:
    st.session_state.summary = None
    st.session_state.video_id = None
</code></code></pre><p><strong>Why Streamlit is perfect for this:</strong></p><p>&#9989; <strong>Simple input</strong> - Just a text box for URL<br>&#9989; <strong>One button</strong> - &#8220;Summarize&#8221; triggers everything<br>&#9989; <strong>Beautiful output</strong> - Markdown renders perfectly<br>&#9989; <strong>Collapsible sections</strong> - Expanders for chapters/insights<br>&#9989; <strong>Fast development</strong> - Built in pure Python<br>&#9989; <strong>Easy deployment</strong> - Free Streamlit Cloud hosting</p><h2>Design Decisions</h2><p><strong>Clean, focused interface:</strong></p><ul><li><p>Single input field (YouTube URL)</p></li><li><p>One primary action (Summarize button)</p></li><li><p>Clear status messages during processing</p></li><li><p>Well-organized output sections</p></li></ul><p><strong>User experience:</strong></p><ul><li><p>Loading indicators during AI processing</p></li><li><p>Collapsible sections to reduce scrolling</p></li><li><p>Emojis for visual hierarchy</p></li><li><p>&#8220;Summarize Another Video&#8221; button to reset</p></li></ul><p><strong>Output organization:</strong></p><ol><li><p>Chapters first (most useful for navigation)</p></li><li><p>Key insights (memorable quotes)</p></li><li><p>Action items (what to do)</p></li><li><p>Summary (overall assessment)</p></li></ol><h2>What You&#8217;ve Accomplished This Week</h2><p>&#127881; <strong>Congratulations!</strong> You&#8217;ve built a complete YouTube summarization system and learned:</p><ul><li><p><strong>Day 1:</strong> Basic transcript extraction and AI summarization</p></li><li><p><strong>Day 2:</strong> Timestamped chapters and actionable insights</p></li><li><p><strong>Day 3:</strong> Production web app with beautiful UI</p></li></ul><p>You now have a <strong>production-ready AI application</strong> that: &#9989; Summarizes any YouTube video instantly<br>&#9989; Breaks videos into navigable chapters<br>&#9989; Extracts key insights and quotes<br>&#9989; Provides actionable takeaways<br>&#9989; Beautiful web interface anyone can use<br>&#9989; Ready to deploy and share</p><p>This is a <strong>complete AI content tool</strong> &#8212; from YouTube URL to structured insights!</p><p></p><h2>View Code Evolution</h2><p>Compare today&#8217;s solution with earlier versions and see how we evolved from command-line tools to a full-featured web application.</p>
      <p>
          <a href="https://dailypythonprojects.substack.com/p/build-a-youtube-video-summarizer-f9f">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Build a YouTube Video Summarizer - Day 2: Timestamped Chapters & Key Insights]]></title><description><![CDATA[We continue building the AI video summarizer by extracting timestamps from the YouTube video and giving the user more granular information about the video.]]></description><link>https://dailypythonprojects.substack.com/p/build-a-youtube-video-summarizer-4e4</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/build-a-youtube-video-summarizer-4e4</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Wed, 15 Apr 2026 11:04:19 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b48216cc-29c8-486f-8530-ca3cbc6de38f_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build an AI-powered YouTube video summarizer that extracts transcripts and generates intelligent summaries using LangChain and Google&#8217;s Gemini AI.</p><ul><li><p><strong>Day 1:</strong> YouTube Video Summarizer</p></li><li><p><strong>Day 2:</strong> Timestamped Chapters &amp; Key Insights <strong>(Today)</strong></p></li><li><p><strong>Day 3:</strong> YouTube Summarizer Web App</p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-13">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p>Yesterday we built a basic video summarizer. Today we&#8217;re <strong>adding timestamped chapters and key insights</strong> &#8212; break videos into sections with timestamps, extract important quotes, and identify actionable takeaways!</p><p>Perfect for long lectures, podcasts, and tutorials where you need to jump to specific sections!</p><h2>Project Task</h2><p>Create an enhanced YouTube summarizer that:</p><ul><li><p>Generates timestamped chapter breakdown</p></li><li><p>Extracts key quotes from the video</p></li><li><p>Identifies action items and takeaways</p></li><li><p>Shows duration for each chapter</p></li><li><p>Highlights the most important moments</p></li><li><p>Provides structured, scannable output</p></li><li><p>Works from command line</p></li></ul><p>This project gives you hands-on practice with timestamp formatting, content segmentation, quote extraction, actionable insight generation, and building tools that make long-form content navigable &#8212; essential skills for content analysis tools!</p><h2>Expected Output</h2><p>Here is how the program works when we give it this video below:</p><div id="youtube2-rNxC16mlO60" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;rNxC16mlO60&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/rNxC16mlO60?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>The program will load the video transcript, send the transcript to the AI model and then provide a breakdown of the video in the terminal including timestamps for each section:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jDuA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eeda435-f807-4c17-912b-eaeddb8464eb_2274x1516.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jDuA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eeda435-f807-4c17-912b-eaeddb8464eb_2274x1516.png 424w, https://substackcdn.com/image/fetch/$s_!jDuA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eeda435-f807-4c17-912b-eaeddb8464eb_2274x1516.png 848w, https://substackcdn.com/image/fetch/$s_!jDuA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eeda435-f807-4c17-912b-eaeddb8464eb_2274x1516.png 1272w, https://substackcdn.com/image/fetch/$s_!jDuA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eeda435-f807-4c17-912b-eaeddb8464eb_2274x1516.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jDuA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eeda435-f807-4c17-912b-eaeddb8464eb_2274x1516.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1eeda435-f807-4c17-912b-eaeddb8464eb_2274x1516.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1521869,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/194283324?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eeda435-f807-4c17-912b-eaeddb8464eb_2274x1516.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jDuA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eeda435-f807-4c17-912b-eaeddb8464eb_2274x1516.png 424w, https://substackcdn.com/image/fetch/$s_!jDuA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eeda435-f807-4c17-912b-eaeddb8464eb_2274x1516.png 848w, https://substackcdn.com/image/fetch/$s_!jDuA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eeda435-f807-4c17-912b-eaeddb8464eb_2274x1516.png 1272w, https://substackcdn.com/image/fetch/$s_!jDuA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eeda435-f807-4c17-912b-eaeddb8464eb_2274x1516.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Moreover, it will extract key insights and action items. For example, for the TED talk video we used, it will make a list of practical tasks you can do to implement the ideas discussed in the video:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!blqF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48de9f75-c7ae-4d38-9145-274b1c6255d7_2286x1524.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!blqF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48de9f75-c7ae-4d38-9145-274b1c6255d7_2286x1524.png 424w, https://substackcdn.com/image/fetch/$s_!blqF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48de9f75-c7ae-4d38-9145-274b1c6255d7_2286x1524.png 848w, https://substackcdn.com/image/fetch/$s_!blqF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48de9f75-c7ae-4d38-9145-274b1c6255d7_2286x1524.png 1272w, https://substackcdn.com/image/fetch/$s_!blqF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48de9f75-c7ae-4d38-9145-274b1c6255d7_2286x1524.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!blqF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48de9f75-c7ae-4d38-9145-274b1c6255d7_2286x1524.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/48de9f75-c7ae-4d38-9145-274b1c6255d7_2286x1524.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1661911,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/194283324?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48de9f75-c7ae-4d38-9145-274b1c6255d7_2286x1524.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!blqF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48de9f75-c7ae-4d38-9145-274b1c6255d7_2286x1524.png 424w, https://substackcdn.com/image/fetch/$s_!blqF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48de9f75-c7ae-4d38-9145-274b1c6255d7_2286x1524.png 848w, https://substackcdn.com/image/fetch/$s_!blqF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48de9f75-c7ae-4d38-9145-274b1c6255d7_2286x1524.png 1272w, https://substackcdn.com/image/fetch/$s_!blqF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48de9f75-c7ae-4d38-9145-274b1c6255d7_2286x1524.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><br></p><h2>Setup Instructions</h2><p><strong>Install Required Packages:</strong></p><pre><code><code>pip install youtube-transcript-api langchain-google-genai</code></code></pre><p><strong>Same packages as Day 1</strong> - no new installations needed!</p><p><strong>Run the tool:</strong></p><pre><code><code>python solution.py</code></code></pre><p>Then paste any YouTube video URL such as <a href="https://www.youtube.com/watch?v=rNxC16mlO60">this one</a> and get chapters, insights, and action items!</p><h2>Understanding Timestamped Chapters</h2><p><strong>How we generate chapters:</strong></p><p>The AI analyzes the full transcript and identifies natural section breaks based on topic changes. Then it creates timestamps in YouTube&#8217;s format: <code>[HH:MM:SS]</code> or <code>[MM:SS]</code>.</p><p></p><p><strong>Our prompt strategy:</strong></p><pre><code><code>prompt = f"""Analyze this YouTube video transcript and provide:

1. CHAPTERS: Break the video into 6-10 logical sections with timestamps
   Format each as: [MM:SS] Chapter Title - Brief description
   
2. KEY INSIGHTS: Extract 3-5 most important quotes from the video
   Include the chapter/timestamp where each quote appears
   
3. ACTION ITEMS: List 3-7 concrete takeaways viewers can implement

Transcript with timestamps:
{transcript_with_times}
"""
</code></code></pre><p><strong>Timestamp formatting:</strong></p><pre><code><code>def format_timestamp(seconds):
    hours = int(seconds // 3600)
    minutes = int((seconds % 3600) // 60)
    secs = int(seconds % 60)
    
    if hours &gt; 0:
        return f"[{hours}:{minutes:02d}:{secs:02d}]"
    else:
        return f"[{minutes:02d}:{secs:02d}]"
</code></code></pre><p><strong>Why include timestamps in the transcript:</strong></p><p>We send the transcript to AI with timestamp markers so it knows <em>when</em> topics are discussed. This allows accurate chapter generation!</p><h2>Understanding Key Insights vs Action Items</h2><p><strong>Key Insights</strong> = The most important <em>ideas</em> from the video</p><ul><li><p>Quotes and concepts that are memorable</p></li><li><p>Core arguments or findings</p></li><li><p>&#8220;Aha moments&#8221; viewers should remember</p></li></ul><p><strong>Action Items</strong> = The <em>concrete steps</em> viewers can take</p><ul><li><p>Specific things to do/build/implement</p></li><li><p>Practical applications of the concepts</p></li><li><p>Next steps for viewers</p><p></p></li></ul><h2>Coming Tomorrow</h2><p>Tomorrow we&#8217;re building a <strong>beautiful web app</strong> with Streamlit where anyone can paste YouTube URLs, get instant summaries with chapters, and navigate long videos effortlessly!</p><h2>View Code Evolution</h2><p>Compare today&#8217;s solution with yesterday&#8217;s version and see how we added timestamp analysis and structured insights.</p>
      <p>
          <a href="https://dailypythonprojects.substack.com/p/build-a-youtube-video-summarizer-4e4">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Build a YouTube Video Summarizer - Day 1: YouTube Video Summarizer]]></title><description><![CDATA[Build a YouTube video summarizer using Python connected to an AI model.]]></description><link>https://dailypythonprojects.substack.com/p/build-a-youtube-video-summarizer</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/build-a-youtube-video-summarizer</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Tue, 14 Apr 2026 18:18:59 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/967ba850-e344-4583-8461-ed35206527c8_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build an AI-powered YouTube video summarizer that extracts transcripts and generates intelligent summaries using LangChain and Google&#8217;s Gemini AI.</p><p><strong>Why build this?</strong> Because we waste hours watching long YouTube videos. Lectures, tutorials, podcasts, conferences &#8212; what if you could get the key points in 30 seconds instead of watching for 2 hours?</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://dailypythonprojects.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Daily Python Projects is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><strong>What you&#8217;ll learn:</strong> This series teaches you how to extract YouTube transcripts, process video content with AI, generate structured summaries, and build tools that save people massive amounts of time. These skills apply to any content processing project.</p><p><strong>Why users love this:</strong> No more scrubbing through long videos looking for that one thing. Paste a YouTube URL, get an instant summary with all the key points. Perfect for students, researchers, and busy professionals!</p><ul><li><p><strong>Day 1:</strong> YouTube Video Summarizer <strong>(Today)</strong></p></li><li><p><strong>Day 2:</strong> Timestamped Chapters &amp; Key Insights</p></li><li><p><strong>Day 3:</strong> YouTube Summarizer Web App</p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-13">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p>We&#8217;re starting with the foundation: a command-line tool that takes any YouTube URL, extracts the transcript, and uses Gemini AI to generate a concise, intelligent summary!</p><p>You&#8217;ll learn how to extract YouTube transcripts programmatically, process long-form content, and use AI to distill hours of video into digestible summaries!</p><h2>Project Task</h2><p>Create a YouTube video summarizer that:</p><ul><li><p>Accepts any YouTube video URL</p></li><li><p>Extracts the full video transcript automatically</p></li><li><p>Gets video metadata (title, duration, channel)</p></li><li><p>Uses LangChain + Gemini to generate summaries</p></li><li><p>Creates concise, readable summaries</p></li><li><p>Handles videos of any length</p></li><li><p>Works from command line</p></li><li><p>No API keys needed for YouTube (uses youtube-transcript-api)</p></li></ul><p>This project gives you hands-on practice with YouTube data extraction, transcript processing, AI summarization, content analysis, and building practical time-saving tools &#8212; essential skills for modern content automation!</p><h2>Expected Output</h2><p>You can run the script with &#8220;python solution.py&#8221; paste a YouTube video URL there such as this &#8220;<a href="https://www.youtube.com/watch?v=YiLkudRIqLg">How to stop thinking</a>&#8221; and the program will print out a summary of the video in the terminal as you can see below:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lXIM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F74c72f53-7f8e-4332-b115-32e7ed4e2ad8_2256x1504.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lXIM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F74c72f53-7f8e-4332-b115-32e7ed4e2ad8_2256x1504.png 424w, https://substackcdn.com/image/fetch/$s_!lXIM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F74c72f53-7f8e-4332-b115-32e7ed4e2ad8_2256x1504.png 848w, https://substackcdn.com/image/fetch/$s_!lXIM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F74c72f53-7f8e-4332-b115-32e7ed4e2ad8_2256x1504.png 1272w, https://substackcdn.com/image/fetch/$s_!lXIM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F74c72f53-7f8e-4332-b115-32e7ed4e2ad8_2256x1504.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lXIM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F74c72f53-7f8e-4332-b115-32e7ed4e2ad8_2256x1504.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/74c72f53-7f8e-4332-b115-32e7ed4e2ad8_2256x1504.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1517609,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/194213045?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F74c72f53-7f8e-4332-b115-32e7ed4e2ad8_2256x1504.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lXIM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F74c72f53-7f8e-4332-b115-32e7ed4e2ad8_2256x1504.png 424w, https://substackcdn.com/image/fetch/$s_!lXIM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F74c72f53-7f8e-4332-b115-32e7ed4e2ad8_2256x1504.png 848w, https://substackcdn.com/image/fetch/$s_!lXIM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F74c72f53-7f8e-4332-b115-32e7ed4e2ad8_2256x1504.png 1272w, https://substackcdn.com/image/fetch/$s_!lXIM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F74c72f53-7f8e-4332-b115-32e7ed4e2ad8_2256x1504.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>Setup Instructions</h2><p><strong>Install Required Packages:</strong></p><pre><code><code>pip install youtube-transcript-api langchain-google-genai</code></code></pre><p><strong>Get Your Google API Key:</strong></p><ol><li><p>Go to <a href="https://aistudio.google.com/app/apikey">Google AI Studio</a></p></li><li><p>Click &#8220;Create API Key&#8221;</p></li><li><p>Copy your key</p></li><li><p>Paste it in the script where it says <code>YOUR_GOOGLE_API_KEY</code></p></li></ol><p><strong>That&#8217;s it!</strong> The YouTube transcript API is completely free and requires no API key.</p><p><strong>What each package does:</strong></p><ul><li><p><code>youtube-transcript-api</code> - Extracts video transcripts from YouTube (FREE!)</p></li><li><p><code>langchain-google-genai</code> - Google Gemini integration for summaries</p></li></ul><p><strong>Run the tool:</strong></p><pre><code><code>python solution.py</code></code></pre><p>Then paste any YouTube URL and get an instant summary!</p><h2>Understanding YouTube Transcript API</h2><p><strong>How it works:</strong></p><p>The <code>youtube-transcript-api</code> library fetches the official YouTube transcript/captions. It doesn&#8217;t use any API keys &#8212; it just accesses the same captions you see when you click &#8220;CC&#8221; on a video.</p><pre><code><code>from youtube_transcript_api import YouTubeTranscriptApi

# Extract video ID from URL
video_id = "dQw4w9WgXcQ"

# Get transcript
transcript = YouTubeTranscriptApi.get_transcript(video_id)

# Transcript format:
# [
#   {'text': 'Hello everyone', 'start': 0.0, 'duration': 2.5},
#   {'text': 'Welcome to my video', 'start': 2.5, 'duration': 3.0},
#   ...
# ]
</code></code></pre><p><strong>Key features:</strong></p><p>&#9989; <strong>Free</strong> - No API key required<br>&#9989; <strong>Fast</strong> - Instant transcript retrieval<br>&#9989; <strong>Accurate</strong> - Uses official YouTube captions<br>&#9989; <strong>Multiple languages</strong> - Supports auto-translated captions<br>&#9989; <strong>No rate limits</strong> - Reasonable usage is unlimited</p><p><strong>Extracting video ID from URL:</strong></p><pre><code><code>from urllib.parse import urlparse, parse_qs

def get_video_id(url):
    # Handle different YouTube URL formats
    # youtube.com/watch?v=VIDEO_ID
    # youtu.be/VIDEO_ID
    # youtube.com/embed/VIDEO_ID
    
    if 'youtu.be' in url:
        return url.split('/')[-1].split('?')[0]
    elif 'youtube.com' in url:
        parsed = urlparse(url)
        return parse_qs(parsed.query).get('v', [None])[0]
</code></code></pre><p><strong>Limitations:</strong></p><ul><li><p>Only works if the video has captions (auto-generated or manual)</p></li><li><p>Can&#8217;t access age-restricted or private videos</p></li><li><p>Very long videos (5+ hours) may be slow to process</p></li></ul><h2>Understanding the Summarization Approach</h2><p><strong>Our strategy:</strong></p><pre><code><code>YouTube URL &#8594; Extract video ID &#8594; Get transcript &#8594; Format as text
                                                        &#8595;
                                            Send to Gemini with prompt
                                                        &#8595;
                                Generate structured summary
</code></code></pre><p><strong>Smart prompting for good summaries:</strong></p><pre><code><code>prompt = f"""Summarize this YouTube video transcript in a clear, 
structured format.

Video Title: {title}
Duration: {duration}

Transcript:
{transcript_text}

Provide:
1. A concise 2-3 paragraph summary
2. Key topics covered (bullet points)
3. Main takeaways (numbered list)
4. Whether the video is worth watching and for whom

Keep it concise but informative."""
</code></code></pre><p><strong>Why this approach works:</strong></p><p><strong>Context first</strong> - Title and duration help AI understand the video<br><strong>Structured output</strong> - Specific sections make it scannable<br><strong>Actionable</strong> - &#8220;Worth watching?&#8221; helps users decide<br><strong>Concise</strong> - Summaries are quick to read</p><h2>Coming Tomorrow</h2><p>Tomorrow we&#8217;ll add <strong>timestamped chapters and key insights</strong> &#8212; break the video into sections with timestamps, extract the most important quotes, and identify action items you can implement immediately!</p><h2>Skeleton and Solution</h2><p>Below you will find both a downloadable skeleton.py file to help you code the project with comment guides and the downloadable solution.py file containing the correct solution.</p><p>Get the code skeleton here:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://share.pythonanywhere.com/view/KD2b8w5v1-81uHENwH0gEg&quot;,&quot;text&quot;:&quot;View Code Skeleton&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://share.pythonanywhere.com/view/KD2b8w5v1-81uHENwH0gEg"><span>View Code Skeleton</span></a></p><p></p><p>Get the code solution here:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://share.pythonanywhere.com/evolution/K9qB5LrigCgQmvMq7Yau8g&quot;,&quot;text&quot;:&quot;View Code Solution&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://share.pythonanywhere.com/evolution/K9qB5LrigCgQmvMq7Yau8g"><span>View Code Solution</span></a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://dailypythonprojects.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Daily Python Projects is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Build an AI PDF Analyzer - Day 3: PDF Analyzer Web App]]></title><description><![CDATA[We build a web GUI so everyone can upload PDFs and ask questions to the AI on their PDFs through the web app.]]></description><link>https://dailypythonprojects.substack.com/p/build-an-ai-pdf-analyzer-day-3-pdf</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/build-an-ai-pdf-analyzer-day-3-pdf</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Fri, 10 Apr 2026 12:35:54 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d4ae9fa8-fa71-4f87-9756-24562581e379_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build a smart PDF analyzer powered by LangChain and Google&#8217;s Gemini AI that lets you upload PDFs and ask questions about them.</p><ul><li><p><strong>Day 1:</strong> PDF Question Answering</p></li><li><p><strong>Day 2:</strong> Multi-PDF Comparison</p></li><li><p><strong>Day 3:</strong> PDF Analyzer Web App <strong>(Today)</strong></p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-12">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p><strong>Welcome to the finale!</strong> We&#8217;ve built command-line tools for single and multiple PDF analysis. Today we&#8217;re creating a <strong>beautiful web app</strong> that anyone can use &#8212; no terminal, no Python knowledge required!</p><p>Upload PDFs through your browser, ask questions in a chat interface, and get instant AI-powered answers. A production-ready app you can deploy and share!</p><h2>Project Task</h2><p>Create a PDF analyzer web app that:</p><ul><li><p>Beautiful web interface built with Streamlit</p></li><li><p>Drag-and-drop PDF upload</p></li><li><p>Upload single or multiple PDFs</p></li><li><p>Clean chat interface for asking questions</p></li><li><p>Displays conversation history</p></li><li><p>Shows which PDFs are loaded</p></li><li><p>Real-time AI responses</p></li><li><p>No command-line needed</p></li><li><p>One-click deployment ready</p></li></ul><p>This project gives you hands-on practice with Streamlit, web app development, file uploads, session state management, chat interfaces, and building production-ready AI applications &#8212; essential skills for deploying AI tools!</p><h2>Expected Output</h2><p><strong>Running the app:</strong></p><pre><code><code>streamlit run solution.py</code></code></pre><p><strong>Browser opens automatically showing:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cTJK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33371c85-86c8-4ec4-bb7f-a9694653fd71_2190x1460.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cTJK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33371c85-86c8-4ec4-bb7f-a9694653fd71_2190x1460.png 424w, https://substackcdn.com/image/fetch/$s_!cTJK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33371c85-86c8-4ec4-bb7f-a9694653fd71_2190x1460.png 848w, https://substackcdn.com/image/fetch/$s_!cTJK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33371c85-86c8-4ec4-bb7f-a9694653fd71_2190x1460.png 1272w, https://substackcdn.com/image/fetch/$s_!cTJK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33371c85-86c8-4ec4-bb7f-a9694653fd71_2190x1460.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cTJK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33371c85-86c8-4ec4-bb7f-a9694653fd71_2190x1460.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/33371c85-86c8-4ec4-bb7f-a9694653fd71_2190x1460.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:406250,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/193788359?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33371c85-86c8-4ec4-bb7f-a9694653fd71_2190x1460.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cTJK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33371c85-86c8-4ec4-bb7f-a9694653fd71_2190x1460.png 424w, https://substackcdn.com/image/fetch/$s_!cTJK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33371c85-86c8-4ec4-bb7f-a9694653fd71_2190x1460.png 848w, https://substackcdn.com/image/fetch/$s_!cTJK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33371c85-86c8-4ec4-bb7f-a9694653fd71_2190x1460.png 1272w, https://substackcdn.com/image/fetch/$s_!cTJK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33371c85-86c8-4ec4-bb7f-a9694653fd71_2190x1460.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The user can click the &#8220;Browser files&#8221; button to upload PDF files. Then, they can chat with their PDFs:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vgnt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb6f163-8b36-4657-a387-a47c5e2607ae_2058x1372.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vgnt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb6f163-8b36-4657-a387-a47c5e2607ae_2058x1372.png 424w, https://substackcdn.com/image/fetch/$s_!vgnt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb6f163-8b36-4657-a387-a47c5e2607ae_2058x1372.png 848w, https://substackcdn.com/image/fetch/$s_!vgnt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb6f163-8b36-4657-a387-a47c5e2607ae_2058x1372.png 1272w, https://substackcdn.com/image/fetch/$s_!vgnt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb6f163-8b36-4657-a387-a47c5e2607ae_2058x1372.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vgnt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb6f163-8b36-4657-a387-a47c5e2607ae_2058x1372.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/edb6f163-8b36-4657-a387-a47c5e2607ae_2058x1372.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:471694,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/193788359?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb6f163-8b36-4657-a387-a47c5e2607ae_2058x1372.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vgnt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb6f163-8b36-4657-a387-a47c5e2607ae_2058x1372.png 424w, https://substackcdn.com/image/fetch/$s_!vgnt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb6f163-8b36-4657-a387-a47c5e2607ae_2058x1372.png 848w, https://substackcdn.com/image/fetch/$s_!vgnt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb6f163-8b36-4657-a387-a47c5e2607ae_2058x1372.png 1272w, https://substackcdn.com/image/fetch/$s_!vgnt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb6f163-8b36-4657-a387-a47c5e2607ae_2058x1372.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>As you can see above, we ask a question that needs statistical analysis performed on the data contained inside the PDFs. And the AI gives a very good answer pointing out students with the highest grade, highlighting the document where that student data is located.</p><p>If you want to try this, you can use the same PDFs that I used:</p><div class="file-embed-wrapper" data-component-name="FileToDOM"><div class="file-embed-container-reader"><div class="file-embed-container-top"><image class="file-embed-thumbnail-default" src="https://substackcdn.com/image/fetch/$s_!0Cy0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack.com%2Fimg%2Fattachment_icon.svg"></image><div class="file-embed-details"><div class="file-embed-details-h1">Report</div><div class="file-embed-details-h2">7.95KB &#8729; PDF file</div></div><a class="file-embed-button wide" href="https://dailypythonprojects.substack.com/api/v1/file/a05548f5-f8e7-4f47-adf6-c05e608d266d.pdf"><span class="file-embed-button-text">Download</span></a></div><a class="file-embed-button narrow" href="https://dailypythonprojects.substack.com/api/v1/file/a05548f5-f8e7-4f47-adf6-c05e608d266d.pdf"><span class="file-embed-button-text">Download</span></a></div></div><div class="file-embed-wrapper" data-component-name="FileToDOM"><div class="file-embed-container-reader"><div class="file-embed-container-top"><image class="file-embed-thumbnail-default" src="https://substackcdn.com/image/fetch/$s_!0Cy0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack.com%2Fimg%2Fattachment_icon.svg"></image><div class="file-embed-details"><div class="file-embed-details-h1">Report2</div><div class="file-embed-details-h2">8.06KB &#8729; PDF file</div></div><a class="file-embed-button wide" href="https://dailypythonprojects.substack.com/api/v1/file/c74c1289-8e48-4b9e-bcae-5463eee2b44c.pdf"><span class="file-embed-button-text">Download</span></a></div><a class="file-embed-button narrow" href="https://dailypythonprojects.substack.com/api/v1/file/c74c1289-8e48-4b9e-bcae-5463eee2b44c.pdf"><span class="file-embed-button-text">Download</span></a></div></div><p></p><h2>Setup Instructions</h2><p><strong>Install Required Packages:</strong></p><pre><code><code>pip install streamlit langchain-google-genai pypdf</code></code></pre><p><strong>Get Your Google API Key:</strong></p><ol><li><p>Go to <a href="https://aistudio.google.com/app/apikey">Google AI Studio</a></p></li><li><p>Click &#8220;Create API Key&#8221;</p></li><li><p>Copy your key</p></li><li><p>Paste it in the script where it says <code>YOUR_GOOGLE_API_KEY</code></p></li></ol><p><strong>Run the web app:</strong></p><pre><code><code>streamlit run solution.py</code></code></pre><p>Your browser will automatically open to </p><p>http://localhost:8501</p><p><strong>Deploy to Streamlit Cloud (Optional):</strong></p><ol><li><p>Push your code to GitHub</p></li><li><p>Go to <a href="https://share.streamlit.io/">share.streamlit.io</a></p></li><li><p>Connect your repo</p></li><li><p>Add your Google API key in Secrets</p></li><li><p>Deploy! Your app is now live and shareable</p></li></ol><h2>Understanding Streamlit</h2><p><strong>What is Streamlit?</strong></p><p>Streamlit is a Python framework for building web apps with zero HTML/CSS/JavaScript. Perfect for data scientists and AI developers!</p><p><strong>Key Streamlit concepts:</strong></p><pre><code><code>import streamlit as st

# Title
st.title("My App")

# File uploader
uploaded_files = st.file_uploader("Upload PDFs", type="pdf", accept_multiple_files=True)

# Chat messages
with st.chat_message("user"):
    st.write("Question here")

with st.chat_message("assistant"):
    st.write("Answer here")

# Text input
question = st.chat_input("Ask a question")

# Session state (persists data between reruns)
if 'messages' not in st.session_state:
    st.session_state.messages = []
</code></code></pre><p><strong>How our app works:</strong></p><pre><code><code>User uploads PDFs &#8594; Extract text &#8594; Store in session_state
                                            &#8595;
User asks question &#8594; Add to messages &#8594; Send to Gemini &#8594; Display answer
                                            &#8595;
                                   Add to session_state
                                            &#8595;
                                    Show conversation history
</code></code></pre><p><strong>Session state management:</strong></p><p>Streamlit reruns your entire script on every interaction. Session state persists data:</p><pre><code><code># Initialize
if 'pdf_content' not in st.session_state:
    st.session_state.pdf_content = ""
    st.session_state.messages = []
    st.session_state.pdf_names = []

# Store PDFs
st.session_state.pdf_content = combined_text
st.session_state.pdf_names = filenames

# Messages persist across reruns
st.session_state.messages.append({"role": "user", "content": question})
st.session_state.messages.append({"role": "assistant", "content": answer})
</code></code></pre><p><strong>Why Streamlit for AI apps?</strong></p><p>&#9989; <strong>Fast development</strong> - Build UIs in pure Python<br>&#9989; <strong>No frontend skills needed</strong> - No HTML/CSS/JS<br>&#9989; <strong>Perfect for AI/ML</strong> - Built-in support for chat, file uploads, charts<br>&#9989; <strong>Free deployment</strong> - Streamlit Cloud hosting<br>&#9989; <strong>Auto-reload</strong> - See changes instantly<br>&#9989; <strong>Beautiful by default</strong> - Professional-looking UIs</p><h2>What You&#8217;ve Accomplished This Week</h2><p>&#127881; <strong>Congratulations!</strong> You&#8217;ve built a complete PDF analysis system and learned:</p><ul><li><p><strong>Day 1:</strong> Single PDF Q&amp;A with LangChain + Gemini</p></li><li><p><strong>Day 2:</strong> Multi-document comparison and analysis</p></li><li><p><strong>Day 3:</strong> Production web app with Streamlit</p></li></ul><p>You now have a <strong>production-ready AI application</strong> that: &#9989; Analyzes single or multiple PDFs<br>&#9989; Answers questions with AI-powered accuracy<br>&#9989; Compares and contrasts multiple documents<br>&#9989; Beautiful web interface anyone can use<br>&#9989; Maintains conversation history<br>&#9989; Ready to deploy and share</p><p>This is a <strong>complete AI document assistant</strong> &#8212; from PDF upload to intelligent answers!</p><h2>View Code Evolution</h2><p>Compare today&#8217;s solution with earlier versions and see how we evolved from command-line tools to a full-featured web application.</p>
      <p>
          <a href="https://dailypythonprojects.substack.com/p/build-an-ai-pdf-analyzer-day-3-pdf">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Build an AI PDF Analyzer - Day 2: Multi-PDF Comparison]]></title><description><![CDATA[This program lets users ask questions related to multiple PDFs, synthesizing and comparing data between them.]]></description><link>https://dailypythonprojects.substack.com/p/build-an-ai-pdf-analyzer-day-2-multi</link><guid isPermaLink="false">https://dailypythonprojects.substack.com/p/build-an-ai-pdf-analyzer-day-2-multi</guid><dc:creator><![CDATA[Ardit Sulce]]></dc:creator><pubDate>Thu, 09 Apr 2026 11:27:06 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/6cce3bd5-8279-4f3c-9686-9f22a1144daa_1160x785.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Projects in this week&#8217;s series:</h2><p>This week, we build a smart PDF analyzer powered by LangChain and Google&#8217;s Gemini AI that lets you upload PDFs and ask questions about them.</p><ul><li><p><strong>Day 1:</strong> PDF Question Answering</p></li><li><p><strong>Day 2:</strong> Multi-PDF Comparison <strong>(Today)</strong></p></li><li><p><strong>Day 3:</strong> PDF Analyzer Web App</p></li></ul><p><a href="https://dailypythonprojects.substack.com/t/week-12">View All Projects This Week</a></p><h2>Today&#8217;s Project</h2><p>Yesterday we built a tool to ask questions about a single PDF. Today we&#8217;re <strong>expanding to multiple PDFs</strong> &#8212; upload several documents and ask comparative questions across all of them!</p><p>Compare resumes, analyze multiple research papers, find differences in contracts, or synthesize information from multiple sources!</p><h2>Project Task</h2><p>Create a multi-PDF comparison tool that:</p><ul><li><p>Loads multiple PDF files from a directory</p></li><li><p>Extracts text from all documents</p></li><li><p>Creates context with all PDFs combined</p></li><li><p>Answers questions across all documents</p></li><li><p>Compares and contrasts information</p></li><li><p>Identifies which document contains specific information</p></li><li><p>Maintains conversation history</p></li><li><p>Works entirely from command line</p></li></ul><p>This project gives you hands-on practice with batch file processing, multi-document analysis, comparative AI reasoning, and building practical document comparison tools &#8212; essential skills for real-world AI applications!</p><h2>Expected Output</h2><p>When the user runs the program, they are asked to enter the directory where the PDF files are located (e.g., <em>documents</em>):</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!H0EO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f72cb14-e8b5-4e24-a606-23568b874c19_1698x1132.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!H0EO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f72cb14-e8b5-4e24-a606-23568b874c19_1698x1132.png 424w, https://substackcdn.com/image/fetch/$s_!H0EO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f72cb14-e8b5-4e24-a606-23568b874c19_1698x1132.png 848w, https://substackcdn.com/image/fetch/$s_!H0EO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f72cb14-e8b5-4e24-a606-23568b874c19_1698x1132.png 1272w, https://substackcdn.com/image/fetch/$s_!H0EO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f72cb14-e8b5-4e24-a606-23568b874c19_1698x1132.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!H0EO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f72cb14-e8b5-4e24-a606-23568b874c19_1698x1132.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7f72cb14-e8b5-4e24-a606-23568b874c19_1698x1132.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:661650,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/193676207?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f72cb14-e8b5-4e24-a606-23568b874c19_1698x1132.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!H0EO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f72cb14-e8b5-4e24-a606-23568b874c19_1698x1132.png 424w, https://substackcdn.com/image/fetch/$s_!H0EO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f72cb14-e8b5-4e24-a606-23568b874c19_1698x1132.png 848w, https://substackcdn.com/image/fetch/$s_!H0EO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f72cb14-e8b5-4e24-a606-23568b874c19_1698x1132.png 1272w, https://substackcdn.com/image/fetch/$s_!H0EO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f72cb14-e8b5-4e24-a606-23568b874c19_1698x1132.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In the next step, the user asks a question about the information contained in the PDF files. In our example, we have some university student data in the PDFs and some students are in both PDFs so below we ask the program to give us the student names that are in both documents:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!t2NB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a475c47-8a57-4f96-b07b-671083aa0f4c_2394x1596.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!t2NB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a475c47-8a57-4f96-b07b-671083aa0f4c_2394x1596.png 424w, https://substackcdn.com/image/fetch/$s_!t2NB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a475c47-8a57-4f96-b07b-671083aa0f4c_2394x1596.png 848w, https://substackcdn.com/image/fetch/$s_!t2NB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a475c47-8a57-4f96-b07b-671083aa0f4c_2394x1596.png 1272w, https://substackcdn.com/image/fetch/$s_!t2NB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a475c47-8a57-4f96-b07b-671083aa0f4c_2394x1596.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!t2NB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a475c47-8a57-4f96-b07b-671083aa0f4c_2394x1596.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7a475c47-8a57-4f96-b07b-671083aa0f4c_2394x1596.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1394722,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dailypythonprojects.substack.com/i/193676207?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a475c47-8a57-4f96-b07b-671083aa0f4c_2394x1596.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!t2NB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a475c47-8a57-4f96-b07b-671083aa0f4c_2394x1596.png 424w, https://substackcdn.com/image/fetch/$s_!t2NB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a475c47-8a57-4f96-b07b-671083aa0f4c_2394x1596.png 848w, https://substackcdn.com/image/fetch/$s_!t2NB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a475c47-8a57-4f96-b07b-671083aa0f4c_2394x1596.png 1272w, https://substackcdn.com/image/fetch/$s_!t2NB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a475c47-8a57-4f96-b07b-671083aa0f4c_2394x1596.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>As you can see the program is aware of the PDF file names and gives us the list of shared student names in both documents, and also highlighting some potential issues with the data integrity in the documents which is great information to have.</p><p>Here is the PDF files we used for this example:</p><div class="file-embed-wrapper" data-component-name="FileToDOM"><div class="file-embed-container-reader"><div class="file-embed-container-top"><image class="file-embed-thumbnail-default" src="https://substackcdn.com/image/fetch/$s_!0Cy0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack.com%2Fimg%2Fattachment_icon.svg"></image><div class="file-embed-details"><div class="file-embed-details-h1">Report</div><div class="file-embed-details-h2">7.95KB &#8729; PDF file</div></div><a class="file-embed-button wide" href="https://dailypythonprojects.substack.com/api/v1/file/fc0f298b-0cf6-47c6-bbab-9c8fe8ea579a.pdf"><span class="file-embed-button-text">Download</span></a></div><a class="file-embed-button narrow" href="https://dailypythonprojects.substack.com/api/v1/file/fc0f298b-0cf6-47c6-bbab-9c8fe8ea579a.pdf"><span class="file-embed-button-text">Download</span></a></div></div><div class="file-embed-wrapper" data-component-name="FileToDOM"><div class="file-embed-container-reader"><div class="file-embed-container-top"><image class="file-embed-thumbnail-default" src="https://substackcdn.com/image/fetch/$s_!0Cy0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack.com%2Fimg%2Fattachment_icon.svg"></image><div class="file-embed-details"><div class="file-embed-details-h1">Report2</div><div class="file-embed-details-h2">8.06KB &#8729; PDF file</div></div><a class="file-embed-button wide" href="https://dailypythonprojects.substack.com/api/v1/file/9c30dbf8-01ce-40bd-933e-a34fa6143f61.pdf"><span class="file-embed-button-text">Download</span></a></div><a class="file-embed-button narrow" href="https://dailypythonprojects.substack.com/api/v1/file/9c30dbf8-01ce-40bd-933e-a34fa6143f61.pdf"><span class="file-embed-button-text">Download</span></a></div></div><p></p><h2>Setup Instructions</h2><p><strong>Install Required Packages:</strong></p><pre><code><code>pip install langchain-google-genai pypdf</code></code></pre><p><strong>Get Your Google API Key:</strong></p><ol><li><p>Go to <a href="https://aistudio.google.com/app/apikey">Google AI Studio</a></p></li><li><p>Click &#8220;Create API Key&#8221;</p></li><li><p>Copy your key</p></li><li><p>Paste it in the script where it says <code>YOUR_GOOGLE_API_KEY</code></p></li></ol><p><strong>Prepare Your PDFs:</strong></p><p>Create a directory with multiple PDFs:</p><pre><code><code>./documents/
&#9500;&#9472;&#9472; report.pdf
&#9500;&#9472;&#9472; report2.pdf
</code></code></pre><p><strong>Run the tool:</strong></p><pre><code><code>python solution.py</code></code></pre><p>Enter the directory path and start comparing!</p><h2>Understanding Multi-Document Analysis</h2><p><strong>How it works:</strong></p><pre><code><code>Directory &#8594; Load all PDFs &#8594; Extract text from each &#8594; Combine into context
                                                              &#8595;
                                                    Single system prompt with all docs
                                                              &#8595;
Your question &#8594; "Which document mentions X?" &#8594; AI compares all documents &#8594; Answer
</code></code></pre><p><strong>Key technique: Document labeling</strong></p><pre><code><code>context = ""
for pdf_file in pdf_files:
    content = extract_text(pdf_file)
    context += f"\n\n=== {pdf_file.name} ===\n{content}\n"
</code></code></pre><p>This labels each document so the AI knows which content comes from which file!</p><p><strong>Multi-document capabilities:</strong></p><p><strong>Comparison</strong> - &#8220;Which resume is stronger?&#8221;<br><strong>Contrast</strong> - &#8220;What are the differences between these papers?&#8221;<br><strong>Synthesis</strong> - &#8220;What do all these contracts have in common?&#8221;<br><strong>Attribution</strong> - &#8220;Which document mentions the refund policy?&#8221;<br><strong>Ranking</strong> - &#8220;Order these candidates by experience level&#8221;</p><p><strong>Use cases:</strong></p><ul><li><p><strong>HR:</strong> Compare job applicant resumes</p></li><li><p><strong>Research:</strong> Analyze multiple papers on the same topic</p></li><li><p><strong>Legal:</strong> Compare contract versions or terms</p></li><li><p><strong>Business:</strong> Analyze competitor reports</p></li><li><p><strong>Academic:</strong> Compare different textbook chapters</p></li></ul><h2>Coming Tomorrow</h2><p>Tomorrow we&#8217;re building a <strong>web app</strong> with Flask/Streamlit that lets anyone upload PDFs through a browser, ask questions, and get instant answers &#8212; no command line needed!</p><h2>View Code Evolution</h2><p>Compare today&#8217;s solution with earlier versions and see how we evolved from single-PDF analysis to multi-document comparison:</p>
      <p>
          <a href="https://dailypythonprojects.substack.com/p/build-an-ai-pdf-analyzer-day-2-multi">
              Read more
          </a>
      </p>
   ]]></content:encoded></item></channel></rss>