chore(api): upgrade graphon to v0.3.1 (#35987)

Co-authored-by: -LAN- <laipz8200@outlook.com>
This commit is contained in:
QuantumGhost 2026-05-11 13:32:17 +08:00 committed by GitHub
parent b108ea42f6
commit 74a04afe27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 67 additions and 34 deletions

View File

@ -378,6 +378,7 @@ class DifyToolNodeRuntime(ToolNodeRuntimeProtocol):
node_id: str,
node_data: ToolNodeData,
variable_pool,
node_execution_id: str | None = None,
) -> ToolRuntimeHandle:
try:
tool_runtime = ToolManager.get_workflow_tool_runtime(

View File

@ -45,7 +45,7 @@ dependencies = [
# Emerging: newer and fast-moving, use compatible pins
"fastopenapi[flask]~=0.7.0",
"graphon~=0.3.0",
"graphon~=0.3.1",
"httpx-sse~=0.4.0",
"json-repair~=0.59.4",
]

View File

@ -91,7 +91,11 @@ def init_llm_node(config: dict) -> LLMNode:
return node
def test_execute_llm():
def _mock_db_session_close(monkeypatch) -> None:
monkeypatch.setattr(db.session, "close", MagicMock())
def test_execute_llm(monkeypatch):
node = init_llm_node(
config={
"id": "llm",
@ -118,7 +122,7 @@ def test_execute_llm():
},
)
db.session.close = MagicMock()
_mock_db_session_close(monkeypatch)
def build_mock_model_instance() -> MagicMock:
from decimal import Decimal
@ -195,7 +199,7 @@ def test_execute_llm():
assert item.node_run_result.outputs.get("usage", {})["total_tokens"] > 0
def test_execute_llm_with_jinja2():
def test_execute_llm_with_jinja2(monkeypatch):
"""
Test execute LLM node with jinja2
"""
@ -233,8 +237,7 @@ def test_execute_llm_with_jinja2():
},
)
# Mock db.session.close()
db.session.close = MagicMock()
_mock_db_session_close(monkeypatch)
def build_mock_model_instance() -> MagicMock:
from decimal import Decimal

View File

@ -83,7 +83,11 @@ def init_parameter_extractor_node(config: dict, memory=None):
return node
def test_function_calling_parameter_extractor(setup_model_mock):
def _mock_db_session_close(monkeypatch) -> None:
monkeypatch.setattr(db.session, "close", MagicMock())
def test_function_calling_parameter_extractor(setup_model_mock, monkeypatch):
"""
Test function calling for parameter extractor.
"""
@ -114,7 +118,7 @@ def test_function_calling_parameter_extractor(setup_model_mock):
mode="chat",
credentials={"openai_api_key": os.environ.get("OPENAI_API_KEY")},
)()
db.session.close = MagicMock()
_mock_db_session_close(monkeypatch)
result = node._run()
@ -124,7 +128,7 @@ def test_function_calling_parameter_extractor(setup_model_mock):
assert result.outputs.get("__reason") == None
def test_instructions(setup_model_mock):
def test_instructions(setup_model_mock, monkeypatch):
"""
Test chat parameter extractor.
"""
@ -155,7 +159,7 @@ def test_instructions(setup_model_mock):
mode="chat",
credentials={"openai_api_key": os.environ.get("OPENAI_API_KEY")},
)()
db.session.close = MagicMock()
_mock_db_session_close(monkeypatch)
result = node._run()
@ -174,7 +178,7 @@ def test_instructions(setup_model_mock):
assert "what's the weather in SF" in prompt.get("text")
def test_chat_parameter_extractor(setup_model_mock):
def test_chat_parameter_extractor(setup_model_mock, monkeypatch):
"""
Test chat parameter extractor.
"""
@ -205,7 +209,7 @@ def test_chat_parameter_extractor(setup_model_mock):
mode="chat",
credentials={"openai_api_key": os.environ.get("OPENAI_API_KEY")},
)()
db.session.close = MagicMock()
_mock_db_session_close(monkeypatch)
result = node._run()
@ -225,7 +229,7 @@ def test_chat_parameter_extractor(setup_model_mock):
assert '<structure>\n{"type": "object"' in prompt.get("text")
def test_completion_parameter_extractor(setup_model_mock):
def test_completion_parameter_extractor(setup_model_mock, monkeypatch):
"""
Test completion parameter extractor.
"""
@ -256,7 +260,7 @@ def test_completion_parameter_extractor(setup_model_mock):
mode="completion",
credentials={"openai_api_key": os.environ.get("OPENAI_API_KEY")},
)()
db.session.close = MagicMock()
_mock_db_session_close(monkeypatch)
result = node._run()
@ -350,7 +354,7 @@ def test_extract_json_from_tool_call():
assert result["location"] == "kawaii"
def test_chat_parameter_extractor_with_memory(setup_model_mock):
def test_chat_parameter_extractor_with_memory(setup_model_mock, monkeypatch):
"""
Test chat parameter extractor with memory.
"""
@ -382,7 +386,7 @@ def test_chat_parameter_extractor_with_memory(setup_model_mock):
mode="chat",
credentials={"openai_api_key": os.environ.get("OPENAI_API_KEY")},
)()
db.session.close = MagicMock()
_mock_db_session_close(monkeypatch)
result = node._run()

