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

Redirect python print to Dynamo console #11000

Merged
merged 4 commits into from
Aug 14, 2020
Merged
Show file tree
Hide file tree
Changes from 2 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
46 changes: 36 additions & 10 deletions src/Libraries/DSCPython/CPythonEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ public enum EvaluationState { Begin, Success, Failed }
public static class CPythonEvaluator
{
private const string DynamoSkipAttributeName = "__dynamoskipconversion__";
private const string DynamoPrintFuncName = "__dynamoprint__";
static PyScope globalScope;
internal static readonly string globalScopeName = "global";

Expand Down Expand Up @@ -197,15 +198,16 @@ public static object EvaluatePythonScript(
// Reset the 'sys.path' value to the default python paths on node evaluation.
var pythonNodeSetupCode = "import sys" + Environment.NewLine + "sys.path = sys.path[0:3]";
scope.Exec(pythonNodeSetupCode);

ProcessAdditionalBindings(scope, bindingNames, bindingValues);
var nodeName = ProcessAdditionalBindings(bindingNames, bindingValues);

int amt = Math.Min(bindingNames.Count, bindingValues.Count);

for (int i = 0; i < amt; i++)
{
scope.Set((string)bindingNames[i], InputMarshaler.Marshal(bindingValues[i]).ToPython());
}

scope.Exec($"sys.stdout.prefix = '{nodeName}'");
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess this code could run into problems when nodeName contains a ' character. Could you escape it and maybe and an ' to a node name in the test so that we are covering it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i added the nodename as a variable to the PyScope, that seems to work better and works with all special characters.


try
{
Expand Down Expand Up @@ -253,6 +255,16 @@ private static PyScope CreateGlobalScope()
import clr
clr.setPreload(True)
");

// Session is null when running unit tests.
if (ExecutionEvents.ActiveSession != null)
{
dynamic logger = ExecutionEvents.ActiveSession.GetParameterValue(ParameterKeys.Logger);
Action<string> logFunction = msg => logger.Log($"{msg}", LogLevel.ConsoleOnly);
scope.Set(DynamoPrintFuncName, logFunction.ToPython());
scope.Exec(RedirectPrint());
}

return scope;
}

Expand All @@ -263,7 +275,7 @@ import clr
/// <param name="scope">Python scope where execution will occur</param>
/// <param name="bindingNames">List of binding names received for evaluation</param>
/// <param name="bindingValues">List of binding values received for evaluation</param>
private static void ProcessAdditionalBindings(PyScope scope, IList bindingNames, IList bindingValues)
private static string ProcessAdditionalBindings(IList bindingNames, IList bindingValues)
{
const string NodeNameInput = "Name";
string nodeName;
Expand All @@ -280,13 +292,27 @@ private static void ProcessAdditionalBindings(PyScope scope, IList bindingNames,
bindingValues.RemoveAt(0);
}

// Session is null when running unit tests.
if (ExecutionEvents.ActiveSession != null)
{
dynamic logger = ExecutionEvents.ActiveSession.GetParameterValue(ParameterKeys.Logger);
Action<string> logFunction = msg => logger.Log($"{nodeName}: {msg}", LogLevel.ConsoleOnly);
scope.Set("DynamoPrint", logFunction.ToPython());
}
return nodeName;
}

private static string RedirectPrint()
{
return String.Format(@"
import sys

class DynamoStdOut:
def __init__(self, log_func):
self.text = ''
self.log_func = log_func
self.prefix = ''
def write(self, text):
if text == '\n':
self.log_func(self.prefix + ': ' + self.text)
self.text = ''
else:
self.text += text
sys.stdout = DynamoStdOut({0})
", DynamoPrintFuncName);
}

/// <summary>
Expand Down
28 changes: 25 additions & 3 deletions src/Libraries/DSIronPython/IronPythonEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public delegate void EvaluationEventHandler(EvaluationState state,
[IsVisibleInDynamoLibrary(false)]
public static class IronPythonEvaluator
{
private const string DynamoPrintFuncName = "__dynamoprint__";
/// <summary> stores a copy of the previously executed code</summary>
private static string prev_code { get; set; }
/// <summary> stores a copy of the previously compiled engine</summary>
Expand Down Expand Up @@ -125,7 +126,7 @@ public static object EvaluateIronPythonScript(
// For backwards compatibility: "sys" was imported by default due to a bug so we keep it that way
scope.ImportModule("sys");

ProcessAdditionalBindings(scope, bindingNames, bindingValues);
ProcessAdditionalBindings(scope, bindingNames, bindingValues, engine);

int amt = Math.Min(bindingNames.Count, bindingValues.Count);

Expand Down Expand Up @@ -161,7 +162,7 @@ public static object EvaluateIronPythonScript(
/// <param name="scope">Python scope where execution will occur</param>
/// <param name="bindingNames">List of binding names received for evaluation</param>
/// <param name="bindingValues">List of binding values received for evaluation</param>
private static void ProcessAdditionalBindings(ScriptScope scope, IList bindingNames, IList bindingValues)
private static void ProcessAdditionalBindings(ScriptScope scope, IList bindingNames, IList bindingValues, ScriptEngine engine)
{
const string NodeNameInput = "Name";
string nodeName;
Expand All @@ -183,10 +184,31 @@ private static void ProcessAdditionalBindings(ScriptScope scope, IList bindingNa
{
dynamic logger = ExecutionEvents.ActiveSession.GetParameterValue(ParameterKeys.Logger);
Action<string> logFunction = msg => logger.Log($"{nodeName}: {msg}", LogLevel.ConsoleOnly);
scope.SetVariable("DynamoPrint", logFunction);
scope.SetVariable(DynamoPrintFuncName, logFunction);
ScriptSource source = engine.CreateScriptSourceFromString(RedirectPrint());
source.Execute(scope);
}
}

private static string RedirectPrint()
{
return String.Format(@"
import sys

class DynamoStdOut:
def __init__(self, log_func):
self.text = ''
self.log_func = log_func
def write(self, text):
if text == '\n':
self.log_func(self.text)
self.text = ''
else:
self.text += text
sys.stdout = DynamoStdOut({0})
", DynamoPrintFuncName);
}

#region Marshalling

/// <summary>
Expand Down
4 changes: 3 additions & 1 deletion test/Libraries/DynamoPythonTests/PythonTestsWithLogging.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ public void DynamoPrintLogsToConsole()
var expectedOutput = "Greeting CPython node: Hello from Python3!!!" + Environment.NewLine
+ "Greeting IronPython node: Hello from Python2!!!" + Environment.NewLine
+ "Greeting CPython String node: Hello from Python3!!!" + Environment.NewLine
+ "Greeting IronPython String node: Hello from Python2!!!" + Environment.NewLine;
+ "Greeting IronPython String node: Hello from Python2!!!" + Environment.NewLine
+ "Multiple print paramter node: Hello Dynamo Print !!!" + Environment.NewLine
+ "Print seperator paramter node: Hello_Dynamo_Print_!!!" + Environment.NewLine;
SHKnudsen marked this conversation as resolved.
Show resolved Hide resolved

CurrentDynamoModel.OpenFileFromPath(Path.Combine(TestDirectory, "core", "python", "DynamoPrint.dyn"));
StringAssert.EndsWith(expectedOutput, CurrentDynamoModel.Logger.LogText);
Expand Down
124 changes: 109 additions & 15 deletions test/core/python/DynamoPrint.dyn
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
{
"ConcreteType": "PythonNodeModels.PythonNode, PythonNodeModels",
"NodeType": "PythonScriptNode",
"Code": "# Load the Python Standard and DesignScript Libraries\r\nimport sys\r\nimport clr\r\nclr.AddReference('ProtoGeometry')\r\nfrom Autodesk.DesignScript.Geometry import *\r\n\r\n# The inputs to this node will be stored as a list in the IN variables.\r\ndataEnteringNode = IN\r\n\r\n# Place your code below this line\r\nDynamoPrint('Hello from Python' + str(sys.version_info.major) + '!!!')\r\n\r\n# Assign your output to the OUT variable.\r\nOUT = 0",
"Code": "# Load the Python Standard and DesignScript Libraries\r\nimport sys\r\nimport clr\r\nclr.AddReference('ProtoGeometry')\r\nfrom Autodesk.DesignScript.Geometry import *\r\n\r\n# The inputs to this node will be stored as a list in the IN variables.\r\ndataEnteringNode = IN\r\n\r\n# Place your code below this line\r\nprint('Hello from Python' + str(sys.version_info.major) + '!!!')\r\n\r\n# Assign your output to the OUT variable.\r\nOUT = 0",
"Engine": "CPython3",
"VariableInputPorts": true,
"Id": "7fa1ef232e13484586b692c71d9e5c70",
Expand Down Expand Up @@ -44,7 +44,7 @@
{
"ConcreteType": "PythonNodeModels.PythonNode, PythonNodeModels",
"NodeType": "PythonScriptNode",
"Code": "# Load the Python Standard and DesignScript Libraries\r\nimport sys\r\nimport clr\r\nclr.AddReference('ProtoGeometry')\r\nfrom Autodesk.DesignScript.Geometry import *\r\n\r\n# The inputs to this node will be stored as a list in the IN variables.\r\ndataEnteringNode = IN\r\n\r\n# Place your code below this line\r\nDynamoPrint('Hello from Python' + str(sys.version_info.major) + '!!!')\r\n\r\n# Assign your output to the OUT variable.\r\nOUT = 0",
"Code": "# Load the Python Standard and DesignScript Libraries\r\nimport sys\r\nimport clr\r\nclr.AddReference('ProtoGeometry')\r\nfrom Autodesk.DesignScript.Geometry import *\r\n\r\n# The inputs to this node will be stored as a list in the IN variables.\r\ndataEnteringNode = IN\r\n\r\n# Place your code below this line\r\nprint 'Hello from Python' + str(sys.version_info.major) + '!!!'\r\n\r\n# Assign your output to the OUT variable.\r\nOUT = 0",
"Engine": "IronPython2",
"VariableInputPorts": true,
"Id": "b518fc1118b745898bf5b840e3ea1c35",
Expand Down Expand Up @@ -116,7 +116,7 @@
{
"ConcreteType": "CoreNodeModels.Input.StringInput, CoreNodeModels",
"NodeType": "StringInputNode",
"InputValue": "import sys\r\nDynamoPrint('Hello from Python' + str(sys.version_info.major) + '!!!')",
"InputValue": "import sys\r\nprint('Hello from Python' + str(sys.version_info.major) + '!!!')",
"Id": "8dd9296336d04ca8850b267e86f04156",
"Inputs": [],
"Outputs": [
Expand Down Expand Up @@ -172,13 +172,77 @@
],
"Replication": "Disabled",
"Description": "Runs a IronPython script from a string."
},
{
"ConcreteType": "PythonNodeModels.PythonNode, PythonNodeModels",
"NodeType": "PythonScriptNode",
"Code": "# Load the Python Standard and DesignScript Libraries\r\nimport sys\r\nimport clr\r\nclr.AddReference('ProtoGeometry')\r\nfrom Autodesk.DesignScript.Geometry import *\r\n\r\n# The inputs to this node will be stored as a list in the IN variables.\r\ndataEnteringNode = IN\r\n\r\n# Place your code below this line\r\nprint(\"Hello\", \"Dynamo\", \"Print\", \"!!!\")\r\n\r\n# Assign your output to the OUT variable.\r\nOUT = 0",
"Engine": "CPython3",
"VariableInputPorts": true,
"Id": "763b6063ac3e428fbe24673220bdac7e",
"Inputs": [
{
"Id": "7e135cf5addc4484826327bb1948da14",
"Name": "IN[0]",
"Description": "Input #0",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Outputs": [
{
"Id": "b09875a66b2c4da994f656e87f8e50e6",
"Name": "OUT",
"Description": "Result of the python script",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Replication": "Disabled",
"Description": "Runs an embedded IronPython script."
},
{
"ConcreteType": "PythonNodeModels.PythonNode, PythonNodeModels",
"NodeType": "PythonScriptNode",
"Code": "# Load the Python Standard and DesignScript Libraries\r\nimport sys\r\nimport clr\r\nclr.AddReference('ProtoGeometry')\r\nfrom Autodesk.DesignScript.Geometry import *\r\n\r\n# The inputs to this node will be stored as a list in the IN variables.\r\ndataEnteringNode = IN\r\n\r\n# Place your code below this line\r\nprint(\"Hello\", \"Dynamo\", \"Print\", \"!!!\", sep='_')\r\n\r\n# Assign your output to the OUT variable.\r\nOUT = 0",
"Engine": "CPython3",
"VariableInputPorts": true,
"Id": "36ae4bcb97a144e6b79f96dabc661b34",
"Inputs": [
{
"Id": "9b0e261a32e6481a91b86dc5b1239cf8",
"Name": "IN[0]",
"Description": "Input #0",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Outputs": [
{
"Id": "4f2809c8e8284c95ba50cfbc38a899bb",
"Name": "OUT",
"Description": "Result of the python script",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Replication": "Disabled",
"Description": "Runs an embedded IronPython script."
}
],
"Connectors": [
{
"Start": "6138bc73890041e5837b37cbd69f47ea",
"End": "1b76bf9b56f943718514b62adb561e28",
"Id": "b938a0e52e4f4036b4c7f2a2d91debeb"
"Id": "97c3927b7c7e463db28e68e12419421c"
},
{
"Start": "e85535a49d2b4850ab95e6950405d015",
Expand All @@ -198,7 +262,17 @@
{
"Start": "b9b37c561566490cb54e6ad8236b71be",
"End": "be1ffa19bc9d4c53a84d8215f262361c",
"Id": "f2bb060269f74f638b266f05b6cb5f98"
"Id": "a15b8a556aae4200902e7a59642eb01d"
},
{
"Start": "cf0b0de0487f440fb84ecda9e67bc7d7",
"End": "7e135cf5addc4484826327bb1948da14",
"Id": "a45af6eca064459ba72a26096229dac0"
},
{
"Start": "b09875a66b2c4da994f656e87f8e50e6",
"End": "9b0e261a32e6481a91b86dc5b1239cf8",
"Id": "d5b795956d5e47e2a43041c477c857cb"
}
],
"Dependencies": [],
Expand All @@ -209,7 +283,7 @@
"ScaleFactor": 1.0,
"HasRunWithoutCrash": true,
"IsVisibleInDynamoLibrary": true,
"Version": "2.8.0.2035",
"Version": "2.8.0.2254",
"RunType": "Automatic",
"RunPeriod": "1000"
},
Expand Down Expand Up @@ -253,8 +327,8 @@
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Excluded": false,
"X": 26.0,
"Y": 283.2000000000001
"X": -30.181998021760705,
"Y": 356.79050445103866
},
{
"ShowGeometry": true,
Expand All @@ -263,8 +337,8 @@
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Excluded": false,
"X": 13.999999999999943,
"Y": 151.59999999999991
"X": -374.5360764375958,
"Y": 289.13073463873121
},
{
"ShowGeometry": true,
Expand All @@ -273,13 +347,33 @@
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Excluded": false,
"X": 315.2,
"Y": 284.40000000000009
"X": 418.90170106619183,
"Y": 295.80718711728127
},
{
"ShowGeometry": true,
"Name": "Multiple print paramter node",
"Id": "763b6063ac3e428fbe24673220bdac7e",
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Excluded": false,
"X": 752.67311794947386,
"Y": 292.82901271984736
},
{
"ShowGeometry": true,
"Name": "Print seperator paramter node",
"Id": "36ae4bcb97a144e6b79f96dabc661b34",
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Excluded": false,
"X": 1085.9907784454263,
"Y": 296.7782117575635
}
],
"Annotations": [],
"X": 0.0,
"Y": 0.0,
"Zoom": 1.0
"X": -310.88830190032672,
"Y": -85.0206499748852,
"Zoom": 0.789161032924816
}
}