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

improve: test CodeExecutor with code templates and extract CodeLanguage enum #4098

Merged
merged 4 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 23 additions & 12 deletions api/core/helper/code_executor/code_executor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from enum import Enum
from typing import Literal, Optional

from httpx import post
Expand Down Expand Up @@ -28,7 +29,25 @@ class Data(BaseModel):
data: Data


class CodeLanguage(str, Enum):
PYTHON3 = 'python3'
JINJA2 = 'jinja2'
JAVASCRIPT = 'javascript'


class CodeExecutor:
code_template_transformers = {
CodeLanguage.PYTHON3: PythonTemplateTransformer,
CodeLanguage.JINJA2: Jinja2TemplateTransformer,
CodeLanguage.JAVASCRIPT: NodeJsTemplateTransformer,
}

code_language_to_running_language = {
CodeLanguage.JAVASCRIPT: 'nodejs',
CodeLanguage.JINJA2: CodeLanguage.PYTHON3,
CodeLanguage.PYTHON3: CodeLanguage.PYTHON3,
}

@classmethod
def execute_code(cls, language: Literal['python3', 'javascript', 'jinja2'], preload: str, code: str) -> str:
"""
Expand All @@ -44,9 +63,7 @@ def execute_code(cls, language: Literal['python3', 'javascript', 'jinja2'], prel
}

data = {
'language': 'python3' if language == 'jinja2' else
'nodejs' if language == 'javascript' else
'python3' if language == 'python3' else None,
'language': cls.code_language_to_running_language.get(language),
'code': code,
'preload': preload
}
Expand Down Expand Up @@ -86,15 +103,9 @@ def execute_workflow_code_template(cls, language: Literal['python3', 'javascript
:param inputs: inputs
:return:
"""
template_transformer = None
if language == 'python3':
template_transformer = PythonTemplateTransformer
elif language == 'jinja2':
template_transformer = Jinja2TemplateTransformer
elif language == 'javascript':
template_transformer = NodeJsTemplateTransformer
else:
raise CodeExecutionException('Unsupported language')
template_transformer = cls.code_template_transformers.get(language)
if not template_transformer:
raise CodeExecutionException(f'Unsupported language {language}')

runner, preload = template_transformer.transform_caller(code, inputs)

Expand Down
6 changes: 3 additions & 3 deletions api/core/tools/provider/builtin/code/tools/simple_code.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any

from core.helper.code_executor.code_executor import CodeExecutor
from core.helper.code_executor.code_executor import CodeExecutor, CodeLanguage
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool

