<?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>DEV Community: Love Otudor</title>
    <description>The latest articles on DEV Community by Love Otudor (@loveotudor).</description>
    <link>https://dev.to/loveotudor</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2892962%2F35aad0e7-7f68-4d57-aab9-314834591403.jpg</url>
      <title>DEV Community: Love Otudor</title>
      <link>https://dev.to/loveotudor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/loveotudor"/>
    <language>en</language>
    <item>
      <title>Building an AI Image Generator with OpenAI</title>
      <dc:creator>Love Otudor</dc:creator>
      <pubDate>Wed, 09 Sep 2026 15:59:56 +0000</pubDate>
      <link>https://dev.to/anvil/building-an-ai-image-generator-with-openai-5541</link>
      <guid>https://dev.to/anvil/building-an-ai-image-generator-with-openai-5541</guid>
      <description>&lt;p&gt;In this tutorial, we're going to build a web application that uses &lt;a href="https://platform.openai.com/docs/api-reference/images" rel="noopener noreferrer"&gt;OpenAI's Images API&lt;/a&gt; to turn drawings into photorealistic images. We'll build the entire app using Python with Anvil.&lt;/p&gt;

&lt;p&gt;When the app is finished, we'll be able to upload an image and click a button to call the Images API. When the model has finished generating a new image, it will be displayed on the screen, and we can download it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fanvil-website-static.s3.eu-west-2.amazonaws.com%2Flearn%2Ftutorials%2Fopen-ai%2Ftest-app.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fanvil-website-static.s3.eu-west-2.amazonaws.com%2Flearn%2Ftutorials%2Fopen-ai%2Ftest-app.gif" alt="OpenAI Image Generator App Demo" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To build the app, we will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an Anvil app&lt;/li&gt;
&lt;li&gt;Drag and drop components to build the UI&lt;/li&gt;
&lt;li&gt;Get an API key from OpenAI&lt;/li&gt;
&lt;li&gt;Write backend logic to call the OpenAI Images API&lt;/li&gt;
&lt;li&gt;Call the API in a background task&lt;/li&gt;
&lt;li&gt;Generate the image and update the UI&lt;/li&gt;
&lt;li&gt;Add the ability to download the image&lt;/li&gt;
&lt;li&gt;Publish our app to the web&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For this tutorial, you will need basic Python knowledge and an OpenAI account with API credits.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you'd prefer to follow the video version of this tutorial, you can find that here: &lt;a href="https://www.youtube.com/watch?v=imJ_YaSHxOk" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=imJ_YaSHxOk&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 - Create an Anvil app
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://anvil.works/login?utm_source=crosspost:dev.to:/learn/tutorials/open-ai"&gt;Log in&lt;/a&gt; to Anvil and click 'Create a new app'. Choose the New M3 theme and select 'Blank Panel Form'.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkhatgm7zh6oxc9xypyt8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkhatgm7zh6oxc9xypyt8.gif" alt="Create a new app and choose the 'New M3' theme" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you can't find the New M3 theme, you may need to open the "Advanced" dropdown&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;First, rename the Form to "MainForm" by right-clicking on it in the App Browser, then rename the app. Click on the name at the top of the screen and give it a name like "OpenAI Image Generator".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6y78cl2edwg0dppwgofp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6y78cl2edwg0dppwgofp.gif" alt="Rename the MainForm and app" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 - Build the UI
&lt;/h2&gt;

&lt;p&gt;We're now looking at the &lt;a href="https://anvil.works/docs/editor/form-editor?utm_source=crosspost:dev.to:/learn/tutorials/open-ai"&gt;Form Editor&lt;/a&gt;, where we can drag and drop components from the &lt;a href="https://anvil.works/docs/editor/form-editor#toolbox?utm_source=crosspost:dev.to:/learn/tutorials/open-ai"&gt;Toolbox&lt;/a&gt; to build our app's UI.&lt;/p&gt;

&lt;p&gt;Let's start by adding a Card to the Form to hold our images and buttons. Drop a &lt;a href="https://anvil.works/docs/ui/components/containers#columnpanel?utm_source=crosspost:dev.to:/learn/tutorials/open-ai"&gt;ColumnPanel&lt;/a&gt; inside the Card to make it easier to lay out the components.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh8gu3i5sysi5y4wa4jrt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh8gu3i5sysi5y4wa4jrt.gif" alt="Add a Card component to the Form" width="760" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a FileLoader
&lt;/h3&gt;

&lt;p&gt;We need a button to upload an image - that's what the &lt;a href="https://anvil.works/docs/ui/app-themes/material-3/components#fileloader" rel="noopener noreferrer"&gt;FileLoader component&lt;/a&gt; is for. Drag and drop a FileLoader into the ColumnPanel.&lt;/p&gt;

