Skip to content

Commit

Permalink
Easily install missing IronPython extension package (#10869)
Browse files Browse the repository at this point in the history
* easily install missing ironpython extension package

* code cleanup

* code cleanup

* code cleanup

* fix regressions

* minor fix

* code cleanup

* use event, remove dependency on PythonMigrationViewExtension from WorkspaceDependencyViewExtension

* code cleanup

* more cleanup

* unsubscribe event

Co-authored-by: Aaron (Qilong) <[email protected]>
  • Loading branch information
aparajit-pratap and QilongTang authored Jul 16, 2020
1 parent e15a260 commit 65751e4
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 52 deletions.
21 changes: 0 additions & 21 deletions src/DynamoCore/Graph/Workspaces/PackageDependencyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,27 +252,6 @@ public void AddDependent(Guid guid)
Nodes.Add(guid);
}

/// <summary>
/// Add the Guids of a dependent nodes
/// </summary>
/// <param name="guids"></param>
internal void AddDependents(IEnumerable<Guid> guids)
{
foreach (var guid in guids)
{
Nodes.Add(guid);
}
}

/// <summary>
/// Remove a dependent node
/// </summary>
/// <param name="guid"></param>
internal void RemoveDependent(Guid guid)
{
Nodes.Remove(guid);
}

/// <summary>
/// Checks whether two PackageDependencyInfo instances are equal
/// They are equal if their Name and Versions are equal
Expand Down
17 changes: 17 additions & 0 deletions src/DynamoCore/Graph/Workspaces/WorkspaceModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,23 @@ public HashSet<Guid> Dependencies
}
}

/// <summary>
/// Event requesting subscribers to return additional package dependencies for
/// current workspace.
/// </summary>
internal event Func<IEnumerable<INodeLibraryDependencyInfo>> RequestPackageDependencies;

/// <summary>
/// Raised when the workspace needs to request for additional package dependencies
/// that can be returned from other subscribers such as view extensions.
/// E.g. The PythonMigrationViewExtension returns additional package dependencies required for Python engines.
/// </summary>
/// <returns></returns>
internal IEnumerable<INodeLibraryDependencyInfo> OnRequestPackageDependencies()
{
return RequestPackageDependencies?.Invoke();
}

/// <summary>
/// NodeLibraries that the nodes in this graph depend on
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/DynamoCore/Models/DynamoModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ public WorkspaceModel CurrentWorkspace
var old = currentWorkspace;
currentWorkspace = value;
OnWorkspaceHidden(old);
OnPropertyChanged("CurrentWorkspace");
OnPropertyChanged(nameof(CurrentWorkspace));
}
}

Expand Down
51 changes: 44 additions & 7 deletions src/PythonMigrationViewExtension/GraphPythonDependencies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ namespace Dynamo.PythonMigration
public class GraphPythonDependencies
{
private ViewLoadedParams ViewLoaded { get; set; }
internal static readonly string PythonPackage = "DSIronPython_Test";
internal static readonly Version PythonPackageVersion = new Version(1, 0, 7);


// A dictionary to mark Custom Nodes if they have a IronPython dependency or not.
internal static Dictionary<Guid, CNPythonDependencyType> CustomNodePythonDependencyMap = new Dictionary<Guid, CNPythonDependencyType>();
Expand All @@ -28,21 +31,39 @@ internal GraphPythonDependencies(ViewLoadedParams viewLoadedParams)
this.ViewLoaded = viewLoadedParams;
}

private static bool IsIronPythonPackageLoaded()
{
try
{
PythonEngineSelector.Instance.GetEvaluatorInfo(PythonEngineVersion.IronPython2,
out string evaluatorClass, out string evaluationMethod);
if (evaluatorClass == PythonEngineSelector.Instance.IronPythonEvaluatorClass &&
evaluationMethod == PythonEngineSelector.Instance.IronPythonEvaluationMethod)
{
return true;
}
}
catch (Exception)
{
return false;
}
return false;
}

