<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jesse Casman</title>
    <description>The latest articles on Forem by Jesse Casman (@jcasman).</description>
    <link>https://forem.com/jcasman</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F215158%2F71cbe307-702a-4bc4-a6a0-cdffcbcde805.jpeg</url>
      <title>Forem: Jesse Casman</title>
      <link>https://forem.com/jcasman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jcasman"/>
    <language>en</language>
    <item>
      <title>HowTo: Start a Python Project with `uv` and Build Your First Flet UI App</title>
      <dc:creator>Jesse Casman</dc:creator>
      <pubDate>Sun, 08 Mar 2026 22:51:00 +0000</pubDate>
      <link>https://forem.com/jcasman/howto-start-a-python-project-with-uv-and-build-your-first-flet-ui-app-ch0</link>
      <guid>https://forem.com/jcasman/howto-start-a-python-project-with-uv-and-build-your-first-flet-ui-app-ch0</guid>
      <description>&lt;p&gt;If you want to learn Python by building practical, portfolio-friendly projects, not just isolated coding exercises, check out the free course &lt;a href="https://industry-python.thinkific.com/products/courses/industry-projects-with-python" rel="noopener noreferrer"&gt;Industry Projects for Python&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this tutorial, from Chapter 1, you will use &lt;code&gt;uv&lt;/code&gt; to create a new Python project, install Flet, and run your first simple desktop UI app. &lt;/p&gt;

&lt;p&gt;If you are a computer science student or early-stage developer, this is a strong pattern to learn because it combines three useful ideas at once: modern Python tooling, clean project setup, and a visible app you can actually show someone.&lt;/p&gt;

&lt;p&gt;A lot of newer Python developers start with &lt;code&gt;pip&lt;/code&gt;, and that makes sense. &lt;code&gt;pip&lt;/code&gt; is the standard installer, it is everywhere, and you need to know it. But once you start building actual projects, especially projects you may want to demo, maintain, or share with other people, a more structured workflow helps. That is where &lt;code&gt;uv&lt;/code&gt; stands out. Instead of just installing packages into the void, &lt;code&gt;uv&lt;/code&gt; helps you create a real project from the start, with an isolated environment and dependency tracking built in. &lt;/p&gt;

&lt;p&gt;It also reinforces one of the most important Python habits early: project dependencies belong to the project, not to your whole machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why learn &lt;code&gt;uv&lt;/code&gt; if &lt;code&gt;pip&lt;/code&gt; already exists?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;pip&lt;/code&gt; works. It is still a core part of the Python ecosystem. But in practice, a beginner using &lt;code&gt;pip&lt;/code&gt; often ends up piecing together several separate commands and concepts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install &lt;/span&gt;flet
pip freeze &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That approach is valid, but it can feel fragmented. You are manually creating the environment, manually activating it, manually installing packages, and often manually managing dependency output.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;uv&lt;/code&gt;, the workflow is more unified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv init &lt;span class="nt"&gt;--python&lt;/span&gt; 3.13 list-comp
&lt;span class="nb"&gt;cd &lt;/span&gt;list-comp
uv add &lt;span class="s2"&gt;"flet[all]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is one of the first places where &lt;code&gt;uv&lt;/code&gt; feels better than a looser &lt;code&gt;pip&lt;/code&gt; workflow. You are not just dropping packages somewhere into Python and hoping the setup stays clean. You are creating an actual project structure from the beginning. For students and junior developers, that is a better mental model.&lt;/p&gt;

&lt;p&gt;So this is not really a matter of "&lt;code&gt;uv&lt;/code&gt; good, &lt;code&gt;pip&lt;/code&gt; bad." It is more accurate to say that &lt;code&gt;pip&lt;/code&gt; is foundational, while &lt;code&gt;uv&lt;/code&gt; provides a smoother modern workflow for starting and managing projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Install &lt;code&gt;uv&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;First, install &lt;code&gt;uv&lt;/code&gt; from the command line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-LsSf&lt;/span&gt; https://astral.sh/uv/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, check whether your terminal can find it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;which uv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you do not see a result, your shell may not have picked up the updated PATH yet. On macOS, a quick fix is often to reload your shell profile instead of restarting your machine.&lt;/p&gt;

&lt;p&gt;Try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.profile
which uv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on your shell, one of these may be the right file instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zprofile
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bash_profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is simple: get &lt;code&gt;uv&lt;/code&gt; installed and confirm that your terminal recognizes it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Initialize a new Python project
&lt;/h2&gt;

&lt;p&gt;Now create a new project folder using Python 3.13:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv init &lt;span class="nt"&gt;--python&lt;/span&gt; 3.13 list-comp
&lt;span class="nb"&gt;cd &lt;/span&gt;list-comp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is already a better starting point than a loose install process. You are not just preparing to run Python code. You are establishing a project with structure, which is what you will need in team environments, internships, and real development work.&lt;/p&gt;

&lt;p&gt;At this stage, you should have a new directory with starter files inside it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Add Flet to the project
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is Flet?
&lt;/h3&gt;

&lt;p&gt;Flet is an open-source Python UI framework for building interactive web, desktop, and mobile apps using pure Python. No HTML/CSS/JavaScript required. It provides a set of UI “controls” based on Google’s Flutter widgets, so you can assemble modern interfaces—buttons, text, layouts, inputs—directly from Python code. &lt;/p&gt;

&lt;h3&gt;
  
  
  Why Flet?
&lt;/h3&gt;

&lt;p&gt;Flet lets you build modern-looking, real UI apps early—without having to first learn a separate frontend stack. You get the motivation and feedback loop of seeing your code on-screen immediately, while still learning Python fundamentals that transfer everywhere.&lt;/p&gt;

&lt;p&gt;Three key benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;One Python codebase that runs across platforms&lt;br&gt;
Flet is designed for web, desktop, and mobile from the same app code, so what you build isn’t “just a toy GUI” that only works on your laptop.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You stay in Python (no HTML/CSS/JavaScript required)&lt;br&gt;
You can build the entire app in Python, which keeps the learning curve focused on programming fundamentals instead of splitting attention across multiple languages and toolchains.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It teaches modern UI architecture (declarative + state)&lt;br&gt;
Flet 1.0 adds a declarative approach inspired by React/SwiftUI-style thinking, so you’re learning concepts that scale to larger apps and map cleanly to what you’ll see later in industry frameworks.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To install Flet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add &lt;span class="s2"&gt;"flet[all]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command adds Flet as a dependency and lets &lt;code&gt;uv&lt;/code&gt; manage the environment and dependency metadata for you.&lt;/p&gt;

