diff --git a/api/core/helper/code_executor/code_executor.py b/api/core/helper/code_executor/code_executor.py index 264a246058d224..a829748b485dc5 100644 --- a/api/core/helper/code_executor/code_executor.py +++ b/api/core/helper/code_executor/code_executor.py @@ -1,15 +1,13 @@ import logging -import time from enum import Enum from threading import Lock -from typing import Literal, Optional +from typing import Optional -from httpx import Timeout, get, post +from httpx import Timeout, post from pydantic import BaseModel from yarl import URL from configs import dify_config -from core.helper.code_executor.entities import CodeDependency from core.helper.code_executor.javascript.javascript_transformer import NodeJsTemplateTransformer from core.helper.code_executor.jinja2.jinja2_transformer import Jinja2TemplateTransformer from core.helper.code_executor.python3.python3_transformer import Python3TemplateTransformer @@ -66,8 +64,7 @@ class CodeExecutor: def execute_code(cls, language: CodeLanguage, preload: str, - code: str, - dependencies: Optional[list[CodeDependency]] = None) -> str: + code: str) -> str: """ Execute code :param language: code language @@ -87,9 +84,6 @@ def execute_code(cls, 'enable_network': True } - if dependencies: - data['dependencies'] = [dependency.model_dump() for dependency in dependencies] - try: response = post(str(url), json=data, headers=headers, timeout=CODE_EXECUTION_TIMEOUT) if response.status_code == 503: @@ -119,7 +113,7 @@ def execute_code(cls, return response.data.stdout or '' @classmethod - def execute_workflow_code_template(cls, language: CodeLanguage, code: str, inputs: dict, dependencies: Optional[list[CodeDependency]] = None) -> dict: + def execute_workflow_code_template(cls, language: CodeLanguage, code: str, inputs: dict) -> dict: """ Execute code :param language: code language @@ -131,67 +125,12 @@ def execute_workflow_code_template(cls, language: CodeLanguage, code: str, input if not template_transformer: raise CodeExecutionException(f'Unsupported language {language}') - runner, preload, dependencies = template_transformer.transform_caller(code, inputs, dependencies) + runner, preload = template_transformer.transform_caller(code, inputs) try: - response = cls.execute_code(language, preload, runner, dependencies) + response = cls.execute_code(language, preload, runner) except CodeExecutionException as e: raise e return template_transformer.transform_response(response) - - @classmethod - def list_dependencies(cls, language: str) -> list[CodeDependency]: - if language not in cls.supported_dependencies_languages: - return [] - - with cls.dependencies_cache_lock: - if language in cls.dependencies_cache: - # check expiration - dependencies = cls.dependencies_cache[language] - if dependencies['expiration'] > time.time(): - return dependencies['data'] - # remove expired cache - del cls.dependencies_cache[language] - - dependencies = cls._get_dependencies(language) - with cls.dependencies_cache_lock: - cls.dependencies_cache[language] = { - 'data': dependencies, - 'expiration': time.time() + 60 - } - - return dependencies - - @classmethod - def _get_dependencies(cls, language: Literal['python3']) -> list[CodeDependency]: - """ - List dependencies - """ - url = URL(CODE_EXECUTION_ENDPOINT) / 'v1' / 'sandbox' / 'dependencies' - - headers = { - 'X-Api-Key': CODE_EXECUTION_API_KEY - } - - running_language = cls.code_language_to_running_language.get(language) - if isinstance(running_language, Enum): - running_language = running_language.value - - data = { - 'language': running_language, - } - - try: - response = get(str(url), params=data, headers=headers, timeout=CODE_EXECUTION_TIMEOUT) - if response.status_code != 200: - raise Exception(f'Failed to list dependencies, got status code {response.status_code}, please check if the sandbox service is running') - response = response.json() - dependencies = response.get('data', {}).get('dependencies', []) - return [ - CodeDependency(**dependency) for dependency in dependencies - if dependency.get('name') not in Python3TemplateTransformer.get_standard_packages() - ] - except Exception as e: - logger.exception(f'Failed to list dependencies: {e}') - return [] \ No newline at end of file + \ No newline at end of file diff --git a/api/core/helper/code_executor/code_node_provider.py b/api/core/helper/code_executor/code_node_provider.py index 761c0e2b2524fa..3f099b7ac5bbb4 100644 --- a/api/core/helper/code_executor/code_node_provider.py +++ b/api/core/helper/code_executor/code_node_provider.py @@ -2,8 +2,6 @@ from pydantic import BaseModel -from core.helper.code_executor.code_executor import CodeExecutor - class CodeNodeProvider(BaseModel): @staticmethod @@ -23,10 +21,6 @@ def get_default_code(cls) -> str: """ pass - @classmethod - def get_default_available_packages(cls) -> list[dict]: - return [p.model_dump() for p in CodeExecutor.list_dependencies(cls.get_language())] - @classmethod def get_default_config(cls) -> dict: return { @@ -50,6 +44,5 @@ def get_default_config(cls) -> dict: "children": None } } - }, - "available_dependencies": cls.get_default_available_packages(), + } } diff --git a/api/core/helper/code_executor/entities.py b/api/core/helper/code_executor/entities.py deleted file mode 100644 index cc10288521ad6b..00000000000000 --- a/api/core/helper/code_executor/entities.py +++ /dev/null @@ -1,6 +0,0 @@ -from pydantic import BaseModel - - -class CodeDependency(BaseModel): - name: str - version: str diff --git a/api/core/helper/code_executor/jinja2/jinja2_formatter.py b/api/core/helper/code_executor/jinja2/jinja2_formatter.py index 63f48a56e2a47f..f1e5da584c660d 100644 --- a/api/core/helper/code_executor/jinja2/jinja2_formatter.py +++ b/api/core/helper/code_executor/jinja2/jinja2_formatter.py @@ -3,7 +3,7 @@ class Jinja2Formatter: @classmethod - def format(cls, template: str, inputs: str) -> str: + def format(cls, template: str, inputs: dict) -> str: """ Format template :param template: template diff --git a/api/core/helper/code_executor/jinja2/jinja2_transformer.py b/api/core/helper/code_executor/jinja2/jinja2_transformer.py index a8f8095d52b6d5..b8cb29600e106e 100644 --- a/api/core/helper/code_executor/jinja2/jinja2_transformer.py +++ b/api/core/helper/code_executor/jinja2/jinja2_transformer.py @@ -1,14 +1,9 @@ from textwrap import dedent -from core.helper.code_executor.python3.python3_transformer import Python3TemplateTransformer from core.helper.code_executor.template_transformer import TemplateTransformer class Jinja2TemplateTransformer(TemplateTransformer): - @classmethod - def get_standard_packages(cls) -> set[str]: - return {'jinja2'} | Python3TemplateTransformer.get_standard_packages() - @classmethod def transform_response(cls, response: str) -> dict: """ diff --git a/api/core/helper/code_executor/python3/python3_transformer.py b/api/core/helper/code_executor/python3/python3_transformer.py index 4a5fa3509325d3..75a5a44d086c3c 100644 --- a/api/core/helper/code_executor/python3/python3_transformer.py +++ b/api/core/helper/code_executor/python3/python3_transformer.py @@ -4,30 +4,6 @@ class Python3TemplateTransformer(TemplateTransformer): - @classmethod - def get_standard_packages(cls) -> set[str]: - return { - 'base64', - 'binascii', - 'collections', - 'datetime', - 'functools', - 'hashlib', - 'hmac', - 'itertools', - 'json', - 'math', - 'operator', - 'os', - 'random', - 're', - 'string', - 'sys', - 'time', - 'traceback', - 'uuid', - } - @classmethod def get_runner_script(cls) -> str: runner_script = dedent(f""" diff --git a/api/core/helper/code_executor/template_transformer.py b/api/core/helper/code_executor/template_transformer.py index da7ef469d91abf..cf66558b658f57 100644 --- a/api/core/helper/code_executor/template_transformer.py +++ b/api/core/helper/code_executor/template_transformer.py @@ -2,9 +2,6 @@ import re from abc import ABC, abstractmethod from base64 import b64encode -from typing import Optional - -from core.helper.code_executor.entities import CodeDependency class TemplateTransformer(ABC): @@ -13,12 +10,7 @@ class TemplateTransformer(ABC): _result_tag: str = '<>' @classmethod - def get_standard_packages(cls) -> set[str]: - return set() - - @classmethod - def transform_caller(cls, code: str, inputs: dict, - dependencies: Optional[list[CodeDependency]] = None) -> tuple[str, str, list[CodeDependency]]: + def transform_caller(cls, code: str, inputs: dict) -> tuple[str, str]: """ Transform code to python runner :param code: code @@ -28,14 +20,7 @@ def transform_caller(cls, code: str, inputs: dict, runner_script = cls.assemble_runner_script(code, inputs) preload_script = cls.get_preload_script() - packages = dependencies or [] - standard_packages = cls.get_standard_packages() - for package in standard_packages: - if package not in packages: - packages.append(CodeDependency(name=package, version='')) - packages = list({dep.name: dep for dep in packages if dep.name}.values()) - - return runner_script, preload_script, packages + return runner_script, preload_script @classmethod def extract_result_str_from_response(cls, response: str) -> str: diff --git a/api/core/workflow/nodes/code/code_node.py b/api/core/workflow/nodes/code/code_node.py index 6dd39ca1788cf3..71adf18a51c134 100644 --- a/api/core/workflow/nodes/code/code_node.py +++ b/api/core/workflow/nodes/code/code_node.py @@ -67,7 +67,6 @@ def _run(self, variable_pool: VariablePool) -> NodeRunResult: language=code_language, code=code, inputs=variables, - dependencies=node_data.dependencies ) # Transform result diff --git a/api/core/workflow/nodes/code/entities.py b/api/core/workflow/nodes/code/entities.py index 83a5416d57ce6a..c0701ecccd2132 100644 --- a/api/core/workflow/nodes/code/entities.py +++ b/api/core/workflow/nodes/code/entities.py @@ -3,7 +3,6 @@ from pydantic import BaseModel from core.helper.code_executor.code_executor import CodeLanguage -from core.helper.code_executor.entities import CodeDependency from core.workflow.entities.base_node_data_entities import BaseNodeData from core.workflow.entities.variable_entities import VariableSelector @@ -16,8 +15,12 @@ class Output(BaseModel): type: Literal['string', 'number', 'object', 'array[string]', 'array[number]', 'array[object]'] children: Optional[dict[str, 'Output']] = None + class Dependency(BaseModel): + name: str + version: str + variables: list[VariableSelector] code_language: Literal[CodeLanguage.PYTHON3, CodeLanguage.JAVASCRIPT] code: str outputs: dict[str, Output] - dependencies: Optional[list[CodeDependency]] = None \ No newline at end of file + dependencies: Optional[list[Dependency]] = None \ No newline at end of file diff --git a/api/tests/integration_tests/workflow/nodes/__mock/code_executor.py b/api/tests/integration_tests/workflow/nodes/__mock/code_executor.py index 13f992136e8fa8..51398ccb329d81 100644 --- a/api/tests/integration_tests/workflow/nodes/__mock/code_executor.py +++ b/api/tests/integration_tests/workflow/nodes/__mock/code_executor.py @@ -6,14 +6,13 @@ from jinja2 import Template from core.helper.code_executor.code_executor import CodeExecutor, CodeLanguage -from core.helper.code_executor.entities import CodeDependency MOCK = os.getenv('MOCK_SWITCH', 'false') == 'true' class MockedCodeExecutor: @classmethod def invoke(cls, language: Literal['python3', 'javascript', 'jinja2'], - code: str, inputs: dict, dependencies: Optional[list[CodeDependency]] = None) -> dict: + code: str, inputs: dict) -> dict: # invoke directly match language: case CodeLanguage.PYTHON3: @@ -24,6 +23,8 @@ def invoke(cls, language: Literal['python3', 'javascript', 'jinja2'], return { "result": Template(code).render(inputs) } + case _: + raise Exception("Language not supported") @pytest.fixture def setup_code_executor_mock(request, monkeypatch: MonkeyPatch): diff --git a/api/tests/integration_tests/workflow/nodes/code_executor/test_code_javascript.py b/api/tests/integration_tests/workflow/nodes/code_executor/test_code_javascript.py index 2d798eb9c2b361..0757caba7b4936 100644 --- a/api/tests/integration_tests/workflow/nodes/code_executor/test_code_javascript.py +++ b/api/tests/integration_tests/workflow/nodes/code_executor/test_code_javascript.py @@ -28,14 +28,6 @@ def test_javascript_with_code_template(): inputs={'arg1': 'Hello', 'arg2': 'World'}) assert result == {'result': 'HelloWorld'} - -def test_javascript_list_default_available_packages(): - packages = JavascriptCodeProvider.get_default_available_packages() - - # no default packages available for javascript - assert len(packages) == 0 - - def test_javascript_get_runner_script(): runner_script = NodeJsTemplateTransformer.get_runner_script() assert runner_script.count(NodeJsTemplateTransformer._code_placeholder) == 1 diff --git a/api/tests/integration_tests/workflow/nodes/code_executor/test_code_python3.py b/api/tests/integration_tests/workflow/nodes/code_executor/test_code_python3.py index d265011d4c29f6..9d7e86cd68c720 100644 --- a/api/tests/integration_tests/workflow/nodes/code_executor/test_code_python3.py +++ b/api/tests/integration_tests/workflow/nodes/code_executor/test_code_python3.py @@ -29,15 +29,6 @@ def test_python3_with_code_template(): assert result == {'result': 'HelloWorld'} -def test_python3_list_default_available_packages(): - packages = Python3CodeProvider.get_default_available_packages() - assert len(packages) > 0 - assert {'requests', 'httpx'}.issubset(p['name'] for p in packages) - - # check JSON serializable - assert len(str(json.dumps(packages))) > 0 - - def test_python3_get_runner_script(): runner_script = Python3TemplateTransformer.get_runner_script() assert runner_script.count(Python3TemplateTransformer._code_placeholder) == 1 diff --git a/web/app/components/workflow/nodes/code/dependency-picker.tsx b/web/app/components/workflow/nodes/code/dependency-picker.tsx deleted file mode 100644 index 3aa6c45f20b813..00000000000000 --- a/web/app/components/workflow/nodes/code/dependency-picker.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import type { FC } from 'react' -import React, { useCallback, useState } from 'react' -import { t } from 'i18next' -import { - RiArrowDownSLine, - RiSearchLine, -} from '@remixicon/react' -import type { CodeDependency } from './types' -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' -import { Check } from '@/app/components/base/icons/src/vender/line/general' -import { XCircle } from '@/app/components/base/icons/src/vender/solid/general' - -type Props = { - value: CodeDependency - available_dependencies: CodeDependency[] - onChange: (dependency: CodeDependency) => void -} - -const DependencyPicker: FC = ({ - available_dependencies, - value, - onChange, -}) => { - const [open, setOpen] = useState(false) - const [searchText, setSearchText] = useState('') - - const handleChange = useCallback((dependency: CodeDependency) => { - return () => { - setOpen(false) - onChange(dependency) - } - }, [onChange]) - - return ( - - setOpen(!open)} className='flex-grow cursor-pointer'> -
-
{value.name}
- -
-
- -
-
- - setSearchText(e.target.value)} - autoFocus - /> - { - searchText && ( -
setSearchText('')} - > - -
- ) - } -
-
- {available_dependencies.filter((v) => { - if (!searchText) - return true - return v.name.toLowerCase().includes(searchText.toLowerCase()) - }).map(dependency => ( -
-
{dependency.name}
- {dependency.name === value.name && } -
- ))} -
-
-
-
- ) -} - -export default React.memo(DependencyPicker) diff --git a/web/app/components/workflow/nodes/code/dependency.tsx b/web/app/components/workflow/nodes/code/dependency.tsx deleted file mode 100644 index 5e868efe318539..00000000000000 --- a/web/app/components/workflow/nodes/code/dependency.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type { FC } from 'react' -import React from 'react' -import RemoveButton from '../_base/components/remove-button' -import type { CodeDependency } from './types' -import DependencyPicker from './dependency-picker' - -type Props = { - available_dependencies: CodeDependency[] - dependencies: CodeDependency[] - handleRemove: (index: number) => void - handleChange: (index: number, dependency: CodeDependency) => void -} - -const Dependencies: FC = ({ - available_dependencies, dependencies, handleRemove, handleChange, -}) => { - return ( -
- {dependencies.map((dependency, index) => ( -
- handleChange(index, dependency)} - /> - handleRemove(index)} - /> -
- ))} -
- ) -} - -export default React.memo(Dependencies) diff --git a/web/app/components/workflow/nodes/code/panel.tsx b/web/app/components/workflow/nodes/code/panel.tsx index 8ab9b3d0e5e45c..838e7190d3abd4 100644 --- a/web/app/components/workflow/nodes/code/panel.tsx +++ b/web/app/components/workflow/nodes/code/panel.tsx @@ -5,7 +5,6 @@ import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confir import useConfig from './use-config' import type { CodeNodeType } from './types' import { CodeLanguage } from './types' -import Dependencies from './dependency' import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list' import OutputVarList from '@/app/components/workflow/nodes/_base/components/variable/output-var-list' import AddButton from '@/app/components/base/button/add-button' @@ -60,11 +59,6 @@ const Panel: FC> = ({ varInputs, inputVarValues, setInputVarValues, - allowDependencies, - availableDependencies, - handleAddDependency, - handleRemoveDependency, - handleChangeDependency, } = useConfig(id, data) return ( @@ -84,31 +78,6 @@ const Panel: FC> = ({ filterVar={filterVar} /> - { - allowDependencies - ? ( -
- -
- handleAddDependency({ name: '', version: '' })} /> - } - tooltip={t(`${i18nPrefix}.advancedDependenciesTip`)!} - > - handleRemoveDependency(index)} - handleChange={(index, dependency) => handleChangeDependency(index, dependency)} - /> - -
-
- ) - : null - } { const appId = useAppStore.getState().appDetail?.id const [allLanguageDefault, setAllLanguageDefault] = useState | null>(null) - const [allLanguageDependencies, setAllLanguageDependencies] = useState | null>(null) useEffect(() => { if (appId) { (async () => { const { config: javaScriptConfig } = await fetchNodeDefault(appId, BlockEnum.Code, { code_language: CodeLanguage.javascript }) as any - const { config: pythonConfig, available_dependencies: pythonDependencies } = await fetchNodeDefault(appId, BlockEnum.Code, { code_language: CodeLanguage.python3 }) as any + const { config: pythonConfig } = await fetchNodeDefault(appId, BlockEnum.Code, { code_language: CodeLanguage.python3 }) as any setAllLanguageDefault({ [CodeLanguage.javascript]: javaScriptConfig as CodeNodeType, [CodeLanguage.python3]: pythonConfig as CodeNodeType, } as any) - setAllLanguageDependencies({ - [CodeLanguage.python3]: pythonDependencies as CodeDependency[], - } as any) })() } }, [appId]) @@ -45,62 +41,6 @@ const useConfig = (id: string, payload: CodeNodeType) => { setInputs, }) - const handleAddDependency = useCallback((dependency: CodeDependency) => { - const newInputs = produce(inputs, (draft) => { - if (!draft.dependencies) - draft.dependencies = [] - draft.dependencies.push(dependency) - }) - setInputs(newInputs) - }, [inputs, setInputs]) - - const handleRemoveDependency = useCallback((index: number) => { - const newInputs = produce(inputs, (draft) => { - if (!draft.dependencies) - draft.dependencies = [] - draft.dependencies.splice(index, 1) - }) - setInputs(newInputs) - }, [inputs, setInputs]) - - const handleChangeDependency = useCallback((index: number, dependency: CodeDependency) => { - const newInputs = produce(inputs, (draft) => { - if (!draft.dependencies) - draft.dependencies = [] - draft.dependencies[index] = dependency - }) - setInputs(newInputs) - }, [inputs, setInputs]) - - const [allowDependencies, setAllowDependencies] = useState(false) - useEffect(() => { - if (!inputs.code_language) - return - if (!allLanguageDependencies) - return - - const newAllowDependencies = !!allLanguageDependencies[inputs.code_language] - setAllowDependencies(newAllowDependencies) - }, [allLanguageDependencies, inputs.code_language]) - - const [availableDependencies, setAvailableDependencies] = useState([]) - useEffect(() => { - if (!inputs.code_language) - return - if (!allLanguageDependencies) - return - - const newAvailableDependencies = produce(allLanguageDependencies[inputs.code_language], (draft) => { - const currentLanguage = inputs.code_language - if (!currentLanguage || !draft || !inputs.dependencies) - return [] - return draft.filter((dependency) => { - return !inputs.dependencies?.find(d => d.name === dependency.name) - }) - }) - setAvailableDependencies(newAvailableDependencies || []) - }, [allLanguageDependencies, inputs.code_language, inputs.dependencies]) - const [outputKeyOrders, setOutputKeyOrders] = useState([]) const syncOutputKeyOrders = useCallback((outputs: OutputVar) => { setOutputKeyOrders(Object.keys(outputs)) @@ -223,11 +163,6 @@ const useConfig = (id: string, payload: CodeNodeType) => { inputVarValues, setInputVarValues, runResult, - availableDependencies, - allowDependencies, - handleAddDependency, - handleRemoveDependency, - handleChangeDependency, } }