forked from TensorStack-AI/TensorStack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPythonManager.cs
More file actions
262 lines (229 loc) · 10.5 KB
/
PythonManager.cs
File metadata and controls
262 lines (229 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
using CSnakes.Runtime;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.IO.Compression;
using System.Net.Http;
using System.Threading.Tasks;
using TensorStack.Common.Common;
using TensorStack.Python.Common;
using TensorStack.Python.Config;
namespace TensorStack.Python
{
/// <summary>
/// PythonManager - Manage Python portable installation and virtual environment creation
/// </summary>
public class PythonManager
{
private readonly ILogger _logger;
private readonly EnvironmentConfig _config;
private readonly string _pythonPath;
private readonly string _pipelinePath;
private readonly string _baseDirectory;
private readonly string _pythonVersion = "3.12.10";
/// <summary>
/// Initializes a new instance of the <see cref="PythonManager"/> class.
/// </summary>
/// <param name="config">The configuration.</param>
/// <param name="baseDirectory">The base directory.</param>
/// <param name="logger">The logger.</param>
public PythonManager(EnvironmentConfig config, string baseDirectory, ILogger logger = default)
{
_logger = logger;
_config = config;
_baseDirectory = Path.GetFullPath(baseDirectory);
_pythonPath = Path.GetFullPath(Path.Join(_config.Directory, "Python"));
_pipelinePath = Path.GetFullPath(Path.Join(_config.Directory, "Pipelines"));
CopyInternalPythonFiles();
CopyInternalPipelineFiles();
}
/// <summary>
/// Initializes a new instance of the <see cref="PythonManager"/> class.
/// </summary>
/// <param name="config">The configuration.</param>
/// <param name="logger">The logger.</param>
public PythonManager(EnvironmentConfig config, ILogger logger = default)
: this(config, AppDomain.CurrentDomain.BaseDirectory, logger) { }
/// <summary>
/// Load an existing environment.
/// </summary>
/// <param name="progressCallback">The progress callback.</param>
public async Task<IPythonEnvironment> LoadAsync(IProgress<PipelineProgress> progressCallback = null)
{
return await LoadInternalAsync(progressCallback);
}
/// <summary>
/// Creates the Python Virtual Environment.
/// If the environment already exists it is loaded after package manager is run (update)
/// </summary>
/// <param name="isRebuild">Delete and rebuild the environment</param>
/// <param name="isReinstall">Delete and rebuild the environment and base Python installation</param>
public Task<IPythonEnvironment> CreateAsync(EnvironmentMode mode, IProgress<PipelineProgress> progressCallback = null)
{
return Task.Run(async () =>
{
var isRebuild = mode == EnvironmentMode.Rebuild;
var isReinstall = mode == EnvironmentMode.Reinstall;
await DownloadAsync(isReinstall, progressCallback);
if (isReinstall || isRebuild)
await DeleteAsync();
return await CreateInternalAsync(progressCallback);
});
}
/// <summary>
/// Delete an environment
/// </summary>
private async Task<bool> DeleteAsync()
{
var path = Path.Combine(_pipelinePath, $".{_config.Environment}");
if (!Directory.Exists(path))
return false;
await Task.Run(() => Directory.Delete(path, true));
return Exists();
}
/// <summary>
/// Checks if a environment exists
/// </summary>
/// <param name="name">The name.</param>
public bool Exists()
{
var path = Path.Combine(_pipelinePath, $".{_config.Environment}");
return Directory.Exists(path);
}
/// <summary>
/// Creates an environment.
/// </summary>
private async Task<IPythonEnvironment> CreateInternalAsync(IProgress<PipelineProgress> progressCallback = null)
{
var requirementsFile = Path.Combine(_pipelinePath, "requirements.txt");
try
{
progressCallback.SendMessage($"Creating Python Virtual Environment (.{_config.Environment})");
await File.WriteAllLinesAsync(requirementsFile, _config.Requirements);
var environment = PythonEnvironmentHelper.CreateEnvironment(_config.Environment, _pythonPath, _pipelinePath, requirementsFile, _pythonVersion, _logger);
progressCallback.SendMessage($"Python Virtual Environment Created");
return environment;
}
finally
{
FileHelper.DeleteFile(requirementsFile);
}
}
/// <summary>
/// Load an existing Environment.
/// </summary>
/// <param name="progressCallback">The progress callback.</param>
/// <returns>A Task<IPythonEnvironment> representing the asynchronous operation.</returns>
/// <exception cref="System.Exception">Environment does not exist</exception>
private async Task<IPythonEnvironment> LoadInternalAsync(IProgress<PipelineProgress> progressCallback = null)
{
if (!Exists())
throw new Exception("Environment does not exist");
return await Task.Run(() =>
{
progressCallback.SendMessage($"Loading Python Virtual Environment (.{_config.Environment})");
var environment = PythonEnvironmentHelper.CreateEnvironment(_config.Environment, _pythonPath, _pipelinePath, _pythonVersion, _logger);
progressCallback.SendMessage($"Python Virtual Environment Loaded");
return environment;
});
}
/// <summary>
/// Downloads and installs Win-Python portable v3.12.10.
/// </summary>
/// <param name="reinstall">if set to <c>true</c> [reinstall].</param>
private async Task DownloadAsync(bool reinstall, IProgress<PipelineProgress> progressCallback = null)
{
var subfolder = "WPy64-312100/python";
var exePath = Path.Combine(_pythonPath, "python.exe");
var downloadPath = Path.Combine(_pythonPath, "Winpython64-3.12.10.0dot.zip");
var pythonUrl = "https://github.com/winpython/winpython/releases/download/15.3.20250425final/Winpython64-3.12.10.0dot.zip";
if (reinstall)
{
progressCallback.SendMessage($"Reinstalling Python {_pythonVersion}...");
if (File.Exists(downloadPath))
File.Delete(downloadPath);
if (Directory.Exists(_pythonPath))
Directory.Delete(_pythonPath, true);
progressCallback.SendMessage($"Python Uninstalled.");
}
// Create Python
Directory.CreateDirectory(_pythonPath);
// Download Python
if (!File.Exists(downloadPath))
{
progressCallback.SendMessage($"Download Python {_pythonVersion}...");
using (var httpClient = new HttpClient())
using (var response = await httpClient.GetAsync(pythonUrl))
{
response.EnsureSuccessStatusCode();
using (var stream = new FileStream(downloadPath, FileMode.Create, FileAccess.Write, FileShare.None))
{
await response.Content.CopyToAsync(stream);
}
}
progressCallback.SendMessage("Python Download Complete.");
}
// Extract ZIP file
if (!File.Exists(exePath))
{
progressCallback.SendMessage($"Installing Python {_pythonVersion}...");
CopyInternalPythonFiles();
using (var archive = ZipFile.OpenRead(downloadPath))
{
foreach (var entry in archive.Entries)
{
if (entry.FullName.StartsWith(subfolder, StringComparison.OrdinalIgnoreCase))
{
var relativePath = entry.FullName.Replace('/', '\\').Substring(subfolder.Length);
if (string.IsNullOrWhiteSpace(relativePath))
continue;
var isDirectory = relativePath.EndsWith('\\');
var destinationPath = Path.Combine(_pythonPath, relativePath.TrimStart('\\').TrimEnd('\\'));
if (isDirectory)
{
Directory.CreateDirectory(destinationPath);
continue;
}
entry.ExtractToFile(destinationPath, overwrite: true);
}
}
}
progressCallback.SendMessage($"Python Install Complete.");
}
}
/// <summary>
/// Copies the internal python files.
/// </summary>
private void CopyInternalPythonFiles()
{
Directory.CreateDirectory(_pythonPath);
CopyFiles(Path.Combine(_baseDirectory, "Python"), _pythonPath);
}
/// <summary>
/// Copies the internal pipeline files.
/// </summary>
private void CopyInternalPipelineFiles()
{
Directory.CreateDirectory(_pipelinePath);
CopyFiles(Path.Combine(_baseDirectory, "Pipelines"), _pipelinePath);
}
/// <summary>
/// Copies the files from source to target.
/// </summary>
/// <param name="sourcePath">The source path.</param>
/// <param name="targetPath">The target path.</param>
private static void CopyFiles(string sourcePath, string targetPath)
{
foreach (var dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories))
Directory.CreateDirectory(dirPath.Replace(sourcePath, targetPath));
foreach (var sourceFile in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories))
{
var targetFile = sourceFile.Replace(sourcePath, targetPath);
if (!File.Exists(targetFile) || File.GetLastWriteTimeUtc(sourceFile) > File.GetLastWriteTimeUtc(targetFile))
{
File.Copy(sourceFile, targetFile, true);
}
}
}
}
}