Basic tests, small fixes

This commit is contained in:
2023-03-08 02:49:56 +01:00
parent ab463bdc37
commit d7a1499da1
6 changed files with 464 additions and 26 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ dyn2py.egg-info
build build
dist dist
docs docs
tests/output_files

11
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"python.testing.unittestArgs": [
"-v",
"-s",
"./tests",
"-p",
"test_*.py"
],
"python.testing.pytestEnabled": false,
"python.testing.unittestEnabled": true
}

View File

@@ -130,6 +130,8 @@ class DynamoFile(File):
"""The uuid of the graph""" """The uuid of the graph"""
name: str name: str
"""The name of the graph, read from the file. Not the name of the file""" """The name of the graph, read from the file. Not the name of the file"""
python_nodes: list["PythonNode"]
"""Python node objects, read from this file"""
open_files: set["DynamoFile"] = set() open_files: set["DynamoFile"] = set()
"""A set of open Dynamo files, before saving""" """A set of open Dynamo files, before saving"""
@@ -195,10 +197,13 @@ class DynamoFile(File):
Returns: Returns:
list[PythonNode]: A list of PythonNodes in the file list[PythonNode]: A list of PythonNodes in the file
""" """
if not self in self.open_files:
self.read()
full_python_nodes = [n for n in self.full_dict["Nodes"] full_python_nodes = [n for n in self.full_dict["Nodes"]
if n["NodeType"] == "PythonScriptNode"] if n["NodeType"] == "PythonScriptNode"]
python_nodes = [] self.python_nodes = []
for p_node in full_python_nodes: for p_node in full_python_nodes:
# The name of the node is stored here: # The name of the node is stored here:
@@ -207,12 +212,12 @@ class DynamoFile(File):
node_dict_from_dyn=p_node, node_dict_from_dyn=p_node,
full_nodeviews_dict_from_dyn=node_views, full_nodeviews_dict_from_dyn=node_views,
source_dynamo_file=self) source_dynamo_file=self)
python_nodes.append(python_node) self.python_nodes.append(python_node)
if not python_nodes: if not self.python_nodes:
raise DynamoFileException("No python nodes in this file!") raise DynamoFileException("No python nodes in this file!")
return python_nodes return self.python_nodes
def get_python_node_by_id(self, node_id: str) -> "PythonNode": def get_python_node_by_id(self, node_id: str) -> "PythonNode":
"""Get a PythonNode object from this Dynamo graph, by its id """Get a PythonNode object from this Dynamo graph, by its id
@@ -223,15 +228,28 @@ class DynamoFile(File):
Returns: Returns:
PythonNode: The PythonNode with the given id PythonNode: The PythonNode with the given id
""" """
python_node_dict = next(( if not self in self.open_files:
n for n in self.full_dict["Nodes"] if n["Id"] == node_id self.read()
), {})
if not python_node_dict:
raise PythonNodeNotFoundException(
f"Node not found with id {node_id}")
python_node = PythonNode( # Find the node, if the nodes are not read yet:
node_dict_from_dyn=python_node_dict) if not self.python_nodes:
python_node_dict = next((
n for n in self.full_dict["Nodes"] if n["Id"] == node_id
), {})
if not python_node_dict:
raise PythonNodeNotFoundException(
f"Node not found with id {node_id}")
python_node = PythonNode(
node_dict_from_dyn=python_node_dict)
else:
python_node = next((
p for p in self.python_nodes if p.id == node_id
), None)
if not python_node:
raise PythonNodeNotFoundException(
f"Node not found with id {node_id}")
return python_node return python_node

View File

