diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e1a58b9..de9fbb2 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -27,7 +27,7 @@ jobs:
if: ${{ true }}
run: |
Write-Output "***Locating iron python package!***"
- if (Test-Path -Path "${{ github.workspace }}\DSIronPython3\package_output\DSIronPython3\bin\python3eval.dll") {
+ if (Test-Path -Path "${{ github.workspace }}\DSIronPython3\package_output\DSIronPython3\extra\python3eval.dll") {
Write-Output "python node dll exists!"
} else {
Write-Error "python node dll was not found!"
diff --git a/DSIronPython3Empty/Class1.cs b/DSIronPython3Empty/Class1.cs
new file mode 100644
index 0000000..cac70a0
--- /dev/null
+++ b/DSIronPython3Empty/Class1.cs
@@ -0,0 +1,11 @@
+namespace DSIronPython3Empty
+{
+ ///
+ /// This class/dll exists so that Dynamo knows this package is loaded,
+ /// even though it really only contains an extension.
+ ///
+ internal class Class1
+ {
+
+ }
+}
diff --git a/DSIronPython3Empty/DSIronPython3Empty.csproj b/DSIronPython3Empty/DSIronPython3Empty.csproj
new file mode 100644
index 0000000..b01ab1c
--- /dev/null
+++ b/DSIronPython3Empty/DSIronPython3Empty.csproj
@@ -0,0 +1,12 @@
+
+
+
+ false
+ false
+ $(SolutionDir)\package_output\DSIronPython3\bin\
+ net8.0
+ enable
+ enable
+
+
+
\ No newline at end of file
diff --git a/IronPython3Extension.sln b/IronPython3Extension.sln
index d1ec540..c230114 100644
--- a/IronPython3Extension.sln
+++ b/IronPython3Extension.sln
@@ -7,7 +7,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IronPython3Extension", "Iro
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "python3eval", "python3eval\python3eval.csproj", "{2B256D7E-4864-4086-A481-6E5B24CBA95E}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IronPython3Tests", "IronPython3Tests\IronPython3Tests.csproj", "{CD76A408-05CF-45E6-A508-6A6BA6A95E75}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IronPython3Tests", "IronPython3Tests\IronPython3Tests.csproj", "{CD76A408-05CF-45E6-A508-6A6BA6A95E75}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DSIronPython3Empty", "DSIronPython3Empty\DSIronPython3Empty.csproj", "{AC582B53-98DA-48D3-97AC-DCDA17EAAFAA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -27,6 +29,10 @@ Global
{CD76A408-05CF-45E6-A508-6A6BA6A95E75}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD76A408-05CF-45E6-A508-6A6BA6A95E75}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD76A408-05CF-45E6-A508-6A6BA6A95E75}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AC582B53-98DA-48D3-97AC-DCDA17EAAFAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AC582B53-98DA-48D3-97AC-DCDA17EAAFAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AC582B53-98DA-48D3-97AC-DCDA17EAAFAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AC582B53-98DA-48D3-97AC-DCDA17EAAFAA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/IronPython3Extension/Extension.cs b/IronPython3Extension/Extension.cs
index 95f435b..07e76b8 100644
--- a/IronPython3Extension/Extension.cs
+++ b/IronPython3Extension/Extension.cs
@@ -1,13 +1,19 @@
using System;
using System.IO;
+using System.Linq;
using System.Reflection;
+using System.Runtime.Loader;
using Dynamo.Extensions;
+using Dynamo.Graph.Workspaces;
using Dynamo.Logging;
+using Dynamo.PythonServices;
namespace IronPython3Extension
{
public class IronPython3Extension : IExtension, ILogSource
{
+ private const string PythonEvaluatorAssembly = "python3eval";
+
#region ILogSource
public event Action MessageLogged;
@@ -29,9 +35,78 @@ public void Dispose()
}
- public void Ready(ReadyParams sp)
+ ///
+ /// Action to be invoked when the Dynamo has started up and is ready
+ /// for user interaction.
+ ///
+ ///
+ public void Ready(ReadyParams rp)
+ {
+ var extraPath = Path.Combine(new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent.FullName, "extra");
+ var alc = new IsolatedPythonContext(Path.Combine(extraPath, $"{PythonEvaluatorAssembly}.dll"));
+ var dsIronAssem = alc.LoadFromAssemblyName(new AssemblyName(PythonEvaluatorAssembly));
+
+ //load the engine into Dynamo ourselves.
+ LoadPythonEngine(dsIronAssem);
+
+ //we used to do this:
+ //but it's not neccesary to load anything into the VM.
+ //instead we skip all the extra work and trigger the side effect we want
+ //which is re executing the graph after the dsIronPython evaluator is loaded into the PythonEngineManager.
+ //rp.StartupParams.LibraryLoader.LoadNodeLibrary(dsIronAssem);
+
+ if (rp.CurrentWorkspaceModel is HomeWorkspaceModel hwm)
+ {
+ foreach (var n in hwm.Nodes)
+ {
+ n.MarkNodeAsModified(true);
+ }
+ hwm.Run();
+ }
+ }
+
+ private static void LoadPythonEngine(Assembly assembly)
{
+ if (assembly == null)
+ {
+ throw new ArgumentNullException($"Error while loading python engine - assembly {PythonEvaluatorAssembly}.dll was not loaded successfully.");
+ }
+
+ // Currently we are using try-catch to validate loaded assembly and Singleton Instance method exist
+ // but we can optimize by checking all loaded types against evaluators interface later
+ try
+ {
+ Type eType = null;
+ PropertyInfo instanceProp = null;
+ try
+ {
+ eType = assembly.GetTypes().FirstOrDefault(x => typeof(PythonEngine).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract);
+ if (eType == null) return;
+
+ instanceProp = eType?.GetProperty("Instance", BindingFlags.NonPublic | BindingFlags.Static);
+ if (instanceProp == null) return;
+ }
+ catch
+ {
+ // Ignore exceptions from iterating assembly types.
+ return;
+ }
+ PythonEngine engine = (PythonEngine)instanceProp.GetValue(null);
+ if (engine == null)
+ {
+ throw new Exception($"Could not get a valid PythonEngine instance by calling the {eType.Name}.Instance method");
+ }
+
+ if (PythonEngineManager.Instance.AvailableEngines.All(x => x.Name != engine.Name))
+ {
+ PythonEngineManager.Instance.AvailableEngines.Add(engine);
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new Exception($"Failed to add a Python engine from assembly {assembly.GetName().Name}.dll with error: {ex.Message}");
+ }
}
public void Shutdown()
@@ -44,4 +119,35 @@ public void Startup(StartupParams sp)
}
}
+ internal class IsolatedPythonContext : AssemblyLoadContext
+ {
+ private AssemblyDependencyResolver resolver;
+
+ public IsolatedPythonContext(string libPath)
+ {
+ resolver = new AssemblyDependencyResolver(libPath);
+ }
+
+ protected override Assembly Load(AssemblyName assemblyName)
+ {
+ string assemblyPath = resolver.ResolveAssemblyToPath(assemblyName);
+ if (assemblyPath != null)
+ {
+ return LoadFromAssemblyPath(assemblyPath);
+ }
+
+ return null;
+ }
+
+ protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
+ {
+ string libraryPath = resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
+ if (libraryPath != null)
+ {
+ return LoadUnmanagedDllFromPath(libraryPath);
+ }
+
+ return IntPtr.Zero;
+ }
+ }
}
diff --git a/IronPython3Extension/IronPython3Extension.csproj b/IronPython3Extension/IronPython3Extension.csproj
index 882532d..ecef528 100644
--- a/IronPython3Extension/IronPython3Extension.csproj
+++ b/IronPython3Extension/IronPython3Extension.csproj
@@ -4,8 +4,8 @@
net8.0
false
false
- $(SolutionDir)\package_output\DSIronPython3\bin\
- 1.0.2
+ $(SolutionDir)\package_output\DSIronPython3\extra\
+ 1.4.0
@@ -21,4 +21,10 @@
+
+
+ Always
+
+
+
diff --git a/IronPython3Extension/IronPython3Extension_ExtensionDefinition.xml b/IronPython3Extension/IronPython3Extension_ExtensionDefinition.xml
new file mode 100644
index 0000000..e715bed
--- /dev/null
+++ b/IronPython3Extension/IronPython3Extension_ExtensionDefinition.xml
@@ -0,0 +1,4 @@
+
+ IronPython3Extension.dll
+ IronPython3Extension.IronPython3Extension
+
\ No newline at end of file
diff --git a/IronPython3Tests/pythonEvalTests.cs b/IronPython3Tests/pythonEvalTests.cs
index bd9554f..e74ef99 100644
--- a/IronPython3Tests/pythonEvalTests.cs
+++ b/IronPython3Tests/pythonEvalTests.cs
@@ -1,4 +1,5 @@
using Dynamo;
+using Microsoft.Extensions.Configuration;
using NUnit.Framework;
using System.Collections;
@@ -46,6 +47,63 @@ public void BindingsWork()
}
}
+ [Test]
+ [Category("UnitTests")]
+ public void SysDiagProccess_AndOtherShimmedCLRTypesWork()
+ {
+
+
+ foreach (var pythonEvaluator in Evaluators)
+ {
+ var output = pythonEvaluator(
+ @"
+import clr
+from System.Reflection import Assembly
+from System.Diagnostics import Process
+dynamoCore = Assembly.Load(""DynamoCore"")
+version_long = dynamoCore.GetName().Version.Major.ToString()
+proc = Process.GetCurrentProcess().ProcessName
+OUT = (version_long,proc)
+",
+ new ArrayList(),
+ new ArrayList()
+ );
+ Assert.AreEqual("3", (output as IList)[0]);
+ //we do this because the process name can change depending
+ //on where tests are running.
+ Assert.IsNotEmpty((string?)(output as IList)[1]);
+ }
+ }
+ [Test]
+ [Category("UnitTests")]
+ public void ConfigLoadShimsCanBeDisabled()
+ {
+
+ var configb = new ConfigurationBuilder();
+ var config = configb.AddInMemoryCollection(new Dictionary()
+ {
+ {"config:CONFIG_ENABLE_NET48SHIMCOMPAT","false" }
+ }).Build();
+
+ var evaluator = new IronPython3.Evaluator.IronPython3Evaluator(config);
+
+ var e = Assert.Throws(() =>
+ {
+ var output = evaluator.Evaluate(
+ @"
+import clr
+from System.Reflection import Assembly
+from System.Diagnostics import Process
+",
+ new ArrayList(),
+ new ArrayList()
+ );
+
+ });
+ StringAssert.Contains("ImportError", e.Message);
+ }
+
+
[Test]
[Category("UnitTests")]
public void DataMarshaling_Output()
diff --git a/package_output/DSIronPython3/pkg.json b/package_output/DSIronPython3/pkg.json
index d7566b7..7d01d95 100644
--- a/package_output/DSIronPython3/pkg.json
+++ b/package_output/DSIronPython3/pkg.json
@@ -2,8 +2,8 @@
"license": "",
"file_hash": null,
"name": "DynamoIronPython3",
- "version": "1.0.0",
- "description": "unofficial python package for the IronPython 3 engine + stdlib ",
+ "version": "1.4.0",
+ "description": "unofficial python package for the IronPython 3 engine + stdlib. If you are using Dynamo < 3.x use package version < 1.2 ",
"group": "",
"keywords": [ "python", "ironpython", "ironpython3" ],
"dependencies": [],
@@ -15,5 +15,5 @@
"site_url": "https://dynamobim.org/",
"repository_url": "https://github.com/DynamoDS/Dynamo",
"contains_binaries": true,
- "node_libraries": ["python3eval"]
+ "node_libraries": [ "DSIronPython3Empty, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" ]
}
\ No newline at end of file
diff --git a/python3eval/appsettings.json b/python3eval/appsettings.json
new file mode 100644
index 0000000..eaeb979
--- /dev/null
+++ b/python3eval/appsettings.json
@@ -0,0 +1,5 @@
+{
+ "config": {
+ "CONFIG_ENABLE_NET48SHIMCOMPAT": "true"
+ }
+}
diff --git a/python3eval/python3eval.csproj b/python3eval/python3eval.csproj
index 05e8eeb..86d9b3c 100644
--- a/python3eval/python3eval.csproj
+++ b/python3eval/python3eval.csproj
@@ -4,9 +4,9 @@
false
false
net8.0
- $(SolutionDir)\package_output\DSIronPython3\bin\
+ $(SolutionDir)\package_output\DSIronPython3\extra\
true
- 1.0.2
+ 1.4.0
@@ -25,18 +25,14 @@
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+ Always
+
+
+
diff --git a/python3eval/pythoneval.cs b/python3eval/pythoneval.cs
index 1a2e612..ae17abe 100644
--- a/python3eval/pythoneval.cs
+++ b/python3eval/pythoneval.cs
@@ -7,18 +7,20 @@
using Autodesk.DesignScript.Runtime;
using Dynamo.Events;
using Dynamo.Logging;
-using Dynamo.PythonServices;
using Dynamo.PythonServices.EventHandlers;
using Dynamo.Session;
using Dynamo.Utilities;
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;
+using Microsoft.Extensions.Configuration;
namespace IronPython3.Evaluator
{
[IsVisibleInDynamoLibrary(false)]
public class IronPython3Evaluator:Dynamo.PythonServices.PythonEngine
{
+ private bool CONFIG_ENABLE_NET48SHIMCOMPAT = false;
+
private const string DynamoPrintFuncName = "__dynamoprint__";
/// stores a copy of the previously executed code
private static string prev_code { get; set; }
@@ -63,6 +65,17 @@ public override object Evaluate(string code, IList bindingNames, IList bindingVa
if (code != prev_code)
{
var pythonEngine = Python.CreateEngine();
+ //to maintain compatability with ironPython code written in .netframework - we load the system.dll shim
+ //which loads many types which used to be in system.dll(mscorlib) like system.diagnostics.process
+ //which were moved in .Net. These types are now importable by python code without users needing to add
+ //clr.addreference.
+ //TODO consider a preference for this.
+ //TODO test smaller shims...netstd.dll
+
+ if (CONFIG_ENABLE_NET48SHIMCOMPAT)
+ {
+ pythonEngine.Runtime.LoadAssembly(Assembly.Load("System"));
+ }
if (!string.IsNullOrEmpty(stdLib))
{
paths = pythonEngine.GetSearchPaths().ToList();
@@ -333,6 +346,26 @@ private static string PythonStandardLibPath()
}
return pythonLibDir;
}
+
+ public IronPython3Evaluator(IConfiguration config = null)
+ {
+ //either inject a config or load from appsettings.json
+ if (config is not null)
+ {
+
+ }
+ else
+ {
+ var configPath = Path.Combine(new FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName, "appsettings.json");
+ config = new ConfigurationBuilder().AddJsonFile(configPath, optional: true).Build();
+ }
+ var enableShimLoad = config.GetSection("config").GetChildren().FirstOrDefault(x => x.Key == nameof(CONFIG_ENABLE_NET48SHIMCOMPAT))?.Value;
+ if (bool.TryParse(enableShimLoad,out var parsed))
+ {
+ CONFIG_ENABLE_NET48SHIMCOMPAT = parsed;
+ }
+
+ }
}
}