internal bool ContainsIronPythonDependencyInCurrentWS()
{
var containsIronPythonDependency = false;
var workspace = ViewLoaded.CurrentWorkspaceModel;

if (workspace == null)
throw new ArgumentNullException(nameof(workspace));
var workspace = ViewLoaded.CurrentWorkspaceModel as WorkspaceModel;

var customNodeManager = ViewLoaded.StartupParams.CustomNodeManager;

if (workspace.Nodes.Any(n => IsIronPythonNode(n)))
if (workspace.Nodes.Any(IsIronPythonNode))
{
containsIronPythonDependency = true;
}

// Check if any of the custom nodes has IronPython dependencies in it.
var customNodeManager = ViewLoaded.StartupParams.CustomNodeManager;
var customNodes = workspace.Nodes.OfType<Function>();

if (CustomNodesContainIronPythonDependency(customNodes, customNodeManager))
Expand All @@ -53,16 +74,32 @@ internal bool ContainsIronPythonDependencyInCurrentWS()
return containsIronPythonDependency;
}

internal IEnumerable<INodeLibraryDependencyInfo> AddPythonPackageDependency()
{
if (!ContainsIronPythonDependencyInCurrentWS())
return null;

var packageInfo = new PackageInfo(PythonPackage, PythonPackageVersion);
var packageDependencyInfo = new PackageDependencyInfo(packageInfo);
packageDependencyInfo.State = IsIronPythonPackageLoaded()
? PackageDependencyState.Loaded
: PackageDependencyState.Missing;

return new[] {packageDependencyInfo};
}