View File

@ -168,6 +168,7 @@ def test_node_variable_collection_get_success(
account, tenant = create_console_account_and_tenant(db_session_with_containers)
app = create_console_app(db_session_with_containers, tenant.id, account.id, AppMode.WORKFLOW)
node_variable = _create_node_variable(db_session_with_containers, app.id, account.id, node_id="node_123")
node_variable_id = node_variable.id
_create_node_variable(db_session_with_containers, app.id, account.id, node_id="node_456", name="other")
response = test_client_with_containers.get(
@ -178,7 +179,7 @@ def test_node_variable_collection_get_success(
assert response.status_code == 200
payload = response.get_json()
assert payload is not None
assert [item["id"] for item in payload["items"]] == [node_variable.id]
assert [item["id"] for item in payload["items"]] == [node_variable_id]
def test_node_variable_collection_get_invalid_node_id(
@ -377,6 +378,7 @@ def test_system_variable_collection_get(
account, tenant = create_console_account_and_tenant(db_session_with_containers)
app = create_console_app(db_session_with_containers, tenant.id, account.id, AppMode.WORKFLOW)
variable = _create_system_variable(db_session_with_containers, app.id, account.id)
variable_id = variable.id
response = test_client_with_containers.get(
f"/console/api/apps/{app.id}/workflows/draft/system-variables",
@ -386,7 +388,7 @@ def test_system_variable_collection_get(
assert response.status_code == 200
payload = response.get_json()
assert payload is not None
assert [item["id"] for item in payload["items"]] == [variable.id]
assert [item["id"] for item in payload["items"]] == [variable_id]
def test_environment_variable_collection_get(

View File

@ -17,6 +17,8 @@ def test_get_oauth_url_successful(
test_client_with_containers: FlaskClient,
) -> None:
account, tenant = create_console_account_and_tenant(db_session_with_containers)
tenant_id = tenant.id
current_tenant_id = account.current_tenant_id
provider = MagicMock()
provider.get_authorization_url.return_value = "http://oauth.provider/auth"
@ -29,7 +31,7 @@ def test_get_oauth_url_successful(
headers=authenticate_console_client(test_client_with_containers, account),
)
assert tenant.id == account.current_tenant_id
assert tenant_id == current_tenant_id
assert response.status_code == 200
assert response.get_json() == {"data": "http://oauth.provider/auth"}
provider.get_authorization_url.assert_called_once()

View File

@ -6,6 +6,7 @@ from unittest.mock import MagicMock, patch
import pytest
from flask import Flask
from sqlalchemy.orm import Session
from controllers.console.auth.error import (
EmailCodeError,
@ -20,13 +21,15 @@ from controllers.console.auth.forgot_password import (
ForgotPasswordSendEmailApi,
)
from controllers.console.error import AccountNotFound, EmailSendIpLimitError
from tests.test_containers_integration_tests.controllers.console.helpers import ensure_dify_setup
class TestForgotPasswordSendEmailApi:
"""Test cases for sending password reset emails."""
@pytest.fixture
def app(self, flask_app_with_containers: Flask):
def app(self, flask_app_with_containers: Flask, db_session_with_containers: Session):
ensure_dify_setup(db_session_with_containers)
return flask_app_with_containers
@pytest.fixture
@ -139,7 +142,8 @@ class TestForgotPasswordCheckApi:
"""Test cases for verifying password reset codes."""
@pytest.fixture
def app(self, flask_app_with_containers: Flask):
def app(self, flask_app_with_containers: Flask, db_session_with_containers: Session):
ensure_dify_setup(db_session_with_containers)
return flask_app_with_containers
@patch("controllers.console.auth.forgot_password.AccountService.is_forgot_password_error_rate_limit")
@ -322,7 +326,8 @@ class TestForgotPasswordResetApi:
"""Test cases for resetting password with verified token."""
@pytest.fixture
def app(self, flask_app_with_containers: Flask):
def app(self, flask_app_with_containers: Flask, db_session_with_containers: Session):
ensure_dify_setup(db_session_with_containers)
return flask_app_with_containers
@pytest.fixture

View File

@ -233,8 +233,6 @@ class TestSegmentTypeAdditionalMethods:
assert SegmentType.GROUP.is_valid([StringSegment(value="b")]) is True
assert SegmentType.GROUP.is_valid(["not-segment"]) is False
def test_unreachable_assertion_branch(self, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(SegmentType, "is_array_type", lambda self: False)
with pytest.raises(AssertionError, match="unreachable"):
SegmentType.ARRAY_STRING.is_valid(["a"])
def test_unreachable_assertion_branch(self):
with pytest.raises(AssertionError, match="Expected code to be unreachable"):
SegmentType.is_valid("not-a-segment-type", None) # type: ignore[arg-type]

View File

@ -613,7 +613,7 @@ def test_combine_message_content_with_role_handles_all_supported_roles():
SystemPromptMessage(content=contents)
)
with pytest.raises(NotImplementedError, match="Role custom is not supported"):
with pytest.raises(AssertionError, match="Expected code to be unreachable"):
llm_utils.combine_message_content_with_role(contents=contents, role="custom") # type: ignore[arg-type]

View File

@ -24,7 +24,14 @@ if TYPE_CHECKING: # pragma: no cover - imported for type checking only
class _StubToolRuntime:
def get_runtime(self, *, node_id: str, node_data: Any, variable_pool: Any) -> ToolRuntimeHandle:
def get_runtime(
self,
*,
node_id: str,
node_data: Any,
variable_pool: Any,
node_execution_id: str | None = None,
) -> ToolRuntimeHandle:
raise NotImplementedError
def get_runtime_parameters(self, *, tool_runtime: ToolRuntimeHandle) -> list[Any]:

View File

@ -7,6 +7,17 @@ from pathlib import Path
def test_moved_core_nodes_resolve_after_importing_production_entrypoints():
api_root = Path(__file__).resolve().parents[4]
# `PYTHONSAFEPATH=1` enables Python's safe-path mode, which suppresses the
# usual implicit insertion of the working directory into `sys.path`.
# Set `PYTHONPATH` explicitly so this subprocess test stays deterministic in
# both CI and local shells that may export `PYTHONSAFEPATH`.
env = os.environ.copy()
existing_pythonpath = env.get("PYTHONPATH")
env["PYTHONPATH"] = (
str(api_root) if not existing_pythonpath else os.pathsep.join([str(api_root), existing_pythonpath])
)
env["PYTHONSAFEPATH"] = "1"
script = textwrap.dedent(
"""
from core.app.apps import workflow_app_runner
@ -34,7 +45,7 @@ def test_moved_core_nodes_resolve_after_importing_production_entrypoints():
completed = subprocess.run(
[sys.executable, "-c", script],
cwd=api_root,
env=os.environ.copy(),
env=env,
capture_output=True,
text=True,
check=False,

8
api/uv.lock generated
View File

@ -1597,7 +1597,7 @@ requires-dist = [
{ name = "gmpy2", specifier = ">=2.3.0" },
{ name = "google-api-python-client", specifier = ">=2.196.0" },
{ name = "google-cloud-aiplatform", specifier = ">=1.151.0,<2.0.0" },
{ name = "graphon", specifier = "~=0.3.0" },
{ name = "graphon", specifier = "~=0.3.1" },
{ name = "gunicorn", specifier = ">=26.0.0" },
{ name = "httpx", extras = ["socks"], specifier = ">=0.28.1,<1.0.0" },
{ name = "httpx-sse", specifier = "~=0.4.0" },
@ -2940,7 +2940,7 @@ httpx = [
[[package]]
name = "graphon"
version = "0.3.0"
version = "0.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "charset-normalizer" },
@ -2961,9 +2961,9 @@ dependencies = [
{ name = "unstructured", extra = ["docx", "epub", "md", "ppt", "pptx"] },
{ name = "webvtt-py" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bf/62/83593d6e7a139ff124711ea05882cadca7065c11a38763aa9360d7e76804/graphon-0.3.0.tar.gz", hash = "sha256:cd38f842ae3dcfa956428b952efbe2a3ea9c1581446647142accbbdeb638b876", size = 241176, upload-time = "2026-04-21T15:18:48.291Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5a/ef/43217842e84160acca64a95858f1689389a50e04a53fc94f2aa836b4eaf7/graphon-0.3.1.tar.gz", hash = "sha256:49971baed1eb16c8e1983f755e659902e4f117a68dc62fad19e91472950b937d", size = 242210, upload-time = "2026-05-07T06:58:21.879Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/f7/81ee8f0368aa6a2d47f97fecc5d4a12865c987906798cbddd0e3b8387f33/graphon-0.3.0-py3-none-any.whl", hash = "sha256:9cca45ebab2a79fd4d04432f55b5b962e9e4f34fa037cc20fee7f18ec80eaa5d", size = 348486, upload-time = "2026-04-21T15:18:46.737Z" },
{ url = "https://files.pythonhosted.org/packages/62/37/bef16ed3d6da7446b36769fa388f4dc79f95337ffa16d6dfc3177152507e/graphon-0.3.1-py3-none-any.whl", hash = "sha256:e6422c7e3f1ce7d2185979c17e08201816ca25d46d400ebdd035c95d501c04fe", size = 349368, upload-time = "2026-05-07T06:58:20.217Z" },
]
[[package]]