From 0ba66ab15524684d47ee480065ec8e73cfb7a1bb Mon Sep 17 00:00:00 2001 From: corevibe555 <45244658+corevibe555@users.noreply.github.com> Date: Wed, 8 Apr 2026 06:10:04 +0300 Subject: [PATCH] refactor(api): deduplicate shared controller request schemas into controller_schemas.py (#34700) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/controllers/common/controller_schemas.py | 63 +++++++++++++++++++ api/controllers/console/explore/audio.py | 10 +-- .../console/explore/conversation.py | 15 +---- api/controllers/console/explore/message.py | 15 +---- .../console/explore/saved_message.py | 14 +---- api/controllers/console/explore/workflow.py | 9 +-- .../service_api/app/conversation.py | 15 +---- api/controllers/service_api/app/message.py | 14 +---- api/controllers/service_api/app/workflow.py | 7 +-- api/controllers/web/audio.py | 10 +-- api/controllers/web/conversation.py | 15 +---- api/controllers/web/message.py | 6 +- api/controllers/web/saved_message.py | 14 +---- api/controllers/web/workflow.py | 9 +-- 14 files changed, 86 insertions(+), 130 deletions(-) create mode 100644 api/controllers/common/controller_schemas.py diff --git a/api/controllers/common/controller_schemas.py b/api/controllers/common/controller_schemas.py new file mode 100644 index 0000000000..e13bf025fc --- /dev/null +++ b/api/controllers/common/controller_schemas.py @@ -0,0 +1,63 @@ +from typing import Any, Literal + +from pydantic import BaseModel, Field, model_validator + +from libs.helper import UUIDStrOrEmpty + +# --- Conversation schemas --- + + +class ConversationRenamePayload(BaseModel): + name: str | None = None + auto_generate: bool = False + + @model_validator(mode="after") + def validate_name_requirement(self): + if not self.auto_generate: + if self.name is None or not self.name.strip(): + raise ValueError("name is required when auto_generate is false") + return self + + +# --- Message schemas --- + + +class MessageListQuery(BaseModel): + conversation_id: UUIDStrOrEmpty + first_id: UUIDStrOrEmpty | None = None + limit: int = Field(default=20, ge=1, le=100) + + +class MessageFeedbackPayload(BaseModel): + rating: Literal["like", "dislike"] | None = None + content: str | None = None + + +# --- Saved message schemas --- + + +class SavedMessageListQuery(BaseModel): + last_id: UUIDStrOrEmpty | None = None + limit: int = Field(default=20, ge=1, le=100) + + +class SavedMessageCreatePayload(BaseModel): + message_id: UUIDStrOrEmpty + + +# --- Workflow schemas --- + + +class WorkflowRunPayload(BaseModel): + inputs: dict[str, Any] + files: list[dict[str, Any]] | None = None + + +# --- Audio schemas --- + + +class TextToAudioPayload(BaseModel): + message_id: str | None = None + voice: str | None = None + text: str | None = None + streaming: bool | None = None diff --git a/api/controllers/console/explore/audio.py b/api/controllers/console/explore/audio.py index b1b01b5f51..a37077af42 100644 --- a/api/controllers/console/explore/audio.py +++ b/api/controllers/console/explore/audio.py @@ -2,10 +2,10 @@ import logging from flask import request from graphon.model_runtime.errors.invoke import InvokeError -from pydantic import BaseModel, Field from werkzeug.exceptions import InternalServerError import services +from controllers.common.controller_schemas import TextToAudioPayload from controllers.common.schema import register_schema_model from controllers.console.app.error import ( AppUnavailableError, @@ -32,14 +32,6 @@ from .. import console_ns logger = logging.getLogger(__name__) - -class TextToAudioPayload(BaseModel): - message_id: str | None = None - voice: str | None = None - text: str | None = None - streaming: bool | None = Field(default=None, description="Enable streaming response") - - register_schema_model(console_ns, TextToAudioPayload) diff --git a/api/controllers/console/explore/conversation.py b/api/controllers/console/explore/conversation.py index 092f509f1c..2eb2054e64 100644 --- a/api/controllers/console/explore/conversation.py +++ b/api/controllers/console/explore/conversation.py @@ -1,10 +1,11 @@ from typing import Any from flask import request -from pydantic import BaseModel, Field, TypeAdapter, model_validator +from pydantic import BaseModel, Field, TypeAdapter from sqlalchemy.orm import sessionmaker from werkzeug.exceptions import NotFound +from controllers.common.controller_schemas import ConversationRenamePayload from controllers.common.schema import register_schema_models from controllers.console.explore.error import NotChatAppError from controllers.console.explore.wraps import InstalledAppResource @@ -32,18 +33,6 @@ class ConversationListQuery(BaseModel): pinned: bool | None = None -class ConversationRenamePayload(BaseModel): - name: str | None = None - auto_generate: bool = False - - @model_validator(mode="after") - def validate_name_requirement(self): - if not self.auto_generate: - if self.name is None or not self.name.strip(): - raise ValueError("name is required when auto_generate is false") - return self - - register_schema_models(console_ns, ConversationListQuery, ConversationRenamePayload) diff --git a/api/controllers/console/explore/message.py b/api/controllers/console/explore/message.py index fcbefcda33..64d55d7ca3 100644 --- a/api/controllers/console/explore/message.py +++ b/api/controllers/console/explore/message.py @@ -3,9 +3,10 @@ from typing import Literal from flask import request from graphon.model_runtime.errors.invoke import InvokeError -from pydantic import BaseModel, Field, TypeAdapter +from pydantic import BaseModel, TypeAdapter from werkzeug.exceptions import InternalServerError, NotFound +from controllers.common.controller_schemas import MessageFeedbackPayload, MessageListQuery from controllers.common.schema import register_schema_models from controllers.console.app.error import ( AppMoreLikeThisDisabledError, @@ -25,7 +26,6 @@ from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotIni from fields.conversation_fields import ResultResponse from fields.message_fields import MessageInfiniteScrollPagination, MessageListItem, SuggestedQuestionsResponse from libs import helper -from libs.helper import UUIDStrOrEmpty from libs.login import current_account_with_tenant from models.enums import FeedbackRating from models.model import AppMode @@ -44,17 +44,6 @@ from .. import console_ns logger = logging.getLogger(__name__) -class MessageListQuery(BaseModel): - conversation_id: UUIDStrOrEmpty - first_id: UUIDStrOrEmpty | None = None - limit: int = Field(default=20, ge=1, le=100) - - -class MessageFeedbackPayload(BaseModel): - rating: Literal["like", "dislike"] | None = None - content: str | None = None - - class MoreLikeThisQuery(BaseModel): response_mode: Literal["blocking", "streaming"] diff --git a/api/controllers/console/explore/saved_message.py b/api/controllers/console/explore/saved_message.py index ea3de91741..9ec4e82324 100644 --- a/api/controllers/console/explore/saved_message.py +++ b/api/controllers/console/explore/saved_message.py @@ -1,28 +1,18 @@ from flask import request -from pydantic import BaseModel, Field, TypeAdapter +from pydantic import TypeAdapter from werkzeug.exceptions import NotFound +from controllers.common.controller_schemas import SavedMessageCreatePayload, SavedMessageListQuery from controllers.common.schema import register_schema_models from controllers.console import console_ns from controllers.console.explore.error import NotCompletionAppError from controllers.console.explore.wraps import InstalledAppResource from fields.conversation_fields import ResultResponse from fields.message_fields import SavedMessageInfiniteScrollPagination, SavedMessageItem -from libs.helper import UUIDStrOrEmpty from libs.login import current_account_with_tenant from services.errors.message import MessageNotExistsError from services.saved_message_service import SavedMessageService - -class SavedMessageListQuery(BaseModel): - last_id: UUIDStrOrEmpty | None = None - limit: int = Field(default=20, ge=1, le=100) - - -class SavedMessageCreatePayload(BaseModel): - message_id: UUIDStrOrEmpty - - register_schema_models(console_ns, SavedMessageListQuery, SavedMessageCreatePayload) diff --git a/api/controllers/console/explore/workflow.py b/api/controllers/console/explore/workflow.py index 42cafc7193..da88de6776 100644 --- a/api/controllers/console/explore/workflow.py +++ b/api/controllers/console/explore/workflow.py @@ -1,11 +1,10 @@ import logging -from typing import Any from graphon.graph_engine.manager import GraphEngineManager from graphon.model_runtime.errors.invoke import InvokeError -from pydantic import BaseModel from werkzeug.exceptions import InternalServerError +from controllers.common.controller_schemas import WorkflowRunPayload from controllers.common.schema import register_schema_model from controllers.console.app.error import ( CompletionRequestError, @@ -34,12 +33,6 @@ from .. import console_ns logger = logging.getLogger(__name__) - -class WorkflowRunPayload(BaseModel): - inputs: dict[str, Any] - files: list[dict[str, Any]] | None = None - - register_schema_model(console_ns, WorkflowRunPayload) diff --git a/api/controllers/service_api/app/conversation.py b/api/controllers/service_api/app/conversation.py index 8c9a3eb5e9..1ec289e2a2 100644 --- a/api/controllers/service_api/app/conversation.py +++ b/api/controllers/service_api/app/conversation.py @@ -2,11 +2,12 @@ from typing import Any, Literal from flask import request from flask_restx import Resource -from pydantic import BaseModel, Field, TypeAdapter, field_validator, model_validator +from pydantic import BaseModel, Field, TypeAdapter, field_validator from sqlalchemy.orm import sessionmaker from werkzeug.exceptions import BadRequest, NotFound import services +from controllers.common.controller_schemas import ConversationRenamePayload from controllers.common.schema import register_schema_models from controllers.service_api import service_api_ns from controllers.service_api.app.error import NotChatAppError @@ -34,18 +35,6 @@ class ConversationListQuery(BaseModel): ) -class ConversationRenamePayload(BaseModel): - name: str | None = Field(default=None, description="New conversation name (required if auto_generate is false)") - auto_generate: bool = Field(default=False, description="Auto-generate conversation name") - - @model_validator(mode="after") - def validate_name_requirement(self): - if not self.auto_generate: - if self.name is None or not self.name.strip(): - raise ValueError("name is required when auto_generate is false") - return self - - class ConversationVariablesQuery(BaseModel): last_id: UUIDStrOrEmpty | None = Field(default=None, description="Last variable ID for pagination") limit: int = Field(default=20, ge=1, le=100, description="Number of variables to return") diff --git a/api/controllers/service_api/app/message.py b/api/controllers/service_api/app/message.py index 77fee9c142..b75b299f6f 100644 --- a/api/controllers/service_api/app/message.py +++ b/api/controllers/service_api/app/message.py @@ -1,5 +1,4 @@ import logging -from typing import Literal from flask import request from flask_restx import Resource @@ -7,6 +6,7 @@ from pydantic import BaseModel, Field, TypeAdapter from werkzeug.exceptions import BadRequest, InternalServerError, NotFound import services +from controllers.common.controller_schemas import MessageFeedbackPayload, MessageListQuery from controllers.common.schema import register_schema_models from controllers.service_api import service_api_ns from controllers.service_api.app.error import NotChatAppError @@ -14,7 +14,6 @@ from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate from core.app.entities.app_invoke_entities import InvokeFrom from fields.conversation_fields import ResultResponse from fields.message_fields import MessageInfiniteScrollPagination, MessageListItem -from libs.helper import UUIDStrOrEmpty from models.enums import FeedbackRating from models.model import App, AppMode, EndUser from services.errors.message import ( @@ -27,17 +26,6 @@ from services.message_service import MessageService logger = logging.getLogger(__name__) -class MessageListQuery(BaseModel): - conversation_id: UUIDStrOrEmpty - first_id: UUIDStrOrEmpty | None = None - limit: int = Field(default=20, ge=1, le=100, description="Number of messages to return") - - -class MessageFeedbackPayload(BaseModel): - rating: Literal["like", "dislike"] | None = Field(default=None, description="Feedback rating") - content: str | None = Field(default=None, description="Feedback content") - - class FeedbackListQuery(BaseModel): page: int = Field(default=1, ge=1, description="Page number") limit: int = Field(default=20, ge=1, le=101, description="Number of feedbacks per page") diff --git a/api/controllers/service_api/app/workflow.py b/api/controllers/service_api/app/workflow.py index d7992a2a3a..e0a64ffe26 100644 --- a/api/controllers/service_api/app/workflow.py +++ b/api/controllers/service_api/app/workflow.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Literal +from typing import Literal from dateutil.parser import isoparse from flask import request @@ -11,6 +11,7 @@ from pydantic import BaseModel, Field from sqlalchemy.orm import sessionmaker from werkzeug.exceptions import BadRequest, InternalServerError, NotFound +from controllers.common.controller_schemas import WorkflowRunPayload as WorkflowRunPayloadBase from controllers.common.schema import register_schema_models from controllers.service_api import service_api_ns from controllers.service_api.app.error import ( @@ -46,9 +47,7 @@ from services.workflow_app_service import WorkflowAppService logger = logging.getLogger(__name__) -class WorkflowRunPayload(BaseModel): - inputs: dict[str, Any] - files: list[dict[str, Any]] | None = None +class WorkflowRunPayload(WorkflowRunPayloadBase): response_mode: Literal["blocking", "streaming"] | None = None diff --git a/api/controllers/web/audio.py b/api/controllers/web/audio.py index 9ba1dc4a3a..0ef4471018 100644 --- a/api/controllers/web/audio.py +++ b/api/controllers/web/audio.py @@ -3,10 +3,11 @@ import logging from flask import request from flask_restx import fields, marshal_with from graphon.model_runtime.errors.invoke import InvokeError -from pydantic import BaseModel, field_validator +from pydantic import field_validator from werkzeug.exceptions import InternalServerError import services +from controllers.common.controller_schemas import TextToAudioPayload as TextToAudioPayloadBase from controllers.web import web_ns from controllers.web.error import ( AppUnavailableError, @@ -34,12 +35,7 @@ from services.errors.audio import ( from ..common.schema import register_schema_models -class TextToAudioPayload(BaseModel): - message_id: str | None = None - voice: str | None = None - text: str | None = None - streaming: bool | None = None - +class TextToAudioPayload(TextToAudioPayloadBase): @field_validator("message_id") @classmethod def validate_message_id(cls, value: str | None) -> str | None: diff --git a/api/controllers/web/conversation.py b/api/controllers/web/conversation.py index d5baa5fb7d..3975dd85c8 100644 --- a/api/controllers/web/conversation.py +++ b/api/controllers/web/conversation.py @@ -1,10 +1,11 @@ from typing import Literal from flask import request -from pydantic import BaseModel, Field, TypeAdapter, field_validator, model_validator +from pydantic import BaseModel, Field, TypeAdapter, field_validator from sqlalchemy.orm import sessionmaker from werkzeug.exceptions import NotFound +from controllers.common.controller_schemas import ConversationRenamePayload from controllers.common.schema import register_schema_models from controllers.web import web_ns from controllers.web.error import NotChatAppError @@ -37,18 +38,6 @@ class ConversationListQuery(BaseModel): return uuid_value(value) -class ConversationRenamePayload(BaseModel): - name: str | None = None - auto_generate: bool = False - - @model_validator(mode="after") - def validate_name_requirement(self): - if not self.auto_generate: - if self.name is None or not self.name.strip(): - raise ValueError("name is required when auto_generate is false") - return self - - register_schema_models(web_ns, ConversationListQuery, ConversationRenamePayload) diff --git a/api/controllers/web/message.py b/api/controllers/web/message.py index c5505dd60d..25cb6b2b9e 100644 --- a/api/controllers/web/message.py +++ b/api/controllers/web/message.py @@ -6,6 +6,7 @@ from graphon.model_runtime.errors.invoke import InvokeError from pydantic import BaseModel, Field, TypeAdapter, field_validator from werkzeug.exceptions import InternalServerError, NotFound +from controllers.common.controller_schemas import MessageFeedbackPayload from controllers.common.schema import register_schema_models from controllers.web import web_ns from controllers.web.error import ( @@ -53,11 +54,6 @@ class MessageListQuery(BaseModel): return uuid_value(value) -class MessageFeedbackPayload(BaseModel): - rating: Literal["like", "dislike"] | None = Field(default=None, description="Feedback rating") - content: str | None = Field(default=None, description="Feedback content") - - class MessageMoreLikeThisQuery(BaseModel): response_mode: Literal["blocking", "streaming"] = Field( description="Response mode", diff --git a/api/controllers/web/saved_message.py b/api/controllers/web/saved_message.py index 29993100f6..5b206f9a98 100644 --- a/api/controllers/web/saved_message.py +++ b/api/controllers/web/saved_message.py @@ -1,27 +1,17 @@ from flask import request -from pydantic import BaseModel, Field, TypeAdapter +from pydantic import TypeAdapter from werkzeug.exceptions import NotFound +from controllers.common.controller_schemas import SavedMessageCreatePayload, SavedMessageListQuery from controllers.common.schema import register_schema_models from controllers.web import web_ns from controllers.web.error import NotCompletionAppError from controllers.web.wraps import WebApiResource from fields.conversation_fields import ResultResponse from fields.message_fields import SavedMessageInfiniteScrollPagination, SavedMessageItem -from libs.helper import UUIDStrOrEmpty from services.errors.message import MessageNotExistsError from services.saved_message_service import SavedMessageService - -class SavedMessageListQuery(BaseModel): - last_id: UUIDStrOrEmpty | None = None - limit: int = Field(default=20, ge=1, le=100) - - -class SavedMessageCreatePayload(BaseModel): - message_id: UUIDStrOrEmpty - - register_schema_models(web_ns, SavedMessageListQuery, SavedMessageCreatePayload) diff --git a/api/controllers/web/workflow.py b/api/controllers/web/workflow.py index 7f5521f9f5..796e090976 100644 --- a/api/controllers/web/workflow.py +++ b/api/controllers/web/workflow.py @@ -1,11 +1,10 @@ import logging -from typing import Any from graphon.graph_engine.manager import GraphEngineManager from graphon.model_runtime.errors.invoke import InvokeError -from pydantic import BaseModel, Field from werkzeug.exceptions import InternalServerError +from controllers.common.controller_schemas import WorkflowRunPayload from controllers.common.schema import register_schema_models from controllers.web import web_ns from controllers.web.error import ( @@ -30,12 +29,6 @@ from models.model import App, AppMode, EndUser from services.app_generate_service import AppGenerateService from services.errors.llm import InvokeRateLimitError - -class WorkflowRunPayload(BaseModel): - inputs: dict[str, Any] = Field(description="Input variables for the workflow") - files: list[dict[str, Any]] | None = Field(default=None, description="Files to be processed by the workflow") - - logger = logging.getLogger(__name__) register_schema_models(web_ns, WorkflowRunPayload)