Shiny Assistant for Python – How to Build Shiny for Python Apps with GPT and GenerativeAI

This article was first published on Appsilon | Enterprise R Shiny Dashboards , and kindly contributed to python-bloggers. (You can report issue about the content on this page here)
Want to share your content on python-bloggers? click here.

We were impressed with Shiny Assistant’s performance for R Shiny apps – but you already know that.

Will Shiny Assistant for Python impress us the same way? You’re about to find out. This article will show you how we used Shiny Assistant to build a Python application around the earthquakes in Japan dataset. This article will not show you a general introduction to Shiny Assistant – we already have that covered.

If you’re considering trying out Shiny Assistant for Python, keep these two things in mind:

  • Shiny Assistant is still in open beta (as of September 2024). Feel free to join the waitlist.
  • Shiny for Python is around a decade younger than R Shiny. The community is smaller and there are just fewer examples and questions online. The documentation is superb, however.

We’ll keep both of these in mind as we write the prompts and give our final verdict.

Keep in mind that the article you’re about to read is long. Very long. It’s a journey through ups and downs. If you’re only interested in our experience in using Shiny Assistant for Python, scroll down to the Pros and Cons section.

Shiny Assistant wasn’t the only thing announced at posit::conf 2024. Read about our experiences from the conference.

Table of Contents:

Should You Be Skeptical About Shiny Assistant for Python?

As we said in the introduction, Shiny for Python is a new technology.

Like 2023 new.

On the other hand, R Shiny was released way back when the Higgs boson was discovered and Gangnam Style was all the rage. That was in 2012.

Nostalgia aside, Shiny for Python doesn’t have as many users as R Shiny, and it’s not used by the same types of developers and companies. In theory, you shouldn’t expect the Assistant to offer the same level of performance and refinement between the two.

Nevertheless, you can switch from R to Python in Shiny Assistant with a single toggle (assuming you have access to the beta):

Image 1 – Shiny Assistant starting screen

Since the switch is so effortless and doesn’t raise any disclaimers in the app, it left us wondering. So, we decided to build a complete app from scratch. We’ll try to follow the same steps we used for R Shiny Assistant to keep things fair, while keeping these differences in mind.

Shiny Assistant in Action – Let’s Build a Shiny for Python App from Scratch

This is the app we managed to build in 19 iterations (prompts):

Image 2 – Final Shiny for Python application

It’s based around the Earthquakes in Japan dataset, and allows the user to control the year range and the magnitude range. As soon as the user is satisfied with the filter criteria and hits the “Apply” button, the map and the histogram are updated.

The map shows a scatter plot of all earthquakes that match the filter conditions. Lighter-colored and larger bubbles indicate stronger magnitude and vice versa.

The histogram shows the count of the earthquakes in 0.1 magnitude bin sizes.

You can find the source code for the app at the end of this section.

Let’s start with the first prompt!

Prompt #1 – Data Familiarization and App Requirements

The idea is to start small and not ask too much at once.

In the first prompt, we want to give the dataset context to the Assistant. We pasted the first 10 rows (but only a few selected columns) and instructed it to make a Pandas DataFrame from it to avoid referencing a file that doesn’t exist.

Next, we told the assistant to create a sidebar layout with range sliders for the earthquake year and magnitude. Below the filters, there should be an action button that updates the app content when pressed.

Nothing will be displayed in the content area for now except for a brief message string to ensure filters work as expected.

Here’s the prompt:

I have a dataset with eartquakes in Japan from 2001 to 2018.
This is the dataset's header row and the first 10 data rows:
```
time	latitude	longitude	depth	mag	place	
2018-11-27T14:34:20.900Z	48.378	154.962	35	4.9	269km SSW of Severo-Kuril'sk, Russia	
2018-11-26T23:33:50.630Z	36.0733	139.783	48.82	4.8	3km SSW of Sakai, Japan	
2018-11-26T13:04:02.250Z	38.8576	141.8384	50.56	4.5	26km SSE of Ofunato, Japan	
2018-11-26T05:20:16.440Z	50.0727	156.142	66.34	4.6	67km S of Severo-Kuril'sk, Russia	
2018-11-25T09:19:05.010Z	33.95	134.4942	38.19	4.6	9km SW of Komatsushima, Japan	
2018-11-25T03:16:46.320Z	48.4158	155.0325	35	4.6	263km SSW of Severo-Kuril'sk, Russia	
2018-11-23T14:30:14.510Z	37.1821	141.1721	46.76	5.2	29km ENE of Iwaki, Japan	
2018-11-23T07:19:51.110Z	29.3424	142.3121	10	4.7	250km N of Chichi-shima, Japan	
2018-11-20T20:16:02.790Z	44.4524	148.0753	101.46	4.7	88km S of Kuril'sk, Russia	
```
Create this dataset from scratch as a Pandas DataFrame.