@@ -6,13 +6,16 @@ LOGLEVELS = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
DEFAULT_LOGLEVEL = "INFO" DEFAULT_LOGLEVEL = "INFO"
FILTERS = ["py", "dyn"] FILTERS = ["py", "dyn"]
class Options(argparse.Namespace): class Options(argparse.Namespace):
"""Class for options for running a conversion like from the command line""" """Class for options for running a conversion like from the command line"""
def __init__( def __init__(
self, self,
source: list[pathlib.Path | str] = [], source: list[pathlib.Path | str] = [],
loglevel: str = DEFAULT_LOGLEVEL, loglevel: str = DEFAULT_LOGLEVEL,
dry_run: bool = False, dry_run: bool = False,
force: bool = False,
backup: bool = False, backup: bool = False,
filter: str = "", filter: str = "",
update: bool = False, update: bool = False,
@@ -24,6 +27,7 @@ class Options(argparse.Namespace):
source (list[pathlib.Path | str], optional): List of files to run on. Defaults to []. source (list[pathlib.Path | str], optional): List of files to run on. Defaults to [].
loglevel (str, optional): log level. Defaults to DEFAULT_LOGLEVEL. loglevel (str, optional): log level. Defaults to DEFAULT_LOGLEVEL.
dry_run (bool, optional): If it's a dry run. Defaults to False. dry_run (bool, optional): If it's a dry run. Defaults to False.
force (bool, optional): Overwrite files, even if they are older. Defaults to False.
backup (bool, optional): Create backup of modified files. Defaults to False. backup (bool, optional): Create backup of modified files. Defaults to False.
filter (str, optional): 'dyn' or 'py' file filter for running on folders. Defaults to "". filter (str, optional): 'dyn' or 'py' file filter for running on folders. Defaults to "".
update (bool, optional): Update mode, like inverse on Dynamo files. Defaults to False. update (bool, optional): Update mode, like inverse on Dynamo files. Defaults to False.
@@ -46,6 +50,7 @@ class Options(argparse.Namespace):
raise ValueError raise ValueError
self.dry_run = dry_run self.dry_run = dry_run
self.force = force
self.backup = backup self.backup = backup
if not filter or filter in FILTERS: if not filter or filter in FILTERS:
@@ -59,16 +64,3 @@ class Options(argparse.Namespace):
self.python_folder = pathlib.Path(python_folder) self.python_folder = pathlib.Path(python_folder)
else: else:
self.python_folder = python_folder self.python_folder = python_folder
# super().__init__(
# source=self.source,
# loglevel=self.loglevel,
# dry_run=self.dry_run,
# backup=self.backup,
# filter=self.filter,
# update=self.update,
# python_folder=self.python_folder,
# )

View File

@@ -0,0 +1,367 @@
{
"Uuid": "3c3b4c05-9716-4e93-9360-ca0637cb5486",
"IsCustomNode": false,
"Description": "",
"Name": "python_nodes",
"ElementResolver": {
"ResolutionMap": {}
},
"Inputs": [],
"Outputs": [],
"Nodes": [
{
"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\r\n#Not renamed, Cpython3\r\n\r\n# Place your code below this line\r\n\r\n# Assign your output to the OUT variable.\r\nOUT = sys.version",
"Engine": "CPython3",
"EngineName": "CPython3",
"VariableInputPorts": true,
"Id": "ff087a3611b0478b95252f67e87be507",
"Inputs": [
{
"Id": "ac5bdff0617e4246b9b03101ec2ecaf1",
"Name": "IN[0]",
"Description": "Input #0",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Outputs": [
{
"Id": "974d45015c8442d1a3b9f77859d827a8",
"Name": "OUT",
"Description": "Result of the python script",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Replication": "Disabled",
"Description": "Runs an embedded Python script."
},
{
"ConcreteType": "PythonNodeModels.PythonNode, PythonNodeModels",
"NodeType": "PythonScriptNode",
"Code": "\"\"\"\r\nMultiline comment \r\nblablabla\r\n\"\"\"\r\n\r\n# 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#Renamed, Cpython3\r\n\r\n# Place your code below this line\r\n\r\n# Assign your output to the OUT variable.\r\nOUT = 0",
"Engine": "CPython3",
"EngineName": "CPython3",
"VariableInputPorts": true,
"Id": "d7704617c75e4bf1a5c387b7c3f001ea",
"Inputs": [
{
"Id": "c98cd3c591ae463eae69ff040a3989eb",
"Name": "IN[0]",
"Description": "Input #0",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Outputs": [
{
"Id": "13c1e8c1f34d40ca97f25950269acd19",
"Name": "OUT",
"Description": "Result of the python script",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Replication": "Disabled",
"Description": "Runs an embedded Python 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#Not renamed, IronPython2\r\n\r\n# Place your code below this line\r\n\r\n# Assign your output to the OUT variable.\r\nOUT = sys.version",
"Engine": "IronPython2",
"EngineName": "IronPython2",
"VariableInputPorts": true,
"Id": "2047ce859d424496963582fbb7b6417f",
"Inputs": [
{
"Id": "6728601fd8054c6ca9cc414655c6912d",
"Name": "IN[0]",
"Description": "Input #0",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Outputs": [
{
"Id": "beb9265bbf8641df8d46fb80e78f8269",
"Name": "OUT",
"Description": "Result of the python script",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Replication": "Disabled",
"Description": "Runs an embedded Python 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#Renamed, IPY2\r\n\r\n# Place your code below this line\r\n\r\n# Assign your output to the OUT variable.\r\nOUT = 0",
"Engine": "CPython3",
"EngineName": "CPython3",
"VariableInputPorts": true,
"Id": "d89c158e2e824a909753d7137fcdf56e",
"Inputs": [
{
"Id": "2a0f4220761341deac9e67ac0b97e02b",
"Name": "IN[0]",
"Description": "Input #0",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Outputs": [
{
"Id": "780c6be36b994a4eb103162a49bbdad8",
"Name": "OUT",
"Description": "Result of the python script",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Replication": "Disabled",
"Description": "Runs an embedded Python script."
},
{
"ConcreteType": "Dynamo.Graph.Nodes.CodeBlockNodeModel, DynamoCore",
"NodeType": "CodeBlockNode",
"Code": "<>:\"/\\|?*",
"Id": "048f431a6a734d5d93a1bcc3d384dae4",
"Inputs": [
{
"Id": "18fc02088224473486e6e3cc732ec492",
"Name": "h",
"Description": "h",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Outputs": [
{
"Id": "9c669d4dabef4f88bbc4a9f19700ff11",
"Name": "",
"Description": "Value of expression at line 1",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Replication": "Disabled",
"Description": "Allows for DesignScript code to be authored directly"
},
{
"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#Renamed, IPY2\r\n\r\n# Place your code below this line\r\n\r\n# Assign your output to the OUT variable.\r\nOUT = 0",
"Engine": "CPython3",
"EngineName": "CPython3",
"VariableInputPorts": true,
"Id": "d65fb4d27998455c8bf15c92883b7213",
"Inputs": [
{
"Id": "ed16a01bf82f46f68f279cfbdb28173a",
"Name": "IN[0]",
"Description": "Input #0",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Outputs": [
{
"Id": "60947b135fd54247a9fcc0b04575a293",
"Name": "OUT",
"Description": "Result of the python script",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Replication": "Disabled",
"Description": "Runs an embedded Python 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#Not renamed, IronPython2\r\n\r\n# Place your code below this line\r\n\r\n# Assign your output to the OUT variable.\r\nOUT = sys.version",
"Engine": "IronPython2",
"EngineName": "IronPython2",
"VariableInputPorts": true,
"Id": "4680a2f79a084cf9a61cbb11e3ce6af4",
"Inputs": [
{
"Id": "4e9c4364066b41a4991c4ff2860f4c12",
"Name": "IN[0]",
"Description": "Input #0",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Outputs": [
{
"Id": "d29abf7c1ac44d7ba10c2dcf020a8f09",
"Name": "OUT",
"Description": "Result of the python script",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Replication": "Disabled",
"Description": "Runs an embedded Python script."
}
],
"Connectors": [],
"Dependencies": [],
"NodeLibraryDependencies": [],
"Thumbnail": "",
"GraphDocumentationURL": null,
"ExtensionWorkspaceData": [
{
"ExtensionGuid": "28992e1d-abb9-417f-8b1b-05e053bee670",
"Name": "Properties",
"Version": "2.12",
"Data": {}
},
{
"ExtensionGuid": "DFBD9CC0-DB40-457A-939E-8C8555555A9D",
"Name": "Generative Design",
"Version": "3.0",
"Data": {}
}
],
"Author": "",
"Linting": {
"activeLinter": "None",
"activeLinterId": "7b75fb44-43fd-4631-a878-29f4d5d8399a",
"warningCount": 0,
"errorCount": 0
},
"Bindings": [],
"View": {
"Dynamo": {
"ScaleFactor": 1.0,
"HasRunWithoutCrash": true,
"IsVisibleInDynamoLibrary": true,
"Version": "2.16.1.2727",
"RunType": "Manual",
"RunPeriod": "1000"
},
"Camera": {
"Name": "Background Preview",
"EyeX": -17.0,
"EyeY": 24.0,
"EyeZ": 50.0,
"LookX": 12.0,
"LookY": -13.0,
"LookZ": -58.0,
"UpX": 0.0,
"UpY": 1.0,
"UpZ": 0.0
},
"ConnectorPins": [],
"NodeViews": [
{
"Id": "ff087a3611b0478b95252f67e87be507",
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Name": "Python Script",
"ShowGeometry": true,
"Excluded": false,
"X": 330.854857997459,
"Y": 370.30582545813525
},
{
"Id": "d7704617c75e4bf1a5c387b7c3f001ea",
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Name": "Renamed Cpython",
"ShowGeometry": true,
"Excluded": false,
"X": 332.19386487425072,
"Y": 553.74976757860486
},
{
"Id": "2047ce859d424496963582fbb7b6417f",
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Name": "Python Script",
"ShowGeometry": true,
"Excluded": false,
"X": 786.04189521113619,
"Y": 262.8632861477945
},
{
"Id": "d89c158e2e824a909753d7137fcdf56e",
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Name": "<>:\"/\\|?*",
"ShowGeometry": true,
"Excluded": false,
"X": 1033.1078482745011,
"Y": 551.97820171368517
},
{
"Id": "048f431a6a734d5d93a1bcc3d384dae4",
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Name": "Code Block",
"ShowGeometry": true,
"Excluded": false,
"X": 614.48767528598637,
"Y": 656.55546226164051
},
{
"Id": "d65fb4d27998455c8bf15c92883b7213",
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Name": "",
"ShowGeometry": true,
"Excluded": false,
"X": 1021.3666290027875,
"Y": 401.8583267396354
},
{
"Id": "4680a2f79a084cf9a61cbb11e3ce6af4",
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Name": "Python Script Renamed",
"ShowGeometry": true,
"Excluded": false,
"X": 934.48445314636979,
"Y": 833.98973786470992
}
],
"Annotations": [],
"X": -97.703074094655562,
"Y": -321.86387971712088,
"Zoom": 1.1923804228516888
}
}

49
tests/test_DynamoFile.py Normal file
View File

@@ -0,0 +1,49 @@
import os
import unittest
import dyn2py
import pathlib
INPUT_DIR = "tests/input_files"
OUTPUT_DIR = "tests/output_files"
def cleanup():
output_dir = pathlib.Path(OUTPUT_DIR)
if output_dir.exists():
for f in output_dir.iterdir():
f.unlink()
else:
output_dir.mkdir()
class TestDynamoFile(unittest.TestCase):
def test_read(self):
dyn = dyn2py.DynamoFile(f"{INPUT_DIR}/python_nodes.dyn")
dyn.read()
self.assertEqual(dyn.uuid, "3c3b4c05-9716-4e93-9360-ca0637cb5486")
self.assertEqual(dyn.name, "python_nodes")
self.assertTrue(dyn in dyn.open_files)
def test_get_python_node(self):
dyn = dyn2py.DynamoFile(f"{INPUT_DIR}/python_nodes.dyn")
py_nodes = dyn.get_python_nodes()
py_node = dyn.get_python_node_by_id("d7704617c75e4bf1a5c387b7c3f001ea")
self.assertEqual(len(py_nodes), 6)
self.assertTrue(py_node)
self.assertTrue(py_node in py_nodes)
self.assertEqual(py_node.checksum, "1f3d9e6153804fe1ed37571a9cda8e26")
with self.assertRaises(dyn2py.PythonNodeNotFoundException):
dyn.get_python_node_by_id("wrongid")
def test_extract_python(self):
cleanup()
opt = dyn2py.Options(python_folder="tests/output_files")
dyn = dyn2py.DynamoFile(f"{INPUT_DIR}/python_nodes.dyn")
dyn.extract_python(options=opt)
output_dir = pathlib.Path("tests/output_files")
self.assertEqual(len(list(output_dir.iterdir())), 6)