&lt;p&gt;After that, inspect the directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a few important things, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;.venv&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;project metadata files&lt;/li&gt;
&lt;li&gt;a lock file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That matters because it gives you two advantages immediately:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Isolation&lt;/strong&gt; — your project has its own environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeatability&lt;/strong&gt; — your dependency setup is tracked in a way that is easier to reproduce later.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is a major reason to learn this workflow early. You are building habits that scale beyond toy examples.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Activate the virtual environment
&lt;/h2&gt;

&lt;p&gt;Even though &lt;code&gt;uv&lt;/code&gt; handles a lot for you, it is still useful to understand how activation works.&lt;/p&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After activation, your shell prompt will usually show that the virtual environment is active.&lt;/p&gt;

&lt;p&gt;This step reinforces an important Python habit: project dependencies belong to the project, not to your whole machine. That sounds small, but it prevents a lot of future confusion. It means your project stays self-contained, and you are less likely to break one project while working on another.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Open the project and look at &lt;code&gt;main.py&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Open the folder in your editor of choice. You might use Cursor, VS Code, or something else. Inside the project, look for &lt;code&gt;main.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You may find starter code there already. That is fine. But instead of leaving this as a basic script, we are going to turn it into a simple UI app with Flet.&lt;/p&gt;

&lt;p&gt;This is where the tutorial becomes more interesting. You are moving from Python setup into something visual and demonstrable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Replace the starter code with a simple Flet app
&lt;/h2&gt;

&lt;p&gt;Update &lt;code&gt;main.py&lt;/code&gt; so it contains this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;flet&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, I am Jesse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yellow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break that down.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;import flet as ft&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This imports Flet and gives it the alias &lt;code&gt;ft&lt;/code&gt;, which is common in Flet examples and keeps the code shorter.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;def main(page: ft.Page) -&amp;gt; None:&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This defines the main function for your app. The &lt;code&gt;page: ft.Page&lt;/code&gt; part is a type hint, which helps make the code easier to understand and can improve editor assistance. The &lt;code&gt;-&amp;gt; None&lt;/code&gt; means the function does not return a value.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;page.add(...)&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This adds UI components to the page. In this case, there is just one component: a large text label.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ft.app(main)&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This runs the Flet app using your &lt;code&gt;main&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;The structure is simple, but it introduces a core idea: instead of printing output in the terminal, you are building a UI by adding elements to a page.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Run the app
&lt;/h2&gt;

&lt;p&gt;Now run the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flet run main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is set up correctly, a desktop window should open and display your text.&lt;/p&gt;

&lt;p&gt;That is the payoff. You used modern Python tooling to create a project, installed a UI framework cleanly, and launched a working graphical app. Even though it is small, it is already more compelling than another “Hello, World” printed to a terminal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: Add an image asset
&lt;/h2&gt;

&lt;p&gt;Now let’s make the app slightly more visual.&lt;/p&gt;

&lt;p&gt;Create an &lt;code&gt;assets&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;assets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Place an image inside it, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assets/cory_front.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update &lt;code&gt;main.py&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;flet&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, I am Jesse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yellow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cory_front.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;assets_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assets&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the app again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flet run main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now see both the text and the image.&lt;/p&gt;

&lt;p&gt;This also introduces another practical concept: organizing project assets in a dedicated folder and explicitly telling the app where those assets live.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this teaches beyond the commands
&lt;/h2&gt;

&lt;p&gt;This tutorial is not just about getting one app window to open. It teaches a better development pattern.&lt;/p&gt;

&lt;p&gt;You learned how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;install a modern Python project tool&lt;/li&gt;
&lt;li&gt;create a structured project directory&lt;/li&gt;
&lt;li&gt;isolate dependencies in a virtual environment&lt;/li&gt;
&lt;li&gt;install and manage packages more cleanly than an ad hoc &lt;code&gt;pip&lt;/code&gt; workflow&lt;/li&gt;
&lt;li&gt;build and run a simple UI app with Flet&lt;/li&gt;
&lt;li&gt;add assets to the project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For beginners, that is a strong combination. Too many tutorials stay at the level of one-off terminal scripts. Those have value, but project-based workflows are what start to feel real. They also give you better material to talk about in interviews, on GitHub, and in portfolio discussions.&lt;/p&gt;

&lt;p&gt;If you want more step-by-step tutorials like this, plus larger real-world Python projects, check out the free course: &lt;strong&gt;&lt;a href="https://industry-python.thinkific.com/products/courses/industry-projects-with-python" rel="noopener noreferrer"&gt;Industry Projects for Python&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is built to help learners go beyond basic syntax and start building projects they can actually explain, show, and use as part of their job preparation.&lt;/p&gt;

</description>
      <category>python</category>
      <category>uv</category>
    </item>
    <item>
      <title>HowTo: Make a Pull Request on an Open Source Repo - Step by Step</title>
      <dc:creator>Jesse Casman</dc:creator>
      <pubDate>Mon, 02 Mar 2026 16:22:50 +0000</pubDate>
      <link>https://forem.com/jcasman/howto-pull-request-on-git-and-github-open-source-repo-step-by-step-3h5</link>
      <guid>https://forem.com/jcasman/howto-pull-request-on-git-and-github-open-source-repo-step-by-step-3h5</guid>
      <description>&lt;p&gt;If you're getting better at programming but still feel unsure about GitHub, learning how to make a pull request is one of the most useful practical skills you can build. It helps you contribute to team projects, understand real developer workflows, and show employers that you can work in a shared codebase, not just code alone. &lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/TzS5GLPHwxo"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;For many beginners, GitHub feels confusing because the terminology arrives all at once: &lt;em&gt;fork&lt;/em&gt;, &lt;em&gt;clone&lt;/em&gt;, &lt;em&gt;branch&lt;/em&gt;, &lt;em&gt;commit&lt;/em&gt;, &lt;em&gt;push&lt;/em&gt;, &lt;em&gt;pull request&lt;/em&gt;. The good news is that the workflow is not complicated once you understand the order of operations. A pull request is simply the review step at the end of a process: you copy the code to your machine, make changes on a separate branch, push those changes to GitHub, and ask the project maintainers to review them.&lt;/p&gt;

&lt;p&gt;This tutorial is aimed at computer science students, first-time contributors, and anyone preparing for internships who wants to build stronger team-development skills. The core workflow here is beginner-friendly and command-line based, which is exactly what makes it valuable: you learn what Git is actually doing instead of just clicking buttons.&lt;/p&gt;