&lt;p&gt;Centre-align the FileLoader using the floating &lt;a href="https://anvil.works/docs/editor/form-editor#object-palette" rel="noopener noreferrer"&gt;Object Palette&lt;/a&gt;. From the &lt;a href="https://anvil.works/docs/editor/form-editor#properties-panel" rel="noopener noreferrer"&gt;Properties Panel&lt;/a&gt;, change its appearance property to &lt;code&gt;filled&lt;/code&gt;. We only want to upload images, so set the &lt;code&gt;file_types&lt;/code&gt; property to &lt;code&gt;image/*&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi1rihc019spw30svdsu7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi1rihc019spw30svdsu7.gif" alt="Adding a FileLoader component" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Image components
&lt;/h3&gt;

&lt;p&gt;We need an &lt;a href="https://anvil.works/docs/ui/components/basic#image" rel="noopener noreferrer"&gt;Image component&lt;/a&gt; to display the uploaded image. Drag and drop an Image component above the FileLoader. Change its name to &lt;code&gt;uploaded_img&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We don't want it visible until an image is uploaded, so click the eye icon on the Object Palette to make it invisible. Also set its &lt;code&gt;display_mode&lt;/code&gt; property to &lt;code&gt;fill_width&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F74cml5jis8pfh12n1muq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F74cml5jis8pfh12n1muq.gif" alt="Adding an Image component" width="760" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add another Image component next to the first one to display our generated image. Name it &lt;code&gt;output_img&lt;/code&gt; and make it invisible too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add the Generate button
&lt;/h3&gt;

&lt;p&gt;We now need a button that, when clicked, will call the OpenAI API. Add a &lt;a href="https://anvil.works/docs/ui/app-themes/material-3/components#button" rel="noopener noreferrer"&gt;Button component&lt;/a&gt; to the page and name it &lt;code&gt;generate_button&lt;/code&gt;. Centre it, change the text to "Turn into photo", and make it invisible to start. We'll make the Button visible once a file is uploaded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftm4hk3bemw8bhxxedlne.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftm4hk3bemw8bhxxedlne.gif" alt="Adding the Generate button" width="760" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Handle the file upload
&lt;/h3&gt;

&lt;p&gt;Let's write some code that will make the &lt;code&gt;uploaded_img&lt;/code&gt; and &lt;code&gt;generate_button&lt;/code&gt; components visible when an image is uploaded.&lt;/p&gt;

&lt;p&gt;Select the FileLoader and click &lt;code&gt;on change event&lt;/code&gt; from the Object Palette. This opens the code view and automatically creates a method that runs when a file is uploaded.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgls9mub30d6foeyrhp1s.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgls9mub30d6foeyrhp1s.png" alt="Add a change event handler to the FileLoader" width="758" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the following code:&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="nd"&gt;@handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file_loader_1&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;change&lt;/span&gt;&lt;span class="sh"&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file_loader_1_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;This method is called when a new file is loaded into this FileLoader&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uploaded_img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uploaded_img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test out the app
&lt;/h3&gt;

&lt;p&gt;Let's test out our UI and the code we just wrote. At the top right of the Anvil Editor, click the green Run button. Upload an image and you should see it appear along with the "Turn into photo" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F48ztxutcm7i1a73rb3al.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F48ztxutcm7i1a73rb3al.gif" alt="Test the app to make sure uploading a file works" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 - Set up OpenAI
&lt;/h2&gt;

&lt;p&gt;We're going to use the &lt;a href="https://platform.openai.com/docs/api-reference/images" rel="noopener noreferrer"&gt;Images API&lt;/a&gt; from OpenAI to turn our drawings into photorealistic images. In order to use this API, we need to get an API key from OpenAI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create an account and add credits
&lt;/h3&gt;

&lt;p&gt;If you don't already have an OpenAI account, create one at &lt;a href="https://platform.openai.com" rel="noopener noreferrer"&gt;platform.openai.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once logged in, go to the Billing page and add credits to your account. The minimum amount that you can add is typically $5, which is plenty for building and testing this app.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7cx26ixem74ku270hi8t.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7cx26ixem74ku270hi8t.png" alt="OpenAI billing page" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify your account
&lt;/h3&gt;

&lt;p&gt;You'll need to verify your account to use the Images API. In the "General" tab, look for a "Verify Organization" button.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you don't see the verification button immediately, you may need to wait a few days for it to appear. Once verified, it will say "Organization verified".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Get your API key
&lt;/h3&gt;

&lt;p&gt;Navigate to "API keys" and click "Create new secret key". Give it a name and copy the key that appears. You won't be able to see the key again after navigating away.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqlmdnzgvtocdufw9igqb.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqlmdnzgvtocdufw9igqb.png" alt="Create an OpenAI secret key" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Store your API key securely
&lt;/h3&gt;

&lt;p&gt;We can now store the key securely in our Anvil app. Back in the app, click the blue '+' button in the Sidebar Menu and choose "App Secrets".&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feljs4x4rn7im88o0bivq.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feljs4x4rn7im88o0bivq.png" alt="Adding App Secrets service" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "Create new secret" and name it &lt;code&gt;OPEN_AI_API_KEY&lt;/code&gt;. Click "Set value" and paste in your API key from OpenAI. This key is now encrypted and stored securely.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftvb1ay3frlonzyfxgmo5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftvb1ay3frlonzyfxgmo5.gif" alt="Create an App Secret and set your OpenAI API key as the value" width="760" height="448"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 - Write backend code to call the API
&lt;/h2&gt;

&lt;p&gt;We can now set up our backend to call the API and get a generated image. &lt;/p&gt;

&lt;h3&gt;
  
  
  Install the &lt;code&gt;openai&lt;/code&gt; pacakage
&lt;/h3&gt;

&lt;p&gt;First, we need to install the OpenAI Python package in our app's server environment.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://anvil.works/docs/editor#sidebar-menu" rel="noopener noreferrer"&gt;Sidebar Menu&lt;/a&gt;, navigate to Settings and select "Python versions". Switch the base package to "Machine Learning", and in the packages section, add &lt;code&gt;openai&lt;/code&gt;. You can leave the version box blank.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkfp28flahy9bnab38x82.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkfp28flahy9bnab38x82.png" alt="Install the  raw `openai` endraw  package in your app's Settings." width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Copy the OpenAI code
&lt;/h3&gt;

&lt;p&gt;Back in the App Browser, click "Add Server Module" to add a server environment to your app. This is a Python environment running on Anvil's secure cloud servers.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcooxd8cuo3iqqu8b808i.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcooxd8cuo3iqqu8b808i.png" alt="Add a Server Module" width="488" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's first copy and paste the code from the Images API documentation that can be found &lt;a href="https://platform.openai.com/docs/guides/image-generation?api=image#edit-images" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Add your API key that's stored in App Secrets to the OpenAI client:&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OPEN_AI_API_KEY&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;We'll need to modify this code so that it works in Anvil but for now, your server code should 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;import&lt;/span&gt; &lt;span class="n"&gt;anvil.secrets&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anvil.server&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OPEN_AI_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Generate a photorealistic image of a gift basket on a white background 
labeled &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Relax &amp;amp; Unwind&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; with a ribbon and handwriting-like font, 
containing all the items in the reference pictures.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-image-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body-lotion.png&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;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bath-bomb.png&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;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;incense-kit.png&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;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;soap.png&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;rb&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;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;image_base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;b64_json&lt;/span&gt;
&lt;span class="n"&gt;image_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_base64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a function
&lt;/h3&gt;

&lt;p&gt;An Anvil server module doesn’t run top to bottom like a normal Python script. Any code we write here will run when it’s called, so we need to turn this code into a function. We’ll then call that function when the &lt;code&gt;generate_button&lt;/code&gt; is clicked.&lt;/p&gt;

&lt;p&gt;Create a function called &lt;code&gt;generate_image&lt;/code&gt; that takes in &lt;code&gt;input_img&lt;/code&gt; as an argument. Indent the code we copied into this function and change the prompt to say "Turn the drawing into a photorealistic image". Your server code should now look something 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;anvil.secrets&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anvil.server&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OPEN_AI_API_KEY&lt;/span&gt;&lt;span class="sh"&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;generate_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_img&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-image-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body-lotion.png&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;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bath-bomb.png&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;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;incense-kit.png&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;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;soap.png&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;rb&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;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Turn the drawing into a photorealistic image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;image_base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;b64_json&lt;/span&gt;
    &lt;span class="n"&gt;image_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_base64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Save the image to a file
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gift-basket.png&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;wb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pass in the input image
&lt;/h3&gt;

&lt;p&gt;We need to pass in a file path for our input image into the OpenAI API. We can get a temporary file path using &lt;a href="https://anvil.works/docs/working-with-files/media/files_on_disk#media-object-to-temporary-file" rel="noopener noreferrer"&gt;&lt;code&gt;anvil.media.TempFile&lt;/code&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;OpenAI checks the MIME type of images based on the file extension, not the actual content, so we also need to append the proper file extension to our temporary file path. &lt;/p&gt;

&lt;p&gt;Add the following import statements to your server code:&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;anvil.media&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mimetypes&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, inside &lt;code&gt;generate_image&lt;/code&gt;, we'll get the MIME type of &lt;code&gt;input_img&lt;/code&gt; and use &lt;code&gt;mimetypes&lt;/code&gt; to find the corresponding file extension. We then need to create a &lt;code&gt;TempFile&lt;/code&gt; and rename the filepath so that it includes this extension:&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;generate_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_img&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;#get the MIME type and extension from the input_img
&lt;/span&gt;    &lt;span class="n"&gt;mime_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input_img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;
    &lt;span class="n"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mimetypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;guess_extension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mime_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;#create a temporary file path
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TempFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tmp_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;#add the extension to the temporary path
&lt;/span&gt;        &lt;span class="n"&gt;new_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tmp_path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-image-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="c1"&gt;#pass the path to the model
&lt;/span&gt;                &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&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;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Turn the drawing into a photorealistic image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;image_base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;b64_json&lt;/span&gt;
        &lt;span class="n"&gt;image_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_base64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Save the image to a file
&lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gift-basket.png&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;wb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a media object
&lt;/h3&gt;

&lt;p&gt;When the model finishes generating an image, we can create an Anvil &lt;a href="https://anvil.works/docs/working-with-files/media" rel="noopener noreferrer"&gt;Media Object&lt;/a&gt; instead of writing to a file. Replace the &lt;code&gt;with&lt;/code&gt; statement at the end of the server function 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;output_img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BlobMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/jpeg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;image_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai-image.jpg&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;The &lt;code&gt;name&lt;/code&gt; argument will be the name of your file when downloaded. You can change this name to anything you'd like.&lt;/p&gt;

&lt;p&gt;Your server code should now 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;import&lt;/span&gt; &lt;span class="n"&gt;anvil.secrets&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anvil.server&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anvil.media&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mimetypes&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OPEN_AI_API_KEY&lt;/span&gt;&lt;span class="sh"&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;generate_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_img&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;#get the MIME type and extension from the input_img
&lt;/span&gt;    &lt;span class="n"&gt;mime_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input_img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;
    &lt;span class="n"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mimetypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;guess_extension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mime_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;#create a temporary file path
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TempFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tmp_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;#add the extension to the temporary path
&lt;/span&gt;        &lt;span class="n"&gt;new_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tmp_path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-image-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="c1"&gt;#pass the path to the model
&lt;/span&gt;                &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&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;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Turn the drawing into a photorealistic image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;image_base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;b64_json&lt;/span&gt;
    &lt;span class="n"&gt;image_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_base64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Save the image to a file
&lt;/span&gt;    &lt;span class="n"&gt;output_img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BlobMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/jpeg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;image_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai-image.jpg&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;h2&gt;
  
  
  Step 5 - Run the function in the background
&lt;/h2&gt;

&lt;p&gt;The image generation request may take some time, so we don't want our server to hang while waiting. To prevent this, we can run the function in the background using &lt;a href="https://anvil.works/docs/background-tasks?utm_source=crosspost:dev.to:/learn/tutorials/open-ai"&gt;Background Tasks&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the background task
&lt;/h3&gt;

&lt;p&gt;To turn the function into a background task, we just need to decorate it with &lt;code&gt;@anvil.server.background_task&lt;/code&gt;.&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="nd"&gt;@anvil.server.background_task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_img&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Launch the background task
&lt;/h3&gt;

&lt;p&gt;We want to be able to launch the background task when the &lt;code&gt;generate_button&lt;/code&gt; is clicked. To do that, we need to create a client-callable function that launches the background task. &lt;/p&gt;

&lt;p&gt;In the ServerModule, add the following function&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="nd"&gt;@anvil.server.callable&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;launch_bg_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_img&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch_background_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;generate_image&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@anvil.server.callable&lt;/code&gt; decorator makes this function callable from our frontend code. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;anvil.server.launch_background_task&lt;/code&gt; returns a &lt;a href="https://anvil.works/docs/background-tasks/communicating-back#task-object" rel="noopener noreferrer"&gt;Task object&lt;/a&gt;, which we can use to check when the background task is finished and get the return value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Store the generated image in a Data Table
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://anvil.works/docs/server/background-tasks/communicating-back#communicating-back-to-the-main-program" rel="noopener noreferrer"&gt;We can't return a Media object directly from a background task&lt;/a&gt;, so we'll store the generated image in a Data Table.&lt;/p&gt;

&lt;p&gt;Choose Data from the Sidebar Menu and click "Add Table" to create a new &lt;a href="https://anvil.works/docs/data-tables?utm_source=crosspost:dev.to:/learn/tutorials/open-ai"&gt;Data Table&lt;/a&gt;. Call this table &lt;code&gt;tasks&lt;/code&gt; and add the following columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;image&lt;/code&gt; (Media column) - for the generated image&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;task_id&lt;/code&gt; (Text column) - for the background task ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv0bbkf0mhoz0b12zb5uu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv0bbkf0mhoz0b12zb5uu.gif" alt="Add a Data Table with a column for the image and a column for the task ID" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add the generated image to the Data Table
&lt;/h2&gt;

&lt;p&gt;When the image has finished generating, we need to add a row to the Data Table and return the row. Add the following lines of code to the bottom of the &lt;code&gt;generate_image&lt;/code&gt; function:&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;task_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;background_task_id&lt;/span&gt;
&lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app_tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;output_img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your finished ServerModule should now look something 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;anvil.secrets&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anvil.server&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anvil.media&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mimetypes&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OPEN_AI_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nd"&gt;@anvil.server.callable&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;launch_bg_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_img&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch_background_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;generate_image&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;

&lt;span class="nd"&gt;@anvil.server.background_task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_img&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;#get the MIME type and extension from the input_img
&lt;/span&gt;    &lt;span class="n"&gt;mime_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input_img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;
    &lt;span class="n"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mimetypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;guess_extension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mime_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;#create a temporary file path
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TempFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tmp_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;#add the extension to the temporary path
&lt;/span&gt;        &lt;span class="n"&gt;new_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tmp_path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-image-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="c1"&gt;#pass the path to the model
&lt;/span&gt;                &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&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;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Turn the drawing into a photorealistic image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;image_base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;b64_json&lt;/span&gt;
    &lt;span class="n"&gt;image_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_base64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Save the image to a file
&lt;/span&gt;    &lt;span class="n"&gt;output_img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BlobMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/jpeg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;image_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai-image.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;task_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;background_task_id&lt;/span&gt;
    &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app_tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;output_img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 6 - Generate the image and update the UI
&lt;/h2&gt;

&lt;p&gt;Now that our server code is finished, we can launch the background task from the &lt;code&gt;generate_button&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Launch the background task when the button is clicked
&lt;/h3&gt;

&lt;p&gt;Switch back to the Design view of MainForm and select the &lt;code&gt;generate_button&lt;/code&gt;. Click &lt;code&gt;on click event&lt;/code&gt; from the Object Palette to set up &lt;a href="https://anvil.works/docs/client/events?utm_source=crosspost:dev.to:/learn/tutorials/open-ai"&gt;a function that will run&lt;/a&gt; when the Button is clicked.&lt;/p&gt;

&lt;p&gt;From this function, we want to call &lt;code&gt;launch_bg_task&lt;/code&gt;, passing in the uploaded file. We also want to disable the &lt;code&gt;generate_button&lt;/code&gt; while the background task is running:&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="nd"&gt;@handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generate_button&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;click&lt;/span&gt;&lt;span class="sh"&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;generate_button_click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;This method is called when the button is clicked&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;launch_bg_task&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_loader_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add a progress indicator
&lt;/h3&gt;

&lt;p&gt;Switch back to Design view, and add a &lt;a href="https://anvil.works/docs/ui/app-themes/material-3/components#linearprogressindicator" rel="noopener noreferrer"&gt;LinearProgressIndicator component&lt;/a&gt; underneath the Image components. We'll use this to indicate to the user that the image is being generated. &lt;/p&gt;

&lt;p&gt;Click on the eye icon from the Object Palette to make the component invisible to start. We'll make it visible while the image is being generated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffvgio2huvyh8uxqo6rqu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffvgio2huvyh8uxqo6rqu.gif" alt="Add a LinearProgressIndicator to show that the image is generating" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Update the &lt;code&gt;generate_button_click&lt;/code&gt; event to make the &lt;code&gt;linear_progress_indicator&lt;/code&gt; visible when clicked:&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="nd"&gt;@handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generate_button&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;click&lt;/span&gt;&lt;span class="sh"&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;generate_button_click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;This method is called when the button is clicked&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;launch_bg_task&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_loader_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="c1"&gt;#indicate that the task is running
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linear_progress_indicator_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update the UI when the image has been generated
&lt;/h3&gt;

&lt;p&gt;We need to poll the server to check if the background task has finished running and our image has been generated. &lt;/p&gt;

&lt;p&gt;Drag and drop a &lt;a href="https://anvil.works/docs/ui/components/basic#timer" rel="noopener noreferrer"&gt;Timer component&lt;/a&gt; onto the Form. It will appear at the top because it's an invisible component. Timers have an &lt;code&gt;interval&lt;/code&gt; property that determines how frequently they raise a &lt;code&gt;tick&lt;/code&gt; event. We can then write a function that runs every time the Timer "ticks".&lt;/p&gt;

&lt;p&gt;From the Properties Panel, set the Timer's &lt;code&gt;interval&lt;/code&gt; to 0. We don't want it to start ticking until we tell it to. &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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5lpa8dk4r681k06mt0xw.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5lpa8dk4r681k06mt0xw.png" alt="Screenshot of the Properties Panel with the interval property set to 0" width="379" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the Object Palette, set up a &lt;code&gt;tick&lt;/code&gt; event handler for the Timer. In here, we want to check if the background task has finished, and if so, we'll update the UI accordingly. We can also use &lt;code&gt;with anvil.server.no_loading_indicator&lt;/code&gt; to stop Anvil's loading spinner from appearing every time we poll the server.&lt;/p&gt;

&lt;p&gt;Your &lt;code&gt;timer_1_tick&lt;/code&gt; function should 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="nd"&gt;@handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timer_1&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;tick&lt;/span&gt;&lt;span class="sh"&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;timer_1_tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;This method is called Every [interval] seconds. Does not trigger if [interval] is 0.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;no_loading_indicator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;#check if the background task has finished
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_completed&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="c1"&gt;#get the image from the Data Table row 
&lt;/span&gt;                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generated_img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_return_value&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="c1"&gt;#display the generated image
&lt;/span&gt;                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output_img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generated_image&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linear_progress_indicator_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output_img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
                &lt;span class="c1"&gt;#stop the timer from ticking
&lt;/span&gt;                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timer_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 7 - Make the generated image downloadable
&lt;/h2&gt;

&lt;p&gt;Finally, we can add the ability to download the generated image. &lt;/p&gt;

&lt;p&gt;Back in Design view, add a Button to the page and name it &lt;code&gt;download_button&lt;/code&gt;. Centre align the Button and make it invisible. Change it's &lt;code&gt;appearance&lt;/code&gt; to &lt;code&gt;tonal&lt;/code&gt; and set its icon to &lt;code&gt;mi:downlaod&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy4wugblms90yd4jfcz5w.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy4wugblms90yd4jfcz5w.gif" alt="Add a Button to download the generated image" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set up a &lt;code&gt;click&lt;/code&gt; event handler for the &lt;code&gt;download_button&lt;/code&gt;, and inside that function, call &lt;code&gt;anvil.media.download(self.generated_image)&lt;/code&gt;.&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="nd"&gt;@handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;download_button&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;click&lt;/span&gt;&lt;span class="sh"&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;download_button_click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;This method is called when the component is clicked.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generated_img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the top of the Form code, make sure to import &lt;code&gt;anvil.media&lt;/code&gt;:&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;anvil.media&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 8 - Test and publish
&lt;/h2&gt;

&lt;p&gt;It's now time to test out the app! Click the green Run button, upload a drawing and click "Turn into photo". Wait for the AI to generate a photorealistic image and then try downloading it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fanvil-website-static.s3.eu-west-2.amazonaws.com%2Flearn%2Ftutorials%2Fopen-ai%2Ftest-app.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fanvil-website-static.s3.eu-west-2.amazonaws.com%2Flearn%2Ftutorials%2Fopen-ai%2Ftest-app.gif" alt="Testing the final app" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To publish your app to the web, click "Publish" at the top right of the editor, then "Publish this app". You'll get a URL you can share with others.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy5y31rrqfkapsfyb7m93.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy5y31rrqfkapsfyb7m93.gif" alt="Publishing the app" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What we built
&lt;/h2&gt;

&lt;p&gt;We built a complete web app that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let's you upload an image&lt;/li&gt;
&lt;li&gt;Connects to OpenAI's Images API&lt;/li&gt;
&lt;li&gt;Uses a background task to call the API and generate an image&lt;/li&gt;
&lt;li&gt;Stores generated images in a Data Table&lt;/li&gt;
&lt;li&gt;Displays the generated image on the UI&lt;/li&gt;
&lt;li&gt;Allows users to download their results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We did it all in Python and deployed it instantly to the web!&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;There's a lot you can do to extend this app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Error handling&lt;/strong&gt;: Catch errors from OpenAI and display helpful messages to users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User accounts&lt;/strong&gt;: Add login functionality so users can see their previously generated images&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments&lt;/strong&gt;: Use Anvil's Stripe integration to charge for API usage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Different models&lt;/strong&gt;: Let users choose between different OpenAI image models or prompts&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  New to Anvil?
&lt;/h2&gt;

&lt;p&gt;If you're new here, welcome! &lt;a href="https://anvil.works?utm_source=crosspost:dev.to:/learn/tutorials/open-ai"&gt;Anvil&lt;/a&gt; is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks - just &lt;strong&gt;build it all in Python&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Yes - Python that &lt;a href="https://anvil.works/docs/client/python?utm_source=crosspost:dev.to:/learn/tutorials/open-ai"&gt;runs in the browser&lt;/a&gt;. Python that &lt;a href="https://anvil.works/docs/server?utm_source=crosspost:dev.to:/learn/tutorials/open-ai"&gt;runs on the server&lt;/a&gt;. Python that &lt;a href="https://anvil.works/docs/client?utm_source=crosspost:dev.to:/learn/tutorials/open-ai"&gt;builds your UI&lt;/a&gt;. A &lt;a href="https://anvil.works/docs/client/adding-ui-elements?utm_source=crosspost:dev.to:/learn/tutorials/open-ai"&gt;drag-and-drop UI editor&lt;/a&gt;. We even have a built-in &lt;a href="https://anvil.works/docs/data-tables?utm_source=crosspost:dev.to:/learn/tutorials/open-ai"&gt;Python database&lt;/a&gt;, in case you don't have your own.&lt;/p&gt;

&lt;p&gt;Why not have a play with the app builder? &lt;strong&gt;It's free!&lt;/strong&gt; Click here to get started:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/learn/tutorials/open-ai"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz42y2qw4zogw3beujfg3.png" alt="Get building" width="306" height="84"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>openai</category>
      <category>webdev</category>
      <category>python</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Build with AI in the Anvil Editor</title>
      <dc:creator>Love Otudor</dc:creator>
      <pubDate>Wed, 01 Jul 2026 15:19:19 +0000</pubDate>
      <link>https://dev.to/anvil/build-with-ai-in-the-anvil-editor-im3</link>
      <guid>https://dev.to/anvil/build-with-ai-in-the-anvil-editor-im3</guid>
      <description>&lt;p&gt;Anvil's AI integration is here! You can now connect to and chat with your favourite AI agent directly from the Anvil Editor.&lt;/p&gt;

&lt;p&gt;Whether you're starting a new project or improving an existing one, AI agents in Anvil understand how Anvil apps are built. That means less time explaining your framework to an AI and more time building. You can ask it to build an entire app from a prompt, or bring it in as a coding assistant to fix bugs, implement features, or explain unfamiliar code.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz3tnldaibci3t5t26cqy.gif" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz3tnldaibci3t5t26cqy.gif" alt="Building an app with the AI agent in the Anvil Editor" width="720" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Read on to find out more, or start building now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/ai-in-anvil"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff5hbzvtv9lo0nqbpwhmu.png" alt="Start building" width="306" height="84"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect to your preferred Agent with no Upcharge
&lt;/h2&gt;

&lt;p&gt;Connect your existing ChatGPT or Claude account from the Anvil Editor and start chatting straight away. Anvil doesn't add any extra charges, so you only pay for your provider as normal, whether you're on a free or paid plan.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn5lu6mcqk5fhfq2qvfua.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn5lu6mcqk5fhfq2qvfua.png" alt="Connect to your model provider" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://anvil.works/enterprise?utm_source=crosspost:dev.to:/blog/ai-in-anvil"&gt;Anvil Enterprise&lt;/a&gt;, you can connect your own enterprise model provider and private endpoints instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate Apps from a Prompt
&lt;/h2&gt;

&lt;p&gt;Describe the app you want to build using plain text, recorded audio, or images, and the agent will build it for you. It can create Forms, layouts, server code, modules, Data Tables, and any other Anvil services your app needs, all in one go. Because the agent understands how Anvil apps are structured, it generates apps that feel familiar, using Python and tools you already know.&lt;/p&gt;

&lt;p&gt;Need a particular look and feel? Instead of describing layouts, spacing, and colours in words, you can attach images to your prompt to guide the design of your app. The agent uses those images to create interfaces that are closer to what you have in mind from the start.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8628rf9zmj33sfilca3b.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8628rf9zmj33sfilca3b.png" alt="Prompt with reference images and generated design" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tweak the Results
&lt;/h2&gt;

&lt;p&gt;The agent writes real, editable code, and every change it makes can be modified or reverted in the Anvil Editor. You can still drag and drop components into the generated UI in the designer like you always have, or edit the properties of the generated UI elements in the Properties panel. The same goes for your Forms, modules, Data Tables, and everything else the agent creates.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwcvfbht0wfm5sfsxo024.gif" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwcvfbht0wfm5sfsxo024.gif" alt="Edit generated design in the drag and drop designer" width="600" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also ask the agent to make changes to the code from chat. You can even mention specific Forms, modules, layouts, or assets in the chat, so the agent knows exactly what you're referring to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the Agent on your existing Apps
&lt;/h2&gt;

&lt;p&gt;The agent works just as well on existing apps. Ask it to implement new features, fix bugs, suggest improvements, or explain unfamiliar code. You can also use agent commands like &lt;code&gt;/review-commit&lt;/code&gt; to review code changes in a particular commit.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flgztyhjkl28zjlj1n6yu.gif" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flgztyhjkl28zjlj1n6yu.gif" alt="Review a commit in an existing app" width="720" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because the agent reads and understands your existing Anvil code, its changes and suggestions fit naturally into your app's architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;Open an app in the Anvil Editor, click the  &lt;strong&gt;✧&lt;/strong&gt;  icon in the sidebar, connect your AI provider, and start chatting.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzuo6uh4kih9svijmcl5s.gif" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzuo6uh4kih9svijmcl5s.gif" alt="Create an app and start prompting" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For tips on getting the best results, check out our &lt;a href="https://anvil.works/docs/ai?utm_source=crosspost:dev.to:/blog/ai-in-anvil"&gt;docs&lt;/a&gt; and &lt;a href="https://anvil.works/docs/how-to/prompting-best-practices?utm_source=crosspost:dev.to:/blog/ai-in-anvil"&gt;prompting best practices&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Looking forward to what you build!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/ai-in-anvil"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff5hbzvtv9lo0nqbpwhmu.png" alt="Start building" width="306" height="84"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  More about Anvil
&lt;/h2&gt;

&lt;p&gt;If you're new here, welcome! &lt;a href="https://anvil.works?utm_source=crosspost:dev.to:/blog/ai-in-anvil"&gt;Anvil&lt;/a&gt; is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks – just &lt;strong&gt;build it all in Python&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/ai-in-anvil"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftlwt9hvvr64hnd8sx5xo.png" alt="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/ai-in-anvil" width="381" height="85"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>python</category>
      <category>agents</category>
    </item>
    <item>
      <title>Build a Web App with MongoDB Using Just Python</title>
      <dc:creator>Love Otudor</dc:creator>
      <pubDate>Tue, 13 Jan 2026 16:37:03 +0000</pubDate>
      <link>https://dev.to/anvil/build-a-web-app-with-mongodb-using-just-python-321a</link>
      <guid>https://dev.to/anvil/build-a-web-app-with-mongodb-using-just-python-321a</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Build and deploy a full-stack web app that interacts with your external database using only Python. No HTML, CSS, or JavaScript required.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/OX5L_8hH7xM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;In this tutorial, we guide you step-by-step on how to create a &lt;strong&gt;Customer Relationship Management (CRM) system&lt;/strong&gt; connected to a &lt;strong&gt;MongoDB Atlas cluster&lt;/strong&gt;, starting completely from scratch. You'll see how to build both the frontend and backend in Python using Anvil and deploy it to the web with &lt;strong&gt;two clicks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build#clone:UJ3PW3CWLBQVGO6Q%3d3WYYRMWKPSTUZJV2NSW7ACBK?utm_source=crosspost:dev.to:/blog/crm-with-mongodb"&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%2Fpkvbvqp0y0tdl5jyjcic.png" alt="Open in Anvil" width="461" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/?utm_source=crosspost:dev.to:/blog/crm-with-mongodb"&gt;&lt;strong&gt;Anvil&lt;/strong&gt;&lt;/a&gt; lets you build full-stack web apps with all the power of Python and the flexibility of code.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installing Python packages in your Server Module&lt;/li&gt;
&lt;li&gt;Securing passwords with App Secrets&lt;/li&gt;
&lt;li&gt;Communicating between Forms with custom events&lt;/li&gt;
&lt;li&gt;Authentication with the Users Service&lt;/li&gt;
&lt;li&gt;Deploying your app on Anvil's free hosting&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  More about Anvil
&lt;/h2&gt;

&lt;p&gt;If you're new here, welcome! &lt;a href="https://anvil.works/?utm_source=crosspost:dev.to:/blog/crm-with-mongodb"&gt;Anvil&lt;/a&gt; is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks – just &lt;strong&gt;build it all in Python&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/crm-with-mongodb"&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%2Fz95af95e963brxl2o9zt.png" alt="Sign up for Anvil" width="800" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>anvil</category>
      <category>webdev</category>
      <category>mongodb</category>
    </item>
    <item>
      <title>Case Study: How Chris Houston Built a BI Tool with Anvil</title>
      <dc:creator>Love Otudor</dc:creator>
      <pubDate>Fri, 11 Jul 2025 14:45:28 +0000</pubDate>
      <link>https://dev.to/anvil/case-study-how-chris-houston-built-a-bi-tool-with-anvil-4l7n</link>
      <guid>https://dev.to/anvil/case-study-how-chris-houston-built-a-bi-tool-with-anvil-4l7n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Query Streams transforms Excel and Google Sheets into powerful BI tools by enabling real-time database queries directly within the spreadsheet—no coding required. Built as a nights-and-weekends side project by Chris using Anvil, Query Streams soft launched just four weeks ago and is already being used by data analysts across multiple companies.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Built by a solo developer&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Production-ready in under 5 months&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Google Sheets add-on is live&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Microsoft Excel add-in is live&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Challenge: When BI Tools Meet Reality
&lt;/h2&gt;

&lt;p&gt;Every data team knows the cycle: Companies invest heavily in BI tools because everyone loves beautiful dashboards. Yet somehow, the most common follow-up question is still:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"The dashboards look great… uh, how do I export this to a spreadsheet?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And then it all ends up in a spreadsheet anyway. Despite years of trying to move away from Excel and Google Sheets, spreadsheets are more popular than ever. According to a 2025 survey by &lt;a href="https://www.alteryx.com/about-us/newsroom/press-release/new-research-reveals-that-ai-brings-productivity-gains-but-reliance-on-spreadsheets-puts-data-quality-at-risk" rel="noopener noreferrer"&gt;Alteryx&lt;/a&gt;, &lt;strong&gt;76% of data analysts still use spreadsheets as their primary tool&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What If We Stopped Fighting Spreadsheets?
&lt;/h3&gt;

&lt;p&gt;Chris had been thinking about this for years. What if, instead of replacing spreadsheets, we empowered them?&lt;br&gt;
&lt;em&gt;“What if we didn’t try to replace spreadsheets with BI tools? What if we made spreadsheets the BI tool?”&lt;/em&gt;&lt;br&gt;
This idea stuck with him until just after Thanksgiving 2024, when he started building a solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Turn Spreadsheets Into the BI Tool
&lt;/h2&gt;

&lt;p&gt;Query Streams is a real-time data integration platform that lets users run SQL queries from Google Sheets and Excel directly against remote databases, without exposing credentials, setting up VPNs, or configuring firewalls. It supports SQL Server, MySQL, PostgreSQL, SQLite, Access, and other databases. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Share and receive queries&lt;/li&gt;
&lt;li&gt;Build live dashboards&lt;/li&gt;
&lt;li&gt;Securely expose specific database queries&lt;/li&gt;
&lt;li&gt;Choose what data to share and with whom&lt;/li&gt;
&lt;/ul&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%2Fqxj7yb3ru10rddalwv9f.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%2Fqxj7yb3ru10rddalwv9f.png" alt="Query Streams Image" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No hassle. No copy-pasting. No manual exports. Just real-time data, right where analysts already work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Query Streams: Smarter Tools, Built Faster with Anvil
&lt;/h2&gt;

&lt;p&gt;The most challenging part of building Query Streams was building the network agent in C# and other technologies that Chris wasn’t familiar with. Once he overcame that technical hurdle, building the web app in Anvil was straightforward.&lt;/p&gt;

&lt;p&gt;With minimal experience in HTML, CSS, and JavaScript, Chris leaned heavily on &lt;a href="https://anvil.works/docs/ui/custom-components/html-components?utm_source=crosspost:dev.to:/case-studies/querystreams"&gt;Anvil’s custom HTML support &lt;/a&gt; and Anvil components to bring his unique UI ideas to life.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I thought we were locked into the Anvil components on the designer. But as I explored custom components in Anvil, I realised that, wow, you can build anything in Anvil.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/chris_houston_7d34a375ece"&gt;&lt;strong&gt;Chris Houston&lt;/strong&gt;&lt;/a&gt; — &lt;em&gt;Founder and CEO, Query Streams&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In just four months, Query Streams was ready to use. Built entirely in Chris's spare time while working a full-time job. Since Anvil is web-based, he could work on Query Streams wherever and whenever he had time—whether at home, in early mornings, or even while travelling with just his laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results: From Idea to Impact
&lt;/h2&gt;

&lt;p&gt;Query Streams has begun gaining traction even before its official launch. In the four weeks since its soft launch with the Google Sheets add-on, teams and analysts across several organisations have started using the platform.&lt;/p&gt;

&lt;p&gt;Now, with the Excel add-in also live on the Microsoft AppSource marketplace, Query Streams has expanded its reach to both major spreadsheet platforms. With both add-ons available, Chris is preparing for the official launch and broader publicity efforts. You can follow along and get updates on &lt;a href="https://x.com/querystreams" rel="noopener noreferrer"&gt;X&lt;/a&gt; and &lt;a href="https://www.linkedin.com/company/querystreams/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Anvil is like a Swiss army knife of awesome! It allowed me to build Query Streams with a lot of ease while working alone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/chris_houston_7d34a375ece"&gt;&lt;strong&gt;Chris Houston&lt;/strong&gt;&lt;/a&gt; — &lt;em&gt;Founder and CEO, Query Streams&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fngnel9z8255z7xkbw135.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%2Fngnel9z8255z7xkbw135.png" alt=" " width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More Information
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Query Streams&lt;/strong&gt; lets you run real-time database queries directly from Excel or Google Sheets — no code required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.querystreams.com/" rel="noopener noreferrer"&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%2Fle7jxf944svyjd9kxnba.png" alt=" " width="608" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re new to &lt;strong&gt;Anvil&lt;/strong&gt; and want to see how you can build powerful web apps using just Python, up to 7× faster than traditional development:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works?utm_source=crosspost:dev.to:/case-studies/querystreams"&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%2Flywzy5mdmou1v27k104o.png" alt=" " width="608" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have questions or want to get in touch? Visit &lt;a href="https://anvil.works/contact?utm_source=crosspost:dev.to:/case-studies/querystreams"&gt;anvil.works/contact&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>businessintelligence</category>
      <category>webdev</category>
      <category>python</category>
      <category>startup</category>
    </item>
    <item>
      <title>Secure Team Collaboration in Anvil</title>
      <dc:creator>Love Otudor</dc:creator>
      <pubDate>Fri, 30 May 2025 14:48:49 +0000</pubDate>
      <link>https://dev.to/anvil/secure-team-collaboration-in-anvil-3680</link>
      <guid>https://dev.to/anvil/secure-team-collaboration-in-anvil-3680</guid>
      <description>&lt;p&gt;You asked and we answered! We put together a practical how-to guide on collaborating as a team in Anvil. Building amazing Anvil apps is even better with your team by your side, and we've developed foolproof methods to make this process hassle-free.&lt;/p&gt;

&lt;p&gt;This guide walks you through how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up secure, real-world workflows for collaborating on Anvil apps while keeping your production environment isolated and safe.&lt;/li&gt;
&lt;li&gt;Separate development and production environments to ensure only well-tested and reviewed changes reach your users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether your team prefers working together on a shared Anvil app or on individual ones, this step-by-step guide will help you implement Git-based workflows that improve code quality and team efficiency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/docs/how-to/collaborate-in-anvil?utm_source=crosspost:devto:/blog/secure-team-collaboration" rel="noopener noreferrer"&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%2Fwrc91ohsy7sslyw2vqwz.png" alt="Check out the full guide here" width="379" height="86"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  More about&amp;nbsp;Anvil
&lt;/h2&gt;

&lt;p&gt;If you're new here, welcome! &lt;a href="https://anvil.works?utm_source=crosspost:devto:/blog/secure-team-collaboration" rel="noopener noreferrer"&gt;Anvil&lt;/a&gt; is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks - just build it all in Python.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build?utm_source=crosspost:devto:/blog/secure-team-collaboration" rel="noopener noreferrer"&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%2Fsw66qxlkopgppg50i2i7.png" alt="Sign up for Anvil" width="800" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>anvil</category>
      <category>webdev</category>
      <category>git</category>
    </item>
    <item>
      <title>Building a Game App with Just Python</title>
      <dc:creator>Love Otudor</dc:creator>
      <pubDate>Wed, 16 Apr 2025 16:51:04 +0000</pubDate>
      <link>https://dev.to/anvil/building-a-game-app-with-just-python-4fbm</link>
      <guid>https://dev.to/anvil/building-a-game-app-with-just-python-4fbm</guid>
      <description>&lt;p&gt;"&lt;strong&gt;Guess the Sketch&lt;/strong&gt;" is a multiplayer game where you draw sketches and challenge friends to guess what they are. Players earn points for correct guesses, with fewer attempts leading to higher scores. Each round, one player is chosen to sketch, while the others try to guess as quickly as possible.&lt;/p&gt;

&lt;p&gt;I built this game using Python and Anvil, and it was surprisingly easy! I didn’t need to write HTML, CSS, or JavaScript. In this post, I will walk you through the key parts of building Guess the Sketch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
Building the UI,&lt;/li&gt;
&lt;li&gt;
Saving and sending sketches,&lt;/li&gt;
&lt;li&gt;
Sending invites,&lt;/li&gt;
&lt;li&gt;
Managing players and scores, and&lt;/li&gt;
&lt;li&gt;
Publishing and sharing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's get started!&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%2Fd4m8wl12p5mv20ylnzts.gif" 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%2Fd4m8wl12p5mv20ylnzts.gif" alt="Game Demo" width="1114" height="868"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build#clone:LBYMHJ3D5VHWUS3G=V2TWD2XDO3Z6MHCCXTEPXQVF" rel="noopener noreferrer"&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%2Fpkvbvqp0y0tdl5jyjcic.png" alt="Open in Anvil" width="461" height="140"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Building the UI
&lt;/h2&gt;

&lt;p&gt;With Anvil’s drag-and-drop editor, building the UI was very intuitive. Instead of writing HTML and CSS, I simply dragged components from the &lt;a href="https://anvil.works/docs/editor/form-editor?utm_source=crosspost:dev.to:/blog/guess-the-sketch-game#toolbox"&gt;Toolbox&lt;/a&gt; onto the Form and arranged them as needed. Here is how easy it was to build the &lt;strong&gt;SketchScreen&lt;/strong&gt;, where players create and name their sketches.&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%2Fezl5ads6zoss1frjfeq7.gif" 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%2Fezl5ads6zoss1frjfeq7.gif" alt="Creating the Sketch Screen in Anvil" width="640" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anvil’s &lt;a href="https://anvil.works/docs/ui/components/canvas?utm_source=crosspost:dev.to:/blog/guess-the-sketch-game"&gt;Canvas component&lt;/a&gt; provided a seamless way to add a drawing area to the game. It is a Python wrapper around the HTML &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element, with built-in &lt;a href="https://anvil.works/docs/api/anvil?utm_source=crosspost:dev.to:/blog/guess-the-sketch-game#Canvas_attributes"&gt;methods and event handlers&lt;/a&gt; that handle drawing effortlessly. I used the &lt;code&gt;mouse_down&lt;/code&gt;, &lt;code&gt;mouse_move&lt;/code&gt;, and &lt;code&gt;mouse_up&lt;/code&gt; events to track real-time mouse movement and draw lines on the canvas. Adding an eraser was as simple as setting the brush colour to match the background of the drawing area.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want to learn more about the Canvas component? Check out this Canvas Tutorial:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/learn/tutorials/canvas?utm_source=crosspost:dev.to:/blog/guess-the-sketch-game"&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%2Fnv7i6eqqgwpg06ac8dbz.png" alt="Canvas Tutorial" width="745" height="90"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Saving and Sending Sketches
&lt;/h2&gt;

&lt;p&gt;Once a player finishes drawing and clicks the &lt;strong&gt;Send Sketch&lt;/strong&gt; button, the image is retrieved from the Canvas using the &lt;code&gt;.get_image()&lt;/code&gt; method:&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;sketch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;canvas_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_image&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This converts the sketch into a &lt;a href="https://anvil.works/docs/working-with-files/media?utm_source=crosspost:dev.to:/blog/guess-the-sketch-game#media-objects"&gt;Media object&lt;/a&gt;, which is then stored in a &lt;a href="https://anvil.works/docs/data-tables?utm_source=crosspost:dev.to:/blog/guess-the-sketch-game"&gt;Data Table&lt;/a&gt; for later retrieval.&lt;/p&gt;

&lt;p&gt;When a player submits a sketch, one of two things happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A new game is created, along with its first round (if the player is starting a new game).&lt;/li&gt;
&lt;li&gt;A new round is added to an existing game, which happens when a player joins an ongoing game and takes their turn.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s how I handled both scenarios:&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;sketch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;canvas_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_image&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Add a round to an existing game
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;add_game_round&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sketch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sketch_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/invite/:id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&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;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game_id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Create a new game
&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;game_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;start_game&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sketch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sketch_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/invite/:id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&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;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;game_id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In both cases, a new game round is created and linked to the current game, whether the game is newly created or ongoing.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://anvil.works/docs/data-tables?utm_source=crosspost:dev.to:/blog/guess-the-sketch-game"&gt;Data Tables&lt;/a&gt;, creating a round is as simple as inserting a new row into the Rounds Table and adding the round ID to the corresponding game's &lt;code&gt;rounds&lt;/code&gt; column (which stores a list of round IDs for that game).&lt;/p&gt;

&lt;p&gt;Here’s how I create and link a round to an existing game:&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="nd"&gt;@anvil.server.callable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;require_user&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_game_round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;game_id&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="n"&gt;sketch_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;# Other game logic...
&lt;/span&gt;  &lt;span class="n"&gt;game&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app_tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;game_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;round&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app_tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rounds_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;game_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;game_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sketch_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sketch_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rounds&lt;/span&gt;&lt;span class="sh"&gt;'&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="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="c1"&gt;# Other game logic... 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Sending Invites
&lt;/h2&gt;

&lt;p&gt;Anvil has a built-in  &lt;a href="https://anvil.works/docs/email?utm_source=crosspost:dev.to:/blog/guess-the-sketch-game"&gt;Email Service&lt;/a&gt;, which makes sending invites as simple as a single function call. I used &lt;code&gt;anvil.email.send()&lt;/code&gt; to email friends a game invite with a preview of the sketch as an  &lt;a href="https://anvil.works/docs/email/attachments?utm_source=crosspost:dev.to:/blog/guess-the-sketch-game#using-inline-attachments"&gt;inline attachment&lt;/a&gt;:&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;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;bcc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;emails&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;from_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; 
    &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is inviting you to Guess the Sketch!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
      Hey,
      I just created this sketch:&amp;lt;br&amp;gt;
      &amp;lt;img src=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cid:sketch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;lt;br&amp;gt;
      Think you can figure out what it is? Click this link to play: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; 
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;inline_attachments&lt;/span&gt; &lt;span class="o"&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;sketch&lt;/span&gt;&lt;span class="sh"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Players receive this email with a preview of the sketch right in their inbox. Here’s how it looks:&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%2F1vuarvjrg1mda7qi0r2b.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%2F1vuarvjrg1mda7qi0r2b.png" alt="Invite Email" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This makes it really easy for players to see the sketch before they click the link to play.&lt;/p&gt;




&lt;h2&gt;
  
  
  Managing Players and Scores
&lt;/h2&gt;

&lt;p&gt;To manage player authentication, I used Anvil’s &lt;a href="https://anvil.works/docs/users?utm_source=crosspost:dev.to:/blog/guess-the-sketch-game"&gt;Users Service&lt;/a&gt;, which automatically created a &lt;strong&gt;Users Table&lt;/strong&gt; that stored player details.&lt;/p&gt;

&lt;p&gt;With just one line, I could add a login Form:&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;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login_with_form&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows players to sign up and log in with their Google accounts, making it easy to track their progress across games. Each new user is automatically added as a row in the Users Table.&lt;/p&gt;

&lt;p&gt;At any point in the app, I can access the currently logged-in player by calling:&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;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tracking Player Scores
&lt;/h3&gt;

&lt;p&gt;To track scores, I added a &lt;code&gt;points&lt;/code&gt; column to the automatically generated &lt;strong&gt;Users Table&lt;/strong&gt;, where I store the total points each player accumulates.&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%2Fynl7wag7d2rcmxeyhpeb.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%2Fynl7wag7d2rcmxeyhpeb.png" alt="Users Table" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For every point a player earns in a game, I update the player’s total score:&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;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Other game logic...
&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;points&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
      &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;points&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;points_gained&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;points&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;points_gained&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that scores remain updated and persistent across game sessions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Publishing and Sharing
&lt;/h2&gt;

&lt;p&gt;Once everything was ready, publishing the game was just one click on the  &lt;a href="https://anvil.works/docs/deployment/environments?utm_source=crosspost:dev.to:/blog/guess-the-sketch-game"&gt;Publish button&lt;/a&gt; in the Anvil editor. I instantly got a public URL, that I could share with friends to start playing right away!&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%2Fd4m8wl12p5mv20ylnzts.gif" 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%2Fd4m8wl12p5mv20ylnzts.gif" alt="Full Game Demo" width="1114" height="868"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to Build Your Own?
&lt;/h2&gt;

&lt;p&gt;These are just some of the highlights, but you can clone the full project and explore how it works yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build#clone:LBYMHJ3D5VHWUS3G=V2TWD2XDO3Z6MHCCXTEPXQVF" rel="noopener noreferrer"&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%2Fo74xkd1d2ganf56ftrnp.png" alt="Image description" width="608" height="140"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  More about Anvil
&lt;/h2&gt;

&lt;p&gt;If you're new here, welcome! &lt;a href="https://anvil.works/?utm_source=crosspost:dev.to:/blog/guess-the-sketch-game"&gt;Anvil&lt;/a&gt; is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks – just &lt;strong&gt;build it all in Python&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/guess-the-sketch-game"&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%2Fow63l4svon8tal3uola3.png" alt="Sign up for Anvil" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>python</category>
      <category>anvil</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Introducing the Interactive Debugger in Anvil</title>
      <dc:creator>Love Otudor</dc:creator>
      <pubDate>Wed, 26 Feb 2025 09:47:49 +0000</pubDate>
      <link>https://dev.to/anvil/introducing-the-interactive-debugger-in-anvil-10kn</link>
      <guid>https://dev.to/anvil/introducing-the-interactive-debugger-in-anvil-10kn</guid>
      <description>&lt;h2&gt;
  
  
  Say Hello to the Interactive Debugger in Anvil
&lt;/h2&gt;

&lt;p&gt;We are very excited to announce the first release of &lt;strong&gt;the Interactive Debugger&lt;/strong&gt; in Anvil. Say goodbye to countless &lt;code&gt;print&lt;/code&gt; statements!&lt;/p&gt;

&lt;p&gt;With the Interactive Debugger, you can now pause your code as it executes, inspect variables, and gain deeper insights into what’s happening in your code—whether on the client or server side.&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%2Fgjo3kcs35m3i2p9i79yq.gif" 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%2Fgjo3kcs35m3i2p9i79yq.gif" alt="Debugger Demo" width="1024" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What is the Interactive Debugger?
&lt;/h2&gt;

&lt;p&gt;The Interactive Debugger lets you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add breakpoints&lt;/strong&gt; to your code to pause execution at specific lines.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inspect variables&lt;/strong&gt; and &lt;strong&gt;values&lt;/strong&gt; at runtime.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debug&lt;/strong&gt; both &lt;strong&gt;client-side&lt;/strong&gt; and &lt;strong&gt;server-side code&lt;/strong&gt;, including &lt;strong&gt;background tasks&lt;/strong&gt; and &lt;strong&gt;HTTP endpoints&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Getting started is simple:&lt;/strong&gt; Click in the gutter next to any executable line of code to add a breakpoint and then run your code. When this breakpoint is hit, the debugger pauses execution and opens the Debugger Window, where you can easily view and inspect all the variables in your code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get Deeper Insights into your Code in real time
&lt;/h2&gt;

&lt;p&gt;With the debugger, you can add and remove breakpoints even while execution is paused, allowing you to adjust your debugging strategy as needed. The debugger toolbar gives you complete control to resume, restart, or stop execution, step over breakpoints, and step into or out of functions effortlessly. This makes it easy to navigate through your code seamlessly and gain a deeper understanding of its behaviour.&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%2Fho05ikm5nko21y3v1162.gif" 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%2Fho05ikm5nko21y3v1162.gif" alt="Add and remove breakpoints" width="1512" height="819"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Easy Debugging Across Client and Server Code
&lt;/h2&gt;

&lt;p&gt;Anvil’s debugger allows you to step into server code directly from client code. For example, you can start in your client-side form code and step directly into your server code while showing the entire call stack on the browser frame.&lt;/p&gt;

&lt;p&gt;Here is a demo illustrating how you can step into server code from client code seamlessly:&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%2F77ho3wq2ct1iuxfk7wqh.gif" 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%2F77ho3wq2ct1iuxfk7wqh.gif" alt="Step into server code" width="760" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Debug Background Tasks and HTTP Endpoints
&lt;/h2&gt;

&lt;p&gt;Debugging background tasks and HTTP endpoints in server modules has never been easier. By adding breakpoints to your background tasks or endpoint calls, you can pause execution, inspect variables, and diagnose issues with accuracy and confidence.&lt;/p&gt;




&lt;h2&gt;
  
  
  Availability
&lt;/h2&gt;

&lt;p&gt;The debugger is available to everyone!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free users can use Anvil’s debugger only on the client-side (Form) code.&lt;/li&gt;
&lt;li&gt;Client-side and server-side debugging is available on a Business Plan or higher.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;This is just the first version of the Anvil Debugger and we have plans for more interesting features, so stay tuned! In the meantime, we would love to hear your feedback! Try out the debugger today and let us know how it works for you.&lt;/p&gt;

&lt;p&gt;For a deep dive into debugging with the interactive debugger in Anvil, as well as additional tools to enhance your debugging process, check out our &lt;a href="https://anvil.works/docs/debugger?utm_source=crosspost:dev.to:/blog/announcing-the-debugger"&gt;Debugging Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy debugging!&lt;/p&gt;




&lt;h2&gt;
  
  
  More about Anvil
&lt;/h2&gt;

&lt;p&gt;If you're new here, welcome! &lt;a href="https://anvil.works?utm_source=crosspost:dev.to:/blog/announcing-the-debugger"&gt;Anvil&lt;/a&gt; is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks – just &lt;strong&gt;build it all in Python&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works?utm_source=crosspost:dev.to:/blog/announcing-the-debugger"&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%2Fow63l4svon8tal3uola3.png" alt="Sign up for Anvil" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>python</category>
      <category>webapp</category>
    </item>
  </channel>
</rss>
