jaclang 0.5.8__py3-none-any.whl → 0.5.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of jaclang might be problematic. Click here for more details.

@@ -25,19 +25,41 @@ class JacUnparseTests(TestCaseMicroSuite, AstSyncTestMixin):
25
25
  schedule=without_format,
26
26
  )
27
27
  before = ast3.dump(code_gen_pure.ir.gen.py_ast[0], indent=2)
28
+ x = code_gen_pure.ir.unparse()
29
+ # print(x)
30
+ # print(f"Testing {code_gen_pure.ir.name}")
31
+ # print(code_gen_pure.ir.pp())
28
32
  code_gen_jac = jac_str_to_pass(
29
- jac_str=code_gen_pure.ir.unparse(),
33
+ jac_str=x,
30
34
  file_path=filename,
31
35
  target=PyastGenPass,
32
36
  schedule=without_format,
33
37
  )
34
38
  after = ast3.dump(code_gen_jac.ir.gen.py_ast[0], indent=2)
35
- self.assertEqual(
36
- len("\n".join(unified_diff(before.splitlines(), after.splitlines()))),
37
- 0,
38
- )
39
+ if "circle_clean_tests.jac" in filename:
40
+ self.assertEqual(
41
+ len(
42
+ [
43
+ i
44
+ for i in unified_diff(
45
+ before.splitlines(), after.splitlines(), n=0
46
+ )
47
+ if "test" not in i
48
+ ]
49
+ ),
50
+ 5,
51
+ )
52
+ else:
53
+ self.assertEqual(
54
+ len(
55
+ "\n".join(unified_diff(before.splitlines(), after.splitlines()))
56
+ ),
57
+ 0,
58
+ )
59
+
39
60
  except Exception as e:
40
- self.skipTest(f"Test failed, but skipping instead of failing: {e}")
61
+ raise e
62
+ # self.skipTest(f"Test failed, but skipping instead of failing: {e}")
41
63
 
42
64
 
43
65
  JacUnparseTests.self_attach_micro_tests()
jaclang/core/aott.py CHANGED
@@ -4,65 +4,230 @@ AOTT: Automated Operational Type Transformation.
4
4
  This has all the necessary functions to perform the AOTT operations.
5
5
  """
6
6
 
7
+ import re
8
+ from enum import Enum
7
9
  from typing import Any
8
10
 
11
+ from jaclang.core.registry import SemInfo, SemRegistry, SemScope
9
12
 
10
- prompt_template = """
13
+
14
+ PROMPT_TEMPLATE = """
11
15
  [System Prompt]
12
- This is an operation you must perform and return the output values. Neither, the methodology,
13
- extra sentences nor the code are not needed.
16
+ This is an operation you must perform and return the output values. Neither, the methodology, extra sentences nor the code are not needed.
17
+ Input/Type formatting: Explanation of the Input (variable_name) (type) = value
14
18
 
15
19
  [Information]
16
- {information_str}
20
+ {information}
17
21
 
18
- [Inputs and Input Type Information]
19
- {input_types_n_information_str}
22
+ [Inputs Information]
23
+ {inputs_information}
20
24
 
21
- [Output Type]
22
- {output_type_str}
25
+ [Output Information]
26
+ {output_information}
23
27
 
24
- [Output Type Explanations]
25
- {output_type_info_str}
28
+ [Type Explanations]
29
+ {type_explanations}
26
30
 
27
31
  [Action]
28
32
  {action}
29
33
 
30
34
  {reason_suffix}
31
- """
35
+ """ # noqa E501
32
36
 
33
- with_reason_suffix = """
37
+ WITH_REASON_SUFFIX = """
34
38
  Reason and return the output result(s) only, adhering to the provided Type in the following format
35
39
 
36
40
  [Reasoning] <Reason>
37
41
  [Output] <Result>
38
42
  """
39
43
 
40
- without_reason_suffix = """Generate and return the output result(s) only, adhering to the provided Type in the
41
- following format
44
+ WITHOUT_REASON_SUFFIX = """Generate and return the output result(s) only, adhering to the provided Type in the following format
42
45
 
43
46
  [Output] <result>
44
- """
47
+ """ # noqa E501
45
48
 
46
49
 