&lt;p&gt;All this information and more is covered in the free &lt;strong&gt;Industry Projects for Python&lt;/strong&gt; course, which teaches beginner-friendly, real-world development skills with Git, GitHub, Python, and more: &lt;a href="https://industry-python.thinkific.com/products/courses/industry-projects-with-python" rel="noopener noreferrer"&gt;https://industry-python.thinkific.com/products/courses/industry-projects-with-python&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why pull requests matter
&lt;/h2&gt;

&lt;p&gt;When you work on code by yourself, it's easy to think programming is only about writing files and running programs. In teams, that is only part of the job. You also need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep your changes isolated&lt;/li&gt;
&lt;li&gt;explain what you changed&lt;/li&gt;
&lt;li&gt;submit work for review&lt;/li&gt;
&lt;li&gt;respond to feedback&lt;/li&gt;
&lt;li&gt;avoid breaking the main branch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is what pull requests are for. They are not just a GitHub feature, they are part of how teams collaborate safely.&lt;/p&gt;

&lt;p&gt;This also makes pull requests useful in internship prep and interviews. If you can explain how you forked a repo, created a branch, committed changes, pushed them, and opened a pull request, you are already speaking the language of real-world software development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Company repo vs open source repo
&lt;/h2&gt;

&lt;p&gt;A useful place to start is the difference between a &lt;strong&gt;company repository workflow&lt;/strong&gt; and an &lt;strong&gt;open source workflow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In a company setting, if you already have write access to the repository, the process is simpler. You can clone the company repo directly, create a branch from &lt;code&gt;main&lt;/code&gt;, make your changes, push your branch to that same repo, and open a pull request for review. The repo belongs to the organization, but you already have permission to work inside it.&lt;/p&gt;

&lt;p&gt;In open source, that is usually not the case. You normally do not have write access to the original repository. So before you can contribute, you create your own copy of the project on GitHub. That copy is called a fork. Then you clone your fork to your computer, make changes there, and submit a pull request back to the original project.&lt;/p&gt;

&lt;p&gt;That one distinction clears up a lot of beginner confusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three-layer model
&lt;/h2&gt;

&lt;p&gt;It helps to think in three layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Upstream repo&lt;/strong&gt;: the original project owned by the maintainer or organization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your fork&lt;/strong&gt;: your personal copy of that project on GitHub&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local repo&lt;/strong&gt;: the copy on your computer where you actually edit files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you understand those three layers, the rest becomes much easier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzwannyutoiioaz4n1bix.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzwannyutoiioaz4n1bix.png" alt="forking a repo on GitHub"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Git and make sure it works
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;a GitHub account&lt;/li&gt;
&lt;li&gt;a terminal (Windows Terminal or macOS Terminal)&lt;/li&gt;
&lt;li&gt;a code editor such as VS Code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any editor will do. The point is learning the workflow.&lt;/p&gt;

&lt;p&gt;The workflow here uses the Git command line, which is worth learning early. After installing Git, verify it in the terminal by checking the version. The example setup uses Git, GitHub, Windows Terminal or macOS Terminal, VS Code, and a Quarto blog repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Fork the repository
&lt;/h2&gt;

&lt;p&gt;Go to the open source repository on GitHub and click &lt;strong&gt;Fork&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzrk8e7hmlgy6kvzsfbh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzrk8e7hmlgy6kvzsfbh.png" alt="Fork button in GitHub"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This creates a copy of the project under your own GitHub account. That matters because your fork is the version where you have write access. Without a fork, you usually cannot push changes to an open source project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Clone your fork, not the upstream repo
&lt;/h2&gt;

&lt;p&gt;This is the most common beginner mistake.&lt;/p&gt;

&lt;p&gt;You want to clone your fork, not the original upstream repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/your-username/repo-name.git
&lt;span class="nb"&gt;cd &lt;/span&gt;repo-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you clone the wrong repository, your later push may fail or go somewhere you did not intend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Create a branch before you change anything
&lt;/h2&gt;

&lt;p&gt;Do not work directly on &lt;code&gt;main&lt;/code&gt;. Create a feature branch first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; new-blog-post-on-pull-requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a new branch and switches you to it immediately.&lt;/p&gt;

&lt;p&gt;Branching feels confusing at first, but the logic is simple: your branch is a safe workspace for your changes. It keeps your work isolated until it is ready for review.&lt;/p&gt;

&lt;p&gt;You can confirm your current branch with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Make your changes locally
&lt;/h2&gt;

&lt;p&gt;Now edit the files you want to change. Your change might be a documentation fix, a README improvement, a typo correction, or a small code update.&lt;/p&gt;

&lt;p&gt;At this stage, everything is still local to your machine. You can use VS Code, Cursor, Notepad, or whatever editor you have available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Stage your changes
&lt;/h2&gt;

&lt;p&gt;Once you have made your edits, stage the files you want to include in the commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you want to add only one file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add path/to/file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then check the current state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the most important Git commands for beginners. Use it constantly. It shows what has changed and what Git is about to include.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Commit with a clear message
&lt;/h2&gt;

&lt;p&gt;A commit is a saved checkpoint in your branch history.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Start blog post on pull requests"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your message should be short but useful. A reviewer should be able to tell what changed from the commit message alone.&lt;/p&gt;

&lt;p&gt;If Git asks for your identity, configure it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; user.email &lt;span class="s2"&gt;"you@example.com"&lt;/span&gt;
git config &lt;span class="nt"&gt;--global&lt;/span&gt; user.name &lt;span class="s2"&gt;"Your Name"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub also offers a privacy-protecting email option if you do not want your personal email exposed in commit metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Check your remote before pushing
&lt;/h2&gt;

&lt;p&gt;Before you upload anything, confirm where &lt;code&gt;origin&lt;/code&gt; points:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In an open source workflow, &lt;code&gt;origin&lt;/code&gt; should usually point to your fork. This is a simple check, but it prevents a lot of frustration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 9: Push your branch to GitHub
&lt;/h2&gt;

&lt;p&gt;Now push your branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push origin new-blog-post-on-pull-requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on your setup, Git may open a browser window so you can authorize the push. That is normal. Once the push succeeds, your changes are on GitHub but only in your fork.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 10: Open the pull request
&lt;/h2&gt;

&lt;p&gt;Go to your fork on GitHub. You should see a prompt like &lt;strong&gt;Compare &amp;amp; pull request&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Click it, then write a useful title and description. Do not treat this as an afterthought. The pull request is your explanation to the maintainer. Say what changed and why.&lt;/p&gt;