Expand All @@ -11,10 +11,10 @@ def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMe
invoke simple code
"""

language = tool_parameters.get('language', 'python3')
language = tool_parameters.get('language', CodeLanguage.PYTHON3)
code = tool_parameters.get('code', '')

if language not in ['python3', 'javascript']:
if language not in [CodeLanguage.PYTHON3, CodeLanguage.JAVASCRIPT]:
raise ValueError(f'Only python3 and javascript are supported, not {language}')

result = CodeExecutor.execute_code(language, '', code)
Expand Down
8 changes: 4 additions & 4 deletions api/core/workflow/nodes/code/code_node.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
from typing import Optional, Union, cast

from core.helper.code_executor.code_executor import CodeExecutionException, CodeExecutor
from core.helper.code_executor.code_executor import CodeExecutionException, CodeExecutor, CodeLanguage
from core.workflow.entities.node_entities import NodeRunResult, NodeType
from core.workflow.entities.variable_pool import VariablePool
from core.workflow.nodes.base_node import BaseNode
Expand Down Expand Up @@ -39,7 +39,7 @@ def get_default_config(cls, filters: Optional[dict] = None) -> dict:
:param filters: filter by node config parameters.
:return:
"""
if filters and filters.get("code_language") == "javascript":
if filters and filters.get("code_language") == CodeLanguage.JAVASCRIPT:
return {
"type": "code",
"config": {
Expand All @@ -53,7 +53,7 @@ def get_default_config(cls, filters: Optional[dict] = None) -> dict:
"value_selector": []
}
],
"code_language": "javascript",
"code_language": CodeLanguage.JAVASCRIPT,
"code": JAVASCRIPT_DEFAULT_CODE,
"outputs": {
"result": {
Expand All @@ -77,7 +77,7 @@ def get_default_config(cls, filters: Optional[dict] = None) -> dict:
"value_selector": []
}
],
"code_language": "python3",
"code_language": CodeLanguage.PYTHON3,
"code": PYTHON_DEFAULT_CODE,
"outputs": {
"result": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import pytest

from core.helper.code_executor.code_executor import CodeExecutionException, CodeExecutor

CODE_LANGUAGE = 'unsupported_language'


def test_unsupported_with_code_template():
with pytest.raises(CodeExecutionException) as e:
CodeExecutor.execute_workflow_code_template(language=CODE_LANGUAGE, code='', inputs={})
assert str(e.value) == f'Unsupported language {CODE_LANGUAGE}'
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from core.helper.code_executor.code_executor import CodeExecutor
from textwrap import dedent

CODE_LANGUAGE = 'javascript'
from core.helper.code_executor.code_executor import CodeExecutor, CodeLanguage
from core.workflow.nodes.code.code_node import JAVASCRIPT_DEFAULT_CODE

CODE_LANGUAGE = CodeLanguage.JAVASCRIPT


def test_javascript_plain():
Expand All @@ -10,9 +13,15 @@ def test_javascript_plain():


def test_javascript_json():
code = """
obj = {'Hello': 'World'}
console.log(JSON.stringify(obj))
"""
code = dedent("""
obj = {'Hello': 'World'}
console.log(JSON.stringify(obj))
""")
result = CodeExecutor.execute_code(language=CODE_LANGUAGE, preload='', code=code)
assert result == '{"Hello":"World"}\n'


def test_javascript_with_code_template():
result = CodeExecutor.execute_workflow_code_template(
language=CODE_LANGUAGE, code=JAVASCRIPT_DEFAULT_CODE, inputs={'arg1': 'Hello', 'arg2': 'World'})
assert result == {'result': 'HelloWorld'}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import base64

from core.helper.code_executor.code_executor import CodeExecutor
from core.helper.code_executor.code_executor import CodeExecutor, CodeLanguage
from core.helper.code_executor.jinja2_transformer import JINJA2_PRELOAD, PYTHON_RUNNER

CODE_LANGUAGE = 'jinja2'
CODE_LANGUAGE = CodeLanguage.JINJA2


def test_jinja2():
Expand All @@ -12,3 +12,9 @@ def test_jinja2():
code = PYTHON_RUNNER.replace('{{code}}', template).replace('{{inputs}}', inputs)
result = CodeExecutor.execute_code(language=CODE_LANGUAGE, preload=JINJA2_PRELOAD, code=code)
assert result == '<<RESULT>>Hello World<<RESULT>>\n'


def test_jinja2_with_code_template():
result = CodeExecutor.execute_workflow_code_template(
language=CODE_LANGUAGE, code='Hello {{template}}', inputs={'template': 'World'})
assert result == {'result': 'Hello World'}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from core.helper.code_executor.code_executor import CodeExecutor
from textwrap import dedent

CODE_LANGUAGE = 'python3'
from core.helper.code_executor.code_executor import CodeExecutor, CodeLanguage
from core.workflow.nodes.code.code_node import PYTHON_DEFAULT_CODE

CODE_LANGUAGE = CodeLanguage.PYTHON3


def test_python3_plain():
Expand All @@ -10,9 +13,15 @@ def test_python3_plain():


def test_python3_json():
code = """
import json
print(json.dumps({'Hello': 'World'}))
"""
code = dedent("""
import json
print(json.dumps({'Hello': 'World'}))
""")
result = CodeExecutor.execute_code(language=CODE_LANGUAGE, preload='', code=code)
assert result == '{"Hello": "World"}\n'


def test_python3_with_code_template():
result = CodeExecutor.execute_workflow_code_template(
language=CODE_LANGUAGE, code=PYTHON_DEFAULT_CODE, inputs={'arg1': 'Hello', 'arg2': 'World'})
assert result == {'result': 'HelloWorld'}