|
| 1 | +# DotNetPythonEmbed |
| 2 | + |
| 3 | +[](https://www.nuget.org/packages/DotNetPythonEmbed/) |
| 4 | +[](https://opensource.org/licenses/MIT) |
| 5 | + |
| 6 | +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. |
| 7 | + |
| 8 | +## Features |
| 9 | + |
| 10 | +- 🐍 **Automatic Python Distribution Management** - Downloads and extracts an embedded Python distribution on first use |
| 11 | +- 📦 **Virtual Environment Support** - Creates and manages isolated Python virtual environments |
| 12 | +- 🔧 **Pip Integration** - Installs Python packages from requirements.txt files |
| 13 | +- 🚀 **Script Execution** - Run Python scripts with full environment isolation |
| 14 | +- 🔄 **Cross-Platform Ready** - Supports both Windows and Unix-based systems |
| 15 | +- 🧹 **Cleanup Utilities** - Remove Python environments when no longer needed |
| 16 | + |
| 17 | +## Installation |
| 18 | + |
| 19 | +Install the package via NuGet: |
| 20 | + |
| 21 | +```bash |
| 22 | +dotnet add package DotNetPythonEmbed |
| 23 | +``` |
| 24 | + |
| 25 | +Or via the Package Manager Console: |
| 26 | + |
| 27 | +```powershell |
| 28 | +Install-Package DotNetPythonEmbed |
| 29 | +``` |
| 30 | + |
| 31 | +## Quick Start |
| 32 | + |
| 33 | +```csharp |
| 34 | +using DotNetPythonEmbed; |
| 35 | + |
| 36 | +// Define the directory where Python will be installed |
| 37 | +var pythonDirectory = Path.Combine(AppContext.BaseDirectory, "python-runtime"); |
| 38 | + |
| 39 | +// Create the manager |
| 40 | +var embedManager = new PythonEmbedManager(pythonDirectory); |
| 41 | + |
| 42 | +// Initialize the Python environment (downloads Python if not present) |
| 43 | +await embedManager.InitPythonEnvironment( |
| 44 | + onOutput: Console.WriteLine, |
| 45 | + onError: Console.Error.WriteLine |
| 46 | +); |
| 47 | + |
| 48 | +// Install dependencies from requirements.txt |
| 49 | +var requirementsPath = "path/to/requirements.txt"; |
| 50 | +if (File.Exists(requirementsPath)) |
| 51 | +{ |
| 52 | + await embedManager.InstallRequirement(requirementsPath, Console.WriteLine, Console.Error.WriteLine); |
| 53 | +} |
| 54 | + |
| 55 | +// Run a Python script |
| 56 | +var scriptPath = "path/to/script.py"; |
| 57 | +await embedManager.RunPython(scriptPath, "--arg1 value1", null, Console.WriteLine, Console.Error.WriteLine); |
| 58 | +``` |
| 59 | + |
| 60 | +## Usage |
| 61 | + |
| 62 | +### 1. Initialize the Python Environment |
| 63 | + |
| 64 | +```csharp |
| 65 | +var pythonDir = Path.Combine(AppContext.BaseDirectory, "python-runtime"); |
| 66 | +var manager = new PythonEmbedManager(pythonDir); |
| 67 | + |
| 68 | +// Optional: Override the default Python version |
| 69 | +manager.PythonEmbedUrl = "https://www.python.org/ftp/python/3.13.9/python-3.13.9-embed-amd64.zip"; |
| 70 | + |
| 71 | +// Initialize (downloads and sets up Python + pip + virtualenv) |
| 72 | +await manager.InitPythonEnvironment( |
| 73 | + onOutput: msg => Console.WriteLine(msg), |
| 74 | + onError: err => Console.Error.WriteLine(err) |
| 75 | +); |
| 76 | +``` |
| 77 | + |
| 78 | +The `InitPythonEnvironment` method: |
| 79 | +- Checks if Python is already installed at the specified directory |
| 80 | +- If not, downloads the embedded Python distribution |
| 81 | +- Extracts the distribution |
| 82 | +- Installs pip |
| 83 | +- Creates a virtual environment named `venv` |
| 84 | + |
| 85 | +### 2. Install Python Packages |
| 86 | + |
| 87 | +```csharp |
| 88 | +var requirementsPath = Path.Combine(repoDirectory, "requirements.txt"); |
| 89 | + |
| 90 | +await manager.InstallRequirement( |
| 91 | +requirementsPath, |
| 92 | + onOutput: Console.WriteLine, |
| 93 | + onError: Console.Error.WriteLine |
| 94 | +); |
| 95 | +``` |
| 96 | + |
| 97 | +The `InstallRequirement` method uses pip within the virtual environment to install packages specified in a requirements.txt file. |
| 98 | + |
| 99 | +### 3. Run Python Scripts |
| 100 | + |
| 101 | +```csharp |
| 102 | +var scriptPath = Path.Combine(projectDirectory, "script.py"); |
| 103 | +var workingDirectory = projectDirectory; |
| 104 | +var scriptArguments = "--input data.txt --output results.json"; |
| 105 | + |
| 106 | +int exitCode = await manager.RunPython( |
| 107 | + pythonScript: scriptPath, |
| 108 | + parameters: scriptArguments, |
| 109 | + workingDirectory: workingDirectory, |
| 110 | + onOutput: Console.WriteLine, |
| 111 | + onError: Console.Error.WriteLine |
| 112 | +); |
| 113 | + |
| 114 | +if (exitCode != 0) |
| 115 | +{ |
| 116 | + Console.WriteLine($"Script failed with exit code: {exitCode}"); |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +### 4. Clean Up |
| 121 | + |
| 122 | +```csharp |
| 123 | +// Remove the Python environment if needed |
| 124 | +manager.RemovePythonEnvironment( |
| 125 | + onOutput: Console.WriteLine, |
| 126 | + onError: Console.Error.WriteLine |
| 127 | +); |
| 128 | +``` |
| 129 | + |
| 130 | +## Complete Example |
| 131 | + |
| 132 | +Here's a complete example that clones a GitHub repository and runs a Python script from it: |
| 133 | + |
| 134 | +```csharp |
| 135 | +using DotNetPythonEmbed; |
| 136 | + |
| 137 | +var repoUrl = "https://github.com/username/python-project.git"; |
| 138 | +var baseDirectory = AppContext.BaseDirectory; |
| 139 | +var repoDirectory = Path.Combine(baseDirectory, "python-project"); |
| 140 | +var pythonDirectory = Path.Combine(baseDirectory, "python-runtime"); |
| 141 | + |
| 142 | +try |
| 143 | +{ |
| 144 | + // Clone the repository (using LibGit2Sharp or similar) |
| 145 | + Console.WriteLine($"Cloning '{repoUrl}' to '{repoDirectory}'."); |
| 146 | + if (!Directory.Exists(repoDirectory)) |
| 147 | + { |
| 148 | + // Repository.Clone(repoUrl, repoDirectory); |
| 149 | + } |
| 150 | + |
| 151 | + // Initialize Python environment |
| 152 | + Console.WriteLine("Initializing embedded Python environment."); |
| 153 | + var embedManager = new PythonEmbedManager(pythonDirectory); |
| 154 | + await embedManager.InitPythonEnvironment(OnOutput, OnError); |
| 155 | + |
| 156 | + // Install requirements |
| 157 | + var requirementsPath = Path.Combine(repoDirectory, "requirements.txt"); |
| 158 | + if (File.Exists(requirementsPath)) |
| 159 | + { |
| 160 | + Console.WriteLine("Installing Python requirements."); |
| 161 | + await embedManager.InstallRequirement(requirementsPath, OnOutput, OnError); |
| 162 | + } |
| 163 | + |
| 164 | + // Run the script |
| 165 | + var scriptPath = Path.Combine(repoDirectory, "main.py"); |
| 166 | + Console.WriteLine($"Executing script '{scriptPath}'."); |
| 167 | + int exitCode = await embedManager.RunPython( |
| 168 | + scriptPath, |
| 169 | + string.Empty, |
| 170 | + repoDirectory, |
| 171 | + OnOutput, |
| 172 | + OnError |
| 173 | + ); |
| 174 | + |
| 175 | + Console.WriteLine($"Script completed with exit code: {exitCode}"); |
| 176 | + |
| 177 | + // Optional: Clean up |
| 178 | + embedManager.RemovePythonEnvironment(OnOutput, OnError); |
| 179 | +} |
| 180 | +catch (Exception ex) |
| 181 | +{ |
| 182 | + Console.Error.WriteLine($"An error occurred: {ex.Message}"); |
| 183 | +} |
| 184 | + |
| 185 | +void OnOutput(string data) |
| 186 | +{ |
| 187 | + if (!string.IsNullOrWhiteSpace(data)) |
| 188 | + Console.WriteLine(data); |
| 189 | +} |
| 190 | + |
| 191 | +void OnError(string data) |
| 192 | +{ |
| 193 | + if (!string.IsNullOrWhiteSpace(data)) |
| 194 | + Console.Error.WriteLine(data); |
| 195 | +} |
| 196 | +``` |
| 197 | + |
| 198 | +## API Reference |
| 199 | + |
| 200 | +### PythonEmbedManager Constructor |
| 201 | + |
| 202 | +```csharp |
| 203 | +public PythonEmbedManager(string pythonDir) |
| 204 | +``` |
| 205 | + |
| 206 | +Creates a new instance of the PythonEmbedManager. |
| 207 | + |
| 208 | +**Parameters:** |
| 209 | +- `pythonDir`: The directory where Python will be installed and managed. |
| 210 | + |
| 211 | +**Throws:** |
| 212 | +- `ArgumentException`: If `pythonDir` is null or empty. |
| 213 | + |
| 214 | +### Properties |
| 215 | + |
| 216 | +#### PythonEmbedUrl |
| 217 | + |
| 218 | +```csharp |
| 219 | +public string PythonEmbedUrl { get; set; } |
| 220 | +``` |
| 221 | + |
| 222 | +Gets or sets the URL to download the embedded Python distribution. Default is Python 3.11.6 for Windows AMD64. |
| 223 | + |
| 224 | +### Methods |
| 225 | + |
| 226 | +#### InitPythonEnvironment |
| 227 | + |
| 228 | +```csharp |
| 229 | +public async Task<int> InitPythonEnvironment(Action<string> onOutput, Action<string> onError) |
| 230 | +``` |
| 231 | + |
| 232 | +Initializes the Python environment by downloading, extracting, and setting up Python with pip and virtualenv. |
| 233 | + |
| 234 | +**Returns:** `0` on success, otherwise an error code. |
| 235 | + |
| 236 | +#### InstallRequirement |
| 237 | + |
| 238 | +```csharp |
| 239 | +public async Task<int> InstallRequirement(string requirementPath, Action<string> onOutput, Action<string> onError) |
| 240 | +``` |
| 241 | + |
| 242 | +Installs Python packages from a requirements.txt file. |
| 243 | + |
| 244 | +**Parameters:** |
| 245 | +- `requirementPath`: Path to the requirements.txt file. |
| 246 | +- `onOutput`: Callback for standard output. |
| 247 | +- `onError`: Callback for error output. |
| 248 | + |
| 249 | +**Returns:** `0` on success, otherwise an error code. |
| 250 | + |
| 251 | +**Throws:** |
| 252 | +- `ArgumentException`: If `requirementPath` is null or empty. |
| 253 | +- `FileNotFoundException`: If the requirements file doesn't exist. |
| 254 | + |
| 255 | +#### RunPython |
| 256 | + |
| 257 | +```csharp |
| 258 | +public async Task<int> RunPython(string pythonScript, string parameters, string workingDirectory, Action<string> onOutput, Action<string> onError) |
| 259 | +``` |
| 260 | + |
| 261 | +Executes a Python script with optional parameters. |
| 262 | + |
| 263 | +**Parameters:** |
| 264 | +- `pythonScript`: Path to the Python script to execute. |
| 265 | +- `parameters`: Command-line arguments to pass to the script. |
| 266 | +- `workingDirectory`: Working directory for script execution (defaults to Python directory). |
| 267 | +- `onOutput`: Callback for standard output. |
| 268 | +- `onError`: Callback for error output. |
| 269 | + |
| 270 | +**Returns:** `0` on success, otherwise an error code. |
| 271 | + |
| 272 | +**Throws:** |
| 273 | +- `ArgumentException`: If `pythonScript` is null or empty. |
| 274 | +- `FileNotFoundException`: If the script file doesn't exist. |
| 275 | + |
| 276 | +#### RemovePythonEnvironment |
| 277 | + |
| 278 | +```csharp |
| 279 | +public int RemovePythonEnvironment(Action<string> onOutput, Action<string> onError) |
| 280 | +``` |
| 281 | + |
| 282 | +Completely removes the Python environment from disk. |
| 283 | + |
| 284 | +**Returns:** `0` on success, `-1` on failure. |
| 285 | + |
| 286 | +## Requirements |
| 287 | + |
| 288 | +- .NET 8.0 or later |
| 289 | +- Internet connection (for initial Python download) |
| 290 | +- Disk space for Python distribution (~50MB) and packages |
| 291 | + |
| 292 | +## Platform Support |
| 293 | + |
| 294 | +- ✅ Windows (x64) |
| 295 | +- ✅ Linux (with appropriate Python embed URL) |
| 296 | +- ✅ macOS (with appropriate Python embed URL) |
| 297 | + |
| 298 | +**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. |
| 299 | + |
| 300 | +## How It Works |
| 301 | + |
| 302 | +1. **Download**: On first run, the embedded Python distribution is downloaded from the official Python FTP server. |
| 303 | +2. **Extract**: The distribution is extracted to the specified directory. |
| 304 | +3. **Bootstrap**: `get-pip.py` is downloaded and executed to install pip. |
| 305 | +4. **Virtual Environment**: A virtual environment (`venv`) is created using `virtualenv`. |
| 306 | +5. **Isolation**: All subsequent operations (package installation, script execution) occur within this isolated environment. |
| 307 | + |
| 308 | +## Use Cases |
| 309 | + |
| 310 | +- 📊 **Data Processing**: Run Python data analysis scripts from .NET applications |
| 311 | +- 🤖 **ML Integration**: Execute machine learning models built with Python frameworks |
| 312 | +- 🔧 **Scripting**: Leverage Python libraries for specific tasks within .NET apps |
| 313 | +- 🎨 **Content Generation**: Use Python tools (like edge-tts, image processing) in .NET workflows |
| 314 | +- 🧪 **Testing**: Run Python-based test scripts or validation tools |
| 315 | + |
| 316 | +## Contributing |
| 317 | + |
| 318 | +Contributions are welcome! Please feel free to submit a Pull Request. |
| 319 | + |
| 320 | +## License |
| 321 | + |
| 322 | +This project is licensed under the MIT License - see the LICENSE file for details. |
| 323 | + |
| 324 | +## Acknowledgments |
| 325 | + |
| 326 | +- Python Software Foundation for the embedded Python distributions |
| 327 | +- The .NET community for feedback and contributions |
| 328 | + |
| 329 | +## Support |
| 330 | + |
| 331 | +If you encounter any issues or have questions: |
| 332 | +- 🐛 [Report a bug](https://github.com/xqiu/DotNetPythonEmbed/issues) |
| 333 | +- 💡 [Request a feature](https://github.com/xqiu/DotNetPythonEmbed/issues) |
| 334 | +- 📖 [View documentation](https://github.com/xqiu/DotNetPythonEmbed) |
| 335 | + |
| 336 | +--- |
| 337 | + |
| 338 | +Made with ❤️ by the DotNetPythonEmbed Contributors |
0 commit comments