&lt;p&gt;Also, do not open a pull request too early. If your work is incomplete, it creates extra friction for maintainers. Open a pull request when your changes are actually ready for review.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens after you open it?
&lt;/h2&gt;

&lt;p&gt;This is where real collaboration starts.&lt;/p&gt;

&lt;p&gt;As the contributor, you usually cannot merge the pull request yourself unless you are also a maintainer. Instead, the maintainer reviews it, tests it, and may comment or request changes. That back-and-forth is normal. It is not a sign that you failed—it is part of the process.&lt;/p&gt;

&lt;p&gt;That review loop is exactly why this skill matters for students. It teaches you how development works in real teams:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you work in branches&lt;/li&gt;
&lt;li&gt;you submit changes for review&lt;/li&gt;
&lt;li&gt;you communicate clearly&lt;/li&gt;
&lt;li&gt;you revise when needed&lt;/li&gt;
&lt;li&gt;you avoid pushing unfinished work directly into the main codebase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are not optional “extra” skills. They are part of being employable as a developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;If you are still early in your GitHub journey, start small. You do not need to make a huge open source contribution on day one. A documentation fix, a blog post, or a minor improvement is enough to learn the workflow.&lt;/p&gt;

&lt;p&gt;Once you can confidently fork a repo, clone your fork, create a branch, commit your changes, push to GitHub, and open a pull request, you are already building the kind of practical experience that helps with internships, interviews, and team-based software work.&lt;/p&gt;

&lt;p&gt;If you want more beginner-friendly, job-focused tutorials like this, the full &lt;strong&gt;Industry Projects for Python&lt;/strong&gt; course is free and built around real workflows that help students move from classroom coding to practical development: &lt;a href="https://industry-python.thinkific.com/products/courses/industry-projects-with-python" rel="noopener noreferrer"&gt;https://industry-python.thinkific.com/products/courses/industry-projects-with-python&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>github</category>
      <category>git</category>
      <category>beginners</category>
      <category>career</category>
    </item>
    <item>
      <title>Python Iterables Explained Visually (Lists, Tuples &amp; Sets)</title>
      <dc:creator>Jesse Casman</dc:creator>
      <pubDate>Mon, 09 Feb 2026 16:08:57 +0000</pubDate>
      <link>https://forem.com/jcasman/python-iterables-explained-visually-lists-tuples-sets-hh3</link>
      <guid>https://forem.com/jcasman/python-iterables-explained-visually-lists-tuples-sets-hh3</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this article, we break down Python’s core data types and show how they behave in real code: lists, tuples, sets, and strings.&lt;/p&gt;

&lt;p&gt;You’ll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The difference between mutable vs immutable objects&lt;/li&gt;
&lt;li&gt;Why tuples can’t be changed but lists can&lt;/li&gt;
&lt;li&gt;How tuple unpacking makes code cleaner and more readable&lt;/li&gt;
&lt;li&gt;How sets remove duplicates automatically&lt;/li&gt;
&lt;li&gt;How to use union, intersection, and difference on sets&lt;/li&gt;
&lt;li&gt;How strings behave like immutable sequences&lt;/li&gt;
&lt;li&gt;How type checking with mypy can catch data type mistakes&lt;/li&gt;
&lt;li&gt;Why strings behave like immutable sequences&lt;/li&gt;
&lt;li&gt;How type checking with mypy can catch data type mistakes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full free course called &lt;a href="https://industry-python.thinkific.com/products/courses/industry-projects-with-python" rel="noopener noreferrer"&gt;Industry Projects with Python&lt;/a&gt; covers Python iterables and much more. Full code for each section is available for download.&lt;/p&gt;

&lt;p&gt;Before we get into the “visual” part, here’s the quick mental model you should keep in your head:&lt;/p&gt;

&lt;h3&gt;
  
  
  LIST — A list is ordered, you can mutate it, and duplicates are fine.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ order&lt;/li&gt;
&lt;li&gt;✅ mutable&lt;/li&gt;
&lt;li&gt;✅ duplicates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  TUPLE — A tuple keeps order and duplicates—but you can’t change items.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ order&lt;/li&gt;
&lt;li&gt;❌ mutable&lt;/li&gt;
&lt;li&gt;✅ duplicates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SET — A set drops duplicates and isn’t guaranteed to be ordered.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;❌ order (no guarantee)&lt;/li&gt;
&lt;li&gt;✅ mutable (you can add/remove items)&lt;/li&gt;
&lt;li&gt;❌ duplicates (unique only)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the code below, we build a “staff party” guest list to make the behavior obvious: you build collections of “people,” render them, then change the data structure and see what changes in output. This is explicitly aimed at “learning about iterables” and using Flet as the display layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  How To Use
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm2b7iizyma1jlrzl75v2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm2b7iizyma1jlrzl75v2.png" alt=" " width="489" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’re implementing a small demo that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates a handful of “items” (images like Corey/Jerry/Kristi/Matt/Susan).&lt;/li&gt;
&lt;li&gt;Stores those items in a list, a tuple, and a set.&lt;/li&gt;
&lt;li&gt;Iterates over each collection (because they’re iterables) and adds/renders each item.&lt;/li&gt;
&lt;li&gt;Demonstrates the key behaviors:

&lt;ul&gt;
&lt;li&gt;Lists allow item reassignment (mutation).&lt;/li&gt;
&lt;li&gt;Tuples reject item reassignment (“does not support item assignment”).&lt;/li&gt;
&lt;li&gt;Sets remove duplicates and don’t promise stable ordering.&lt;/li&gt;
&lt;li&gt;Set algebra (&amp;amp;, |, -) gives intersection/union/difference.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Key “moving parts” (classes / methods / operators)
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Core Python concepts
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;list[T]&lt;/code&gt;: ordered, mutable, duplicates allowed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tuple[T, ...]&lt;/code&gt;: ordered, immutable, duplicates allowed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;set[T]&lt;/code&gt;: unique elements, iteration order not guaranteed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;for x&lt;/code&gt; in iterable: iteration works across all three.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Set operations:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;a &amp;amp; b&lt;/code&gt; (intersection): elements in both sets&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;a | b&lt;/code&gt; (union): elements in either set&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;a - b&lt;/code&gt; (difference): elements in a that are not in b&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flet concepts (for the “visual” angle)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ft.Image(...)&lt;/code&gt;: creates a UI control you can render.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;page.add(control)&lt;/code&gt;: appends controls to the page.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ft.run(main)&lt;/code&gt;: runs your app, calling main(page).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The “visualization” is simple on purpose: create a collection of UI controls, iterate it, and call &lt;code&gt;page.add(...)&lt;/code&gt; for each item.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to run it (practical workflow)
&lt;/h3&gt;

