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

Easily install missing IronPython extension package #10869

Merged
merged 15 commits into from
Jul 16, 2020
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)
aparajit-pratap marked this conversation as resolved.
Show resolved Hide resolved
{
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();
}
aparajit-pratap marked this conversation as resolved.
Show resolved Hide resolved

/// <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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These will be updated once the IronPython extension is published to PM prod.



// 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 });
}

}
}