Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor python engines api #14940

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/DynamoPackages/PackageLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ private void TryLoadPackageIntoLibrary(Package package)


PythonServices.PythonEngineManager.Instance.
LoadPythonEngine(package.LoadedAssemblies.Select(x => x.Assembly));
LoadPythonEngine(Log, package.LoadedAssemblies.Select(x => x.Assembly));

Log($"Loaded Package {package.Name} {package.VersionName} from {package.RootDirectory}");
PackgeLoaded?.Invoke(package);
Expand Down
119 changes: 105 additions & 14 deletions src/NodeServices/PythonServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,50 @@
public static PythonEngineManager Instance { get { return lazy.Value; } }
#endregion

//TODO see DYN-6550 when hiding/replacing this obsolete field.
/// <summary>
/// A readonly collection of all the loaded Python engines
/// </summary>
public ReadOnlyCollection<PythonEngine> PythonEngines => new ReadOnlyCollection<PythonEngine>(AvailableEngines);

/// <summary>
/// An observable collection of all the loaded Python engines
/// </summary>
[Obsolete("AvailableEngines field will be replaced in a future Dynamo release.")]
public ObservableCollection<PythonEngine> AvailableEngines;
internal ObservableCollection<PythonEngine> AvailableEngines;

public delegate void PythonEngineChangedHandler(PythonEngine engine);

private Action<PythonEngine> customizeEngine;

/// <summary>
/// Provides an easy way to run initialization code on PythonEngine instances
/// </summary>
/// <param name="initAction">Action to be called on PythonEngines.</param>
/// <param name="callOnExistingEngines">If true, the action will be called on existing PythonEngines. If false the action will be called when new Python engines are added.</param>
public void ApplyInitializationAction(Action<PythonEngine> initAction, bool callOnExistingEngines = true)
{
if (initAction != null)
{
customizeEngine = initAction;

if (callOnExistingEngines)
{
foreach (var engine in AvailableEngines)
{
initAction(engine);
}
}
}
}

/// <summary>
/// Event that is triggered for every new engine that is added.
/// </summary>
public event PythonEngineChangedHandler PythonEngineAdded;

/// <summary>
/// Event that is triggered for every engine that is removed.
/// </summary>
public event PythonEngineChangedHandler PythonEngineRemoved;

#region Constant strings

Expand All @@ -132,16 +170,11 @@

internal static string PythonEvaluatorSingletonInstance = "Instance";

internal static string IronPythonEvaluatorClass = "IronPythonEvaluator";
internal static string IronPythonEvaluationMethod = "EvaluateIronPythonScript";

internal static string CPythonEvaluatorClass = "CPythonEvaluator";
internal static string CPythonEvaluationMethod = "EvaluatePythonScript";

internal static string IronPythonAssemblyName = "DSIronPython";
internal static string CPythonAssemblyName = "DSCPython";

internal static string IronPythonTypeName = IronPythonAssemblyName + "." + IronPythonEvaluatorClass;
internal static string CPythonTypeName = CPythonAssemblyName + "." + CPythonEvaluatorClass;

internal static string PythonInputMarshalerProperty = "InputMarshaler";
Expand All @@ -157,7 +190,7 @@
private PythonEngineManager()
{
AvailableEngines = new ObservableCollection<PythonEngine>();

AvailableEngines.CollectionChanged += AvailableEngines_CollectionChanged;
// We check only for the default python engine because it is the only one loaded by static references.
// Other engines can only be loaded through package manager
LoadDefaultPythonEngine(AppDomain.CurrentDomain.GetAssemblies().
Expand All @@ -166,6 +199,48 @@
AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler((object sender, AssemblyLoadEventArgs args) => LoadDefaultPythonEngine(args.LoadedAssembly));
}

private void AvailableEngines_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
for (var ii=0; ii< e.NewItems.Count; ii++)
{
try
{
PythonEngineAdded?.Invoke(e.NewItems[ii] as PythonEngine);
}
catch(Exception ex)
{
Console.WriteLine("PythonEngineAdded event failed with error : " + ex.Message + Environment.NewLine + ex.StackTrace);
}

try
{
customizeEngine.Invoke(e.NewItems[ii] as PythonEngine);
}
catch (Exception ex)
{
Console.WriteLine("CustomizeEngine failed with error : " + ex.Message + Environment.NewLine + ex.StackTrace);
}
}
}

if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
{
for (var ii = 0; ii < e.OldItems.Count; ii++)
{
try
{
PythonEngineRemoved?.Invoke(e.OldItems[ii] as PythonEngine);
}
catch (Exception ex)
{
Console.WriteLine("PythonEngineRemoved event failed with error : " + ex.Message + Environment.NewLine + ex.StackTrace);
}
}
}
}

private void LoadDefaultPythonEngine(Assembly a)
{
if (a == null ||
Expand All @@ -180,7 +255,7 @@
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine($"Failed to load {CPythonAssemblyName} with error: {e.Message}");
Console.WriteLine($"Failed to load {CPythonAssemblyName} with error: {e.Message}");
}
}

Expand All @@ -189,16 +264,32 @@
return AvailableEngines.FirstOrDefault(x => x.Name == name);
}

// This method can throw exceptions.
internal void LoadPythonEngine(IEnumerable<Assembly> assemblies)
/// <summary>
/// Load Python Engines from an array of assemblies
/// </summary>
/// <param name="assemblies"></param>
internal void LoadPythonEngine(Action<string> logger, IEnumerable<Assembly> assemblies)

Check warning on line 271 in src/NodeServices/PythonServices.cs

View workflow job for this annotation

GitHub Actions / analyze

Parameter 'logger' has no matching param tag in the XML comment for 'PythonEngineManager.LoadPythonEngine(Action<string>, IEnumerable<Assembly>)' (but other parameters do)
{
foreach (var a in assemblies)
{
LoadPythonEngine(a);
try
{
LoadPythonEngine(a);
}
catch(Exception e)
{
logger?.Invoke("Failed when looking for PythonEngines with error: " + e.Message + Environment.NewLine + e.StackTrace);
}
}
}

// This method can throw exceptions.
/// <summary>
/// Load Python Engines from an assembly
/// </summary>
/// <param name="assembly"></param>
/// <exception cref="Exception"></exception>
/// Make this public to support custom loading of Python Engines (ex load in isolated alc on the python assembly side)
/// How to transition to Dynamo loading in isolated ALC ?
private void LoadPythonEngine(Assembly assembly)
{
if (assembly == null)
Expand Down
Loading