&lt;p&gt;We use &lt;code&gt;uv&lt;/code&gt; and run Flet locally. A typical setup looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure you have Python installed.&lt;/li&gt;
&lt;li&gt;Install dependencies with &lt;code&gt;uv&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Put your images in an &lt;code&gt;assets/&lt;/code&gt; folder (or configure paths accordingly).&lt;/li&gt;
&lt;li&gt;Run the Flet app locally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code Example
&lt;/h3&gt;

&lt;p&gt;Below is a working example. The full code is available as a zip file in Chapter 13 of the course. It includes images.&lt;/p&gt;

&lt;p&gt;If you have a folder of images (like &lt;code&gt;assets/cory.png&lt;/code&gt;, &lt;code&gt;assets/jerry.png&lt;/code&gt;, etc.), you can render the result by iterating a collection of &lt;code&gt;ft.Image&lt;/code&gt; controls and adding them to the page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;flet&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# In the original demo, the "people" are images (iterables of controls).
&lt;/span&gt;    &lt;span class="n"&gt;cory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cory.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;jerry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jerry.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;kristi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kristi.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;matt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matt.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;susan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;susan.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;party_set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;kristi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jerry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kristi&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;# duplicates collapse
&lt;/span&gt;    &lt;span class="n"&gt;party_set2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;matt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;susan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jerry&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Show "difference" visually: who is only in party_set?
&lt;/span&gt;    &lt;span class="n"&gt;difference_set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;party_set&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;party_set2&lt;/span&gt;

    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Iterable Staff Party&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BOLD&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;difference_set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why this works as a learning tool:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You’re forced to iterate (for person in difference_set) because that’s how you “render many items” from any iterable.&lt;/li&gt;
&lt;li&gt;You can see duplicates vanish when switching from list/tuple to set (the transcript explicitly calls out that only one “Kristi” remains).&lt;/li&gt;
&lt;li&gt;You hit the tuple immutability wall immediately if you try to assign into a tuple (“does not support item assignment”).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s the core takeaway: pick the iterable based on the rules you need—mutation, ordering, and uniqueness—and then let iteration be the common “consumption interface” across all of them.&lt;/p&gt;

&lt;p&gt;Full Industry for Python free course is available here: &lt;a href="https://industry-python.thinkific.com/products/courses/industry-projects-with-python" rel="noopener noreferrer"&gt;https://industry-python.thinkific.com/products/courses/industry-projects-with-python&lt;/a&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>programming</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>New Python Courseware for Early-Career CS Students</title>
      <dc:creator>Jesse Casman</dc:creator>
      <pubDate>Thu, 08 Jan 2026 23:08:20 +0000</pubDate>
      <link>https://forem.com/jcasman/new-python-courseware-for-early-career-cs-students-2e81</link>
      <guid>https://forem.com/jcasman/new-python-courseware-for-early-career-cs-students-2e81</guid>
      <description>&lt;h2&gt;
  
  
  Course Focus
&lt;/h2&gt;

&lt;p&gt;Many students encounter computer science for the first time through command-line programs and abstract problem sets. While those foundations matter, they often fail to show how real software works—and why the concepts you’re learning actually matter.&lt;/p&gt;

&lt;p&gt;This course bridges that gap.&lt;/p&gt;

&lt;p&gt;Instead of learning Python in isolation, you’ll learn it through interactive, visual applications where your code immediately controls what appears on screen. This approach helps concepts “click” faster, builds confidence earlier, and mirrors how real applications are designed.&lt;/p&gt;

&lt;p&gt;Most importantly, this course forces a transition many students never make: from writing small scripts → to producing industry-aligned projects you can meaningfully talk about in interviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this course is for
&lt;/h2&gt;

&lt;p&gt;This course is designed for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-school students curious about computer science and software engineering&lt;/li&gt;
&lt;li&gt;First- and second-year CS students preparing for internships or tougher coursework&lt;/li&gt;
&lt;li&gt;Self-taught beginners who want structure and real projects&lt;/li&gt;
&lt;li&gt;Students who feel comfortable with basics but struggle to connect concepts together&lt;/li&gt;
&lt;li&gt;Anyone who wants to build something real without needing HTML, CSS, or JavaScript first&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No prior experience with UI frameworks or AI is required. You’ll grow into those topics step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Usable Apps You Can Modify
&lt;/h2&gt;

&lt;p&gt;You won’t just read about programming concepts—you’ll use them in real applications.&lt;/p&gt;

&lt;p&gt;You’ll build interactive Python apps where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Characters (Cory, Susan, Jerry, and others) are modeled as Python objects&lt;/li&gt;
&lt;li&gt;Lists and dictionaries generate UI elements dynamically&lt;/li&gt;
&lt;li&gt;User input (buttons, dropdowns, sliders) controls application state&lt;/li&gt;
&lt;li&gt;Event handlers connect interface actions to Python logic&lt;/li&gt;
&lt;li&gt;Images, layouts, and real-time updates make your code visible and tangible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Through these projects, core Python ideas—classes, objects, lists, dictionaries, comprehensions, and control flow—become concrete instead of abstract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gain Internship-level skills
&lt;/h2&gt;

&lt;p&gt;As the course progresses, you move beyond static interfaces into modern application patterns used in industry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;State management and event-driven logic&lt;/li&gt;
&lt;li&gt;Asynchronous programming for responsive applications&lt;/li&gt;
&lt;li&gt;Clean separation between UI, logic, and data&lt;/li&gt;
&lt;li&gt;Deployment workflows from local development to the cloud&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ll learn not just how things work, but why they’re structured the way they are—an essential skill for interviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your edge: building a real AI application
&lt;/h2&gt;

&lt;p&gt;In the later chapters, you’ll build an AI application that goes far beyond a “toy demo.”&lt;/p&gt;

&lt;p&gt;You will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run large language models locally using Ollama&lt;/li&gt;
&lt;li&gt;Build a chat interface with message history, roles, scrolling, and live updates&lt;/li&gt;
&lt;li&gt;Implement async + streaming responses, the feature that makes modern AI apps feel real&lt;/li&gt;
&lt;li&gt;Understand performance tradeoffs, prompt design, and system architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the kind of project that signals real understanding—not just tool usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you’ll be able to demo and explain
&lt;/h2&gt;

