Unverified Commit 1c450e27 authored by Yeuoly's avatar Yeuoly

feat: support empty code output children

parent bbc76cb8
...@@ -153,11 +153,13 @@ class CodeNode(BaseNode): ...@@ -153,11 +153,13 @@ class CodeNode(BaseNode):
raise ValueError(f'{variable} in input form is out of range.') raise ValueError(f'{variable} in input form is out of range.')
if isinstance(value, float): if isinstance(value, float):
value = round(value, MAX_PRECISION) # raise error if precision is too high
if len(str(value).split('.')[1]) > MAX_PRECISION:
raise ValueError(f'{variable} in output form has too high precision.')
return value return value
def _transform_result(self, result: dict, output_schema: dict[str, CodeNodeData.Output], def _transform_result(self, result: dict, output_schema: Optional[dict[str, CodeNodeData.Output]],
prefix: str = '', prefix: str = '',
depth: int = 1) -> dict: depth: int = 1) -> dict:
""" """
...@@ -170,6 +172,47 @@ class CodeNode(BaseNode): ...@@ -170,6 +172,47 @@ class CodeNode(BaseNode):
raise ValueError("Depth limit reached, object too deep.") raise ValueError("Depth limit reached, object too deep.")
transformed_result = {} transformed_result = {}
if output_schema is None:
# validate output thought instance type
for output_name, output_value in result.items():
if isinstance(output_value, dict):
self._transform_result(
result=output_value,
output_schema=None,
prefix=f'{prefix}.{output_name}' if prefix else output_name,
depth=depth + 1
)
elif isinstance(output_value, (int, float)):
self._check_number(
value=output_value,
variable=f'{prefix}.{output_name}' if prefix else output_name
)
elif isinstance(output_value, str):
self._check_string(
value=output_value,
variable=f'{prefix}.{output_name}' if prefix else output_name
)
elif isinstance(output_value, list):
if all(isinstance(value, (int, float)) for value in output_value):
for value in output_value:
self._check_number(
value=value,
variable=f'{prefix}.{output_name}' if prefix else output_name
)
elif all(isinstance(value, str) for value in output_value):
for value in output_value:
self._check_string(
value=value,
variable=f'{prefix}.{output_name}' if prefix else output_name
)
else:
raise ValueError(f'Output {prefix}.{output_name} is not a valid array. make sure all elements are of the same type.')
else:
raise ValueError(f'Output {prefix}.{output_name} is not a valid type.')
return result
parameters_validated = {}
for output_name, output_config in output_schema.items(): for output_name, output_config in output_schema.items():
if output_config.type == 'object': if output_config.type == 'object':
# check if output is object # check if output is object
...@@ -237,6 +280,12 @@ class CodeNode(BaseNode): ...@@ -237,6 +280,12 @@ class CodeNode(BaseNode):
else: else:
raise ValueError(f'Output type {output_config.type} is not supported.') raise ValueError(f'Output type {output_config.type} is not supported.')
parameters_validated[output_name] = True
# check if all output parameters are validated
if len(parameters_validated) != len(result):
raise ValueError('Not all output parameters are validated.')
return transformed_result return transformed_result
@classmethod @classmethod
......
from typing import Literal, Union from typing import Literal, Optional
from pydantic import BaseModel from pydantic import BaseModel
...@@ -12,7 +12,7 @@ class CodeNodeData(BaseNodeData): ...@@ -12,7 +12,7 @@ class CodeNodeData(BaseNodeData):
""" """
class Output(BaseModel): class Output(BaseModel):
type: Literal['string', 'number', 'object', 'array[string]', 'array[number]'] type: Literal['string', 'number', 'object', 'array[string]', 'array[number]']
children: Union[None, dict[str, 'Output']] children: Optional[dict[str, 'Output']]
variables: list[VariableSelector] variables: list[VariableSelector]
answer: str answer: str
......
import pytest import pytest
from core.app.entities.app_invoke_entities import InvokeFrom
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.nodes.code.code_node import CodeNode from core.workflow.nodes.code.code_node import CodeNode
from models.workflow import WorkflowNodeExecutionStatus, WorkflowRunStatus from models.workflow import WorkflowNodeExecutionStatus
from tests.integration_tests.workflow.nodes.__mock.code_executor import setup_code_executor_mock from tests.integration_tests.workflow.nodes.__mock.code_executor import setup_code_executor_mock
@pytest.mark.parametrize('setup_code_executor_mock', [['none']], indirect=True) @pytest.mark.parametrize('setup_code_executor_mock', [['none']], indirect=True)
...@@ -15,7 +16,13 @@ def test_execute_code(setup_code_executor_mock): ...@@ -15,7 +16,13 @@ def test_execute_code(setup_code_executor_mock):
''' '''
# trim first 4 spaces at the beginning of each line # trim first 4 spaces at the beginning of each line
code = '\n'.join([line[4:] for line in code.split('\n')]) code = '\n'.join([line[4:] for line in code.split('\n')])
node = CodeNode(config={ node = CodeNode(
tenant_id='1',
app_id='1',
workflow_id='1',
user_id='1',
user_from=InvokeFrom.WEB_APP,
config={
'id': '1', 'id': '1',
'data': { 'data': {
'outputs': { 'outputs': {
...@@ -38,7 +45,8 @@ def test_execute_code(setup_code_executor_mock): ...@@ -38,7 +45,8 @@ def test_execute_code(setup_code_executor_mock):
'code_language': 'python3', 'code_language': 'python3',
'code': code 'code': code
} }
}) }
)
# construct variable pool # construct variable pool
pool = VariablePool(system_variables={}, user_inputs={}) pool = VariablePool(system_variables={}, user_inputs={})
...@@ -61,7 +69,13 @@ def test_execute_code_output_validator(setup_code_executor_mock): ...@@ -61,7 +69,13 @@ def test_execute_code_output_validator(setup_code_executor_mock):
''' '''
# trim first 4 spaces at the beginning of each line # trim first 4 spaces at the beginning of each line
code = '\n'.join([line[4:] for line in code.split('\n')]) code = '\n'.join([line[4:] for line in code.split('\n')])
node = CodeNode(config={ node = CodeNode(
tenant_id='1',
app_id='1',
workflow_id='1',
user_id='1',
user_from=InvokeFrom.WEB_APP,
config={
'id': '1', 'id': '1',
'data': { 'data': {
"outputs": { "outputs": {
...@@ -84,7 +98,8 @@ def test_execute_code_output_validator(setup_code_executor_mock): ...@@ -84,7 +98,8 @@ def test_execute_code_output_validator(setup_code_executor_mock):
'code_language': 'python3', 'code_language': 'python3',
'code': code 'code': code
} }
}) }
)
# construct variable pool # construct variable pool
pool = VariablePool(system_variables={}, user_inputs={}) pool = VariablePool(system_variables={}, user_inputs={})
...@@ -108,7 +123,13 @@ def test_execute_code_output_validator_depth(): ...@@ -108,7 +123,13 @@ def test_execute_code_output_validator_depth():
''' '''
# trim first 4 spaces at the beginning of each line # trim first 4 spaces at the beginning of each line
code = '\n'.join([line[4:] for line in code.split('\n')]) code = '\n'.join([line[4:] for line in code.split('\n')])
node = CodeNode(config={ node = CodeNode(
tenant_id='1',
app_id='1',
workflow_id='1',
user_id='1',
user_from=InvokeFrom.WEB_APP,
config={
'id': '1', 'id': '1',
'data': { 'data': {
"outputs": { "outputs": {
...@@ -161,7 +182,8 @@ def test_execute_code_output_validator_depth(): ...@@ -161,7 +182,8 @@ def test_execute_code_output_validator_depth():
'code_language': 'python3', 'code_language': 'python3',
'code': code 'code': code
} }
}) }
)
# construct result # construct result
result = { result = {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment