mirror of
https://github.com/langgenius/dify.git
synced 2026-10-04 13:47:55 +08:00
refactor(web): dep-inject payloads with @model_validate (#41501)
This commit is contained in:
1 parent
bf64390d7c
commit
8894b09c3b
6 files changed
+27
-40
No files matched your search
@@ -6,6 +6,7 @@ from werkzeug.exceptions import InternalServerError
|
||||
|
||||
import services
|
||||
from controllers.common.controller_schemas import TextToAudioPayload as TextToAudioPayloadBase
|
||||
from controllers.console.wraps import model_validate
|
||||
from controllers.web import web_ns
|
||||
from controllers.web.error import (
|
||||
AppUnavailableError,
|
||||
@@ -131,11 +132,10 @@ class TextApi(WebApiResource):
|
||||
)
|
||||
# response-contract:ignore provider audio bytes; TODO: model binary audio response if shape is standardized.
|
||||
@web_ns.response(200, "Success")
|
||||
def post(self, app_model: App, end_user: EndUser):
|
||||
@model_validate(TextToAudioPayload)
|
||||
def post(self, payload: TextToAudioPayload, app_model: App, end_user: EndUser):
|
||||
"""Convert text to audio"""
|
||||
try:
|
||||
payload = TextToAudioPayload.model_validate(web_ns.payload or {})
|
||||
|
||||
message_id = payload.message_id
|
||||
text = payload.text
|
||||
voice = payload.voice
|
||||
|
||||
@@ -8,6 +8,7 @@ from werkzeug.exceptions import NotFound
|
||||
|
||||
from controllers.common.controller_schemas import ConversationRenamePayload
|
||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||
from controllers.console.wraps import model_validate
|
||||
from controllers.web import web_ns
|
||||
from controllers.web.error import NotChatAppError
|
||||
from controllers.web.wraps import WebApiResource
|
||||
@@ -153,15 +154,14 @@ class ConversationRenameApi(WebApiResource):
|
||||
)
|
||||
@web_ns.response(200, "Conversation renamed successfully", web_ns.models[SimpleConversation.__name__])
|
||||
@web_ns.expect(web_ns.models[ConversationRenamePayload.__name__])
|
||||
def post(self, app_model: App, end_user: EndUser, c_id: UUID):
|
||||
@model_validate(ConversationRenamePayload)
|
||||
def post(self, payload: ConversationRenamePayload, app_model: App, end_user: EndUser, c_id: UUID):
|
||||
app_mode = AppMode.value_of(app_model.mode)
|
||||
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT}:
|
||||
raise NotChatAppError()
|
||||
|
||||
conversation_id = str(c_id)
|
||||
|
||||
payload = ConversationRenamePayload.model_validate(web_ns.payload or {})
|
||||
|
||||
try:
|
||||
session = db.session()
|
||||
conversation = ConversationService.rename(
|
||||
|
||||
@@ -9,6 +9,7 @@ from controllers.common.errors import (
|
||||
RemoteFileUploadError,
|
||||
UnsupportedFileTypeError,
|
||||
)
|
||||
from controllers.console.wraps import model_validate
|
||||
from core.file import remote_fetcher
|
||||
from extensions.ext_database import db
|
||||
from fields.file_fields import FileWithSignedUrl, RemoteFileInfo
|
||||
@@ -86,7 +87,8 @@ class RemoteFileUploadApi(WebApiResource):
|
||||
)
|
||||
@web_ns.response(201, "Remote file uploaded", web_ns.models[FileWithSignedUrl.__name__])
|
||||
@web_ns.expect(web_ns.models[RemoteFileUploadPayload.__name__])
|
||||
def post(self, app_model: App, end_user: EndUser):
|
||||
@model_validate(RemoteFileUploadPayload)
|
||||
def post(self, payload: RemoteFileUploadPayload, app_model: App, end_user: EndUser):
|
||||
"""Upload a file from a remote URL.
|
||||
|
||||
Downloads a file from the provided remote URL and uploads it
|
||||
@@ -108,7 +110,6 @@ class RemoteFileUploadApi(WebApiResource):
|
||||
FileTooLargeError: File exceeds size limit
|
||||
UnsupportedFileTypeError: File type not supported
|
||||
"""
|
||||
payload = RemoteFileUploadPayload.model_validate(web_ns.payload or {})
|
||||
url = str(payload.url)
|
||||
|
||||
try:
|
||||
|
||||
@@ -102,10 +102,8 @@ class TestConversationRenameApi:
|
||||
ConversationRenameApi().post(_completion_app(), _end_user(), uuid4())
|
||||
|
||||
@patch("controllers.web.conversation.ConversationService.rename")
|
||||
@patch("controllers.web.conversation.web_ns")
|
||||
def test_rename_success(self, mock_ns: MagicMock, mock_rename: MagicMock, app: Flask) -> None:
|
||||
def test_rename_success(self, mock_rename: MagicMock, app: Flask) -> None:
|
||||
c_id = uuid4()
|
||||
mock_ns.payload = {"name": "New Name", "auto_generate": False}
|
||||
conv = SimpleNamespace(
|
||||
id=str(c_id),
|
||||
name="New Name",
|
||||
@@ -126,10 +124,8 @@ class TestConversationRenameApi:
|
||||
"controllers.web.conversation.ConversationService.rename",
|
||||
side_effect=ConversationNotExistsError(),
|
||||
)
|
||||
@patch("controllers.web.conversation.web_ns")
|
||||
def test_rename_not_found(self, mock_ns: MagicMock, mock_rename: MagicMock, app: Flask) -> None:
|
||||
def test_rename_not_found(self, mock_rename: MagicMock, app: Flask) -> None:
|
||||
c_id = uuid4()
|
||||
mock_ns.payload = {"name": "X", "auto_generate": False}
|
||||
|
||||
with app.test_request_context(f"/conversations/{c_id}/name", method="POST", json={"name": "X"}):
|
||||
with pytest.raises(NotFound, match="Conversation Not Exists"):
|
||||
|
||||
@@ -146,24 +146,21 @@ class TestAudioApi:
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestTextApi:
|
||||
@patch("controllers.web.audio.AudioService.transcript_tts", return_value="audio-bytes")
|
||||
@patch("controllers.web.audio.web_ns")
|
||||
def test_happy_path(self, mock_ns: MagicMock, mock_tts: MagicMock, app: Flask) -> None:
|
||||
mock_ns.payload = {"text": "hello", "voice": "alloy"}
|
||||
|
||||
with app.test_request_context("/text-to-audio", method="POST"):
|
||||
def test_happy_path(self, mock_tts: MagicMock, app: Flask) -> None:
|
||||
with app.test_request_context("/text-to-audio", method="POST", json={"text": "hello", "voice": "alloy"}):
|
||||
result = TextApi().post(_app_model(), _end_user())
|
||||
|
||||
assert result == "audio-bytes"
|
||||
mock_tts.assert_called_once()
|
||||
|
||||
@patch("controllers.web.audio.AudioService.transcript_tts", return_value="audio-bytes")
|
||||
@patch("controllers.web.audio.web_ns")
|
||||
def test_happy_path_with_message_ref(self, mock_ns: MagicMock, mock_tts: MagicMock, app: Flask) -> None:
|
||||
def test_happy_path_with_message_ref(self, mock_tts: MagicMock, app: Flask) -> None:
|
||||
message_id = "550e8400-e29b-41d4-a716-446655440000"
|
||||
mock_ns.payload = {"text": "hello", "message_id": message_id}
|
||||
app_model = _app_model()
|
||||
|
||||
with app.test_request_context("/text-to-audio", method="POST"):
|
||||
with app.test_request_context(
|
||||
"/text-to-audio", method="POST", json={"text": "hello", "message_id": message_id}
|
||||
):
|
||||
result = TextApi().post(app_model, _end_user())
|
||||
|
||||
assert result == "audio-bytes"
|
||||
@@ -177,10 +174,7 @@ class TestTextApi:
|
||||
"controllers.web.audio.AudioService.transcript_tts",
|
||||
side_effect=InvokeError(description="invoke failed"),
|
||||
)
|
||||
@patch("controllers.web.audio.web_ns")
|
||||
def test_invoke_error_mapped(self, mock_ns: MagicMock, mock_tts: MagicMock, app: Flask) -> None:
|
||||
mock_ns.payload = {"text": "hello"}
|
||||
|
||||
with app.test_request_context("/text-to-audio", method="POST"):
|
||||
def test_invoke_error_mapped(self, mock_tts: MagicMock, app: Flask) -> None:
|
||||
with app.test_request_context("/text-to-audio", method="POST", json={"text": "hello"}):
|
||||
with pytest.raises(CompletionRequestError):
|
||||
TextApi().post(_app_model(), _end_user())
|
||||
@@ -134,12 +134,10 @@ class TestRemoteFileUploadApi:
|
||||
@patch("controllers.web.remote_files.FileService")
|
||||
@patch("controllers.web.remote_files.helpers.guess_file_info_from_response")
|
||||
@patch("controllers.web.remote_files.remote_fetcher")
|
||||
@patch("controllers.web.remote_files.web_ns")
|
||||
@patch("controllers.web.remote_files.db")
|
||||
def test_upload_success(
|
||||
self,
|
||||
mock_db: MagicMock,
|
||||
mock_ns: MagicMock,
|
||||
mock_proxy: MagicMock,
|
||||
mock_guess: MagicMock,
|
||||
mock_file_svc_cls: MagicMock,
|
||||
@@ -148,7 +146,6 @@ class TestRemoteFileUploadApi:
|
||||
sqlite_engine: Engine,
|
||||
) -> None:
|
||||
mock_db.engine = sqlite_engine
|
||||
mock_ns.payload = {"url": "https://example.com/file.pdf"}
|
||||
head_resp = MagicMock()
|
||||
head_resp.status_code = 200
|
||||
head_resp.content = b"pdf-content"
|
||||
@@ -164,7 +161,9 @@ class TestRemoteFileUploadApi:
|
||||
|
||||
mock_file_svc_cls.return_value.upload_file.return_value = _upload_file()
|
||||
|
||||
with app.test_request_context("/remote-files/upload", method="POST"):
|
||||
with app.test_request_context(
|
||||
"/remote-files/upload", method="POST", json={"url": "https://example.com/file.pdf"}
|
||||
):
|
||||
result, status = RemoteFileUploadApi().post(_app_model(), _end_user())
|
||||
|
||||
assert status == 201
|
||||
@@ -173,16 +172,13 @@ class TestRemoteFileUploadApi:
|
||||
@patch("controllers.web.remote_files.FileService.is_file_size_within_limit", return_value=False)
|
||||
@patch("controllers.web.remote_files.helpers.guess_file_info_from_response")
|
||||
@patch("controllers.web.remote_files.remote_fetcher")
|
||||
@patch("controllers.web.remote_files.web_ns")
|
||||
def test_file_too_large(
|
||||
self,
|
||||
mock_ns: MagicMock,
|
||||
mock_proxy: MagicMock,
|
||||
mock_guess: MagicMock,
|
||||
mock_size_check: MagicMock,
|
||||
app: Flask,
|
||||
) -> None:
|
||||
mock_ns.payload = {"url": "https://example.com/big.zip"}
|
||||
head_resp = MagicMock()
|
||||
head_resp.status_code = 200
|
||||
mock_proxy.make_request.return_value = head_resp
|
||||
@@ -190,18 +186,18 @@ class TestRemoteFileUploadApi:
|
||||
filename="big.zip", extension="zip", mimetype="application/zip", size=999999999
|
||||
)
|
||||
|
||||
with app.test_request_context("/remote-files/upload", method="POST"):
|
||||
with app.test_request_context(
|
||||
"/remote-files/upload", method="POST", json={"url": "https://example.com/big.zip"}
|
||||
):
|
||||
with pytest.raises(FileTooLargeError):
|
||||
RemoteFileUploadApi().post(_app_model(), _end_user())
|
||||
|
||||
@patch("controllers.web.remote_files.remote_fetcher")
|
||||
@patch("controllers.web.remote_files.web_ns")
|
||||
def test_fetch_failure_raises(self, mock_ns: MagicMock, mock_proxy: MagicMock, app: Flask) -> None:
|
||||
def test_fetch_failure_raises(self, mock_proxy: MagicMock, app: Flask) -> None:
|
||||
import httpx
|
||||
|
||||
mock_ns.payload = {"url": "https://example.com/bad"}
|
||||
mock_proxy.make_request.side_effect = httpx.RequestError("connection failed")
|
||||
|
||||
with app.test_request_context("/remote-files/upload", method="POST"):
|
||||
with app.test_request_context("/remote-files/upload", method="POST", json={"url": "https://example.com/bad"}):
|
||||
with pytest.raises(RemoteFileUploadError):
|
||||
RemoteFileUploadApi().post(_app_model(), _end_user())
|
||||
Reference in new issue
Block a user