KEEP THIS IN MIND ALWAYS:
- Use double quotes
- Use snake case typing for variables and functions

Your task now is to create a layout for a Shiny application in Python.
Here are my requirements:
- The user should be able to select a year range, so extract year from the `time` attribute and make a two-way (min-max) slider filter from it
- The user should be able to have the same filter type for the `mag` column, representing earthquake magnitude
- Put both of these filters in a sidebar and have the main content (charts) update ONLY WHEN the "Apply" button is clicked. Put this button below the filters.
- In the main content section, do nothing for now - just display the values of the filters so I can verify everything works.
- Also, have the responsive design in mind, as users will use this app on desktop, tablet, and mobile devices
Image 3 – Shiny Assistant output screen

The preview feature of Shiny Assistant didn’t work at the time.

But the code it generated gave us confidence that we were on the right track. Both sliders were there, and the Assistant even generated a title for the app:

Image 4 – Generated Shiny for Python app

You can change the filters and click on the “Apply” button to test if the input components are successfully connected:

Image 5 – Generated Shiny for Python app (2)

There are a couple of issues – no initial value is shown (before clicking on the button), and the value formatting could be improved.

Let’s work on the latter first.

Prompt #2 – Fixing the Filter Components

The following prompt instructed Shiny Assistant to remove commas from the year values and to always round the magnitude to a single decimal point:

I'm satisfied with the results, good job!
Before moving forward and creating the first UI component, fix the following:
- Change the format of the Year Range slider. It represents years and it currently formats them as "2,001". I need them formatted as "2001"
- Change the format of the Magnitude Range slider. The values should have 1 decimal place always.
Image 6 – Unexpected keyword argument error

The generated code brought us to the first error.

Shiny Assistant used a keyword argument that’s not available in the function. Let’s see if it can solve this bug with minimal instructions.

Prompt #3 – Input Slider Bug Fix

The following prompt told the Assistant that the keyword argument doesn’t exist in the function, and that it should try to figure out another way to format the magnitude value:

The code you generated raised an error: TypeError: input_slider() got an unexpected keyword argument 'format'
The argument with this name doesn't exist, so please try another way of formatting the magnitude slider to only have 1 decimal place.
Image 7 – Generated Shiny for Python app

The Assistant removed the `format` keyword and managed to meet our requirements in some other way.

Everything worked, but that’s about to change.

Prompt #4 – Map with Plotly

The sidebar was more or less finished.

The next order of business is to construct a map component. We wanted the Assistant to use Plotly to create a scatter map centered around Japan, having red marker points on the earthquake coordinates. The size and color of the marker should depend on the magnitude:

Great job, everything now works as expected!

Your next task is to start building the chart components. The first one is an interactive map. It should:
- Be centered around Japan to show both Japan and all earthquakes surrounding it
- Allow the user to zoom in and out with map controls (+ and -), but not with a mouse scroll wheel
- Show earthquakes as red scatter points, with the size of a scatter point being dependent on the magnitude of the earthquake
- Reduce the opacity of individual markers
- Add a map to a card element and make it take the full width of the card (no margin and padding)

I would prefer if you would use Plotly to create a map. If you do, make sure to remove its default toolbar.
Image 8 – Plotly invalid property error

Once again, Shiny Assistant for Python used a property that doesn’t exist. Keep in mind that this error message was shown only after we clicked on the “Apply” button.

Will the fix be as simple as before?

Prompt #5 – Map Invalid Property Bug Fix

The next prompt instructed to Assistant to solve the invalid property error and also to provide a default map (from default filter values) when the app is first launched.

This way, the user won’t be left with a blank screen before playing around with the filters and clicking “Apply”:

You're heading in the right direction.
The code you've generated produces an error: Error: Invalid property specified for object of type plotly.graph_objs.layout.Mapbox: 'scrollZoom'. Did you mean "zoom"?
Fix this error.
Also, the map only renders if the "Apply" button is pressed. That's not ideal for when the user first opens the app.
When the app is launched, show the map with the default filter values and from there on, only update it when the "Apply" button is pressed.
Image 9 – Plotly @render.plot error