47
50
  def aott_raise(
48
- information_str: str,
49
- input_types_n_information_str: str,
50
- output_type_str: str,
51
- output_type_info_str: str,
51
+ information: str,
52
+ inputs_information: str,
53
+ output_information: str,
54
+ type_explanations: str,
52
55
  action: str,
53
56
  reason: bool,
54
57
  ) -> str:
55
58
  """AOTT Raise uses the information (Meanings types values) provided to generate a prompt(meaning in)."""
56
- return prompt_template.format(
57
- information_str=information_str,
58
- input_types_n_information_str=input_types_n_information_str,
59
- output_type_str=output_type_str,
60
- output_type_info_str=output_type_info_str,
59
+ return PROMPT_TEMPLATE.format(
60
+ information=information,
61
+ inputs_information=inputs_information,
62
+ output_information=output_information,
63
+ type_explanations=type_explanations,
61
64
  action=action,
62
- reason_suffix=with_reason_suffix if reason else without_reason_suffix,
65
+ reason_suffix=WITH_REASON_SUFFIX if reason else WITHOUT_REASON_SUFFIX,
63
66
  )
64
67
 
65
68
 
66
- def aott_lower(meaning_out: str, output_type_info: tuple) -> Any: # noqa: ANN401
67
- """AOTT Lower uses the meaning out provided by the language model and return the result in the desired type."""
68
- return meaning_out
69
+ def get_reasoning_output(s: str) -> tuple:
70
+ """Get the reasoning and output from the meaning out string."""
71
+ reasoning_match = re.search(r"\[Reasoning\](.*)\[Output\]", s)
72
+ output_match = re.search(r"\[Output\](.*)", s)
73
+
74
+ if reasoning_match and output_match:
75
+ reasoning = reasoning_match.group(1)
76
+ output = output_match.group(1)
77
+ return (reasoning.strip(), output.strip())
78
+ elif output_match:
79
+ output = output_match.group(1)
80
+ return (None, output.strip())
81
+ else:
82
+ return (None, None)
83
+
84
+
85
+ def get_info_types(
86
+ scope: SemScope, mod_registry: SemRegistry, incl_info: list[tuple[str, str]]
87
+ ) -> tuple[str, list[str]]:
88
+ """Filter the registry data based on the scope and return the info string."""
89
+ collected_types = []
90
+ avail_scopes = []
91
+ while True:
92
+ avail_scopes.append(str(scope))
93
+ if not scope.parent:
94
+ break
95
+ scope = scope.parent
96
+
97
+ filtered_registry = SemRegistry()
98
+ for _scope, sem_info_list in mod_registry.registry.items():
99
+ if str(_scope) in avail_scopes:
100
+ filtered_registry.registry[_scope] = sem_info_list
101
+
102
+ info_str = []
103
+ for incl in incl_info:
104
+ _, sem_info = filtered_registry.lookup(name=incl[0])
105
+ if sem_info and isinstance(sem_info, SemInfo):
106
+ (
107
+ collected_types.extend(extract_non_primary_type(sem_info.type))
108
+ if sem_info.type
109
+ else None
110
+ )
111
+ info_str.append(
112
+ f"{sem_info.semstr} ({sem_info.name}) ({sem_info.type}) = {get_object_string(incl[1])}"
113
+ )
114
+ return ("\n".join(info_str), collected_types)
115
+
116
+
117
+ def get_object_string(obj: Any) -> Any: # noqa: ANN401
118
+ """Get the string representation of the input object."""
119
+ if isinstance(obj, str):
120
+ return f'"{obj}"'
121
+ elif isinstance(obj, (int, float, bool)):
122
+ return str(obj)
123
+ elif isinstance(obj, list):
124
+ return "[" + ", ".join(get_object_string(item) for item in obj) + "]"
125
+ elif isinstance(obj, tuple):
126
+ return "(" + ", ".join(get_object_string(item) for item in obj) + ")"
127
+ elif isinstance(obj, dict):
128
+ return (
129
+ "{"
130
+ + ", ".join(
131
+ f"{get_object_string(key)}: {get_object_string(value)}"
132
+ for key, value in obj.items()
133
+ )
134
+ + "}"
135
+ )
136
+ elif isinstance(obj, Enum):
137
+ return f"{obj.__class__.__name__}.{obj.name}"
138
+ elif hasattr(obj, "__dict__"):
139
+ args = ", ".join(
140
+ f"{key}={get_object_string(value)}"
141
+ for key, value in vars(obj).items()
142
+ if key != "_jac_"
143
+ )
144
+ return f"{obj.__class__.__name__}({args})"
145
+ else:
146
+ return str(obj)
147
+
148
+
149
+ def get_all_type_explanations(type_list: list, mod_registry: SemRegistry) -> dict:
150
+ """Get all type explanations from the input type list."""
151
+ collected_type_explanations = {}
152
+ for type_item in type_list:
153
+ type_explanation = get_type_explanation(type_item, mod_registry)
154
+ if type_explanation is not None:
155
+ type_explanation_str, nested_types = type_explanation
156
+ if type_item not in collected_type_explanations:
157
+ collected_type_explanations[type_item] = type_explanation_str
158
+ if nested_types:
159
+ nested_collected_type_explanations = get_all_type_explanations(
160
+ list(nested_types), mod_registry
161
+ )
162
+ for k, v in nested_collected_type_explanations.items():
163
+ if k not in collected_type_explanations:
164
+ collected_type_explanations[k] = v
165
+ return collected_type_explanations
166
+
167
+
168
+ def get_type_explanation(
169
+ type_str: str, mod_registry: SemRegistry
170
+ ) -> tuple[str | None, set[str] | None]:
171
+ """Get the type explanation of the input type string."""
172
+ scope, sem_info = mod_registry.lookup(name=type_str)
173
+ if isinstance(sem_info, SemInfo) and sem_info.type:
174
+ sem_info_scope = SemScope(sem_info.name, sem_info.type, scope)
175
+ _, type_info = mod_registry.lookup(scope=sem_info_scope)
176
+ type_info_str = []
177
+ type_info_types = []
178
+ if sem_info.type == "Enum" and isinstance(type_info, list):
179
+ for enum_item in type_info:
180
+ type_info_str.append(
181
+ f"{enum_item.semstr} (EnumItem) ({enum_item.name})"
182
+ )
183
+ elif sem_info.type in ["obj", "class", "node", "edge"] and isinstance(
184
+ type_info, list
185
+ ):
186
+ for arch_item in type_info:
187
+ type_info_str.append(
188
+ f"{arch_item.semstr} ({arch_item.type}) ({arch_item.name})"
189
+ )
190
+ if arch_item.type and extract_non_primary_type(arch_item.type):
191
+ type_info_types.extend(extract_non_primary_type(arch_item.type))
192
+ return (
193
+ f"{sem_info.semstr} ({sem_info.type}) ({sem_info.name}) = {', '.join(type_info_str)}",
194
+ set(type_info_types),
195
+ )
196
+ return None, None
197
+
198
+
199
+ def extract_non_primary_type(type_str: str) -> list:
200
+ """Extract non-primary types from the type string."""
201
+ if not type_str:
202
+ return []
203
+ pattern = r"(?:\[|,\s*|\|)([a-zA-Z_][a-zA-Z0-9_]*)|([a-zA-Z_][a-zA-Z0-9_]*)"
204
+ matches = re.findall(pattern, type_str)
205
+ primary_types = [
206
+ "str",
207
+ "int",
208
+ "float",
209
+ "bool",
210
+ "list",
211
+ "dict",
212
+ "tuple",
213
+ "set",
214
+ "Any",
215
+ "None",
216
+ ]
217
+ non_primary_types = [m for t in matches for m in t if m and m not in primary_types]
218
+ return non_primary_types
219
+
220
+
221
+ def get_type_annotation(data: Any) -> str: # noqa: ANN401
222
+ """Get the type annotation of the input data."""
223
+ if isinstance(data, dict):
224
+ class_name = next(
225
+ (value.__class__.__name__ for value in data.values() if value is not None),
226
+ None,
227
+ )
228
+ if class_name:
229
+ return f"dict[str, {class_name}]"
230
+ else:
231
+ return "dict[str, Any]"
232
+ else:
233
+ return str(type(data).__name__)
jaclang/core/construct.py CHANGED
@@ -340,7 +340,7 @@ class JacTestResult(unittest.TextTestResult):
340
340
  ) -> None:
341
341
  """Initialize FailFastTestResult object."""
342
342
  super().__init__(stream, descriptions, verbosity) # noqa
343
- self.failures_count = 0
343
+ self.failures_count = JacTestCheck.failcount
344
344
  self.max_failures = max_failures
345
345
 
346
346
  def addFailure(self, test, err) -> None: # noqa
@@ -379,6 +379,7 @@ class JacTestCheck:
379
379
  test_case = unittest.TestCase()
380
380
  test_suite = unittest.TestSuite()
381
381
  breaker = False
382
+ failcount = 0
382
383
 
383
384
  @staticmethod
384
385
  def reset() -> None:
@@ -395,7 +396,11 @@ class JacTestCheck:
395
396
  if result.wasSuccessful():
396
397
  print("Passed successfully.")
397
398
  else:
398
- JacTestCheck.breaker = True
399
+ fails = len(result.failures)
400
+ JacTestCheck.failcount += fails
401
+ JacTestCheck.breaker = (
402
+ (JacTestCheck.failcount >= maxfail) if maxfail else True
403
+ )
399
404
 
400
405
  @staticmethod
401
406
  def add_test(test_fun: Callable) -> None:
@@ -0,0 +1,115 @@
1
+ """Registry Utilities.
2
+
3
+ This module contains classes and functions for managing the registry of
4
+ semantic information.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Optional
10
+
11
+
12
+ class SemInfo:
13
+ """Semantic information class."""
14
+
15
+ def __init__(
16
+ self, name: str, type: Optional[str] = None, semstr: Optional[str] = None
17
+ ) -> None:
18
+ """Initialize the class."""
19
+ self.name = name
20
+ self.type = type
21
+ self.semstr = semstr
22
+
23
+ def __repr__(self) -> str:
24
+ """Return the string representation of the class."""
25
+ return f"{self.semstr} ({self.type}) ({self.name})"
26
+
27
+
28
+ class SemScope:
29
+ """Scope class."""
30
+
31
+ def __init__(
32
+ self, scope: str, type: str, parent: Optional[SemScope] = None
33
+ ) -> None:
34
+ """Initialize the class."""
35
+ self.parent = parent
36
+ self.type = type
37
+ self.scope = scope
38
+
39
+ def __str__(self) -> str:
40
+ """Return the string representation of the class."""
41
+ if self.parent:
42
+ return f"{self.parent}.{self.scope}({self.type})"
43
+ return f"{self.scope}({self.type})"
44
+
45
+ def __repr__(self) -> str:
46
+ """Return the string representation of the class."""
47
+ return self.__str__()
48
+
49
+ @staticmethod
50
+ def get_scope_from_str(scope_str: str) -> Optional[SemScope]:
51
+ """Get scope from string."""
52
+ scope_list = scope_str.split(".")
53
+ parent = None
54
+ for scope in scope_list:
55
+ scope_name, scope_type = scope.split("(")
56
+ scope_type = scope_type[:-1]
57
+ parent = SemScope(scope_name, scope_type, parent)
58
+ return parent
59
+
60
+
61
+ class SemRegistry:
62
+ """Registry class."""
63
+
64
+ def __init__(self) -> None:
65
+ """Initialize the class."""
66
+ self.registry: dict[SemScope, list[SemInfo]] = {}
67
+
68
+ def add(self, scope: SemScope, seminfo: SemInfo) -> None:
69
+ """Add semantic information to the registry."""
70
+ for k in self.registry.keys():
71
+ if str(k) == str(scope):
72
+ scope = k
73
+ break
74
+ else:
75
+ self.registry[scope] = []
76
+ self.registry[scope].append(seminfo)
77
+
78
+ def lookup(
79
+ self,
80
+ scope: Optional[SemScope] = None,
81
+ name: Optional[str] = None,
82
+ type: Optional[str] = None,
83
+ ) -> tuple[Optional[SemScope], Optional[SemInfo | list[SemInfo]]]:
84
+ """Lookup semantic information in the registry."""
85
+ if scope:
86
+ for k, v in self.registry.items():
87
+ if str(k) == str(scope):
88
+ if name:
89
+ for i in v:
90
+ if i.name == name:
91
+ return k, i
92
+ elif type:
93
+ for i in v:
94
+ if i.type == type:
95
+ return k, i
96
+ else:
97
+ return k, v
98
+ else:
99
+ for k, v in self.registry.items():
100
+ if name:
101
+ for i in v:
102
+ if i.name == name:
103
+ return k, i
104
+ elif type:
105
+ for i in v:
106
+ if i.type == type:
107
+ return k, i
108
+ return None, None
109
+
110
+ def pp(self) -> None:
111
+ """Pretty print the registry."""
112
+ for k, v in self.registry.items():
113
+ print(k)
114
+ for i in v:
115
+ print(f" {i.name} {i.type} {i.semstr}")
jaclang/core/utils.py CHANGED
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Callable, TYPE_CHECKING
6
6
 
7
+ import jaclang.compiler.absyntree as ast
8
+ from jaclang.core.registry import SemScope
7
9
 
8
10
  if TYPE_CHECKING:
9
11
  from jaclang.core.construct import NodeAnchor, NodeArchitype
@@ -88,3 +90,26 @@ def traverse_graph(
88
90
  else:
89
91
 
90
92
  dfs(other_nd, cur_depth + 1)
93
+
94
+
95
+ def get_sem_scope(node: ast.AstNode) -> SemScope:
96
+ """Get scope of the node."""
97
+ a = (
98
+ node.name
99
+ if isinstance(node, ast.Module)
100
+ else node.name.value if isinstance(node, (ast.Enum, ast.Architype)) else ""
101
+ )
102
+ if isinstance(node, ast.Module):
103
+ return SemScope(a, "Module", None)
104
+ elif isinstance(node, (ast.Enum, ast.Architype)):
105
+ node_type = (
106
+ node.__class__.__name__
107
+ if isinstance(node, ast.Enum)
108
+ else node.arch_type.value
109
+ )
110
+ if node.parent:
111
+ return SemScope(a, node_type, get_sem_scope(node.parent))
112
+ else:
113
+ if node.parent:
114
+ return get_sem_scope(node.parent)
115
+ return SemScope("", "", None)
jaclang/plugin/default.py CHANGED
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import fnmatch
6
6
  import os
7
+ import pickle
7
8
  import types
8
9
  from dataclasses import field
9
10
  from functools import wraps
@@ -11,7 +12,15 @@ from typing import Any, Callable, Optional, Type
11
12
 
12
13
  from jaclang.compiler.absyntree import Module
13
14
  from jaclang.compiler.constant import EdgeDir, colors
14
- from jaclang.core.aott import aott_lower, aott_raise
15
+ from jaclang.core.aott import (
16
+ aott_raise,
17
+ extract_non_primary_type,
18
+ get_all_type_explanations,
19
+ get_info_types,
20
+ get_object_string,
21
+ get_reasoning_output,
22
+ get_type_annotation,
23
+ )
15
24
  from jaclang.core.construct import (
16
25
  Architype,
17
26
  DSFunc,
@@ -28,6 +37,7 @@ from jaclang.core.construct import (
28
37
  root,
29
38
  )
30
39
  from jaclang.core.importer import jac_importer
40
+ from jaclang.core.registry import SemScope
31
41
  from jaclang.core.utils import traverse_graph
32
42
  from jaclang.plugin.feature import JacFeature as Jac
33
43
  from jaclang.plugin.spec import T
@@ -235,6 +245,7 @@ class JacFeatureDefaults:
235
245
  if JacTestCheck.breaker and (xit or maxfail):
236
246
  break
237
247
  JacTestCheck.breaker = False
248
+ JacTestCheck.failcount = 0
238
249
  print("No test files found.") if not test_file else None
239
250
 
240
251
  return True
@@ -427,33 +438,66 @@ class JacFeatureDefaults:
427
438
  @staticmethod
428
439
  @hookimpl
429
440
  def with_llm(
441
+ file_loc: str,
430
442
  model: Any, # noqa: ANN401
431
443
  model_params: dict[str, Any],
432
- incl_info: tuple,
433
- excl_info: tuple,
444
+ scope: str,
445
+ incl_info: list[tuple[str, str]],
446
+ excl_info: list[tuple[str, str]],
434
447
  inputs: tuple,
435
448
  outputs: tuple,
436
449
  action: str,
437
450
  ) -> Any: # noqa: ANN401
438
451
  """Jac's with_llm feature."""
