11// Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
4+ using System . ComponentModel ;
5+ using System . Runtime . CompilerServices ;
46#pragma warning disable ASPIREEXTENSION001
57using Aspire . Hosting . ApplicationModel ;
68using Aspire . Hosting . Python ;
@@ -14,25 +16,22 @@ namespace Aspire.Hosting;
1416public static class PythonAppResourceBuilderExtensions
1517{
1618 /// <summary>
17- /// Adds a python application with a virtual environment to the application model.
19+ /// Adds a python application to the application model.
1820 /// </summary>
1921 /// <param name="builder">The <see cref="IDistributedApplicationBuilder"/> to add the resource to.</param>
2022 /// <param name="name">The name of the resource.</param>
2123 /// <param name="appDirectory">The path to the directory containing the python app files.</param>
2224 /// <param name="scriptPath">The path to the script relative to the app directory to run.</param>
23- /// <param name="scriptArgs">The arguments for the script.</param>
2425 /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
2526 /// <remarks>
2627 /// <para>
27- /// The virtual environment must be initialized before running the app. By default the virtual environment folder is expected
28- /// to be named <c>.venv</c> and be located in the app directory. If the virtual environment is located in a different directory
29- /// this default can be specified by using the <see cref="AddPythonApp(IDistributedApplicationBuilder, string, string, string, string, string[])"/>
30- /// overload of this method.
28+ /// By default, the virtual environment folder is expected to be named <c>.venv</c> and located in the app directory.
29+ /// Use <see cref="WithVirtualEnvironment(IResourceBuilder{PythonAppResource}, string)"/> to specify a different virtual environment path.
30+ /// Use <c>WithArgs</c> to pass arguments to the script.
3131 /// </para>
3232 /// <para>
33- /// The virtual environment is setup individually for each app to allow each app to use a different version of
34- /// Python and dependencies. To setup a virtual environment use the <c>python -m venv .venv</c> command in the app
35- /// directory. This will create a virtual environment in the <c>.venv</c> directory.
33+ /// The virtual environment must be initialized before running the app. To setup a virtual environment use the
34+ /// <c>python -m venv .venv</c> command in the app directory.
3635 /// </para>
3736 /// <para>
3837 /// To restore dependencies in the virtual environment first activate the environment by executing the activation
@@ -44,23 +43,49 @@ public static class PythonAppResourceBuilderExtensions
4443 /// your Python app.
4544 /// </para>
4645 /// <example>
47- /// Add a python app or executable to the application model. In this example the python code entry point is located in the <c>PythonApp</c> directory
48- /// if this path is relative then it is assumed to be relative to the AppHost directory, and the virtual environment path if relative
49- /// is relative to the app directory. In the example below, if the app host directory is <c>$HOME/repos/MyApp/src/MyApp.AppHost</c> then
50- /// the AppPath would be <c>$HOME/repos/MyApp/src/MyApp.AppHost/PythonApp</c> and the virtual environment path (defaulted) would
51- /// be <c>$HOME/repos/MyApp/src/MyApp.AppHost/PythonApp/.venv</c>.
46+ /// Add a python app to the application model:
5247 /// <code lang="csharp">
5348 /// var builder = DistributedApplication.CreateBuilder(args);
5449 ///
55- /// builder.AddPythonApp("python-app", "PythonApp", "main.py");
50+ /// builder.AddPythonApp("python-app", "../python-app", "main.py")
51+ /// .WithArgs("arg1", "arg2");
5652 ///
5753 /// builder.Build().Run();
5854 /// </code>
5955 /// </example>
6056 /// </remarks>
57+ [ OverloadResolutionPriority ( 1 ) ]
58+ public static IResourceBuilder < PythonAppResource > AddPythonApp (
59+ this IDistributedApplicationBuilder builder , [ ResourceName ] string name , string appDirectory , string scriptPath )
60+ => AddPythonAppCore ( builder , name , appDirectory , scriptPath , ".venv" ) ;
61+
62+ /// <summary>
63+ /// Adds a python application with a virtual environment to the application model.
64+ /// </summary>
65+ /// <param name="builder">The <see cref="IDistributedApplicationBuilder"/> to add the resource to.</param>
66+ /// <param name="name">The name of the resource.</param>
67+ /// <param name="appDirectory">The path to the directory containing the python app files.</param>
68+ /// <param name="scriptPath">The path to the script relative to the app directory to run.</param>
69+ /// <param name="scriptArgs">The arguments for the script.</param>
70+ /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
71+ /// <remarks>
72+ /// <para>
73+ /// This overload is obsolete. Use the overload without parameters and chain with <c>WithArgs</c>:
74+ /// <code lang="csharp">
75+ /// builder.AddPythonApp("name", "dir", "script.py")
76+ /// .WithArgs("arg1", "arg2");
77+ /// </code>
78+ /// </para>
79+ /// </remarks>
80+ [ Obsolete ( "Use AddPythonApp(builder, name, appDirectory, scriptPath) and chain with .WithArgs(...) instead." ) ]
81+ [ EditorBrowsable ( EditorBrowsableState . Never ) ]
6182 public static IResourceBuilder < PythonAppResource > AddPythonApp (
6283 this IDistributedApplicationBuilder builder , string name , string appDirectory , string scriptPath , params string [ ] scriptArgs )
63- => AddPythonApp ( builder , name , appDirectory , scriptPath , ".venv" , scriptArgs ) ;
84+ {
85+ ThrowIfNullOrContainsIsNullOrEmpty ( scriptArgs ) ;
86+ return AddPythonAppCore ( builder , name , appDirectory , scriptPath , ".venv" )
87+ . WithArgs ( scriptArgs ) ;
88+ }
6489
6590 /// <summary>
6691 /// Adds a python application with a virtual environment to the application model.
@@ -74,58 +99,47 @@ public static IResourceBuilder<PythonAppResource> AddPythonApp(
7499 /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
75100 /// <remarks>
76101 /// <para>
77- /// The virtual environment is setup individually for each app to allow each app to use a different version of
78- /// Python and dependencies. To setup a virtual environment use the <c>python -m venv .venv</c> command in the app
79- /// directory. This will create a virtual environment in the <c>.venv</c> directory (where <c>.venv</c> is the name of your
80- /// virtual environment directory).
81- /// </para>
82- /// <para>
83- /// To restore dependencies in the virtual environment first activate the environment by executing the activation
84- /// script and then use the <c>pip install -r requirements.txt</c> command to restore dependencies.
85- /// </para>
86- /// <para>
87- /// To receive traces, logs, and metrics from the python app in the dashboard, the app must be instrumented with OpenTelemetry.
88- /// You can instrument your app by adding the <c>opentelemetry-distro</c>, and <c>opentelemetry-exporter-otlp</c> to
89- /// your Python app.
90- /// </para>
91- /// <example>
92- /// Add a python app or executable to the application model. In this example the python code is located in the <c>PythonApp</c> directory
93- /// if this path is relative then it is assumed to be relative to the AppHost directory, and the virtual environment path if relative
94- /// is relative to the app directory. In the example below, if the app host directory is <c>$HOME/repos/MyApp/src/MyApp.AppHost</c> then
95- /// the AppPath would be <c>$HOME/repos/MyApp/src/MyApp.AppHost/PythonApp</c> and the virtual environment path (defaulted) would
96- /// be <c>$HOME/repos/MyApp/src/MyApp.AppHost/PythonApp/.venv</c>.
102+ /// This overload is obsolete. Use the overload without parameters and chain with <c>WithVirtualEnvironment</c> and <c>WithArgs</c>:
97103 /// <code lang="csharp">
98- /// var builder = DistributedApplication.CreateBuilder(args);
99- ///
100- /// builder.AddPythonApp("python-app", "PythonApp", "main.py");
101- ///
102- /// builder.Build().Run();
104+ /// builder.AddPythonApp("name", "dir", "script.py")
105+ /// .WithVirtualEnvironment("myenv")
106+ /// .WithArgs("arg1", "arg2");
103107 /// </code>
104- /// </example >
108+ /// </para >
105109 /// </remarks>
110+ [ Obsolete ( "Use AddPythonApp(builder, name, appDirectory, scriptPath) and chain with .WithVirtualEnvironment(...).WithArgs(...) instead." ) ]
111+ [ EditorBrowsable ( EditorBrowsableState . Never ) ]
106112 public static IResourceBuilder < PythonAppResource > AddPythonApp (
107113 this IDistributedApplicationBuilder builder , string name , string appDirectory , string scriptPath ,
108114 string virtualEnvironmentPath , params string [ ] scriptArgs )
115+ {
116+ ThrowIfNullOrContainsIsNullOrEmpty ( scriptArgs ) ;
117+ return AddPythonAppCore ( builder , name , appDirectory , scriptPath , virtualEnvironmentPath )
118+ . WithArgs ( scriptArgs ) ;
119+ }
120+
121+ private static IResourceBuilder < PythonAppResource > AddPythonAppCore (
122+ IDistributedApplicationBuilder builder , string name , string appDirectory , string scriptPath ,
123+ string virtualEnvironmentPath )
109124 {
110125 ArgumentNullException . ThrowIfNull ( builder ) ;
111126 ArgumentException . ThrowIfNullOrEmpty ( name ) ;
112- ArgumentException . ThrowIfNullOrEmpty ( appDirectory ) ;
127+ ArgumentNullException . ThrowIfNull ( appDirectory ) ;
113128 ArgumentException . ThrowIfNullOrEmpty ( scriptPath ) ;
114- ArgumentException . ThrowIfNullOrEmpty ( virtualEnvironmentPath ) ;
115- ThrowIfNullOrContainsIsNullOrEmpty ( scriptArgs ) ;
129+ ArgumentNullException . ThrowIfNull ( virtualEnvironmentPath ) ;
116130
117131 appDirectory = PathNormalizer . NormalizePathForCurrentPlatform ( Path . Combine ( builder . AppHostDirectory , appDirectory ) ) ;
118132 var virtualEnvironment = new VirtualEnvironment ( Path . IsPathRooted ( virtualEnvironmentPath )
119133 ? virtualEnvironmentPath
120134 : Path . Join ( appDirectory , virtualEnvironmentPath ) ) ;
121135
122- var pythonExecutable = virtualEnvironment . GetRequiredExecutable ( "python" ) ;
136+ var pythonExecutable = virtualEnvironment . GetExecutable ( "python" ) ;
123137
124138 var resource = new PythonAppResource ( name , pythonExecutable , appDirectory ) ;
125139
126140 var resourceBuilder = builder . AddResource ( resource ) . WithArgs ( context =>
127141 {
128- AddArguments ( scriptPath , scriptArgs , context ) ;
142+ context . Args . Add ( scriptPath ) ;
129143 } ) ;
130144
131145 resourceBuilder . WithOtlpExporter ( ) ;
@@ -153,16 +167,6 @@ public static IResourceBuilder<PythonAppResource> AddPythonApp(
153167 return resourceBuilder ;
154168 }
155169
156- private static void AddArguments ( string scriptPath , string [ ] scriptArgs , CommandLineArgsCallbackContext context )
157- {
158- context . Args . Add ( scriptPath ) ;
159-
160- foreach ( var arg in scriptArgs )
161- {
162- context . Args . Add ( arg ) ;
163- }
164- }
165-
166170 private static void ThrowIfNullOrContainsIsNullOrEmpty ( string [ ] scriptArgs )
167171 {
168172 ArgumentNullException . ThrowIfNull ( scriptArgs ) ;
@@ -179,4 +183,25 @@ private static void ThrowIfNullOrContainsIsNullOrEmpty(string[] scriptArgs)
179183 }
180184 }
181185 }
186+
187+ /// <summary>
188+ /// Configures a custom virtual environment path for the Python application.
189+ /// </summary>
190+ /// <param name="builder">The resource builder.</param>
191+ /// <param name="virtualEnvironmentPath">The path to the virtual environment. Can be absolute or relative to the app directory.</param>
192+ /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
193+ public static IResourceBuilder < PythonAppResource > WithVirtualEnvironment (
194+ this IResourceBuilder < PythonAppResource > builder , string virtualEnvironmentPath )
195+ {
196+ ArgumentNullException . ThrowIfNull ( builder ) ;
197+ ArgumentException . ThrowIfNullOrEmpty ( virtualEnvironmentPath ) ;
198+
199+ var virtualEnvironment = new VirtualEnvironment ( Path . IsPathRooted ( virtualEnvironmentPath )
200+ ? virtualEnvironmentPath
201+ : Path . Join ( builder . Resource . WorkingDirectory , virtualEnvironmentPath ) ) ;
202+ // Update the command to use the new virtual environment
203+ builder . WithCommand ( virtualEnvironment . GetExecutable ( "python" ) ) ;
204+
205+ return builder ;
206+ }
182207}
0 commit comments