This was the place where the first bit of frustration took place.

The error concerning an invalid property name was replaced with a specific Plotly error. It seems like the Assistant doesn’t know how to render Plotly objects.

Prompt #6 – Map Render Plot Bug Fix

To mitigate, we’ve pasted the error message into the chat window.

There was also something else we noticed when inspecting the code. The Assistant has used Plotly Express to create the map chart. Our preference is to use Graph Objects when possible, as it offers more customization.

The following prompt should address both:

Your code produces the following error:
```
Error: @render.plot doesn't know to render objects of type '<class 'plotly.graph_objs._figure.figure'="">'. Consider either requesting support for this type of plot object, and/or  explictly saving the object to a (png) file and using @render.image.
```
Can you fix it and also switch from Plotly express to Plotly graph objects?
Image 10 – Plotly @render.plot error (2)

The generated code was updated to use Plotly Graph Objects, but the same error still shows.

It’s time for a manual intervention.

Prompt #7 – Map Render Plot Bug Fix (2)

Upon inspecting the docs, we saw that a Shiny for Python application should use the `output_widget()` function in the UI and `@render_widget` decorator in the server to render a Plotly chart.

That’s what we’ve included in a follow-up prompt:

Your code still produces the same error.
I did some exploring, and it looks like you need to use the `output_widget()` function in the UI and `@render_widget` decorator in the server.
Both are imported from the `shinywidgets` package.
Try this approach next.
Image 11 – Rendered Plotly map

The map looks horrible, but at least it works!

If you change the slider values, the map updates to show only the earthquakes that match the filter criteria.

The problem is – it happens immediately, and not only after the “Apply” button gets clicked.

Image 12 – Rendered Plotly map (2)

Let’s tweak the looks of the map next.

Prompt #8 – Map Update Bug Fix

A map with the default filter values should be displayed when the app is first launched. When the user changes the sliders and clicks on the “Apply” button, only then should the map be updated.

The following prompt tries to address this issue, and also tweak the map marker styles and zoom level:

Your code now outputs a map, but there are a couple of issues:
- The map updates as soon as the slider value changes. I want it updated only when the "Apply" button is clicked. That being said, you should still output a default map when the app is first launched - don't wait for the user to click on the button.
- The map seems to be too zoomed in by default.
- The markers are too large. Make the size depend on the magnitude and do the same for the marker color as well.
- I can still see Plotly's toolbar. Remove it entirely.
Image 13 – Updated Plotly map

The map is now almost exactly where we want it!

The only problem is that we can’t zoom in or out. It’s entirely our responsibility, as we told the Assistant to completely remove the toolbar.

Prompt #9 – Map Toolbar Bug Fix

The toolbar should include the option to zoom in and out, and also to reset the zoom level and map position:

You're almost there - the only mistake to fix is from my end.
Please bring back the toolbar, but only with the options to zoom in, zoom out, and reset the zoom level.
Leave everything else as is.
Image 14 – Zoom controls on a map

Works like a charm!

You can tweak the map aesthetics further if you wish, but we’ll call it a day. Let’s shift our efforts to a new chart component next.

Prompt #10 – Magnitude Histogram

We want to show a histogram with earthquake counts by magnitude in 0.1 increments below the map.

Our instructions were fairly detailed, as you can see from the prompt below:

That's it regarding the map. You can now remove the text output that shows the current slider values - I don't need it anymore.
Below the map, add another card with a histogram showing the magnitudes.
Add a title as an HTML element.
Regarding the histogram, it should display the magnitude counts in bins that are 0.1 apart. For example, if the weakest earthquare has a magnitude of 4.5, and the highest has 9.1, there 4.6 in between them, so the histogram should have (4.6 / 0.1) bins. Always cast this number to integer.
If possible, add a thin black border to each bar in the histogram.
Image 15 – Panel Sidebar error

However, Shiny Assistant made a UI mistake that’s easy to fix, but shouldn’t have happened in the first place. It altered the code that was working and broke the app.

Prompt #11 – Shiny Layout Bug Fix

In short, Shiny Assistant once again referenced a function that doesn’t exist – `panel_sidebar`. The function did exist in earlier releases of Shiny for Python, so we can understand (and forgive) the confusion.

The Assistant also decided to use Plotly Express to plot a histogram.