// This function returns true, if any of the custom nodes in the input list has an IronPython dependency.
// It traverses all CN's in the given list of customNodes and marks them as true in CustomNodePythonDependency,
// if the prarent custom node or any of its child custom nodes contain an IronPython dependency.
internal static bool CustomNodesContainIronPythonDependency(IEnumerable<Function> customNodes, ICustomNodeManager customNodeManager)
private static bool CustomNodesContainIronPythonDependency(IEnumerable<Function> customNodes, ICustomNodeManager customNodeManager)
{
var containIronPythonDependency = false;

foreach (var customNode in customNodes)
{
customNodeManager.TryGetFunctionWorkspace(customNode.FunctionSignature, false, out ICustomNodeWorkspaceModel customNodeWS);
if (!customNodeManager.TryGetFunctionWorkspace(customNode.FunctionSignature, false,
out ICustomNodeWorkspaceModel customNodeWS))
continue;

// If a custom node workspace is already checked for IronPython dependencies,
// check the CustomNodePythonDependency dictionary instead of processing it again.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public void Loaded(ViewLoadedParams p)
DynamoViewModel = LoadedParams.DynamoWindow.DataContext as DynamoViewModel;
CurrentWorkspace = LoadedParams.CurrentWorkspaceModel as WorkspaceModel;
CustomNodeManager = (CustomNodeManager) LoadedParams.StartupParams.CustomNodeManager;
CurrentWorkspace.RequestPackageDependencies += PythonDependencies.AddPythonPackageDependency;
Dispatcher = Dispatcher.CurrentDispatcher;
DynamoView = LoadedParams.DynamoWindow as DynamoView;

Expand Down Expand Up @@ -119,6 +120,7 @@ private void UnsubscribeFromDynamoEvents()
LoadedParams.CurrentWorkspaceChanged -= OnCurrentWorkspaceChanged;
DynamoViewModel.CurrentSpaceViewModel.Model.NodeAdded -= OnNodeAdded;
DynamoViewModel.Model.Logger.NotificationLogged -= OnNotificationLogged;
CurrentWorkspace.RequestPackageDependencies -= PythonDependencies.AddPythonPackageDependency;
}

private void OnNotificationLogged(NotificationMessage obj)
Expand Down Expand Up @@ -160,8 +162,13 @@ private void OnCurrentWorkspaceChanged(IWorkspaceModel workspace)
{
NotificationTracker.Remove(CurrentWorkspace.Guid);
GraphPythonDependencies.CustomNodePythonDependencyMap.Clear();

CurrentWorkspace.RequestPackageDependencies -= PythonDependencies.AddPythonPackageDependency;

CurrentWorkspace = workspace as WorkspaceModel;

CurrentWorkspace.RequestPackageDependencies += PythonDependencies.AddPythonPackageDependency;

if (Configuration.DebugModes.IsEnabled("Python2ObsoleteMode")
&& !Models.DynamoModel.IsTestMode
&& PythonDependencies.ContainsIronPythonDependencyInCurrentWS())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,12 @@ private void OnWorkspacePropertyChanged(object sender, PropertyChangedEventArgs
/// <param name="ws">workspace model</param>
internal void DependencyRegen(WorkspaceModel ws)
{
this.RestartBanner.Visibility = Visibility.Hidden;
var packageDependencies = ws.NodeLibraryDependencies.Where(d => d is PackageDependencyInfo);
RestartBanner.Visibility = Visibility.Hidden;
var packageDependencies = ws.NodeLibraryDependencies.Where(d => d is PackageDependencyInfo).ToList();

var pythonPackageDependencies = ws.OnRequestPackageDependencies();
if (pythonPackageDependencies != null)
packageDependencies.AddRange(pythonPackageDependencies);

if (packageDependencies.Any(d => d.State != PackageDependencyState.Loaded))
{
Expand All @@ -137,7 +141,8 @@ internal void DependencyRegen(WorkspaceModel ws)
// If package is set to uninstall state, update the package info
foreach (var package in dependencyViewExtension.pmExtension.PackageLoader.LocalPackages.Where(x => x.MarkedForUninstall))
{
(packageDependencies.Where(x => x.Name == package.Name).FirstOrDefault() as PackageDependencyInfo).State = PackageDependencyState.RequiresRestart;
(packageDependencies.FirstOrDefault(x => x.Name == package.Name) as PackageDependencyInfo).State =
PackageDependencyState.RequiresRestart;
hasPackageMarkedForUninstall = true;
}
this.RestartBanner.Visibility = hasPackageMarkedForUninstall ? Visibility.Visible: Visibility.Hidden;
Expand Down
11 changes: 11 additions & 0 deletions test/DynamoCoreWpfTests/DynamoTestUIBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Windows;
using Dynamo.Configuration;
using Dynamo.Controls;
using Dynamo.Graph.Nodes;
Expand Down Expand Up @@ -78,6 +79,16 @@ public virtual void Start()
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}

protected static void RaiseLoadedEvent(FrameworkElement element)
{
MethodInfo eventMethod = typeof(FrameworkElement).GetMethod("OnLoaded",
BindingFlags.Instance | BindingFlags.NonPublic);

RoutedEventArgs args = new RoutedEventArgs(FrameworkElement.LoadedEvent);

eventMethod.Invoke(element, new object[] { args });
}

/// <summary>
/// Derived test classes can override this method to provide different configurations.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,24 @@
using Dynamo;
using Dynamo.Configuration;
using Dynamo.Graph.Nodes.CustomNodes;
using Dynamo.Graph.Workspaces;
using Dynamo.Models;
using Dynamo.PythonMigration;
using Dynamo.Utilities;
using Dynamo.Wpf.Extensions;
using DynamoCoreWpfTests.Utility;
using GraphLayout;
using NUnit.Framework;

namespace DynamoCoreWpfTests
{
class PythonMigrationViewExtensionTests : DynamoTestUIBase
{
private PythonMigrationViewExtension viewExtension = new PythonMigrationViewExtension();

private string CoreTestDirectory { get { return Path.Combine(GetTestDirectory(ExecutingDirectory), "core"); } }

private List<string> raisedEvents = new List<string>();


/// <summary>
/// This test is created to check if the extension displays a dialog to the user
/// when opening a saved dyn file that contains python nodes with their engine set to `IronPython2`
Expand Down Expand Up @@ -231,7 +233,7 @@ public void CanDetectIronPythonNodesInGraph()

/// <summary>
/// Checks if pressing the `More Information` button on the IronPythonInfoDialog window
/// will open the DocumentaionBrowser ViewExtension.
/// will open the DocumentationBrowser ViewExtension.
/// </summary>
[Test]
public void CanOpenDocumentationBrowserWhenMoreInformationIsClicked()
Expand Down Expand Up @@ -293,13 +295,36 @@ public void CustomNodeContainsIronPythonDependencyTest()
var examplePath = Path.Combine(UnitTestBase.TestDirectory, @"core\python", "PythonCustomNodeHomeWorkspace.dyn");
Open(examplePath);

var customNodes = ViewModel.Model.CurrentWorkspace.Nodes.OfType<Function>();
var customNodeManager = ViewModel.Model.CustomNodeManager;
var pythonMigration = View.viewExtensionManager.ViewExtensions
.Where(x => x.Name == viewExtension.Name)
.Select(x => x)
.First() as PythonMigrationViewExtension;

var result = GraphPythonDependencies.CustomNodesContainIronPythonDependency(customNodes, customNodeManager);
var result = pythonMigration.PythonDependencies.ContainsIronPythonDependencyInCurrentWS();

Assert.IsTrue(result);
DispatcherUtil.DoEvents();
}

[Test]
public void IronPythonPackageLoadedTest()
{
RaiseLoadedEvent(View);

Open(Path.Combine(CoreTestDirectory, @"python\python.dyn"));

var loadedParams = new ViewLoadedParams(View, ViewModel);
viewExtension.Loaded(loadedParams);

var currentWorkspace = ViewModel.Model.CurrentWorkspace;

var pkgDependencyInfo = currentWorkspace.OnRequestPackageDependencies().FirstOrDefault();

Assert.IsTrue(pkgDependencyInfo != null);
Assert.AreEqual(PackageDependencyState.Loaded, pkgDependencyInfo.State);
Assert.AreEqual(GraphPythonDependencies.PythonPackage, pkgDependencyInfo.Name);
Assert.AreEqual(GraphPythonDependencies.PythonPackageVersion, pkgDependencyInfo.Version);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public void RestartBannerDefaultStateTest()

var loadedParams = new ViewLoadedParams(View, ViewModel);
viewExtension.pmExtension = this.Model.ExtensionManager.Extensions.OfType<PackageManagerExtension>().FirstOrDefault();
viewExtension.DependencyView = new WorkspaceDependencyView(viewExtension, loadedParams);
viewExtension.Loaded(loadedParams);

var CurrentWorkspace = ViewModel.Model.CurrentWorkspace;
viewExtension.DependencyView.DependencyRegen(CurrentWorkspace);
// Restart banner should not display by default
Expand All @@ -67,10 +68,9 @@ public void DownloadSpecifiedVersionOfPackageTest()

var loadedParams = new ViewLoadedParams(View, ViewModel);
viewExtension.pmExtension = this.Model.ExtensionManager.Extensions.OfType<PackageManagerExtension>().FirstOrDefault();
viewExtension.DependencyView = new WorkspaceDependencyView(viewExtension, loadedParams);
viewExtension.Loaded(loadedParams);

var CurrentWorkspace = ViewModel.Model.CurrentWorkspace;
var info = CurrentWorkspace.NodeLibraryDependencies.Find(x => x.Name == "Dynamo Samples");

// This is equivalent to uninstall the package
var package = viewExtension.pmExtension.PackageLoader.LocalPackages.Where(x => x.Name == "Dynamo Samples").FirstOrDefault();
Expand Down Expand Up @@ -160,7 +160,7 @@ public void DependencyRegenCrashingDynamoTest()

var loadedParams = new ViewLoadedParams(View, ViewModel);
viewExtension.pmExtension = this.Model.ExtensionManager.Extensions.OfType<PackageManagerExtension>().FirstOrDefault();
viewExtension.DependencyView = new WorkspaceDependencyView(viewExtension, loadedParams);
viewExtension.Loaded(loadedParams);

var homeWorkspaceModel = ViewModel.Model.Workspaces.OfType<HomeWorkspaceModel>().FirstOrDefault();

Expand All @@ -185,7 +185,7 @@ public void KeepInstalledVersionOfPackageTest()

var loadedParams = new ViewLoadedParams(View, ViewModel);
viewExtension.pmExtension = this.Model.ExtensionManager.Extensions.OfType<PackageManagerExtension>().FirstOrDefault();
viewExtension.DependencyView = new WorkspaceDependencyView(viewExtension, loadedParams);
viewExtension.Loaded(loadedParams);

var CurrentWorkspace = ViewModel.Model.CurrentWorkspace;
var info = CurrentWorkspace.NodeLibraryDependencies.Find(x => x.Name == "Dynamo Samples");
Expand Down Expand Up @@ -239,14 +239,6 @@ public void TestPropertiesWithCodeInIt()

}

public static void RaiseLoadedEvent(FrameworkElement element)
{
MethodInfo eventMethod = typeof(FrameworkElement).GetMethod("OnLoaded",
BindingFlags.Instance | BindingFlags.NonPublic);

RoutedEventArgs args = new RoutedEventArgs(FrameworkElement.LoadedEvent);

eventMethod.Invoke(element, new object[] { args });
}

}
}

0 comments on commit 65751e4

Please sign in to comment.