diff --git a/src/DynamoPackages/PackageLoader.cs b/src/DynamoPackages/PackageLoader.cs index 352e44aa99a..93d332c16d9 100644 --- a/src/DynamoPackages/PackageLoader.cs +++ b/src/DynamoPackages/PackageLoader.cs @@ -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); diff --git a/src/NodeServices/PythonServices.cs b/src/NodeServices/PythonServices.cs index 6f6c0f2ce4a..8fdb95ea327 100644 --- a/src/NodeServices/PythonServices.cs +++ b/src/NodeServices/PythonServices.cs @@ -111,12 +111,50 @@ internal static readonly Lazy public static PythonEngineManager Instance { get { return lazy.Value; } } #endregion - //TODO see DYN-6550 when hiding/replacing this obsolete field. + /// + /// A readonly collection of all the loaded Python engines + /// + public ReadOnlyCollection PythonEngines => new ReadOnlyCollection(AvailableEngines); + /// /// An observable collection of all the loaded Python engines /// - [Obsolete("AvailableEngines field will be replaced in a future Dynamo release.")] - public ObservableCollection AvailableEngines; + internal ObservableCollection AvailableEngines; + + public delegate void PythonEngineChangedHandler(PythonEngine engine); + + private Action customizeEngine; + + /// + /// Provides an easy way to run initialization code on PythonEngine instances + /// + /// Action to be called on PythonEngines. + /// If true, the action will be called on existing PythonEngines. If false the action will be called when new Python engines are added. + public void ApplyInitializationAction(Action initAction, bool callOnExistingEngines = true) + { + if (initAction != null) + { + customizeEngine = initAction; + + if (callOnExistingEngines) + { + foreach (var engine in AvailableEngines) + { + initAction(engine); + } + } + } + } + + /// + /// Event that is triggered for every new engine that is added. + /// + public event PythonEngineChangedHandler PythonEngineAdded; + + /// + /// Event that is triggered for every engine that is removed. + /// + public event PythonEngineChangedHandler PythonEngineRemoved; #region Constant strings @@ -132,16 +170,11 @@ internal static readonly Lazy 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"; @@ -157,7 +190,7 @@ internal static readonly Lazy private PythonEngineManager() { AvailableEngines = new ObservableCollection(); - + 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(). @@ -166,6 +199,48 @@ private PythonEngineManager() 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 || @@ -180,7 +255,7 @@ private void LoadDefaultPythonEngine(Assembly a) } 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}"); } } @@ -189,16 +264,32 @@ private PythonEngine GetEngine(string name) return AvailableEngines.FirstOrDefault(x => x.Name == name); } - // This method can throw exceptions. - internal void LoadPythonEngine(IEnumerable assemblies) + /// + /// Load Python Engines from an array of assemblies + /// + /// + internal void LoadPythonEngine(Action logger, IEnumerable assemblies) { 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. + /// + /// Load Python Engines from an assembly + /// + /// + /// + /// 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)