Both issues were addressed with the following prompt:

Your code generates the following error: AttributeError: module 'shiny.ui' has no attribute 'panel_sidebar'. Did you mean: 'page_sidebar'?
DO NOT CHANGE THE CODE THAT'S WORKING!
Only add new code for the histogram. Also, don't use Plotly express. Always use Plotly graph objects.
Image 16 – Plotly histogram

The app works again!

You can see the histogram below the map, and it updates correctly as the filter values are changed:

Image 17 – Plotly histogram (2)

But we have two new issues:

  • The histogram has two titles (and looks terrible).
  • The filters are not visible as you scroll through the app.

Both should be relatively easy to fix.

Prompt #12 – Magnitude Histogram Style Tweaks

The following prompt instructed the Assistant to remove the title and toolbar from the histogram, and also to change the color and theme – that’s all easy to do manually.

The tricky part comes from the toolbar not having a fixed position – we don’t have specific instructions on how to solve it.

Let’s write the requirements into a prompt and hope for the best:

Your code now works, but there are a couple of things you should fix:
- Remove the title from the histogram - leave only the H3 title in the UI.
- Completely remove the toolbar from the histogram
- Change the histogram color to "#0099f9"
- Remove the blue background color from the histogram - make it transparent and add light dotted gridlines
- The sidebar is not visible when I scroll to see the histogram. Can you do anything about it?
Image 18 – Panel Sidebar error

And we’re back to the well-known `panel_sidebar` error.

Prompt #13 – Shiny Layout Bug Fix (2)

It should be an easy fix, right?

After all, this wasn’t the first time we’ve seen this error message. This is the prompt we entered into the chat, hoping for the best:

Your code generates the following error: AttributeError: module 'shiny.ui' has no attribute 'panel_sidebar'. Did you mean: 'page_sidebar'?
AGAIN, DO NOT CHANGE THE CODE THAT'S WORKING!
Fix the error and don't repeat it in the future.
Image 19 – Panel Main error

The `panel_sidebar` error is gone, but we have another one that’s similar – `panel_main`.

Prompt #14 – Shiny Layout Bug Fix (3)

Long story short, Shiny Assistant wrapped the main app contents into a function `panel_main()`. That function doesn’t exist, and is not necessary, as all of the main app components should be written one after the other, right below the sidebar function.

We ran this prompt to instruct Shiny Assistant to remove the function entirely:

Your code generates the following error: AttributeError: module 'shiny.ui' has no attribute 'panel_main'
This function does not exist. Remove it entirely.
Image 20 – Updated histogram

The app is working again!

The sidebar is now visible at all times, but its width was reduced by half for some reason.

Prompt #15 – Sidebar Position Fix

Shiny Assistant added the `position: fixed` property to the sidebar component. It’s the only thing that changed, and yet it somehow broke the aesthetics.

The fix wasn’t immediately visible to us, so we instructed it to look for a workaround, as the current solution decreased the width of the components:

I see you've added "position: fixed" to the sidebar. This decreased the width of the filter components.
Is there a workaround? I want it to look like before, but always be visible to the user.
Image 21 – Sidebar style fix

It found a solution by including CSS code in the Python script.

That was a good way to solve the problem, but we’d prefer for all the styles to be in a separate file.

Prompt #16 – External CSS File

For that reason, we decided to ask the Assistant to create an external CSS file with a custom font and a responsive layout.

Hopefully, it will also move the sidebar styles to this file:

That's it for the functinality.
The styles need a couple of tweaks, though. Create and link an external CSS file and change the following styles:
- Add a custom font (e.g., Raleway).
- Make the app responsive for tablet and mobile devices.
Image 22 – Responsive design issue

The assistant successfully did what was asked of it, but it broke the layout on larger screens. In other words, the app was now configured to always be in a “mobile” layout.

Prompt #17 – Responsive Design for Mobile Devices

Unfortunately, further instructions on how to tweak the layout for different screen sizes didn’t work.

This is the prompt we used:

Good job with the responsive design for mobile devices.
However, the desktop layout now also looks like a layout for mobile devices.
I want you to stick to the original desktop layout, and only switch to mobile depending on the screen size.
Image 23 – Responsive design issue (2)

The layout was still the same, so it was time to try something different.

Prompt #18 – Switching to Bootstrap

And that something is a CSS framework. Bootstrap was the first one that came to mind:

Mobile layout is still shown on desktop!
Can you switch to a CSS framework like Bootstrap to fix the responsive design issue?
Image 24 – Updated layout with Bootstrap

The look of the app changed only slightly, but it was fully working in the desktop mode.

When resized to mobile, the app correctly rendered components one below the other:

Image 25 – Updated layout on mobile devices

You could spend more time playing around with the styles, and we encourage you to do so.

Prompt #19 – Better Project Structure

The `app.py` got long through the previous 18 prompts, with close to 200 lines of code.

Any additional features wouldn’t be easy to maintain in the long run. For this reason, we decided to ask the Assistant to extract the chart and data preprocessing logic into separate files:

That works, great job!
Here's the final task for today:
- Extract logic like data preprocessing and chart logic to external Python files. I want to make app.py as lean as possible.
Image 26 – Shiny for Python app after refactoring

It worked like a charm and didn’t break any functionality. `app.py` was reduced to 87 lines of code.

These additional files were created in the project directory:

.
├── app.py
├── chart_logic.py
├── data
│   └── Japan earthquakes 2001 - 2018.csv
├── data_processing.py
└── styles.css

This is where we’ll call it a day.

You can always optimize further and introduce new features, but we feel like we have enough information to give you our thoughts on Shiny Assistant for Python.

Shiny for Python Application Source Code

You can find the source code for the application in the subsections that follow. The dataset is available here.

data_processing.py

import pandas as pd


def load_and_process_data():
   data = pd.read_csv("data/Japan earthquakes 2001 - 2018.csv")


   data["time"] = pd.to_datetime(data["time"])
   data["year"] = data["time"].dt.year


   min_year = int(data["year"].min())
   max_year = int(data["year"].max())
   min_mag = round(float(data["mag"].min()), 1)
   max_mag = round(float(data["mag"].max()), 1)


   return data, min_year, max_year, min_mag, max_mag


def filter_data(data, year_range, mag_range):
   return data[
       (data["year"] >= year_range[0]) &
       (data["year"] <= year_range[1]) &
       (data["mag"] >= mag_range[0]) &
       (data["mag"] <= mag_range[1])
   ]

chart_logic.py

import plotly.graph_objects as go


def create_map(df, min_mag):
   fig = go.Figure(go.Scattermapbox(
       lat=df["latitude"],
       lon=df["longitude"],
       mode='markers',
       marker=go.scattermapbox.Marker(
           size=(df["mag"] - min_mag + 0.1) * 5,
           color=df["mag"],
           colorscale="Viridis",
           colorbar=dict(title="Magnitude"),
           opacity=0.7
       ),
       text=df.apply(lambda row: f"Place: {row['place']}Magnitude: {row['mag']}Depth: {row['depth']} km", axis=1),
       hoverinfo='text'
   ))


   fig.update_layout(
       mapbox_style="open-street-map",
       mapbox=dict(
           center=dict(lat=df["latitude"].mean(), lon=df["longitude"].mean()),
           zoom=3
       ),
       showlegend=False,
       height=600,
       margin={"r":0,"t":0,"l":0,"b":0},
   )
  
   fig.update_layout(modebar=dict(remove=[
       "pan2d", "select2d", "lasso2d", "autoScale2d", "hoverClosestCartesian",
       "hoverCompareCartesian", "toggleSpikelines"
   ]))
  
   return fig


def create_histogram(df):
   min_mag = df["mag"].min()
   max_mag = df["mag"].max()
   bin_count = int((max_mag - min_mag) / 0.1) + 1
  
   bins = [min_mag + i * 0.1 for i in range(bin_count + 1)]
  
   fig = go.Figure(data=[go.Histogram(
       x=df["mag"],
       xbins=dict(
           start=min_mag,
           end=max_mag,
           size=0.1
       ),
       marker=dict(
           color="#0099f9",
           line=dict(
               color='black',
               width=1
           )
       )
   )])
  
   fig.update_layout(
       xaxis_title="Magnitude",
       yaxis_title="Count",
       bargap=0.1,
       plot_bgcolor="rgba(0,0,0,0)",
       xaxis=dict(
           showgrid=True,
           gridcolor='lightgray',
           gridwidth=1,
           griddash='dot'
       ),
       yaxis=dict(
           showgrid=True,
           gridcolor='lightgray',
           gridwidth=1,
           griddash='dot'
       )
   )
  
   fig.update_layout(modebar=dict(remove=[
       "autoScale2d", "autoscale", "editInChartStudio", "editinchartstudio",
       "hoverCompareCartesian", "hovercompare", "lasso", "lasso2d", "orbitRotation",
       "orbitrotation", "pan", "pan2d", "pan3d", "reset", "resetCameraDefault3d",
       "resetCameraLastSave3d", "resetGeo", "resetSankeyGroup", "resetScale2d",
       "resetViewMapbox", "resetViews", "resetcameradefault", "resetcameralastsave",
       "resetsankeygroup", "resetscale", "resetview", "resetviews", "select",
       "select2d", "sendDataToCloud", "senddatatocloud", "tableRotation",
       "tablerotation", "toImage", "toggleHover", "toggleSpikelines", "togglehover",
       "togglespikelines", "toimage", "zoom", "zoom2d", "zoom3d", "zoomIn2d",
       "zoomInGeo", "zoomInMapbox", "zoomOut2d", "zoomOutGeo", "zoomOutMapbox",
       "zoomin", "zoomout"
   ]))
  
   return fig