&lt;p&gt;By the end of the course, you’ll be able to confidently walk through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A full-stack mental model: virtual environments → local servers → cloud deployment → HTTPS&lt;/li&gt;
&lt;li&gt;Async and real-time patterns used in modern Python applications&lt;/li&gt;
&lt;li&gt;Local LLM integration, streaming output, and UI updates&lt;/li&gt;
&lt;li&gt;Architectural decisions and tradeoffs in clear, interview-ready language&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether you’re preparing for your first internship, building a high-school portfolio, or strengthening your foundation before college CS courses, these skills transfer directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use this course
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Follow along hands-on and build each project yourself&lt;/li&gt;
&lt;li&gt;Experiment freely—breaking things is encouraged&lt;/li&gt;
&lt;li&gt;Rebuild projects from scratch to strengthen understanding&lt;/li&gt;
&lt;li&gt;Use the finished apps as portfolio pieces or interview demos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The course is free while the curriculum is being developed, and your feedback helps shape future modules.&lt;/p&gt;

&lt;p&gt;Start the course here (free, full code included):&lt;br&gt;
&lt;a href="https://industry-python.thinkific.com/products/courses/industry-projects-with-python" rel="noopener noreferrer"&gt;https://industry-python.thinkific.com/products/courses/industry-projects-with-python&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>career</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Versioning Your Database with SQLAlchemy and Alembic: From Models to Safe Migrations</title>
      <dc:creator>Jesse Casman</dc:creator>
      <pubDate>Wed, 19 Nov 2025 16:12:57 +0000</pubDate>
      <link>https://forem.com/jcasman/versioning-your-database-with-sqlalchemy-and-alembic-from-models-to-safe-migrations-3i1c</link>
      <guid>https://forem.com/jcasman/versioning-your-database-with-sqlalchemy-and-alembic-from-models-to-safe-migrations-3i1c</guid>
      <description>&lt;p&gt;SQLAlchemy and Alembic give you a safer, more controlled way to evolve your schema over time. In the FastOpp Country Store project, that shows up when you do something simple but dangerous in real life: &lt;strong&gt;add a new column to an existing table in SQLite.&lt;/strong&gt; Can you migrate your data without blowing everything up?&lt;/p&gt;

&lt;p&gt;This tutorial follows the flow of the &lt;a href="https://youtu.be/-pRdc1Bg9xI?si=44IQfNOVJ_yAZWyG" rel="noopener noreferrer"&gt;“FastOpp Country Store 05 – Add New Column to SQL Table” video&lt;/a&gt;. You will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change a SQLModel database model in FastOpp (SQLModel runs on top of SQLAlchemy)&lt;/li&gt;
&lt;li&gt;Create and run database migrations (via Alembic under the hood)&lt;/li&gt;
&lt;li&gt;Fix a real migration error from SQLite&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  My Ah-ha! Moment
&lt;/h2&gt;

&lt;p&gt;I've been working with Django for a number of years on multiple projects for clients. I just got a breakthrough understanding working on a project called FastOpp. The big realization was this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When using SQLAlchemy and Alembic, you &lt;strong&gt;first create a Python migration file&lt;/strong&gt;, and only later, when you run &lt;code&gt;alembic upgrade&lt;/code&gt;, does that file actually change your database schema.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At first, that feels like extra work. Why not just apply the changes directly?&lt;/p&gt;

&lt;p&gt;Because that two-step process is the safety net.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;alembic revision --autogenerate&lt;/code&gt; looks at your SQLAlchemy models versus the current database and writes a &lt;strong&gt;Python migration script&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;At this point, &lt;strong&gt;nothing&lt;/strong&gt; has happened to your database.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You get a chance to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Review and edit the migration.&lt;/li&gt;
&lt;li&gt;Commit it to git.&lt;/li&gt;
&lt;li&gt;Have others review it in a PR.&lt;/li&gt;
&lt;li&gt;Test it on dev/staging.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Only when you explicitly run something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;alembic upgrade &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(or the FastOpp wrapper around that command) does Alembic connect to the database and apply those changes.&lt;/p&gt;

&lt;p&gt;That separation—&lt;strong&gt;generate now, apply later&lt;/strong&gt;—is what keeps accidental, destructive schema changes from landing silently in production. It is dramatically safer than systems that auto-migrate whenever the app starts and hope nothing catastrophic is in the diff.&lt;/p&gt;

&lt;p&gt;Once this clicks, Alembic stops feeling like extra steps and starts looking like exactly what it is: &lt;strong&gt;version control for your schema.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What the FastOpp Video Actually Does
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Oppkey/fastopp" rel="noopener noreferrer"&gt;FastOpp&lt;/a&gt; is a FastAPI-based starter that gives you a Django-like experience for AI apps: admin panel, authentication, SQL database models and migrations, and Jinja2 templates with Tailwind/DaisyUI, all wired together in an opinionated structure.&lt;/p&gt;

&lt;p&gt;In Part 5 of the Country Store series, the flow is roughly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start from a working FastOpp Country Store app using SQLite.&lt;/li&gt;
&lt;li&gt;Add a new column to an existing SQLModel table (&lt;code&gt;Food&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Use the FastOpp migration command (a thin wrapper around Alembic) to:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Create&lt;/strong&gt; a migration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upgrade&lt;/strong&gt; the database.

&lt;ol&gt;
&lt;li&gt;Hit a SQLite migration issue around nullability/defaults.&lt;/li&gt;
&lt;li&gt;Edit the Alembic-generated migration file to fix the problem.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1 – Add a New Column to the &lt;code&gt;Food&lt;/code&gt; Model
&lt;/h2&gt;

&lt;p&gt;The video works with a &lt;code&gt;Food&lt;/code&gt; model representing items in the Country Store. The key step is adding a new &lt;code&gt;price&lt;/code&gt; column to that table.&lt;/p&gt;

&lt;p&gt;The resulting model looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlmodel&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SQLModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Food&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SQLModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;foods&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore
&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;table=True&lt;/code&gt; and &lt;code&gt;__tablename__ = "foods"&lt;/code&gt; define the underlying table name.&lt;/li&gt;
&lt;li&gt;Existing columns (&lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;image_url&lt;/code&gt;) are already in the database.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;new&lt;/strong&gt; column is &lt;code&gt;price: float = Field(default=0.0, nullable=False)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this moment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Python model knows about &lt;code&gt;price&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The SQLite database does &lt;strong&gt;not&lt;/strong&gt; yet have a &lt;code&gt;price&lt;/code&gt; column on &lt;code&gt;foods&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gap between “model” and “database” is exactly what the migration will fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 – Create a Migration (Alembic Revision)
&lt;/h2&gt;

&lt;p&gt;FastOpp exposes a migration command that calls Alembic under the hood. Conceptually, it’s equivalent to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;alembic revision &lt;span class="nt"&gt;--autogenerate&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"add price to foods"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Imports your SQLModel metadata (including &lt;code&gt;Food&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Compares it to the existing SQLite schema.&lt;/li&gt;
&lt;li&gt;Writes a new migration script in your &lt;code&gt;migrations&lt;/code&gt;/&lt;code&gt;alembic/versions&lt;/code&gt; directory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simplified version of the generated migration might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;alembic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sa&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upgrade&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;foods&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;downgrade&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;foods&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is only a &lt;strong&gt;plan&lt;/strong&gt;. The database still does not have the &lt;code&gt;price&lt;/code&gt; column. You are in the safe stage where you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspect the migration.&lt;/li&gt;
&lt;li&gt;Adjust nullability or defaults.&lt;/li&gt;
&lt;li&gt;Commit it to git.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3 – Run the Migration and Handle SQLite Constraints
&lt;/h2&gt;

&lt;p&gt;Next, run the upgrade. In raw Alembic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;alembic upgrade &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In FastOpp, we use a wrapper command via &lt;code&gt;oppman.py&lt;/code&gt;, but the end result is the same: Alembic executes the &lt;code&gt;upgrade()&lt;/code&gt; function against your SQLite database.&lt;/p&gt;

&lt;p&gt;Here is where SQLite can cause trouble. Adding a &lt;strong&gt;non-nullable&lt;/strong&gt; column (&lt;code&gt;nullable=False&lt;/code&gt;) to a table that already has rows is a classic problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQLite needs a value for &lt;code&gt;price&lt;/code&gt; on every existing row.&lt;/li&gt;
&lt;li&gt;If the migration doesn’t specify a default at the DB level (&lt;code&gt;server_default&lt;/code&gt;), SQLite tries to use &lt;code&gt;NULL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NULL&lt;/code&gt; contradicts &lt;code&gt;NOT NULL&lt;/code&gt;, and you get an error similar to:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;sqlite3.OperationalError: Cannot add a NOT NULL column with default value NULL&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even though the SQLModel field has &lt;code&gt;default=0.0&lt;/code&gt;, Alembic’s autogenerate may not automatically turn that into a proper &lt;code&gt;server_default&lt;/code&gt; on the database column. So you end up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and no default on the DB side.&lt;/p&gt;

&lt;p&gt;This is exactly the kind of mismatch the video walks through: the application-level default and the database-level constraints are not fully aligned, and SQLite forces you to resolve it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 – Fix the Migration Script for SQLite
&lt;/h2&gt;

&lt;p&gt;The correct place to fix this is the &lt;strong&gt;migration file&lt;/strong&gt;, not manual SQL pokes to your database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A – Allow &lt;code&gt;NULL&lt;/code&gt; Temporarily
&lt;/h3&gt;

&lt;p&gt;For a demo app like Country Store, the easiest fix is to relax the constraint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upgrade&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;foods&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQLite can now add the &lt;code&gt;price&lt;/code&gt; column and set &lt;code&gt;NULL&lt;/code&gt; on all existing rows.&lt;/p&gt;

&lt;p&gt;Your application code can treat &lt;code&gt;None&lt;/code&gt; as “unknown price” or fall back to &lt;code&gt;0.0&lt;/code&gt; at the Python layer. This keeps the migration simple and safe for a tutorial.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B – Set a Database Default
&lt;/h3&gt;

&lt;p&gt;If you really want &lt;code&gt;NOT NULL&lt;/code&gt; at the database level, then you need to explicitly tell SQLite what to put into &lt;code&gt;price&lt;/code&gt; for old rows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upgrade&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;foods&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;server_default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now SQLite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adds the &lt;code&gt;price&lt;/code&gt; column.&lt;/li&gt;
&lt;li&gt;Fills &lt;code&gt;0.0&lt;/code&gt; for every existing row.&lt;/li&gt;
&lt;li&gt;Enforces &lt;code&gt;NOT NULL&lt;/code&gt; going forward.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQLModel’s &lt;code&gt;default=0.0&lt;/code&gt; is not enough by itself.&lt;/li&gt;
&lt;li&gt;You must encode your intention into the migration so SQLite knows what to do with existing data.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 5 – Re-run the Migration
&lt;/h2&gt;

&lt;p&gt;After editing the migration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;alembic upgrade &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(or the FastOpp wrapper)&lt;/p&gt;

&lt;p&gt;This time the migration should succeed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;foods&lt;/code&gt; table gains a &lt;code&gt;price&lt;/code&gt; column.&lt;/li&gt;
&lt;li&gt;Existing rows either have &lt;code&gt;NULL&lt;/code&gt; or &lt;code&gt;0.0&lt;/code&gt;, depending on your choice.&lt;/li&gt;
&lt;li&gt;Alembic records that the migration has been applied.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anyone else working on the FastOpp project just has to pull the code and run the same upgrade command to get the same schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Flow Matters
&lt;/h2&gt;

&lt;p&gt;This one change to the &lt;code&gt;Food&lt;/code&gt; model (&lt;code&gt;price&lt;/code&gt; added to &lt;code&gt;foods&lt;/code&gt;) quietly demonstrates several important habits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Change the model first.&lt;/strong&gt;&lt;br&gt;
The SQLModel definition is your desired end state.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generate and review a migration.&lt;/strong&gt;&lt;br&gt;
Let Alembic autogenerate the migration, then edit it so SQLite’s constraints and your data model actually match.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fix problems in code, not with ad-hoc SQL.&lt;/strong&gt;&lt;br&gt;
When SQLite complains about nullability or defaults, the answer is to adjust the migration script and run it again.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Wire the change through to the UI.&lt;/strong&gt;&lt;br&gt;
Jinja2 templates, with variable interpolation and simple helpers like &lt;code&gt;.split()&lt;/code&gt;, give you a clean way to expose new columns.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you internalize this pattern, adding a new column like &lt;code&gt;price&lt;/code&gt; to &lt;code&gt;foods&lt;/code&gt; stops being a risk and becomes a predictable, repeatable process—exactly what you want in a real project.&lt;/p&gt;

</description>
      <category>alembic</category>
      <category>sqlalchemy</category>
      <category>sql</category>
      <category>python</category>
    </item>
    <item>
      <title>FastOpp: A Student-Friendly Starter for AI Web Apps</title>
      <dc:creator>Jesse Casman</dc:creator>
      <pubDate>Wed, 17 Sep 2025 05:30:27 +0000</pubDate>
      <link>https://forem.com/jcasman/fastopp-a-student-friendly-starter-for-ai-web-apps-1cef</link>
      <guid>https://forem.com/jcasman/fastopp-a-student-friendly-starter-for-ai-web-apps-1cef</guid>
      <description>&lt;p&gt;If you’re a student or novice developer curious about building AI web applications, FastOpp is worth a look. It's a well-scaffolded, beginner-friendly opinionated open source technology stack for building AI web apps. It bridges the gap between learning frameworks and real-world AI-first apps. You can explore the live site here: &lt;a href="https://fastopp-site.fly.dev/" rel="noopener noreferrer"&gt;https://fastopp-site.fly.dev/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Example demo site, extending functionality, like you can do: &lt;a href="https://fastopp-site.fly.dev/example" rel="noopener noreferrer"&gt;https://fastopp-site.fly.dev/example&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mentorship &amp;amp; Support Offered for Students:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weekly video meetings with experienced mentor&lt;/li&gt;
&lt;li&gt;Structured learning path with tutorials and GitHub issues&lt;/li&gt;
&lt;li&gt;Professional reference from Jesse Casman, President of Oppkey, a Silicon Valley-based Developer Relations firm (available after minimum 3 months of contributions)&lt;/li&gt;
&lt;li&gt;Open source contribution experience that can be added to your resume&lt;/li&gt;
&lt;li&gt;Git and GitHub experience including push, pull requests, and code reviews&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What is FastOpp?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;FastOpp is an open source starter package based on FastAPI built for creating AI web applications more easily. It offers ready-built components—admin panels, templating, authentication, database migrations—packaged in an opinion-driven structure so you can skip boilerplate and focus on building features. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Admin panel like Django, with role-based authentication.&lt;/p&gt;

&lt;p&gt;SQL database models and migrations inspired by Django’s model structure.&lt;/p&gt;

&lt;p&gt;HTML templates (Jinja2) with modern UI (Tailwind, DaisyUI, AlpineJS, HTMX) so you can build simple but decent UIs without heavy JavaScript.&lt;/p&gt;

&lt;p&gt;API endpoints with auto-generated documentation (FastAPI style) so you can later add React, Flutter or other frontends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Management tools:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt; • oppman.py for core commands (server start/stop, migrations, backups)&lt;br&gt;
  • oppdemo.py for switching between demo/full mode vs minimal mode, handling sample/demo data&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who is FastOpp For?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;FastOpp targets students and junior devs who:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Know Python and want to build web apps with AI/LLMs.&lt;/li&gt;
&lt;li&gt;Are more comfortable with backend logic in Python than heavy JavaScript frontends.&lt;/li&gt;
&lt;li&gt;Prefer templates and integrated UI over writing a bunch of frontend code.&lt;/li&gt;
&lt;li&gt;May have used Django or Flask and found async / LLM integration hard. FastOpp helps with that.&lt;/li&gt;
&lt;li&gt;It is not optimized for production-grade, high-traffic applications. If you need enterprise-level security, scaling, or complex frontends you might need more tools. But for learning, MVPs, hackathons, or portfolio pieces, it’s strong.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What You’ll Learn Through FastOpp&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You’ll gain practical experience with a real stack of tools used in today’s developer environments. This means you won’t just learn syntax—you’ll learn how modern apps are put together and deployed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Technologies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FastAPI:&lt;/strong&gt; an async Python web framework with automatic interactive documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SQLite / PostgreSQL:&lt;/strong&gt; relational databases with migrations for structured data storage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jinja2 Templates:&lt;/strong&gt; server-side rendering for web pages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tailwind CSS + DaisyUI:&lt;/strong&gt; modern, utility-first styling and ready-made UI components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTMX + Alpine.js:&lt;/strong&gt; lightweight JavaScript helpers for adding interactivity without full SPA complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication:&lt;/strong&gt; role-based login and access control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Management Scripts:&lt;/strong&gt; Python commands (oppman.py, oppdemo.py) to handle server tasks, migrations, and demo mode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demonstration of Modern Web Technologies and Development Tools&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By using FastOpp you’ll also work with the tools and workflows expected in professional settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Environment Variables: manage secrets and settings securely.&lt;/li&gt;
&lt;li&gt;Migrations: database versioning to evolve schemas without breaking data.&lt;/li&gt;
&lt;li&gt;Static and Dynamic Files: organize assets and serve them correctly in apps.&lt;/li&gt;
&lt;li&gt;Admin Interfaces: built-in panels for managing users and content.&lt;/li&gt;
&lt;li&gt;API Endpoints + Docs: every app automatically generates docs for testing and integration.&lt;/li&gt;
&lt;li&gt;LLM Integration: practice connecting to and using large language models through APIs.&lt;/li&gt;
&lt;li&gt;Deployment Concepts: running apps locally, testing in demo mode, and preparing for cloud platforms like Fly.io.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren’t toy examples—they’re the same concepts you’ll use when moving into internships, jobs, or contributing to other open source projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why You Should Try It&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You’ll get hands-on experience building AI web apps with a scaffolded yet flexible base.&lt;/p&gt;

&lt;p&gt;You’ll learn migration workflows, authentication, templating, LLM integration. Good skills on resume.&lt;/p&gt;

&lt;p&gt;Faster prototype → you can build projects to share or show in portfolio.&lt;/p&gt;

&lt;p&gt;You can see inside another’s project architecture: how things like management scripts, sample/demo data, file uploads, static vs dynamic parts are organized.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;FastOpp is a well-scaffolded, beginner-friendly opinionated open source technology stack for building AI web apps. It doesn’t hide complexity, but it reduces boilerplate so you can focus on what you care about: prototypes, features, learning. &lt;/p&gt;

&lt;p&gt;If you want to build apps, learn web + AI integration, or boost your portfolio, contributing to or using FastOpp is a good move.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check out the live site and start experimenting today: &lt;a href="https://fastopp-site.fly.dev/" rel="noopener noreferrer"&gt;https://fastopp-site.fly.dev/&lt;/a&gt;
&lt;/h3&gt;

</description>
      <category>ai</category>
      <category>fastapi</category>
      <category>opensource</category>
      <category>python</category>
    </item>
  </channel>
</rss>