452
+ with open(
453
+ os.path.join(
454
+ os.path.dirname(file_loc),
455
+ "__jac_gen__",
456
+ os.path.basename(file_loc).replace(".jac", ".registry.pkl"),
457
+ ),
458
+ "rb",
459
+ ) as f:
460
+ mod_registry = pickle.load(f)
461
+
462
+ _scope = SemScope.get_scope_from_str(scope)
463
+ assert _scope is not None
464
+
439
465
  reason = False
440
466
  if "reason" in model_params:
441
467
  reason = model_params.pop("reason")
442
- input_types_n_information_str = "" # TODO: We have to generate this
443
- output_type_str = "" # TODO: We have to generate this
444
- output_type_info_str = "" # TODO: We have to generate this
445
- information_str = "" # TODO: We have to generate this
468
+
469
+ type_collector: list = []
470
+ information, collected_types = get_info_types(_scope, mod_registry, incl_info)
471
+ type_collector.extend(collected_types)
472
+
473
+ inputs_information_list = []
474
+ for i in inputs:
475
+ typ_anno = get_type_annotation(i[3])
476
+ type_collector.extend(extract_non_primary_type(typ_anno))
477
+ inputs_information_list.append(
478
+ f"{i[0]} ({i[2]}) ({typ_anno}) = {get_object_string(i[3])}"
479
+ )
480
+ inputs_information = "\n".join(inputs_information_list)
481
+
482
+ output_information = f"{outputs[0]} ({outputs[2]})"
483
+ type_collector.extend(extract_non_primary_type(outputs[2]))
484
+
485
+ type_explanations_list = list(
486
+ get_all_type_explanations(type_collector, mod_registry).values()
487
+ )
488
+ type_explanations = "\n".join(type_explanations_list)
489
+
446
490
  meaning_in = aott_raise(
447
- information_str,
448
- input_types_n_information_str,
449
- output_type_str,
450
- output_type_info_str,
491
+ information,
492
+ inputs_information,
493
+ output_information,
494
+ type_explanations,
451
495
  action,
452
496
  reason,
453
497
  )