styles.css

body {
   font-family: 'Raleway', sans-serif;
}


.sidebar {
   position: fixed;
   top: 0;
   bottom: 0;
   left: 0;
   z-index: 100;
   padding: 48px 0 0;
   box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
}


@media (max-width: 767.98px) {
   .sidebar {
       position: static;
       padding-top: 20px;
   }
}


.sidebar-sticky {
   position: relative;
   top: 0;
   height: calc(100vh - 48px);
   padding-top: .5rem;
   overflow-x: hidden;
   overflow-y: auto;
}


h2, h3 {
   color: #333;
}


.btn-primary {
   background-color: #0099f9;
   border-color: #0099f9;
}


.btn-primary:hover {
   background-color: #007acc;
   border-color: #007acc;
}

app.py

from shiny import App, ui, render, reactive
from shinywidgets import output_widget, render_widget
from data_processing import load_and_process_data, filter_data
from chart_logic import create_map, create_histogram


data, min_year, max_year, min_mag, max_mag = load_and_process_data()


app_ui = ui.page_fluid(
   ui.head_content(
       ui.tags.link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"),
       ui.tags.link(rel="stylesheet", href="https://fonts.googleapis.com/css2?family=Raleway:wght@400;700&display=swap"),
       ui.tags.link(rel="stylesheet", href="styles.css")
   ),
   ui.div(
       {"class": "container-fluid"},
       ui.div(
           {"class": "row"},
           ui.div(
               {"class": "col-md-3 col-lg-2 d-md-block bg-light sidebar"},
               ui.div(
                   {"class": "position-sticky pt-3"},
                   ui.h2("Japan Earthquake Data"),
                   ui.input_slider(
                       "year_range",
                       "Year Range",
                       min=min_year,
                       max=max_year,
                       value=[min_year, max_year],
                       step=1,
                       sep=""
                   ),
                   ui.input_slider(
                       "mag_range",
                       "Magnitude Range",
                       min=min_mag,
                       max=max_mag,
                       value=[min_mag, max_mag],
                       step=0.1
                   ),
                   ui.input_action_button("apply", "Apply", class_="btn btn-primary mt-3")
               )
           ),
           ui.div(
               {"class": "col-md-9 ms-sm-auto col-lg-10 px-md-4"},
               ui.div(
                   {"class": "mt-4"},
                   ui.div(
                       {"class": "card mb-4"},
                       ui.div(
                           {"class": "card-body"},
                           ui.h3("Earthquake Map", class_="card-title"),
                           output_widget("map", height="600px")
                       )
                   ),
                   ui.div(
                       {"class": "card"},
                       ui.div(
                           {"class": "card-body"},
                           ui.h3("Magnitude Distribution", class_="card-title"),
                           output_widget("histogram", height="400px")
                       )
                   )
               )
           )
       )
   )
)


def server(input, output, session):
   filtered_data = reactive.Value(data)


   @reactive.Effect
   @reactive.event(input.apply)
   def _():
       filtered_data.set(filter_data(data, input.year_range(), input.mag_range()))


   @output
   @render_widget
   def map():
       return create_map(filtered_data.get(), min_mag)


   @output
   @render_widget
   def histogram():
       return create_histogram(filtered_data.get())


app = App(app_ui, server)

Shiny Assistant for Python Pros and Cons

What’s the verdict, should you try out the beta version of Shiny Assistant for Python?

