A .NET 8 library that provides utilities for managing an embedded Python distribution and virtual environments within .NET applications. This package enables seamless integration of Python scripts and packages into your .NET projects without requiring users to have Python installed on their system.
- 🐍 Automatic Python Distribution Management - Downloads and extracts an embedded Python distribution on first use
- 📦 Virtual Environment Support - Creates and manages isolated Python virtual environments
- 🔧 Pip Integration - Installs Python packages from requirements.txt files or editable projects
- 📦 Ad-hoc Package Installs - Install additional packages at runtime via arbitrary pip arguments
- ⚡ CUDA-Aware PyTorch Setup - Detects local CUDA drivers and installs the matching PyTorch build
- 🚀 Script Execution - Run Python scripts with full environment isolation and process callbacks
- 🔄 Cross-Platform Ready - Supports both Windows and Unix-based systems
- 🧹 Cleanup Utilities - Remove Python environments when no longer needed
Install the package via NuGet:
dotnet add package DotNetPythonEmbedOr via the Package Manager Console:
Install-Package DotNetPythonEmbedusingDotNetPythonEmbed;// Define the directory where Python will be installedvarpythonDirectory=Path.Combine(AppContext.BaseDirectory,"python-runtime");// Create the managervarembedManager=newPythonEmbedManager(pythonDirectory);// Initialize the Python environment (downloads Python if not present)awaitembedManager.InitPythonEnvironment(onOutput:Console.WriteLine,onError:Console.Error.WriteLine);// Install dependencies from requirements.txtvarrequirementsPath="path/to/requirements.txt";if(File.Exists(requirementsPath)){awaitembedManager.InstallRequirement(requirementsPath,Console.WriteLine,Console.Error.WriteLine);}// Run a Python script and capture the spawned process (optional)varscriptPath="path/to/script.py";awaitembedManager.RunPython(pythonScript:scriptPath,parameters:"--arg1 value1",workingDirectory:Path.GetDirectoryName(scriptPath)!,onOutput:Console.WriteLine,onError:Console.Error.WriteLine,onProcessStarted: process =>Console.WriteLine($"Python PID: {process.Id}"));varpythonDir=Path.Combine(AppContext.BaseDirectory,"python-runtime");varmanager=newPythonEmbedManager(pythonDir);// Optional: Override the default Python versionmanager.PythonEmbedUrl="https://www.python.org/ftp/python/3.13.9/python-3.13.9-embed-amd64.zip";// Initialize (downloads and sets up Python + pip + virtualenv)awaitmanager.InitPythonEnvironment(onOutput: msg =>Console.WriteLine(msg),onError: err =>Console.Error.WriteLine(err));The InitPythonEnvironment method:
- Checks if Python is already installed at the specified directory
- If not, downloads the embedded Python distribution
- Extracts the distribution
- Installs pip
- Creates a virtual environment named
venv
varrequirementsPath=Path.Combine(repoDirectory,"requirements.txt");awaitmanager.InstallRequirement(requirementsPath,onOutput:Console.WriteLine,onError:Console.Error.WriteLine);The InstallRequirement method uses pip within the virtual environment to install packages specified in a requirements.txt file.
awaitmanager.InstallRequirementInEditorMode(workDir:Path.Combine(repoDirectory,"python-package"),onOutput:Console.WriteLine,onError:Console.Error.WriteLine);Use InstallRequirementInEditorMode to mirror pip install -e . behaviour—perfect for local development workflows or Git-based dependencies.
awaitmanager.InstallPackagesAsync(packages:new[]{"requests==2.32.3","uvicorn"},extraIndexUrl:null,onOutput:Console.WriteLine,onError:Console.Error.WriteLine);InstallPackagesAsync gives you full control over pip arguments so that additional tooling can be installed on demand.
awaitmanager.InstallTorchWithCudaAsync(torchVersion:"2.6.0",cudaOverride:null,onOutput:Console.WriteLine,onError:Console.Error.WriteLine);InstallTorchWithCudaAsync auto-detects the host CUDA driver (or honours an override) and installs torch, torchvision, and torchaudio from the appropriate wheel index.
varscriptPath=Path.Combine(projectDirectory,"script.py");varworkingDirectory=projectDirectory;varscriptArguments="--input data.txt --output results.json";intexitCode=awaitmanager.RunPython(pythonScript:scriptPath,parameters:scriptArguments,workingDirectory:workingDirectory,onOutput:Console.WriteLine,onError:Console.Error.WriteLine,onProcessStarted: process =>Console.WriteLine($"Python PID: {process.Id}"));if(exitCode!=0){Console.WriteLine($"Script failed with exit code: {exitCode}");}// Remove the Python environment if neededmanager.RemovePythonEnvironment(onOutput:Console.WriteLine,onError:Console.Error.WriteLine);Here's a complete example that clones a GitHub repository and runs a Python script from it. It also demonstrates editable installs, ad-hoc dependencies, and CUDA-aware PyTorch setup:
usingDotNetPythonEmbed;varrepoUrl="https://github.com/username/python-project.git";varbaseDirectory=AppContext.BaseDirectory;varrepoDirectory=Path.Combine(baseDirectory,"python-project");varpythonDirectory=Path.Combine(baseDirectory,"python-runtime");try{// Clone the repository (using LibGit2Sharp or similar)Console.WriteLine($"Cloning '{repoUrl}' to '{repoDirectory}'.");if(!Directory.Exists(repoDirectory)){// Repository.Clone(repoUrl, repoDirectory);}// Initialize Python environmentConsole.WriteLine("Initializing embedded Python environment.");varembedManager=newPythonEmbedManager(pythonDirectory);awaitembedManager.InitPythonEnvironment(OnOutput,OnError);// Install requirements if presentvarrequirementsPath=Path.Combine(repoDirectory,"requirements.txt");if(File.Exists(requirementsPath)){Console.WriteLine("Installing Python requirements.");awaitembedManager.InstallRequirement(requirementsPath,OnOutput,OnError);}// Optionally install a local project in editable modeawaitembedManager.InstallRequirementInEditorMode(repoDirectory,OnOutput,OnError);// Install extra tooling on demandawaitembedManager.InstallPackagesAsync(new[]{"gradio"},extraIndexUrl:null,OnOutput,OnError);// Ensure torch matches the local CUDA stackawaitembedManager.InstallTorchWithCudaAsync(torchVersion:"2.6.0",cudaOverride:null,OnOutput,OnError);// Run the scriptvarscriptPath=Path.Combine(repoDirectory,"main.py");Console.WriteLine($"Executing script '{scriptPath}'.");intexitCode=awaitembedManager.RunPython(pythonScript:scriptPath,parameters:string.Empty,workingDirectory:repoDirectory,onOutput:OnOutput,onError:OnError,onProcessStarted: process =>Console.WriteLine($"Python PID: {process.Id}"));Console.WriteLine($"Script completed with exit code: {exitCode}");// Optional: Clean upembedManager.RemovePythonEnvironment(OnOutput,OnError);}catch(Exceptionex){Console.Error.WriteLine($"An error occurred: {ex.Message}");}voidOnOutput(stringdata){if(!string.IsNullOrWhiteSpace(data)){Console.WriteLine(data);}}voidOnError(stringdata){if(!string.IsNullOrWhiteSpace(data)){Console.Error.WriteLine(data);}}publicPythonEmbedManager(string pythonDir)Creates a new instance of the PythonEmbedManager.
Parameters:
pythonDir: The directory where Python will be installed and managed.
Throws:
ArgumentException: IfpythonDiris null or empty.
publicstringPythonEmbedUrl{get;set;}Gets or sets the URL to download the embedded Python distribution. Default is Python 3.11.6 for Windows AMD64.
publicasyncTask<int>InitPythonEnvironment(Action<string>onOutput,Action<string>onError)Initializes the Python environment by downloading, extracting, and setting up Python with pip and virtualenv.
Returns:0 on success, otherwise an error code.
publicasyncTask<int>InstallRequirement(stringrequirementPath,Action<string>onOutput,Action<string>onError)Installs Python packages from a requirements.txt file.
Parameters:
requirementPath: Path to the requirements.txt file.onOutput: Callback for standard output.onError: Callback for error output.
Returns:0 on success, otherwise an error code.
Throws:
ArgumentException: IfrequirementPathis null or empty.FileNotFoundException: If the requirements file doesn't exist.
publicasyncTask<int>InstallRequirementInEditorMode(stringworkDir,Action<string>onOutput,Action<string>onError)Installs a Python project using pip install -e <workDir>.
Parameters:
workDir: Directory that contains asetup.pyorpyproject.tomlfile.onOutput: Callback for standard output.onError: Callback for error output.
Returns:0 on success, otherwise an error code.
Throws:
ArgumentException: IfworkDiris null or empty.
publicasyncTask<int>InstallPackagesAsync(IEnumerable<string>packages,string?extraIndexUrl,Action<string>onOutput,Action<string>onError)Installs arbitrary pip packages inside the embedded virtual environment.
Parameters:
packages: Collection of pip arguments (e.g. package names, version specifiers).extraIndexUrl: Optional alternative package index.onOutput: Callback for standard output.onError: Callback for error output.
Returns:0 on success, otherwise an error code.
Throws:
ArgumentNullException: Ifpackagesis null.ArgumentException: If no packages are provided.
publicasyncTask<int>InstallTorchWithCudaAsync(string?torchVersion,string?cudaOverride,Action<string>onOutput,Action<string>onError)Installs the CUDA-compatible PyTorch stack (torch, torchvision, torchaudio).
Parameters:
torchVersion: Optional PyTorch version (for example,"2.6.0").cudaOverride: Optional CUDA tag or version (for example,"cu126"or"12.6").onOutput: Callback for standard output.onError: Callback for error output.
Returns:0 on success, otherwise an error code.
publicasyncTask<int>RunPython(stringpythonScript,stringparameters,stringworkingDirectory,Action<string>onOutput,Action<string>onError,Action<Process>onProcessStarted)Executes a Python script with optional parameters and exposes the spawned Process instance.
Parameters:
pythonScript: Path to the Python script to execute.parameters: Command-line arguments to pass to the script.workingDirectory: Working directory for script execution (defaults to the Python directory when null or empty).onOutput: Callback for standard output.onError: Callback for error output.onProcessStarted: Callback invoked once the Python process has started.
Returns:0 on success, otherwise an error code.
Throws:
ArgumentException: IfpythonScriptis null or empty.FileNotFoundException: If the script file doesn't exist.
publicintRemovePythonEnvironment(Action<string>onOutput,Action<string>onError)Completely removes the Python environment from disk.
Returns:0 on success, -1 on failure.
- .NET 8.0 or later
- Internet connection (for initial Python download)
- Disk space for Python distribution (~50MB) and packages
- ✅ Windows (x64)
- ✅ Linux (with appropriate Python embed URL)
- ✅ macOS (with appropriate Python embed URL)
Note: The default PythonEmbedUrl points to a Windows AMD64 distribution. For other platforms, set the PythonEmbedUrl property to an appropriate Python embeddable distribution for your target platform.
- Download: On first run, the embedded Python distribution is downloaded from the official Python FTP server.
- Extract: The distribution is extracted to the specified directory.
- Bootstrap:
get-pip.pyis downloaded and executed to install pip. - Virtual Environment: A virtual environment (
venv) is created usingvirtualenv. - Isolation: All subsequent operations (package installation, script execution) occur within this isolated environment.
- 📊 Data Processing: Run Python data analysis scripts from .NET applications
- 🤖 ML Integration: Execute machine learning models built with Python frameworks
- 🔧 Scripting: Leverage Python libraries for specific tasks within .NET apps
- 🎨 Content Generation: Use Python tools (like edge-tts, image processing) in .NET workflows
- 🧪 Testing: Run Python-based test scripts or validation tools
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
- Python Software Foundation for the embedded Python distributions
- The .NET community for feedback and contributions
If you encounter any issues or have questions:
Made with ❤️ by the DotNetPythonEmbed Contributors