From 7a90edcd3fce4e42d9306ed4e795ea4ff5b9d437 Mon Sep 17 00:00:00 2001 From: Roman Kuzmenko Date: Tue, 29 Oct 2024 10:04:17 -0700 Subject: [PATCH 1/5] Support for type annotated parameters --- cadquery/cqgi.py | 49 +++++++++++++++++++++++++++++++++++++++++++++- partcad.yaml | 3 +++ tests/test_cqgi.py | 6 +++--- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/cadquery/cqgi.py b/cadquery/cqgi.py index 87f2ce917..99bf64b7f 100644 --- a/cadquery/cqgi.py +++ b/cadquery/cqgi.py @@ -2,6 +2,7 @@ The CadQuery Gateway Interface. Provides classes and tools for executing CadQuery scripts """ +import sys import ast import traceback import time @@ -65,7 +66,9 @@ def _find_vars(self): assignment_finder = ConstantAssignmentFinder(self.metadata) for node in self.ast_tree.body: - if isinstance(node, ast.Assign): + if isinstance(node, ast.AnnAssign): + assignment_finder.visit_AnnAssign(node) + elif isinstance(node, ast.Assign): assignment_finder.visit_Assign(node) def _find_descriptions(self): @@ -564,6 +567,31 @@ def handle_assignment(self, var_name, value_node): print("Unable to handle assignment for variable '%s'" % var_name) pass + def handle_ann_assignment(self, var_name, annotation_id, value_node): + try: + if annotation_id == "int" or annotation_id == "float": + self.cqModel.add_script_parameter( + InputParameter.create( + value_node, var_name, NumberParameterType, value_node.n + ) + ) + elif annotation_id == "str": + self.cqModel.add_script_parameter( + InputParameter.create( + value_node, var_name, StringParameterType, value_node.s + ) + ) + elif annotation_id == "bool": + self.cqModel.add_script_parameter( + InputParameter.create( + value_node, var_name, BooleanParameterType, value_node.s + ) + ) + + except: + print("Unable to handle annotated assignment for variable '%s'" % var_name) + pass + def visit_Assign(self, node): try: @@ -595,3 +623,22 @@ def visit_Assign(self, node): print("Unable to handle assignment for node '%s'" % ast.dump(left_side)) return node + + def visit_AnnAssign(self, node): + + try: + left_side = node.target + + # do not handle attribute assignments + if isinstance(left_side, ast.Attribute): + return + + annTypes = ["int", "float", "str", "bool"] + + if hasattr(node, "annotation") and node.annotation.id in annTypes: + self.handle_ann_assignment(left_side.id, node.annotation.id, node.value) + except: + traceback.print_exc() + print("Unable to handle annotated assignment for node '%s'" % ast.dump(left_side)) + + return node diff --git a/partcad.yaml b/partcad.yaml index 87d9b95b1..5f05a841c 100644 --- a/partcad.yaml +++ b/partcad.yaml @@ -1,3 +1,6 @@ +# This is a PartCAD package. +# See https://partcad.org/ and https://github.com/openvmp/partcad for more information. + name: /pub/examples/script/cadquery desc: CadQuery examples url: https://github.com/CadQuery/cadquery diff --git a/tests/test_cqgi.py b/tests/test_cqgi.py index 33f371ad5..0c08f9ec2 100644 --- a/tests/test_cqgi.py +++ b/tests/test_cqgi.py @@ -15,7 +15,7 @@ TESTSCRIPT = textwrap.dedent( """ height=2.0 - width=3.0 + width:float=3.0 (a,b) = (1.0,1.0) o = (2, 2, 0) foo="bar" @@ -31,7 +31,7 @@ width=3.0 (a,b) = (1.0,1.0) o = (2, 2, 0) - foo="bar" + foo:str="bar" debug(foo, { "color": 'yellow' } ) result = "%s|%s|%s|%s|%s" % ( str(height) , str(width) , foo , str(a) , str(o) ) show_object(result) @@ -135,7 +135,7 @@ def test_that_two_results_are_returned(self): """ h = 1 show_object(h) - h = 2 + h: int = 2 show_object(h) """ ) From b8ce84596d37433df4d0f02bfd01beb7a1d2e2f1 Mon Sep 17 00:00:00 2001 From: Roman Kuzmenko Date: Tue, 29 Oct 2024 19:34:25 -0700 Subject: [PATCH 2/5] Coding style fix --- cadquery/cqgi.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cadquery/cqgi.py b/cadquery/cqgi.py index 99bf64b7f..a9275af62 100644 --- a/cadquery/cqgi.py +++ b/cadquery/cqgi.py @@ -639,6 +639,9 @@ def visit_AnnAssign(self, node): self.handle_ann_assignment(left_side.id, node.annotation.id, node.value) except: traceback.print_exc() - print("Unable to handle annotated assignment for node '%s'" % ast.dump(left_side)) + print( + "Unable to handle annotated assignment for node '%s'" + % ast.dump(left_side) + ) return node From d091f694a6bc153171a0fa88c7066a755a4da9ce Mon Sep 17 00:00:00 2001 From: Roman Kuzmenko Date: Wed, 30 Oct 2024 18:31:45 -0700 Subject: [PATCH 3/5] Updated unit tests to improve coverage --- cadquery/cqgi.py | 22 +++++++--------------- tests/test_cqgi.py | 19 +++++++++++++++++-- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/cadquery/cqgi.py b/cadquery/cqgi.py index a9275af62..e1bb707d4 100644 --- a/cadquery/cqgi.py +++ b/cadquery/cqgi.py @@ -625,23 +625,15 @@ def visit_Assign(self, node): return node def visit_AnnAssign(self, node): + left_side = node.target - try: - left_side = node.target - - # do not handle attribute assignments - if isinstance(left_side, ast.Attribute): - return + # do not handle Attribute or Subscript + if not isinstance(left_side, ast.Name): + return - annTypes = ["int", "float", "str", "bool"] + annTypes = ["int", "float", "str", "bool"] - if hasattr(node, "annotation") and node.annotation.id in annTypes: - self.handle_ann_assignment(left_side.id, node.annotation.id, node.value) - except: - traceback.print_exc() - print( - "Unable to handle annotated assignment for node '%s'" - % ast.dump(left_side) - ) + if hasattr(node, "annotation") and isinstance(node.annotation, ast.Name) and node.annotation.id in annTypes: + self.handle_ann_assignment(left_side.id, node.annotation.id, node.value) return node diff --git a/tests/test_cqgi.py b/tests/test_cqgi.py index 0c08f9ec2..df1288b47 100644 --- a/tests/test_cqgi.py +++ b/tests/test_cqgi.py @@ -16,6 +16,7 @@ """ height=2.0 width:float=3.0 + transparent=False (a,b) = (1.0,1.0) o = (2, 2, 0) foo="bar" @@ -29,6 +30,7 @@ """ height=2.0 width=3.0 + transparent:bool=False (a,b) = (1.0,1.0) o = (2, 2, 0) foo:str="bar" @@ -45,7 +47,7 @@ def test_parser(self): model = cqgi.CQModel(TESTSCRIPT) metadata = model.metadata self.assertEqual( - set(metadata.parameters.keys()), {"height", "width", "a", "b", "foo", "o"} + set(metadata.parameters.keys()), {"height", "width", "transparent", "a", "b", "foo", "o"} ) def test_build_with_debug(self): @@ -166,6 +168,16 @@ def test_that_assigning_string_to_number_fails(self): result = cqgi.parse(script).build({"h": "a string"}) self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError)) + def test_that_assigning_string_to_annotated_list_fails(self): + script = textwrap.dedent( + """ + h: list[float] = [20.0] + show_object(h) + """ + ) + result = cqgi.parse(script).build({"h": "a string"}) + self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError)) + def test_that_assigning_unknown_var_fails(self): script = textwrap.dedent( """ @@ -222,7 +234,10 @@ def test_that_only_top_level_vars_are_detected(self): def do_stuff(): x = 1 - y = 2 + y: int = 2 + class Foo: + z = 3 + zz: int = 4 show_object( "result" ) """ From fb46ccbae3827593a23ed394a0d3297615d6c3bc Mon Sep 17 00:00:00 2001 From: Roman Kuzmenko Date: Thu, 31 Oct 2024 19:05:14 -0700 Subject: [PATCH 4/5] Fixed coding style --- cadquery/cqgi.py | 6 +++++- tests/test_cqgi.py | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cadquery/cqgi.py b/cadquery/cqgi.py index e1bb707d4..10b9ff188 100644 --- a/cadquery/cqgi.py +++ b/cadquery/cqgi.py @@ -633,7 +633,11 @@ def visit_AnnAssign(self, node): annTypes = ["int", "float", "str", "bool"] - if hasattr(node, "annotation") and isinstance(node.annotation, ast.Name) and node.annotation.id in annTypes: + if ( + hasattr(node, "annotation") + and isinstance(node.annotation, ast.Name) + and node.annotation.id in annTypes + ): self.handle_ann_assignment(left_side.id, node.annotation.id, node.value) return node diff --git a/tests/test_cqgi.py b/tests/test_cqgi.py index df1288b47..d802c44e7 100644 --- a/tests/test_cqgi.py +++ b/tests/test_cqgi.py @@ -47,7 +47,8 @@ def test_parser(self): model = cqgi.CQModel(TESTSCRIPT) metadata = model.metadata self.assertEqual( - set(metadata.parameters.keys()), {"height", "width", "transparent", "a", "b", "foo", "o"} + set(metadata.parameters.keys()), + {"height", "width", "transparent", "a", "b", "foo", "o"} ) def test_build_with_debug(self): From 35afce9f03595009491b362fbb818e1c716db722 Mon Sep 17 00:00:00 2001 From: Roman Kuzmenko Date: Thu, 31 Oct 2024 21:05:43 -0700 Subject: [PATCH 5/5] Fixed coding style --- tests/test_cqgi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cqgi.py b/tests/test_cqgi.py index d802c44e7..c3ec3006d 100644 --- a/tests/test_cqgi.py +++ b/tests/test_cqgi.py @@ -48,7 +48,7 @@ def test_parser(self): metadata = model.metadata self.assertEqual( set(metadata.parameters.keys()), - {"height", "width", "transparent", "a", "b", "foo", "o"} + {"height", "width", "transparent", "a", "b", "foo", "o"}, ) def test_build_with_debug(self):