454
498
  meaning_out = model.__infer__(meaning_in, **model_params)
455
- output_type_info = (None, None) # TODO: We have to generate this
456
- return aott_lower(meaning_out, output_type_info)
499
+ reasoning, output = get_reasoning_output(meaning_out)
500
+ return output
457
501
 
458
502
 
459
503
  class JacBuiltin:
jaclang/plugin/feature.py CHANGED
@@ -227,18 +227,22 @@ class JacFeature:
227
227
 
228
228
  @staticmethod
229
229
  def with_llm(
230
+ file_loc: str,
230
231
  model: Any, # noqa: ANN401
231
232
  model_params: dict[str, Any],
232
- incl_info: tuple,
233
- excl_info: tuple,
233
+ scope: str,
234
+ incl_info: list[tuple[str, str]],
235
+ excl_info: list[tuple[str, str]],
234
236
  inputs: tuple,
235
237
  outputs: tuple,
236
238
  action: str,
237
239
  ) -> Any: # noqa: ANN401
238
240
  """Jac's with_llm feature."""
239
241
  return pm.hook.with_llm(
242
+ file_loc=file_loc,
240
243
  model=model,
241
244
  model_params=model_params,
245
+ scope=scope,
242
246
  incl_info=incl_info,
243
247
  excl_info=excl_info,
244
248
  inputs=inputs,
jaclang/plugin/spec.py CHANGED
@@ -211,10 +211,12 @@ class JacFeatureSpec:
211
211
  @staticmethod
212
212
  @hookspec(firstresult=True)
213
213
  def with_llm(
214
+ file_loc: str,
214
215
  model: Any, # noqa: ANN401
215
216
  model_params: dict[str, Any],
216
- incl_info: tuple,
217
- excl_info: tuple,
217
+ scope: str,
218
+ incl_info: list[tuple[str, str]],
219
+ excl_info: list[tuple[str, str]],
218
220
  inputs: tuple,
219
221
  outputs: tuple,
220
222
  action: str,