diff --git a/examples/python/CommunityToolkit.Aspire.Hosting.Python.Extensions.AppHost/Program.cs b/examples/python/CommunityToolkit.Aspire.Hosting.Python.Extensions.AppHost/Program.cs
index fd0fe3ad..66e43ead 100644
--- a/examples/python/CommunityToolkit.Aspire.Hosting.Python.Extensions.AppHost/Program.cs
+++ b/examples/python/CommunityToolkit.Aspire.Hosting.Python.Extensions.AppHost/Program.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS0612 // Type or member is obsolete
#pragma warning disable CS0618 // Type or member is obsolete
+#pragma warning disable CTASPIRE001 // Experimental API
var builder = DistributedApplication.CreateBuilder(args);
builder.AddUvicornApp("uvicornapp", "../uvicornapp-api", "main:app");
@@ -7,4 +8,7 @@
builder.AddUvApp("uvapp", "../uv-api", "uv-api")
.WithHttpEndpoint(env: "PORT");
+builder.AddStreamlitApp("streamlitapp", "../streamlit-api", "app.py")
+ .WithHttpEndpoint(env: "PORT");
+
builder.Build().Run();
\ No newline at end of file
diff --git a/examples/python/streamlit-api/.gitignore b/examples/python/streamlit-api/.gitignore
new file mode 100644
index 00000000..77ac7549
--- /dev/null
+++ b/examples/python/streamlit-api/.gitignore
@@ -0,0 +1,3 @@
+.venv/
+__pycache__/
+*.pyc
diff --git a/examples/python/streamlit-api/.python-version b/examples/python/streamlit-api/.python-version
new file mode 100644
index 00000000..e4fba218
--- /dev/null
+++ b/examples/python/streamlit-api/.python-version
@@ -0,0 +1 @@
+3.12
diff --git a/examples/python/streamlit-api/README.md b/examples/python/streamlit-api/README.md
new file mode 100644
index 00000000..04b5d7c8
--- /dev/null
+++ b/examples/python/streamlit-api/README.md
@@ -0,0 +1,57 @@
+# Streamlit API Example
+
+This is a simple Streamlit application that demonstrates how to run Streamlit apps in Aspire using the Python Extensions.
+
+## Prerequisites
+
+- Python 3.12 or later
+- .NET 8.0 or later
+- Streamlit package
+
+## Setup
+
+1. Create a virtual environment:
+ ```bash
+ python -m venv .venv
+ ```
+
+2. Activate the virtual environment:
+ - On Windows:
+ ```bash
+ .venv\Scripts\activate
+ ```
+ - On macOS/Linux:
+ ```bash
+ source .venv/bin/activate
+ ```
+
+3. Install dependencies:
+ ```bash
+ pip install streamlit
+ ```
+
+## Running the App
+
+The app can be run as part of the Aspire AppHost project or standalone.
+
+### Run with Aspire
+
+Navigate to the AppHost project and run:
+```bash
+dotnet run
+```
+
+### Run Standalone
+
+From this directory, run:
+```bash
+streamlit run app.py
+```
+
+## About the App
+
+This is a basic Streamlit app that demonstrates:
+- Simple text display
+- Environment variable reading (PORT)
+- Session state management
+- Interactive buttons
diff --git a/examples/python/streamlit-api/app.py b/examples/python/streamlit-api/app.py
new file mode 100644
index 00000000..1c8b4f4f
--- /dev/null
+++ b/examples/python/streamlit-api/app.py
@@ -0,0 +1,18 @@
+import streamlit as st
+import os
+
+st.title("Hello, Aspire!")
+
+st.write("This is a simple Streamlit app running in Aspire.")
+
+port = os.environ.get("PORT", "8501")
+st.write(f"Running on port: {port}")
+
+# Add a simple counter
+if "counter" not in st.session_state:
+ st.session_state.counter = 0
+
+if st.button("Click me!"):
+ st.session_state.counter += 1
+
+st.write(f"Button clicked {st.session_state.counter} times")
diff --git a/examples/python/streamlit-api/pyproject.toml b/examples/python/streamlit-api/pyproject.toml
new file mode 100644
index 00000000..390870e1
--- /dev/null
+++ b/examples/python/streamlit-api/pyproject.toml
@@ -0,0 +1,12 @@
+[project]
+name = "streamlit-api"
+version = "0.1.0"
+description = "Test project for streamlit-api"
+requires-python = ">=3.12"
+dependencies = [
+ "streamlit>=1.40.0",
+]
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
diff --git a/src/CommunityToolkit.Aspire.Hosting.Python.Extensions/README.md b/src/CommunityToolkit.Aspire.Hosting.Python.Extensions/README.md
index e9ff639f..aa2c241e 100644
--- a/src/CommunityToolkit.Aspire.Hosting.Python.Extensions/README.md
+++ b/src/CommunityToolkit.Aspire.Hosting.Python.Extensions/README.md
@@ -13,6 +13,7 @@
Provides extensions methods and resource definitions for the .NET Aspire AppHost to extend the support for Python applications. Current support includes:
- Uvicorn
- Uv
+- Streamlit
## Getting Started
@@ -46,6 +47,15 @@ var uvicorn = builder.AddUvApp("uvapp", "../uv-api", "uv-api")
.WithHttpEndpoint(env: "PORT");
```
+### Streamlit example usage
+
+Then, in the _Program.cs_ file of your AppHost project, define a Streamlit resource, then call `Add`:
+
+```csharp
+var streamlit = builder.AddStreamlitApp("streamlitapp", "../streamlit-api", "app.py")
+ .WithHttpEndpoint(env: "PORT");
+```
+
## Additional Information
https://learn.microsoft.com/dotnet/aspire/community-toolkit/hosting-python-extensions
diff --git a/src/CommunityToolkit.Aspire.Hosting.Python.Extensions/StreamlitAppHostingExtension.cs b/src/CommunityToolkit.Aspire.Hosting.Python.Extensions/StreamlitAppHostingExtension.cs
new file mode 100644
index 00000000..d2ed5e76
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.Python.Extensions/StreamlitAppHostingExtension.cs
@@ -0,0 +1,104 @@
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Python;
+
+namespace Aspire.Hosting;
+
+///
+/// Provides extension methods for adding Streamlit applications to an .
+///
+public static class StreamlitAppHostingExtension
+{
+ ///
+ /// Adds a Streamlit application to the application model.
+ ///
+ /// The to add the resource to.
+ /// The name of the Streamlit application.
+ /// The path to the directory containing the Streamlit application.
+ /// The path to the Python script to be run by Streamlit (relative to appDirectory).
+ /// An for the Streamlit application resource.
+ ///
+ ///
+ /// This method uses the Aspire.Hosting.Python integration to run Streamlit applications.
+ /// By default, it uses the .venv virtual environment in the app directory.
+ /// Use standard Python extension methods like WithVirtualEnvironment, WithPip, or WithUv to customize the environment.
+ ///
+ ///
+ /// **⚠️ EXPERIMENTAL:** This integration is experimental and subject to change. The underlying implementation
+ /// will be updated to use public APIs when they become available in Aspire.Hosting.Python (expected in Aspire 13.1).
+ ///
+ ///
+ ///
+ /// Add a Streamlit application to the application model:
+ ///
+ /// var builder = DistributedApplication.CreateBuilder(args);
+ ///
+ /// builder.AddStreamlitApp("dashboard", "../streamlit-app", "app.py")
+ /// .WithHttpEndpoint(env: "PORT");
+ ///
+ /// builder.Build().Run();
+ ///
+ ///
+ [Experimental("CTASPIRE001", UrlFormat = "https://github.com/CommunityToolkit/Aspire/issues/{0}")]
+ public static IResourceBuilder AddStreamlitApp(
+ this IDistributedApplicationBuilder builder,
+ [ResourceName] string name,
+ string appDirectory,
+ string scriptPath)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentException.ThrowIfNullOrWhiteSpace(name);
+ ArgumentNullException.ThrowIfNull(appDirectory);
+ ArgumentException.ThrowIfNullOrWhiteSpace(scriptPath);
+
+ // Use AddPythonExecutable to run streamlit from the virtual environment
+ var pythonBuilder = builder.AddPythonExecutable(name, appDirectory, "streamlit")
+ .WithDebugging()
+ .WithHttpEndpoint(env: "PORT")
+ .WithArgs(context =>
+ {
+ context.Args.Add("run");
+ context.Args.Add(scriptPath);
+
+ // Add --server.headless to run without browser opening
+ context.Args.Add("--server.headless");
+ context.Args.Add("true");
+
+ // Configure server port from endpoint
+ var endpoint = ((IResourceWithEndpoints)context.Resource).GetEndpoint("http");
+ context.Args.Add("--server.port");
+ context.Args.Add(endpoint.Property(EndpointProperty.TargetPort));
+
+ // Configure server address
+ context.Args.Add("--server.address");
+ if (builder.ExecutionContext.IsPublishMode)
+ {
+ context.Args.Add("0.0.0.0");
+ }
+ else
+ {
+ context.Args.Add(endpoint.EndpointAnnotation.TargetHost);
+ }
+ });
+
+ // Create a StreamlitAppResource wrapping the PythonAppResource
+ // This allows for Streamlit-specific extension methods in the future
+ var streamlitResource = new StreamlitAppResource(
+ pythonBuilder.Resource.Name,
+ pythonBuilder.Resource.Command,
+ pythonBuilder.Resource.WorkingDirectory);
+
+ // Copy annotations from the Python resource
+ foreach (var annotation in pythonBuilder.Resource.Annotations)
+ {
+ streamlitResource.Annotations.Add(annotation);
+ }
+
+ // Replace the resource in the builder
+ builder.Resources.Remove(pythonBuilder.Resource);
+ var streamlitBuilder = builder.AddResource(streamlitResource);
+
+ return streamlitBuilder;
+ }
+}
diff --git a/src/CommunityToolkit.Aspire.Hosting.Python.Extensions/StreamlitAppResource.cs b/src/CommunityToolkit.Aspire.Hosting.Python.Extensions/StreamlitAppResource.cs
new file mode 100644
index 00000000..02aa1a2c
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.Python.Extensions/StreamlitAppResource.cs
@@ -0,0 +1,12 @@
+using Aspire.Hosting.Python;
+
+namespace Aspire.Hosting.ApplicationModel;
+
+///
+/// Represents a Streamlit application.
+///
+/// The name of the resource.
+/// The path to the executable used to run the Streamlit app.
+/// The working directory for streamlit.
+public class StreamlitAppResource(string name, string executablePath, string workingDirectory)
+ : PythonAppResource(name, executablePath, workingDirectory), IResourceWithServiceDiscovery;
diff --git a/tests/CommunityToolkit.Aspire.Hosting.Python.Extensions.Tests/AppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.Python.Extensions.Tests/AppHostTests.cs
index de0d94f1..b68e6566 100644
--- a/tests/CommunityToolkit.Aspire.Hosting.Python.Extensions.Tests/AppHostTests.cs
+++ b/tests/CommunityToolkit.Aspire.Hosting.Python.Extensions.Tests/AppHostTests.cs
@@ -10,6 +10,7 @@ public class AppHostTests(AspireIntegrationTestFixture();
+
+ var resource = appModel.Resources.OfType().SingleOrDefault();
+
+ Assert.NotNull(resource);
+
+ // Command will be the full path to streamlit executable in venv, so just check it contains "streamlit"
+ Assert.Contains("streamlit", resource.Command);
+ Assert.Equal(NormalizePathForCurrentPlatform("../../examples/python/streamlit-api"), resource.WorkingDirectory);
+ }
+
static string NormalizePathForCurrentPlatform(string path)
{
if (string.IsNullOrWhiteSpace(path) == true)