mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 02:43:49 +08:00
Merge branch 'main' into jzh
This commit is contained in:
commit
2b1496c857
@ -1568,12 +1568,14 @@ class WorkflowDraftVariable(Base):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Relationship to WorkflowDraftVariableFile
|
# WorkflowDraftVariableFile uses TypeBase while WorkflowDraftVariable uses Base, so the relationship
|
||||||
|
# must resolve the class object lazily instead of relying on string lookup across registries.
|
||||||
variable_file: Mapped[Optional["WorkflowDraftVariableFile"]] = orm.relationship(
|
variable_file: Mapped[Optional["WorkflowDraftVariableFile"]] = orm.relationship(
|
||||||
|
lambda: WorkflowDraftVariableFile,
|
||||||
foreign_keys=[file_id],
|
foreign_keys=[file_id],
|
||||||
lazy="raise",
|
lazy="raise",
|
||||||
uselist=False,
|
uselist=False,
|
||||||
primaryjoin="WorkflowDraftVariableFile.id == WorkflowDraftVariable.file_id",
|
primaryjoin=lambda: orm.foreign(WorkflowDraftVariable.file_id) == WorkflowDraftVariableFile.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Cache for deserialized value
|
# Cache for deserialized value
|
||||||
@ -1892,7 +1894,7 @@ class WorkflowDraftVariable(Base):
|
|||||||
return self.last_edited_at is not None
|
return self.last_edited_at is not None
|
||||||
|
|
||||||
|
|
||||||
class WorkflowDraftVariableFile(Base):
|
class WorkflowDraftVariableFile(TypeBase):
|
||||||
"""Stores metadata about files associated with large workflow draft variables.
|
"""Stores metadata about files associated with large workflow draft variables.
|
||||||
|
|
||||||
This model acts as an intermediary between WorkflowDraftVariable and UploadFile,
|
This model acts as an intermediary between WorkflowDraftVariable and UploadFile,
|
||||||
@ -1906,18 +1908,7 @@ class WorkflowDraftVariableFile(Base):
|
|||||||
__tablename__ = "workflow_draft_variable_files"
|
__tablename__ = "workflow_draft_variable_files"
|
||||||
|
|
||||||
# Primary key
|
# Primary key
|
||||||
id: Mapped[str] = mapped_column(
|
id: Mapped[str] = mapped_column(StringUUID, primary_key=True, default_factory=lambda: str(uuidv7()), init=False)
|
||||||
StringUUID,
|
|
||||||
primary_key=True,
|
|
||||||
default=lambda: str(uuidv7()),
|
|
||||||
)
|
|
||||||
|
|
||||||
created_at: Mapped[datetime] = mapped_column(
|
|
||||||
DateTime,
|
|
||||||
nullable=False,
|
|
||||||
default=naive_utc_now,
|
|
||||||
server_default=func.current_timestamp(),
|
|
||||||
)
|
|
||||||
|
|
||||||
tenant_id: Mapped[str] = mapped_column(
|
tenant_id: Mapped[str] = mapped_column(
|
||||||
StringUUID,
|
StringUUID,
|
||||||
@ -1969,15 +1960,23 @@ class WorkflowDraftVariableFile(Base):
|
|||||||
nullable=False,
|
nullable=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Relationship to UploadFile
|
# Rows are created with `upload_file_id`; callers should load this relationship explicitly when needed.
|
||||||
upload_file: Mapped["UploadFile"] = orm.relationship(
|
upload_file: Mapped["UploadFile"] = orm.relationship(
|
||||||
UploadFile,
|
UploadFile,
|
||||||
foreign_keys=[upload_file_id],
|
foreign_keys=[upload_file_id],
|
||||||
lazy="raise",
|
lazy="raise",
|
||||||
|
init=False,
|
||||||
uselist=False,
|
uselist=False,
|
||||||
primaryjoin=lambda: orm.foreign(WorkflowDraftVariableFile.upload_file_id) == UploadFile.id,
|
primaryjoin=lambda: orm.foreign(WorkflowDraftVariableFile.upload_file_id) == UploadFile.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
created_at: Mapped[datetime] = mapped_column(
|
||||||
|
DateTime,
|
||||||
|
nullable=False,
|
||||||
|
default_factory=naive_utc_now,
|
||||||
|
server_default=func.current_timestamp(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def is_system_variable_editable(name: str) -> bool:
|
def is_system_variable_editable(name: str) -> bool:
|
||||||
return name in _EDITABLE_SYSTEM_VARIABLE
|
return name in _EDITABLE_SYSTEM_VARIABLE
|
||||||
|
|||||||
@ -1083,10 +1083,9 @@ class DraftVariableSaver:
|
|||||||
mimetype=content_type,
|
mimetype=content_type,
|
||||||
user=self._user,
|
user=self._user,
|
||||||
)
|
)
|
||||||
|
assert self._user.current_tenant_id
|
||||||
# Create WorkflowDraftVariableFile record
|
# Create WorkflowDraftVariableFile record
|
||||||
variable_file = WorkflowDraftVariableFile(
|
variable_file = WorkflowDraftVariableFile(
|
||||||
id=uuidv7(),
|
|
||||||
upload_file_id=upload_file.id,
|
upload_file_id=upload_file.id,
|
||||||
size=original_size,
|
size=original_size,
|
||||||
length=original_length,
|
length=original_length,
|
||||||
@ -1095,6 +1094,7 @@ class DraftVariableSaver:
|
|||||||
tenant_id=self._user.current_tenant_id,
|
tenant_id=self._user.current_tenant_id,
|
||||||
user_id=self._user.id,
|
user_id=self._user.id,
|
||||||
)
|
)
|
||||||
|
variable_file.id = str(uuidv7())
|
||||||
engine = bind = self._session.get_bind()
|
engine = bind = self._session.get_bind()
|
||||||
assert isinstance(engine, Engine)
|
assert isinstance(engine, Engine)
|
||||||
with sessionmaker(bind=engine, expire_on_commit=False).begin() as session:
|
with sessionmaker(bind=engine, expire_on_commit=False).begin() as session:
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import Any, NamedTuple
|
from typing import Any, NamedTuple
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from flask_restx import marshal
|
from flask_restx import marshal
|
||||||
@ -29,15 +29,18 @@ class TestWorkflowDraftVariableFields:
|
|||||||
def test_serialize_full_content(self):
|
def test_serialize_full_content(self):
|
||||||
"""Test that _serialize_full_content uses pre-loaded relationships."""
|
"""Test that _serialize_full_content uses pre-loaded relationships."""
|
||||||
# Create mock objects with relationships pre-loaded
|
# Create mock objects with relationships pre-loaded
|
||||||
mock_variable_file = MagicMock(spec=WorkflowDraftVariableFile)
|
mock_variable = WorkflowDraftVariable(
|
||||||
mock_variable_file.size = 100000
|
file_id="test-file-id",
|
||||||
mock_variable_file.length = 50
|
variable_file=WorkflowDraftVariableFile(
|
||||||
mock_variable_file.value_type = SegmentType.OBJECT
|
size=100000,
|
||||||
mock_variable_file.upload_file_id = "test-upload-file-id"
|
length=50,
|
||||||
|
value_type=SegmentType.OBJECT,
|
||||||
mock_variable = MagicMock(spec=WorkflowDraftVariable)
|
upload_file_id="test-upload-file-id",
|
||||||
mock_variable.file_id = "test-file-id"
|
tenant_id=str(uuid.uuid4()),
|
||||||
mock_variable.variable_file = mock_variable_file
|
app_id=str(uuid.uuid4()),
|
||||||
|
user_id=str(uuid.uuid4()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Mock the file helpers
|
# Mock the file helpers
|
||||||
with patch("controllers.console.app.workflow_draft_variable.file_helpers", autospec=True) as mock_file_helpers:
|
with patch("controllers.console.app.workflow_draft_variable.file_helpers", autospec=True) as mock_file_helpers:
|
||||||
@ -84,7 +87,7 @@ class TestWorkflowDraftVariableFields:
|
|||||||
|
|
||||||
expected_without_value: OrderedDict[str, Any] = OrderedDict(
|
expected_without_value: OrderedDict[str, Any] = OrderedDict(
|
||||||
{
|
{
|
||||||
"id": str(conv_var.id),
|
"id": conv_var.id,
|
||||||
"type": conv_var.get_variable_type().value,
|
"type": conv_var.get_variable_type().value,
|
||||||
"name": "conv_var",
|
"name": "conv_var",
|
||||||
"description": "",
|
"description": "",
|
||||||
@ -117,7 +120,7 @@ class TestWorkflowDraftVariableFields:
|
|||||||
|
|
||||||
expected_without_value = OrderedDict(
|
expected_without_value = OrderedDict(
|
||||||
{
|
{
|
||||||
"id": str(sys_var.id),
|
"id": sys_var.id,
|
||||||
"type": sys_var.get_variable_type().value,
|
"type": sys_var.get_variable_type().value,
|
||||||
"name": "sys_var",
|
"name": "sys_var",
|
||||||
"description": "",
|
"description": "",
|
||||||
@ -149,7 +152,7 @@ class TestWorkflowDraftVariableFields:
|
|||||||
|
|
||||||
expected_without_value: OrderedDict[str, Any] = OrderedDict(
|
expected_without_value: OrderedDict[str, Any] = OrderedDict(
|
||||||
{
|
{
|
||||||
"id": str(node_var.id),
|
"id": node_var.id,
|
||||||
"type": node_var.get_variable_type().value,
|
"type": node_var.get_variable_type().value,
|
||||||
"name": "node_var",
|
"name": "node_var",
|
||||||
"description": "",
|
"description": "",
|
||||||
@ -180,19 +183,22 @@ class TestWorkflowDraftVariableFields:
|
|||||||
node_var.id = str(uuid.uuid4())
|
node_var.id = str(uuid.uuid4())
|
||||||
node_var.last_edited_at = naive_utc_now()
|
node_var.last_edited_at = naive_utc_now()
|
||||||
variable_file = WorkflowDraftVariableFile(
|
variable_file = WorkflowDraftVariableFile(
|
||||||
id=str(uuidv7()),
|
|
||||||
upload_file_id=str(uuid.uuid4()),
|
upload_file_id=str(uuid.uuid4()),
|
||||||
size=1024,
|
size=1024,
|
||||||
length=10,
|
length=10,
|
||||||
value_type=SegmentType.ARRAY_STRING,
|
value_type=SegmentType.ARRAY_STRING,
|
||||||
|
tenant_id=str(uuidv7()),
|
||||||
|
app_id=str(uuidv7()),
|
||||||
|
user_id=str(uuidv7()),
|
||||||
)
|
)
|
||||||
|
variable_file.id = str(uuidv7())
|
||||||
node_var.variable_file = variable_file
|
node_var.variable_file = variable_file
|
||||||
node_var.file_id = variable_file.id
|
node_var.file_id = variable_file.id
|
||||||
|
|
||||||
expected_without_value: OrderedDict[str, Any] = OrderedDict(
|
expected_without_value: OrderedDict[str, Any] = OrderedDict(
|
||||||
{
|
{
|
||||||
"id": str(node_var.id),
|
"id": node_var.id,
|
||||||
"type": node_var.get_variable_type().value,
|
"type": node_var.get_variable_type(),
|
||||||
"name": "node_var",
|
"name": "node_var",
|
||||||
"description": "",
|
"description": "",
|
||||||
"selector": ["test_node", "node_var"],
|
"selector": ["test_node", "node_var"],
|
||||||
@ -235,7 +241,7 @@ class TestWorkflowDraftVariableList:
|
|||||||
node_var.id = str(uuid.uuid4())
|
node_var.id = str(uuid.uuid4())
|
||||||
node_var_dict = OrderedDict(
|
node_var_dict = OrderedDict(
|
||||||
{
|
{
|
||||||
"id": str(node_var.id),
|
"id": node_var.id,
|
||||||
"type": node_var.get_variable_type().value,
|
"type": node_var.get_variable_type().value,
|
||||||
"name": "test_var",
|
"name": "test_var",
|
||||||
"description": "",
|
"description": "",
|
||||||
|
|||||||
@ -33,42 +33,6 @@ class TestDraftVarLoaderSimple:
|
|||||||
fallback_variables=[],
|
fallback_variables=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_load_offloaded_variable_string_type_unit(self, draft_var_loader):
|
|
||||||
"""Test _load_offloaded_variable with string type - isolated unit test."""
|
|
||||||
# Create mock objects
|
|
||||||
upload_file = Mock(spec=UploadFile)
|
|
||||||
upload_file.key = "storage/key/test.txt"
|
|
||||||
|
|
||||||
variable_file = Mock(spec=WorkflowDraftVariableFile)
|
|
||||||
variable_file.value_type = SegmentType.STRING
|
|
||||||
variable_file.upload_file = upload_file
|
|
||||||
|
|
||||||
draft_var = Mock(spec=WorkflowDraftVariable)
|
|
||||||
draft_var.id = "draft-var-id"
|
|
||||||
draft_var.node_id = "test-node-id"
|
|
||||||
draft_var.name = "test_variable"
|
|
||||||
draft_var.description = "test description"
|
|
||||||
draft_var.get_selector.return_value = ["test-node-id", "test_variable"]
|
|
||||||
draft_var.variable_file = variable_file
|
|
||||||
|
|
||||||
test_content = "This is the full string content"
|
|
||||||
|
|
||||||
with patch("services.workflow_draft_variable_service.storage") as mock_storage:
|
|
||||||
mock_storage.load.return_value = test_content.encode()
|
|
||||||
|
|
||||||
# Execute the method
|
|
||||||
selector_tuple, variable = draft_var_loader._load_offloaded_variable(draft_var)
|
|
||||||
|
|
||||||
# Verify results
|
|
||||||
assert selector_tuple == ("test-node-id", "test_variable")
|
|
||||||
assert variable.id == "draft-var-id"
|
|
||||||
assert variable.name == "test_variable"
|
|
||||||
assert variable.description == "test description"
|
|
||||||
assert variable.value == test_content
|
|
||||||
|
|
||||||
# Verify storage was called correctly
|
|
||||||
mock_storage.load.assert_called_once_with("storage/key/test.txt")
|
|
||||||
|
|
||||||
def test_load_offloaded_variable_object_type_unit(self, draft_var_loader):
|
def test_load_offloaded_variable_object_type_unit(self, draft_var_loader):
|
||||||
"""Test _load_offloaded_variable with object type - isolated unit test."""
|
"""Test _load_offloaded_variable with object type - isolated unit test."""
|
||||||
# Create mock objects
|
# Create mock objects
|
||||||
@ -139,47 +103,6 @@ class TestDraftVarLoaderSimple:
|
|||||||
result = draft_var_loader._selector_to_tuple(selector)
|
result = draft_var_loader._selector_to_tuple(selector)
|
||||||
assert result == ("node_id", "var_name")
|
assert result == ("node_id", "var_name")
|
||||||
|
|
||||||
def test_load_offloaded_variable_number_type_unit(self, draft_var_loader):
|
|
||||||
"""Test _load_offloaded_variable with number type - isolated unit test."""
|
|
||||||
# Create mock objects
|
|
||||||
upload_file = Mock(spec=UploadFile)
|
|
||||||
upload_file.key = "storage/key/test_number.json"
|
|
||||||
|
|
||||||
variable_file = Mock(spec=WorkflowDraftVariableFile)
|
|
||||||
variable_file.value_type = SegmentType.NUMBER
|
|
||||||
variable_file.upload_file = upload_file
|
|
||||||
|
|
||||||
draft_var = Mock(spec=WorkflowDraftVariable)
|
|
||||||
draft_var.id = "draft-var-id"
|
|
||||||
draft_var.node_id = "test-node-id"
|
|
||||||
draft_var.name = "test_number"
|
|
||||||
draft_var.description = "test number description"
|
|
||||||
draft_var.get_selector.return_value = ["test-node-id", "test_number"]
|
|
||||||
draft_var.variable_file = variable_file
|
|
||||||
|
|
||||||
test_number = 123.45
|
|
||||||
test_json_content = json.dumps(test_number)
|
|
||||||
|
|
||||||
with patch("services.workflow_draft_variable_service.storage") as mock_storage:
|
|
||||||
mock_storage.load.return_value = test_json_content.encode()
|
|
||||||
from graphon.variables.segments import FloatSegment
|
|
||||||
|
|
||||||
mock_segment = FloatSegment(value=test_number)
|
|
||||||
draft_var.build_segment_from_serialized_value.return_value = mock_segment
|
|
||||||
|
|
||||||
# Execute the method
|
|
||||||
selector_tuple, variable = draft_var_loader._load_offloaded_variable(draft_var)
|
|
||||||
|
|
||||||
# Verify results
|
|
||||||
assert selector_tuple == ("test-node-id", "test_number")
|
|
||||||
assert variable.id == "draft-var-id"
|
|
||||||
assert variable.name == "test_number"
|
|
||||||
assert variable.description == "test number description"
|
|
||||||
|
|
||||||
# Verify method calls
|
|
||||||
mock_storage.load.assert_called_once_with("storage/key/test_number.json")
|
|
||||||
draft_var.build_segment_from_serialized_value.assert_called_once_with(SegmentType.NUMBER, test_number)
|
|
||||||
|
|
||||||
def test_load_offloaded_variable_array_type_unit(self, draft_var_loader):
|
def test_load_offloaded_variable_array_type_unit(self, draft_var_loader):
|
||||||
"""Test _load_offloaded_variable with array type - isolated unit test."""
|
"""Test _load_offloaded_variable with array type - isolated unit test."""
|
||||||
# Create mock objects
|
# Create mock objects
|
||||||
@ -229,12 +152,13 @@ class TestDraftVarLoaderSimple:
|
|||||||
variable_file.value_type = SegmentType.FILE
|
variable_file.value_type = SegmentType.FILE
|
||||||
variable_file.upload_file = upload_file
|
variable_file.upload_file = upload_file
|
||||||
|
|
||||||
draft_var = WorkflowDraftVariable()
|
draft_var = WorkflowDraftVariable(
|
||||||
draft_var.id = "draft-var-id"
|
id="draft-var-id",
|
||||||
draft_var.app_id = "app-1"
|
app_id="app-1",
|
||||||
draft_var.node_id = "test-node-id"
|
node_id="test-node-id",
|
||||||
draft_var.name = "test_file"
|
name="test_file",
|
||||||
draft_var.description = "test file description"
|
description="test file description",
|
||||||
|
)
|
||||||
draft_var._set_selector(["test-node-id", "test_file"])
|
draft_var._set_selector(["test-node-id", "test_file"])
|
||||||
draft_var.variable_file = variable_file
|
draft_var.variable_file = variable_file
|
||||||
|
|
||||||
|
|||||||
@ -200,7 +200,7 @@ class TestDraftVariableSaver:
|
|||||||
user=mock_user,
|
user=mock_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_draft_saver_with_small_variables(self, draft_saver, mock_session):
|
def test_draft_saver_with_small_variables(self, draft_saver: DraftVariableSaver, mock_session):
|
||||||
with patch(
|
with patch(
|
||||||
"services.workflow_draft_variable_service.DraftVariableSaver._try_offload_large_variable", autospec=True
|
"services.workflow_draft_variable_service.DraftVariableSaver._try_offload_large_variable", autospec=True
|
||||||
) as _mock_try_offload:
|
) as _mock_try_offload:
|
||||||
@ -212,18 +212,21 @@ class TestDraftVariableSaver:
|
|||||||
assert draft_var.file_id is None
|
assert draft_var.file_id is None
|
||||||
_mock_try_offload.return_value = None
|
_mock_try_offload.return_value = None
|
||||||
|
|
||||||
def test_draft_saver_with_large_variables(self, draft_saver, mock_session):
|
def test_draft_saver_with_large_variables(self, draft_saver: DraftVariableSaver, mock_session):
|
||||||
with patch(
|
with patch(
|
||||||
"services.workflow_draft_variable_service.DraftVariableSaver._try_offload_large_variable", autospec=True
|
"services.workflow_draft_variable_service.DraftVariableSaver._try_offload_large_variable", autospec=True
|
||||||
) as _mock_try_offload:
|
) as _mock_try_offload:
|
||||||
mock_segment = StringSegment(value="small value")
|
mock_segment = StringSegment(value="small value")
|
||||||
mock_draft_var_file = WorkflowDraftVariableFile(
|
mock_draft_var_file = WorkflowDraftVariableFile(
|
||||||
id=str(uuidv7()),
|
tenant_id=str(uuidv7()),
|
||||||
|
app_id=str(uuidv7()),
|
||||||
|
user_id=str(uuidv7()),
|
||||||
size=1024,
|
size=1024,
|
||||||
length=10,
|
length=10,
|
||||||
value_type=SegmentType.ARRAY_STRING,
|
value_type=SegmentType.ARRAY_STRING,
|
||||||
upload_file_id=str(uuid.uuid4()),
|
upload_file_id=str(uuidv7()),
|
||||||
)
|
)
|
||||||
|
mock_draft_var_file.id = str(uuidv7())
|
||||||
|
|
||||||
_mock_try_offload.return_value = mock_segment, mock_draft_var_file
|
_mock_try_offload.return_value = mock_segment, mock_draft_var_file
|
||||||
draft_var = draft_saver._create_draft_variable(name="small_var", value=mock_segment, visible=True)
|
draft_var = draft_saver._create_draft_variable(name="small_var", value=mock_segment, visible=True)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user