You already know our thoughts on the negative sides of using Generative AI for R and R Shiny – It shortens your attention span; It’s just a matter of time before you hit a wall; It doesn’t save you that much time; You shouldn’t share sensitive data. All of these still apply.

The rest of the section will walk you through the pros and cons specific to Shiny Assistant for Python.

Shiny Assistant Pros

Here are the things we loved about Shiny Assistant for Python:

  • It’s great for getting you started – If you don’t have a lot of experience with Shiny, the Assistant can write starter code for you. Just list your requirements and it’ll get it right most of the time. Keep in mind that starter code doesn’t equal production-ready code, so manage your expectations.
  • It works surprisingly well, given the circumstances – Shiny for Python is about 15 months old at the time of writing. After trying both flavors of the Assistant, we can tell that the difference exists, but it’s not as significant as we initially thought. It will take some time for it to catch up with R Shiny, but it’s far from unusable.
  • It’s aware of a wider Python ecosystem – You’ve seen today that we’ve worked with Pandas and Plotly. Python is a general-purpose programming language, which means you can ask the assistant to do more generic Python things. For example, if you need to implement a pipeline for downloading and processing the dataset before showing it in a Shiny app.
  • It’s free – As of now, Shiny Assistant won’t cost you a dime. There is an option to add your Anthropic API key which will charge you on a per-request basis, but the Assistant itself is free.
  • It’s a great tool to start learning – If you just want to get started with Shiny for Python and learn it faster through data and examples relevant to you, look no further than Shiny Assistant. It has everything you need, and it can fix the most common bugs you’ll encounter as a beginner.

Shiny Assistant Cons

The Assistant is currently in closed beta (October 2024), so we expect some pain points from this list resolved in the future:

  • Assistant modes don’t work – Just like in the R version, the switch from “Code only”, “Concise”, and “Verbose” assistant modes doesn’t make any difference. We wanted to use “Code only” to reduce the response time, but this mode still generated a lot of explainer text. It looks like a prompting error, so it shouldn’t take the Posit’s team too much time to get it working.
  • The live preview is buggy and doesn’t reflect the app – If you tell the Assistant your data is in a CSV file and provide a sample, it’ll try to read it in the code, resulting in a crash. Other times, we found the app preview not updating after fixing a bug, even if the same code works when running the app locally. Technically, this has little to do with the Shiny Assistant, but it’s worth pointing out since it’s part of the UI.
  • It can be slow at times – Shiny Assistant always generates the code from scratch. If you have a larger application split between multiple files, this can be a huge time waste. We’d love to see the option to generate only the code that has to be updated, but this could introduce additional errors if you like to just copy/paste. Still, a toggle or an additional assistant mode would be a nice-to-have feature.
  • It works on a very limited set of data – In the grand scheme of things, Shiny isn’t the most used application framework out there. Adding the fact that Shiny for Python was released in 2023, it’s easy to see that there’s not so much data running in the background. Of course, this will change as time goes on.
  • It’s not available as an IDE plugin – In the future, we’d love to see Shiny Assistant as an integral part of Python-specific IDEs, or as a downloadable plugin (like Copilot in VSCode).

Summing up Shiny Assistant for Python

Shiny Assistant is a good place to start writing and prototyping your apps in 2025 and beyond.

It works great with R and Python. The latter is surprising since Shiny for Python is more than a decade younger than its R equivalent. You can use the tool to learn Shiny in a context that’s relevant to you, as an idea generator to explore new features, and as a tool for creating apps from scratch. Just keep in mind that some manual intervention will be required.

What we’d love to see in the future are refinements in the Assistant UI, particularly regarding the code generation speed and output formats. Sometimes you don’t need a couple of paragraphs of explainer text – you’re just there for the code. Other times, you need more detailed step-by-step instructions. We’d also love to see it running in Positron or VSCode.

These are all issues that are likely to be resolved before the first public release. Stay tuned to the Appsilon blog and subscribe to Shiny Weekly to never miss a thing.

If you haven’t tried Shiny Assistant yet, join the waitlist.

We’d love to hear your thoughts on Shiny Assistant for Python. Join our Slack community and share your experiences so far.

Looking to share your Shiny for Python apps? Try Tapyr – Appsilon’s Rhino equivalent for Python.

The post appeared first on appsilon.com/blog/.

To leave a comment for the author, please follow the link and comment on their blog: Appsilon | Enterprise R Shiny Dashboards .

Want to share your content on python-bloggers? click here.