diff --git a/api/controllers/console/app/advanced_prompt_template.py b/api/controllers/console/app/advanced_prompt_template.py index 5885d7b447..075345d860 100644 --- a/api/controllers/console/app/advanced_prompt_template.py +++ b/api/controllers/console/app/advanced_prompt_template.py @@ -5,18 +5,20 @@ from controllers.console.wraps import account_initialization_required, setup_req from libs.login import login_required from services.advanced_prompt_template_service import AdvancedPromptTemplateService +parser = ( + reqparse.RequestParser() + .add_argument("app_mode", type=str, required=True, location="args", help="Application mode") + .add_argument("model_mode", type=str, required=True, location="args", help="Model mode") + .add_argument("has_context", type=str, required=False, default="true", location="args", help="Whether has context") + .add_argument("model_name", type=str, required=True, location="args", help="Model name") +) + @console_ns.route("/app/prompt-templates") class AdvancedPromptTemplateList(Resource): @api.doc("get_advanced_prompt_templates") @api.doc(description="Get advanced prompt templates based on app mode and model configuration") - @api.expect( - api.parser() - .add_argument("app_mode", type=str, required=True, location="args", help="Application mode") - .add_argument("model_mode", type=str, required=True, location="args", help="Model mode") - .add_argument("has_context", type=str, default="true", location="args", help="Whether has context") - .add_argument("model_name", type=str, required=True, location="args", help="Model name") - ) + @api.expect(parser) @api.response( 200, "Prompt templates retrieved successfully", fields.List(fields.Raw(description="Prompt template data")) ) @@ -25,13 +27,6 @@ class AdvancedPromptTemplateList(Resource): @login_required @account_initialization_required def get(self): - parser = ( - reqparse.RequestParser() - .add_argument("app_mode", type=str, required=True, location="args") - .add_argument("model_mode", type=str, required=True, location="args") - .add_argument("has_context", type=str, required=False, default="true", location="args") - .add_argument("model_name", type=str, required=True, location="args") - ) args = parser.parse_args() return AdvancedPromptTemplateService.get_prompt(args) diff --git a/api/controllers/console/app/agent.py b/api/controllers/console/app/agent.py index 717263a74d..fde28fdb98 100644 --- a/api/controllers/console/app/agent.py +++ b/api/controllers/console/app/agent.py @@ -8,17 +8,19 @@ from libs.login import login_required from models.model import AppMode from services.agent_service import AgentService +parser = ( + reqparse.RequestParser() + .add_argument("message_id", type=uuid_value, required=True, location="args", help="Message UUID") + .add_argument("conversation_id", type=uuid_value, required=True, location="args", help="Conversation UUID") +) + @console_ns.route("/apps//agent/logs") class AgentLogApi(Resource): @api.doc("get_agent_logs") @api.doc(description="Get agent execution logs for an application") @api.doc(params={"app_id": "Application ID"}) - @api.expect( - api.parser() - .add_argument("message_id", type=str, required=True, location="args", help="Message UUID") - .add_argument("conversation_id", type=str, required=True, location="args", help="Conversation UUID") - ) + @api.expect(parser) @api.response(200, "Agent logs retrieved successfully", fields.List(fields.Raw(description="Agent log entries"))) @api.response(400, "Invalid request parameters") @setup_required @@ -27,12 +29,6 @@ class AgentLogApi(Resource): @get_app_model(mode=[AppMode.AGENT_CHAT]) def get(self, app_model): """Get agent logs""" - parser = ( - reqparse.RequestParser() - .add_argument("message_id", type=uuid_value, required=True, location="args") - .add_argument("conversation_id", type=uuid_value, required=True, location="args") - ) - args = parser.parse_args() return AgentService.get_agent_logs(app_model, args["conversation_id"], args["message_id"]) diff --git a/api/controllers/console/app/annotation.py b/api/controllers/console/app/annotation.py index 23b6809aa5..bc4113b5c7 100644 --- a/api/controllers/console/app/annotation.py +++ b/api/controllers/console/app/annotation.py @@ -251,6 +251,13 @@ class AnnotationExportApi(Resource): return response, 200 +parser = ( + reqparse.RequestParser() + .add_argument("question", required=True, type=str, location="json") + .add_argument("answer", required=True, type=str, location="json") +) + + @console_ns.route("/apps//annotations/") class AnnotationUpdateDeleteApi(Resource): @api.doc("update_delete_annotation") @@ -259,6 +266,7 @@ class AnnotationUpdateDeleteApi(Resource): @api.response(200, "Annotation updated successfully", annotation_fields) @api.response(204, "Annotation deleted successfully") @api.response(403, "Insufficient permissions") + @api.expect(parser) @setup_required @login_required @account_initialization_required @@ -268,11 +276,6 @@ class AnnotationUpdateDeleteApi(Resource): def post(self, app_id, annotation_id): app_id = str(app_id) annotation_id = str(annotation_id) - parser = ( - reqparse.RequestParser() - .add_argument("question", required=True, type=str, location="json") - .add_argument("answer", required=True, type=str, location="json") - ) args = parser.parse_args() annotation = AppAnnotationService.update_app_annotation_directly(args, app_id, annotation_id) return annotation diff --git a/api/controllers/console/app/app.py b/api/controllers/console/app/app.py index 17505d69b2..037f13d297 100644 --- a/api/controllers/console/app/app.py +++ b/api/controllers/console/app/app.py @@ -353,12 +353,15 @@ class AppExportApi(Resource): } +parser = reqparse.RequestParser().add_argument("name", type=str, required=True, location="json", help="Name to check") + + @console_ns.route("/apps//name") class AppNameApi(Resource): @api.doc("check_app_name") @api.doc(description="Check if app name is available") @api.doc(params={"app_id": "Application ID"}) - @api.expect(api.parser().add_argument("name", type=str, required=True, location="args", help="Name to check")) + @api.expect(parser) @api.response(200, "Name availability checked") @setup_required @login_required @@ -367,7 +370,6 @@ class AppNameApi(Resource): @marshal_with(app_detail_fields) @edit_permission_required def post(self, app_model): - parser = reqparse.RequestParser().add_argument("name", type=str, required=True, location="json") args = parser.parse_args() app_service = AppService() diff --git a/api/controllers/console/app/app_import.py b/api/controllers/console/app/app_import.py index d902c129ad..02dbd42515 100644 --- a/api/controllers/console/app/app_import.py +++ b/api/controllers/console/app/app_import.py @@ -1,6 +1,7 @@ from flask_restx import Resource, marshal_with, reqparse from sqlalchemy.orm import Session +from controllers.console import api from controllers.console.app.wraps import get_app_model from controllers.console.wraps import ( account_initialization_required, @@ -18,9 +19,23 @@ from services.feature_service import FeatureService from .. import console_ns +parser = ( + reqparse.RequestParser() + .add_argument("mode", type=str, required=True, location="json") + .add_argument("yaml_content", type=str, location="json") + .add_argument("yaml_url", type=str, location="json") + .add_argument("name", type=str, location="json") + .add_argument("description", type=str, location="json") + .add_argument("icon_type", type=str, location="json") + .add_argument("icon", type=str, location="json") + .add_argument("icon_background", type=str, location="json") + .add_argument("app_id", type=str, location="json") +) + @console_ns.route("/apps/imports") class AppImportApi(Resource): + @api.expect(parser) @setup_required @login_required @account_initialization_required @@ -30,18 +45,6 @@ class AppImportApi(Resource): def post(self): # Check user role first current_user, _ = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("mode", type=str, required=True, location="json") - .add_argument("yaml_content", type=str, location="json") - .add_argument("yaml_url", type=str, location="json") - .add_argument("name", type=str, location="json") - .add_argument("description", type=str, location="json") - .add_argument("icon_type", type=str, location="json") - .add_argument("icon", type=str, location="json") - .add_argument("icon_background", type=str, location="json") - .add_argument("app_id", type=str, location="json") - ) args = parser.parse_args() # Create service with session diff --git a/api/controllers/console/app/statistic.py b/api/controllers/console/app/statistic.py index 8c8253b786..37ed3d9e27 100644 --- a/api/controllers/console/app/statistic.py +++ b/api/controllers/console/app/statistic.py @@ -80,16 +80,19 @@ WHERE return jsonify({"data": response_data}) +parser = ( + reqparse.RequestParser() + .add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args", help="Start date (YYYY-MM-DD HH:MM)") + .add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args", help="End date (YYYY-MM-DD HH:MM)") +) + + @console_ns.route("/apps//statistics/daily-conversations") class DailyConversationStatistic(Resource): @api.doc("get_daily_conversation_statistics") @api.doc(description="Get daily conversation statistics for an application") @api.doc(params={"app_id": "Application ID"}) - @api.expect( - api.parser() - .add_argument("start", type=str, location="args", help="Start date (YYYY-MM-DD HH:MM)") - .add_argument("end", type=str, location="args", help="End date (YYYY-MM-DD HH:MM)") - ) + @api.expect(parser) @api.response( 200, "Daily conversation statistics retrieved successfully", @@ -102,11 +105,6 @@ class DailyConversationStatistic(Resource): def get(self, app_model): account, _ = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args") - .add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args") - ) args = parser.parse_args() assert account.timezone is not None @@ -148,11 +146,7 @@ class DailyTerminalsStatistic(Resource): @api.doc("get_daily_terminals_statistics") @api.doc(description="Get daily terminal/end-user statistics for an application") @api.doc(params={"app_id": "Application ID"}) - @api.expect( - api.parser() - .add_argument("start", type=str, location="args", help="Start date (YYYY-MM-DD HH:MM)") - .add_argument("end", type=str, location="args", help="End date (YYYY-MM-DD HH:MM)") - ) + @api.expect(parser) @api.response( 200, "Daily terminal statistics retrieved successfully", @@ -165,11 +159,6 @@ class DailyTerminalsStatistic(Resource): def get(self, app_model): account, _ = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args") - .add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args") - ) args = parser.parse_args() sql_query = """SELECT @@ -213,11 +202,7 @@ class DailyTokenCostStatistic(Resource): @api.doc("get_daily_token_cost_statistics") @api.doc(description="Get daily token cost statistics for an application") @api.doc(params={"app_id": "Application ID"}) - @api.expect( - api.parser() - .add_argument("start", type=str, location="args", help="Start date (YYYY-MM-DD HH:MM)") - .add_argument("end", type=str, location="args", help="End date (YYYY-MM-DD HH:MM)") - ) + @api.expect(parser) @api.response( 200, "Daily token cost statistics retrieved successfully", @@ -230,11 +215,6 @@ class DailyTokenCostStatistic(Resource): def get(self, app_model): account, _ = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args") - .add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args") - ) args = parser.parse_args() sql_query = """SELECT @@ -281,11 +261,7 @@ class AverageSessionInteractionStatistic(Resource): @api.doc("get_average_session_interaction_statistics") @api.doc(description="Get average session interaction statistics for an application") @api.doc(params={"app_id": "Application ID"}) - @api.expect( - api.parser() - .add_argument("start", type=str, location="args", help="Start date (YYYY-MM-DD HH:MM)") - .add_argument("end", type=str, location="args", help="End date (YYYY-MM-DD HH:MM)") - ) + @api.expect(parser) @api.response( 200, "Average session interaction statistics retrieved successfully", @@ -298,11 +274,6 @@ class AverageSessionInteractionStatistic(Resource): def get(self, app_model): account, _ = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args") - .add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args") - ) args = parser.parse_args() sql_query = """SELECT @@ -365,11 +336,7 @@ class UserSatisfactionRateStatistic(Resource): @api.doc("get_user_satisfaction_rate_statistics") @api.doc(description="Get user satisfaction rate statistics for an application") @api.doc(params={"app_id": "Application ID"}) - @api.expect( - api.parser() - .add_argument("start", type=str, location="args", help="Start date (YYYY-MM-DD HH:MM)") - .add_argument("end", type=str, location="args", help="End date (YYYY-MM-DD HH:MM)") - ) + @api.expect(parser) @api.response( 200, "User satisfaction rate statistics retrieved successfully", @@ -382,11 +349,6 @@ class UserSatisfactionRateStatistic(Resource): def get(self, app_model): account, _ = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args") - .add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args") - ) args = parser.parse_args() sql_query = """SELECT @@ -439,11 +401,7 @@ class AverageResponseTimeStatistic(Resource): @api.doc("get_average_response_time_statistics") @api.doc(description="Get average response time statistics for an application") @api.doc(params={"app_id": "Application ID"}) - @api.expect( - api.parser() - .add_argument("start", type=str, location="args", help="Start date (YYYY-MM-DD HH:MM)") - .add_argument("end", type=str, location="args", help="End date (YYYY-MM-DD HH:MM)") - ) + @api.expect(parser) @api.response( 200, "Average response time statistics retrieved successfully", @@ -456,11 +414,6 @@ class AverageResponseTimeStatistic(Resource): def get(self, app_model): account, _ = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args") - .add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args") - ) args = parser.parse_args() sql_query = """SELECT @@ -504,11 +457,7 @@ class TokensPerSecondStatistic(Resource): @api.doc("get_tokens_per_second_statistics") @api.doc(description="Get tokens per second statistics for an application") @api.doc(params={"app_id": "Application ID"}) - @api.expect( - api.parser() - .add_argument("start", type=str, location="args", help="Start date (YYYY-MM-DD HH:MM)") - .add_argument("end", type=str, location="args", help="End date (YYYY-MM-DD HH:MM)") - ) + @api.expect(parser) @api.response( 200, "Tokens per second statistics retrieved successfully", @@ -520,12 +469,6 @@ class TokensPerSecondStatistic(Resource): @account_initialization_required def get(self, app_model): account, _ = current_account_with_tenant() - - parser = ( - reqparse.RequestParser() - .add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args") - .add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args") - ) args = parser.parse_args() sql_query = """SELECT diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index 8c451cd08c..31077e371b 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -586,6 +586,13 @@ class DraftWorkflowNodeRunApi(Resource): return workflow_node_execution +parser_publish = ( + reqparse.RequestParser() + .add_argument("marked_name", type=str, required=False, default="", location="json") + .add_argument("marked_comment", type=str, required=False, default="", location="json") +) + + @console_ns.route("/apps//workflows/publish") class PublishedWorkflowApi(Resource): @api.doc("get_published_workflow") @@ -610,6 +617,7 @@ class PublishedWorkflowApi(Resource): # return workflow, if not found, return None return workflow + @api.expect(parser_publish) @setup_required @login_required @account_initialization_required @@ -620,12 +628,8 @@ class PublishedWorkflowApi(Resource): Publish workflow """ current_user, _ = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("marked_name", type=str, required=False, default="", location="json") - .add_argument("marked_comment", type=str, required=False, default="", location="json") - ) - args = parser.parse_args() + + args = parser_publish.parse_args() # Validate name and comment length if args.marked_name and len(args.marked_name) > 20: @@ -680,6 +684,9 @@ class DefaultBlockConfigsApi(Resource): return workflow_service.get_default_block_configs() +parser_block = reqparse.RequestParser().add_argument("q", type=str, location="args") + + @console_ns.route("/apps//workflows/default-workflow-block-configs/") class DefaultBlockConfigApi(Resource): @api.doc("get_default_block_config") @@ -687,6 +694,7 @@ class DefaultBlockConfigApi(Resource): @api.doc(params={"app_id": "Application ID", "block_type": "Block type"}) @api.response(200, "Default block configuration retrieved successfully") @api.response(404, "Block type not found") + @api.expect(parser_block) @setup_required @login_required @account_initialization_required @@ -696,8 +704,7 @@ class DefaultBlockConfigApi(Resource): """ Get default block config """ - parser = reqparse.RequestParser().add_argument("q", type=str, location="args") - args = parser.parse_args() + args = parser_block.parse_args() q = args.get("q") @@ -713,8 +720,18 @@ class DefaultBlockConfigApi(Resource): return workflow_service.get_default_block_config(node_type=block_type, filters=filters) +parser_convert = ( + reqparse.RequestParser() + .add_argument("name", type=str, required=False, nullable=True, location="json") + .add_argument("icon_type", type=str, required=False, nullable=True, location="json") + .add_argument("icon", type=str, required=False, nullable=True, location="json") + .add_argument("icon_background", type=str, required=False, nullable=True, location="json") +) + + @console_ns.route("/apps//convert-to-workflow") class ConvertToWorkflowApi(Resource): + @api.expect(parser_convert) @api.doc("convert_to_workflow") @api.doc(description="Convert application to workflow mode") @api.doc(params={"app_id": "Application ID"}) @@ -735,14 +752,7 @@ class ConvertToWorkflowApi(Resource): current_user, _ = current_account_with_tenant() if request.data: - parser = ( - reqparse.RequestParser() - .add_argument("name", type=str, required=False, nullable=True, location="json") - .add_argument("icon_type", type=str, required=False, nullable=True, location="json") - .add_argument("icon", type=str, required=False, nullable=True, location="json") - .add_argument("icon_background", type=str, required=False, nullable=True, location="json") - ) - args = parser.parse_args() + args = parser_convert.parse_args() else: args = {} @@ -756,8 +766,18 @@ class ConvertToWorkflowApi(Resource): } +parser_workflows = ( + reqparse.RequestParser() + .add_argument("page", type=inputs.int_range(1, 99999), required=False, default=1, location="args") + .add_argument("limit", type=inputs.int_range(1, 100), required=False, default=10, location="args") + .add_argument("user_id", type=str, required=False, location="args") + .add_argument("named_only", type=inputs.boolean, required=False, default=False, location="args") +) + + @console_ns.route("/apps//workflows") class PublishedAllWorkflowApi(Resource): + @api.expect(parser_workflows) @api.doc("get_all_published_workflows") @api.doc(description="Get all published workflows for an application") @api.doc(params={"app_id": "Application ID"}) @@ -774,16 +794,9 @@ class PublishedAllWorkflowApi(Resource): """ current_user, _ = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("page", type=inputs.int_range(1, 99999), required=False, default=1, location="args") - .add_argument("limit", type=inputs.int_range(1, 100), required=False, default=20, location="args") - .add_argument("user_id", type=str, required=False, location="args") - .add_argument("named_only", type=inputs.boolean, required=False, default=False, location="args") - ) - args = parser.parse_args() - page = int(args.get("page", 1)) - limit = int(args.get("limit", 10)) + args = parser_workflows.parse_args() + page = args["page"] + limit = args["limit"] user_id = args.get("user_id") named_only = args.get("named_only", False) diff --git a/api/controllers/console/app/workflow_run.py b/api/controllers/console/app/workflow_run.py index 311aa81279..23c228efbe 100644 --- a/api/controllers/console/app/workflow_run.py +++ b/api/controllers/console/app/workflow_run.py @@ -30,23 +30,25 @@ def _parse_workflow_run_list_args(): Returns: Parsed arguments containing last_id, limit, status, and triggered_from filters """ - parser = reqparse.RequestParser() - parser.add_argument("last_id", type=uuid_value, location="args") - parser.add_argument("limit", type=int_range(1, 100), required=False, default=20, location="args") - parser.add_argument( - "status", - type=str, - choices=WORKFLOW_RUN_STATUS_CHOICES, - location="args", - required=False, - ) - parser.add_argument( - "triggered_from", - type=str, - choices=["debugging", "app-run"], - location="args", - required=False, - help="Filter by trigger source: debugging or app-run", + parser = ( + reqparse.RequestParser() + .add_argument("last_id", type=uuid_value, location="args") + .add_argument("limit", type=int_range(1, 100), required=False, default=20, location="args") + .add_argument( + "status", + type=str, + choices=WORKFLOW_RUN_STATUS_CHOICES, + location="args", + required=False, + ) + .add_argument( + "triggered_from", + type=str, + choices=["debugging", "app-run"], + location="args", + required=False, + help="Filter by trigger source: debugging or app-run", + ) ) return parser.parse_args() @@ -58,28 +60,30 @@ def _parse_workflow_run_count_args(): Returns: Parsed arguments containing status, time_range, and triggered_from filters """ - parser = reqparse.RequestParser() - parser.add_argument( - "status", - type=str, - choices=WORKFLOW_RUN_STATUS_CHOICES, - location="args", - required=False, - ) - parser.add_argument( - "time_range", - type=time_duration, - location="args", - required=False, - help="Time range filter (e.g., 7d, 4h, 30m, 30s)", - ) - parser.add_argument( - "triggered_from", - type=str, - choices=["debugging", "app-run"], - location="args", - required=False, - help="Filter by trigger source: debugging or app-run", + parser = ( + reqparse.RequestParser() + .add_argument( + "status", + type=str, + choices=WORKFLOW_RUN_STATUS_CHOICES, + location="args", + required=False, + ) + .add_argument( + "time_range", + type=time_duration, + location="args", + required=False, + help="Time range filter (e.g., 7d, 4h, 30m, 30s)", + ) + .add_argument( + "triggered_from", + type=str, + choices=["debugging", "app-run"], + location="args", + required=False, + help="Filter by trigger source: debugging or app-run", + ) ) return parser.parse_args() diff --git a/api/controllers/console/datasets/rag_pipeline/datasource_auth.py b/api/controllers/console/datasets/rag_pipeline/datasource_auth.py index 2111ee2ecf..f83ee69beb 100644 --- a/api/controllers/console/datasets/rag_pipeline/datasource_auth.py +++ b/api/controllers/console/datasets/rag_pipeline/datasource_auth.py @@ -3,7 +3,7 @@ from flask_restx import Resource, reqparse from werkzeug.exceptions import Forbidden, NotFound from configs import dify_config -from controllers.console import console_ns +from controllers.console import api, console_ns from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required from core.model_runtime.errors.validate import CredentialsValidateFailedError from core.model_runtime.utils.encoders import jsonable_encoder @@ -121,8 +121,16 @@ class DatasourceOAuthCallback(Resource): return redirect(f"{dify_config.CONSOLE_WEB_URL}/oauth-callback") +parser_datasource = ( + reqparse.RequestParser() + .add_argument("name", type=StrLen(max_length=100), required=False, nullable=True, location="json", default=None) + .add_argument("credentials", type=dict, required=True, nullable=False, location="json") +) + + @console_ns.route("/auth/plugin/datasource/") class DatasourceAuth(Resource): + @api.expect(parser_datasource) @setup_required @login_required @account_initialization_required @@ -130,14 +138,7 @@ class DatasourceAuth(Resource): def post(self, provider_id: str): _, current_tenant_id = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument( - "name", type=StrLen(max_length=100), required=False, nullable=True, location="json", default=None - ) - .add_argument("credentials", type=dict, required=True, nullable=False, location="json") - ) - args = parser.parse_args() + args = parser_datasource.parse_args() datasource_provider_id = DatasourceProviderID(provider_id) datasource_provider_service = DatasourceProviderService() @@ -168,8 +169,14 @@ class DatasourceAuth(Resource): return {"result": datasources}, 200 +parser_datasource_delete = reqparse.RequestParser().add_argument( + "credential_id", type=str, required=True, nullable=False, location="json" +) + + @console_ns.route("/auth/plugin/datasource//delete") class DatasourceAuthDeleteApi(Resource): + @api.expect(parser_datasource_delete) @setup_required @login_required @account_initialization_required @@ -181,10 +188,7 @@ class DatasourceAuthDeleteApi(Resource): plugin_id = datasource_provider_id.plugin_id provider_name = datasource_provider_id.provider_name - parser = reqparse.RequestParser().add_argument( - "credential_id", type=str, required=True, nullable=False, location="json" - ) - args = parser.parse_args() + args = parser_datasource_delete.parse_args() datasource_provider_service = DatasourceProviderService() datasource_provider_service.remove_datasource_credentials( tenant_id=current_tenant_id, @@ -195,8 +199,17 @@ class DatasourceAuthDeleteApi(Resource): return {"result": "success"}, 200 +parser_datasource_update = ( + reqparse.RequestParser() + .add_argument("credentials", type=dict, required=False, nullable=True, location="json") + .add_argument("name", type=StrLen(max_length=100), required=False, nullable=True, location="json") + .add_argument("credential_id", type=str, required=True, nullable=False, location="json") +) + + @console_ns.route("/auth/plugin/datasource//update") class DatasourceAuthUpdateApi(Resource): + @api.expect(parser_datasource_update) @setup_required @login_required @account_initialization_required @@ -205,13 +218,7 @@ class DatasourceAuthUpdateApi(Resource): _, current_tenant_id = current_account_with_tenant() datasource_provider_id = DatasourceProviderID(provider_id) - parser = ( - reqparse.RequestParser() - .add_argument("credentials", type=dict, required=False, nullable=True, location="json") - .add_argument("name", type=StrLen(max_length=100), required=False, nullable=True, location="json") - .add_argument("credential_id", type=str, required=True, nullable=False, location="json") - ) - args = parser.parse_args() + args = parser_datasource_update.parse_args() datasource_provider_service = DatasourceProviderService() datasource_provider_service.update_datasource_credentials( @@ -251,8 +258,16 @@ class DatasourceHardCodeAuthListApi(Resource): return {"result": jsonable_encoder(datasources)}, 200 +parser_datasource_custom = ( + reqparse.RequestParser() + .add_argument("client_params", type=dict, required=False, nullable=True, location="json") + .add_argument("enable_oauth_custom_client", type=bool, required=False, nullable=True, location="json") +) + + @console_ns.route("/auth/plugin/datasource//custom-client") class DatasourceAuthOauthCustomClient(Resource): + @api.expect(parser_datasource_custom) @setup_required @login_required @account_initialization_required @@ -260,12 +275,7 @@ class DatasourceAuthOauthCustomClient(Resource): def post(self, provider_id: str): _, current_tenant_id = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("client_params", type=dict, required=False, nullable=True, location="json") - .add_argument("enable_oauth_custom_client", type=bool, required=False, nullable=True, location="json") - ) - args = parser.parse_args() + args = parser_datasource_custom.parse_args() datasource_provider_id = DatasourceProviderID(provider_id) datasource_provider_service = DatasourceProviderService() datasource_provider_service.setup_oauth_custom_client_params( @@ -291,8 +301,12 @@ class DatasourceAuthOauthCustomClient(Resource): return {"result": "success"}, 200 +parser_default = reqparse.RequestParser().add_argument("id", type=str, required=True, nullable=False, location="json") + + @console_ns.route("/auth/plugin/datasource//default") class DatasourceAuthDefaultApi(Resource): + @api.expect(parser_default) @setup_required @login_required @account_initialization_required @@ -300,8 +314,7 @@ class DatasourceAuthDefaultApi(Resource): def post(self, provider_id: str): _, current_tenant_id = current_account_with_tenant() - parser = reqparse.RequestParser().add_argument("id", type=str, required=True, nullable=False, location="json") - args = parser.parse_args() + args = parser_default.parse_args() datasource_provider_id = DatasourceProviderID(provider_id) datasource_provider_service = DatasourceProviderService() datasource_provider_service.set_default_datasource_provider( @@ -312,8 +325,16 @@ class DatasourceAuthDefaultApi(Resource): return {"result": "success"}, 200 +parser_update_name = ( + reqparse.RequestParser() + .add_argument("name", type=StrLen(max_length=100), required=True, nullable=False, location="json") + .add_argument("credential_id", type=str, required=True, nullable=False, location="json") +) + + @console_ns.route("/auth/plugin/datasource//update-name") class DatasourceUpdateProviderNameApi(Resource): + @api.expect(parser_update_name) @setup_required @login_required @account_initialization_required @@ -321,12 +342,7 @@ class DatasourceUpdateProviderNameApi(Resource): def post(self, provider_id: str): _, current_tenant_id = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("name", type=StrLen(max_length=100), required=True, nullable=False, location="json") - .add_argument("credential_id", type=str, required=True, nullable=False, location="json") - ) - args = parser.parse_args() + args = parser_update_name.parse_args() datasource_provider_id = DatasourceProviderID(provider_id) datasource_provider_service = DatasourceProviderService() datasource_provider_service.update_datasource_provider_name( diff --git a/api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py b/api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py index 5fe8572dfa..1e77a988bd 100644 --- a/api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py +++ b/api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py @@ -9,7 +9,7 @@ from sqlalchemy.orm import Session from werkzeug.exceptions import Forbidden, InternalServerError, NotFound import services -from controllers.console import console_ns +from controllers.console import api, console_ns from controllers.console.app.error import ( ConversationCompletedError, DraftWorkflowNotExist, @@ -148,8 +148,12 @@ class DraftRagPipelineApi(Resource): } +parser_run = reqparse.RequestParser().add_argument("inputs", type=dict, location="json") + + @console_ns.route("/rag/pipelines//workflows/draft/iteration/nodes//run") class RagPipelineDraftRunIterationNodeApi(Resource): + @api.expect(parser_run) @setup_required @login_required @account_initialization_required @@ -162,8 +166,7 @@ class RagPipelineDraftRunIterationNodeApi(Resource): # The role of the current user in the ta table must be admin, owner, or editor current_user, _ = current_account_with_tenant() - parser = reqparse.RequestParser().add_argument("inputs", type=dict, location="json") - args = parser.parse_args() + args = parser_run.parse_args() try: response = PipelineGenerateService.generate_single_iteration( @@ -184,6 +187,7 @@ class RagPipelineDraftRunIterationNodeApi(Resource): @console_ns.route("/rag/pipelines//workflows/draft/loop/nodes//run") class RagPipelineDraftRunLoopNodeApi(Resource): + @api.expect(parser_run) @setup_required @login_required @account_initialization_required @@ -197,8 +201,7 @@ class RagPipelineDraftRunLoopNodeApi(Resource): if not current_user.has_edit_permission: raise Forbidden() - parser = reqparse.RequestParser().add_argument("inputs", type=dict, location="json") - args = parser.parse_args() + args = parser_run.parse_args() try: response = PipelineGenerateService.generate_single_loop( @@ -217,8 +220,18 @@ class RagPipelineDraftRunLoopNodeApi(Resource): raise InternalServerError() +parser_draft_run = ( + reqparse.RequestParser() + .add_argument("inputs", type=dict, required=True, nullable=False, location="json") + .add_argument("datasource_type", type=str, required=True, location="json") + .add_argument("datasource_info_list", type=list, required=True, location="json") + .add_argument("start_node_id", type=str, required=True, location="json") +) + + @console_ns.route("/rag/pipelines//workflows/draft/run") class DraftRagPipelineRunApi(Resource): + @api.expect(parser_draft_run) @setup_required @login_required @account_initialization_required @@ -232,14 +245,7 @@ class DraftRagPipelineRunApi(Resource): if not current_user.has_edit_permission: raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument("inputs", type=dict, required=True, nullable=False, location="json") - .add_argument("datasource_type", type=str, required=True, location="json") - .add_argument("datasource_info_list", type=list, required=True, location="json") - .add_argument("start_node_id", type=str, required=True, location="json") - ) - args = parser.parse_args() + args = parser_draft_run.parse_args() try: response = PipelineGenerateService.generate( @@ -255,8 +261,21 @@ class DraftRagPipelineRunApi(Resource): raise InvokeRateLimitHttpError(ex.description) +parser_published_run = ( + reqparse.RequestParser() + .add_argument("inputs", type=dict, required=True, nullable=False, location="json") + .add_argument("datasource_type", type=str, required=True, location="json") + .add_argument("datasource_info_list", type=list, required=True, location="json") + .add_argument("start_node_id", type=str, required=True, location="json") + .add_argument("is_preview", type=bool, required=True, location="json", default=False) + .add_argument("response_mode", type=str, required=True, location="json", default="streaming") + .add_argument("original_document_id", type=str, required=False, location="json") +) + + @console_ns.route("/rag/pipelines//workflows/published/run") class PublishedRagPipelineRunApi(Resource): + @api.expect(parser_published_run) @setup_required @login_required @account_initialization_required @@ -270,17 +289,7 @@ class PublishedRagPipelineRunApi(Resource): if not current_user.has_edit_permission: raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument("inputs", type=dict, required=True, nullable=False, location="json") - .add_argument("datasource_type", type=str, required=True, location="json") - .add_argument("datasource_info_list", type=list, required=True, location="json") - .add_argument("start_node_id", type=str, required=True, location="json") - .add_argument("is_preview", type=bool, required=True, location="json", default=False) - .add_argument("response_mode", type=str, required=True, location="json", default="streaming") - .add_argument("original_document_id", type=str, required=False, location="json") - ) - args = parser.parse_args() + args = parser_published_run.parse_args() streaming = args["response_mode"] == "streaming" @@ -381,8 +390,17 @@ class PublishedRagPipelineRunApi(Resource): # # return result # +parser_rag_run = ( + reqparse.RequestParser() + .add_argument("inputs", type=dict, required=True, nullable=False, location="json") + .add_argument("datasource_type", type=str, required=True, location="json") + .add_argument("credential_id", type=str, required=False, location="json") +) + + @console_ns.route("/rag/pipelines//workflows/published/datasource/nodes//run") class RagPipelinePublishedDatasourceNodeRunApi(Resource): + @api.expect(parser_rag_run) @setup_required @login_required @account_initialization_required @@ -396,13 +414,7 @@ class RagPipelinePublishedDatasourceNodeRunApi(Resource): if not current_user.has_edit_permission: raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument("inputs", type=dict, required=True, nullable=False, location="json") - .add_argument("datasource_type", type=str, required=True, location="json") - .add_argument("credential_id", type=str, required=False, location="json") - ) - args = parser.parse_args() + args = parser_rag_run.parse_args() inputs = args.get("inputs") if inputs is None: @@ -429,6 +441,7 @@ class RagPipelinePublishedDatasourceNodeRunApi(Resource): @console_ns.route("/rag/pipelines//workflows/draft/datasource/nodes//run") class RagPipelineDraftDatasourceNodeRunApi(Resource): + @api.expect(parser_rag_run) @setup_required @login_required @account_initialization_required @@ -442,13 +455,7 @@ class RagPipelineDraftDatasourceNodeRunApi(Resource): if not current_user.has_edit_permission: raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument("inputs", type=dict, required=True, nullable=False, location="json") - .add_argument("datasource_type", type=str, required=True, location="json") - .add_argument("credential_id", type=str, required=False, location="json") - ) - args = parser.parse_args() + args = parser_rag_run.parse_args() inputs = args.get("inputs") if inputs is None: @@ -473,8 +480,14 @@ class RagPipelineDraftDatasourceNodeRunApi(Resource): ) +parser_run_api = reqparse.RequestParser().add_argument( + "inputs", type=dict, required=True, nullable=False, location="json" +) + + @console_ns.route("/rag/pipelines//workflows/draft/nodes//run") class RagPipelineDraftNodeRunApi(Resource): + @api.expect(parser_run_api) @setup_required @login_required @account_initialization_required @@ -489,10 +502,7 @@ class RagPipelineDraftNodeRunApi(Resource): if not current_user.has_edit_permission: raise Forbidden() - parser = reqparse.RequestParser().add_argument( - "inputs", type=dict, required=True, nullable=False, location="json" - ) - args = parser.parse_args() + args = parser_run_api.parse_args() inputs = args.get("inputs") if inputs == None: @@ -607,8 +617,12 @@ class DefaultRagPipelineBlockConfigsApi(Resource): return rag_pipeline_service.get_default_block_configs() +parser_default = reqparse.RequestParser().add_argument("q", type=str, location="args") + + @console_ns.route("/rag/pipelines//workflows/default-workflow-block-configs/") class DefaultRagPipelineBlockConfigApi(Resource): + @api.expect(parser_default) @setup_required @login_required @account_initialization_required @@ -622,8 +636,7 @@ class DefaultRagPipelineBlockConfigApi(Resource): if not current_user.has_edit_permission: raise Forbidden() - parser = reqparse.RequestParser().add_argument("q", type=str, location="args") - args = parser.parse_args() + args = parser_default.parse_args() q = args.get("q") @@ -639,8 +652,18 @@ class DefaultRagPipelineBlockConfigApi(Resource): return rag_pipeline_service.get_default_block_config(node_type=block_type, filters=filters) +parser_wf = ( + reqparse.RequestParser() + .add_argument("page", type=inputs.int_range(1, 99999), required=False, default=1, location="args") + .add_argument("limit", type=inputs.int_range(1, 100), required=False, default=10, location="args") + .add_argument("user_id", type=str, required=False, location="args") + .add_argument("named_only", type=inputs.boolean, required=False, default=False, location="args") +) + + @console_ns.route("/rag/pipelines//workflows") class PublishedAllRagPipelineApi(Resource): + @api.expect(parser_wf) @setup_required @login_required @account_initialization_required @@ -654,16 +677,9 @@ class PublishedAllRagPipelineApi(Resource): if not current_user.has_edit_permission: raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument("page", type=inputs.int_range(1, 99999), required=False, default=1, location="args") - .add_argument("limit", type=inputs.int_range(1, 100), required=False, default=20, location="args") - .add_argument("user_id", type=str, required=False, location="args") - .add_argument("named_only", type=inputs.boolean, required=False, default=False, location="args") - ) - args = parser.parse_args() - page = int(args.get("page", 1)) - limit = int(args.get("limit", 10)) + args = parser_wf.parse_args() + page = args["page"] + limit = args["limit"] user_id = args.get("user_id") named_only = args.get("named_only", False) @@ -691,8 +707,16 @@ class PublishedAllRagPipelineApi(Resource): } +parser_wf_id = ( + reqparse.RequestParser() + .add_argument("marked_name", type=str, required=False, location="json") + .add_argument("marked_comment", type=str, required=False, location="json") +) + + @console_ns.route("/rag/pipelines//workflows/") class RagPipelineByIdApi(Resource): + @api.expect(parser_wf_id) @setup_required @login_required @account_initialization_required @@ -707,19 +731,13 @@ class RagPipelineByIdApi(Resource): if not current_user.has_edit_permission: raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument("marked_name", type=str, required=False, location="json") - .add_argument("marked_comment", type=str, required=False, location="json") - ) - args = parser.parse_args() + args = parser_wf_id.parse_args() # Validate name and comment length if args.marked_name and len(args.marked_name) > 20: raise ValueError("Marked name cannot exceed 20 characters") if args.marked_comment and len(args.marked_comment) > 100: raise ValueError("Marked comment cannot exceed 100 characters") - args = parser.parse_args() # Prepare update data update_data = {} @@ -752,8 +770,12 @@ class RagPipelineByIdApi(Resource): return workflow +parser_parameters = reqparse.RequestParser().add_argument("node_id", type=str, required=True, location="args") + + @console_ns.route("/rag/pipelines//workflows/published/processing/parameters") class PublishedRagPipelineSecondStepApi(Resource): + @api.expect(parser_parameters) @setup_required @login_required @account_initialization_required @@ -763,8 +785,7 @@ class PublishedRagPipelineSecondStepApi(Resource): """ Get second step parameters of rag pipeline """ - parser = reqparse.RequestParser().add_argument("node_id", type=str, required=True, location="args") - args = parser.parse_args() + args = parser_parameters.parse_args() node_id = args.get("node_id") if not node_id: raise ValueError("Node ID is required") @@ -777,6 +798,7 @@ class PublishedRagPipelineSecondStepApi(Resource): @console_ns.route("/rag/pipelines//workflows/published/pre-processing/parameters") class PublishedRagPipelineFirstStepApi(Resource): + @api.expect(parser_parameters) @setup_required @login_required @account_initialization_required @@ -786,8 +808,7 @@ class PublishedRagPipelineFirstStepApi(Resource): """ Get first step parameters of rag pipeline """ - parser = reqparse.RequestParser().add_argument("node_id", type=str, required=True, location="args") - args = parser.parse_args() + args = parser_parameters.parse_args() node_id = args.get("node_id") if not node_id: raise ValueError("Node ID is required") @@ -800,6 +821,7 @@ class PublishedRagPipelineFirstStepApi(Resource): @console_ns.route("/rag/pipelines//workflows/draft/pre-processing/parameters") class DraftRagPipelineFirstStepApi(Resource): + @api.expect(parser_parameters) @setup_required @login_required @account_initialization_required @@ -809,8 +831,7 @@ class DraftRagPipelineFirstStepApi(Resource): """ Get first step parameters of rag pipeline """ - parser = reqparse.RequestParser().add_argument("node_id", type=str, required=True, location="args") - args = parser.parse_args() + args = parser_parameters.parse_args() node_id = args.get("node_id") if not node_id: raise ValueError("Node ID is required") @@ -823,6 +844,7 @@ class DraftRagPipelineFirstStepApi(Resource): @console_ns.route("/rag/pipelines//workflows/draft/processing/parameters") class DraftRagPipelineSecondStepApi(Resource): + @api.expect(parser_parameters) @setup_required @login_required @account_initialization_required @@ -832,8 +854,7 @@ class DraftRagPipelineSecondStepApi(Resource): """ Get second step parameters of rag pipeline """ - parser = reqparse.RequestParser().add_argument("node_id", type=str, required=True, location="args") - args = parser.parse_args() + args = parser_parameters.parse_args() node_id = args.get("node_id") if not node_id: raise ValueError("Node ID is required") @@ -845,8 +866,16 @@ class DraftRagPipelineSecondStepApi(Resource): } +parser_wf_run = ( + reqparse.RequestParser() + .add_argument("last_id", type=uuid_value, location="args") + .add_argument("limit", type=int_range(1, 100), required=False, default=20, location="args") +) + + @console_ns.route("/rag/pipelines//workflow-runs") class RagPipelineWorkflowRunListApi(Resource): + @api.expect(parser_wf_run) @setup_required @login_required @account_initialization_required @@ -856,12 +885,7 @@ class RagPipelineWorkflowRunListApi(Resource): """ Get workflow run list """ - parser = ( - reqparse.RequestParser() - .add_argument("last_id", type=uuid_value, location="args") - .add_argument("limit", type=int_range(1, 100), required=False, default=20, location="args") - ) - args = parser.parse_args() + args = parser_wf_run.parse_args() rag_pipeline_service = RagPipelineService() result = rag_pipeline_service.get_rag_pipeline_paginate_workflow_runs(pipeline=pipeline, args=args) @@ -961,8 +985,18 @@ class RagPipelineTransformApi(Resource): return result +parser_var = ( + reqparse.RequestParser() + .add_argument("datasource_type", type=str, required=True, location="json") + .add_argument("datasource_info", type=dict, required=True, location="json") + .add_argument("start_node_id", type=str, required=True, location="json") + .add_argument("start_node_title", type=str, required=True, location="json") +) + + @console_ns.route("/rag/pipelines//workflows/draft/datasource/variables-inspect") class RagPipelineDatasourceVariableApi(Resource): + @api.expect(parser_var) @setup_required @login_required @account_initialization_required @@ -974,14 +1008,7 @@ class RagPipelineDatasourceVariableApi(Resource): Set datasource variables """ current_user, _ = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("datasource_type", type=str, required=True, location="json") - .add_argument("datasource_info", type=dict, required=True, location="json") - .add_argument("start_node_id", type=str, required=True, location="json") - .add_argument("start_node_title", type=str, required=True, location="json") - ) - args = parser.parse_args() + args = parser_var.parse_args() rag_pipeline_service = RagPipelineService() workflow_node_execution = rag_pipeline_service.set_datasource_variables( diff --git a/api/controllers/console/explore/recommended_app.py b/api/controllers/console/explore/recommended_app.py index 751012757a..11c7a1bc18 100644 --- a/api/controllers/console/explore/recommended_app.py +++ b/api/controllers/console/explore/recommended_app.py @@ -1,7 +1,7 @@ from flask_restx import Resource, fields, marshal_with, reqparse from constants.languages import languages -from controllers.console import console_ns +from controllers.console import api, console_ns from controllers.console.wraps import account_initialization_required from libs.helper import AppIconUrlField from libs.login import current_user, login_required @@ -35,15 +35,18 @@ recommended_app_list_fields = { } +parser_apps = reqparse.RequestParser().add_argument("language", type=str, location="args") + + @console_ns.route("/explore/apps") class RecommendedAppListApi(Resource): + @api.expect(parser_apps) @login_required @account_initialization_required @marshal_with(recommended_app_list_fields) def get(self): # language args - parser = reqparse.RequestParser().add_argument("language", type=str, location="args") - args = parser.parse_args() + args = parser_apps.parse_args() language = args.get("language") if language and language in languages: diff --git a/api/controllers/console/remote_files.py b/api/controllers/console/remote_files.py index 96c86dc0db..47c7ecde9a 100644 --- a/api/controllers/console/remote_files.py +++ b/api/controllers/console/remote_files.py @@ -10,6 +10,7 @@ from controllers.common.errors import ( RemoteFileUploadError, UnsupportedFileTypeError, ) +from controllers.console import api from core.file import helpers as file_helpers from core.helper import ssrf_proxy from extensions.ext_database import db @@ -36,12 +37,15 @@ class RemoteFileInfoApi(Resource): } +parser_upload = reqparse.RequestParser().add_argument("url", type=str, required=True, help="URL is required") + + @console_ns.route("/remote-files/upload") class RemoteFileUploadApi(Resource): + @api.expect(parser_upload) @marshal_with(file_fields_with_signed_url) def post(self): - parser = reqparse.RequestParser().add_argument("url", type=str, required=True, help="URL is required") - args = parser.parse_args() + args = parser_upload.parse_args() url = args["url"] diff --git a/api/controllers/console/setup.py b/api/controllers/console/setup.py index 1200349e2d..22929c851e 100644 --- a/api/controllers/console/setup.py +++ b/api/controllers/console/setup.py @@ -49,6 +49,7 @@ class SetupApi(Resource): "email": fields.String(required=True, description="Admin email address"), "name": fields.String(required=True, description="Admin name (max 30 characters)"), "password": fields.String(required=True, description="Admin password"), + "language": fields.String(required=False, description="Admin language"), }, ) ) diff --git a/api/controllers/console/tag/tags.py b/api/controllers/console/tag/tags.py index 40ae7fb4d0..ca8259238b 100644 --- a/api/controllers/console/tag/tags.py +++ b/api/controllers/console/tag/tags.py @@ -2,7 +2,7 @@ from flask import request from flask_restx import Resource, marshal_with, reqparse from werkzeug.exceptions import Forbidden -from controllers.console import console_ns +from controllers.console import api, console_ns from controllers.console.wraps import account_initialization_required, setup_required from fields.tag_fields import dataset_tag_fields from libs.login import current_account_with_tenant, login_required @@ -16,6 +16,19 @@ def _validate_name(name): return name +parser_tags = ( + reqparse.RequestParser() + .add_argument( + "name", + nullable=False, + required=True, + help="Name must be between 1 to 50 characters.", + type=_validate_name, + ) + .add_argument("type", type=str, location="json", choices=Tag.TAG_TYPE_LIST, nullable=True, help="Invalid tag type.") +) + + @console_ns.route("/tags") class TagListApi(Resource): @setup_required @@ -30,6 +43,7 @@ class TagListApi(Resource): return tags, 200 + @api.expect(parser_tags) @setup_required @login_required @account_initialization_required @@ -39,20 +53,7 @@ class TagListApi(Resource): if not (current_user.has_edit_permission or current_user.is_dataset_editor): raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument( - "name", - nullable=False, - required=True, - help="Name must be between 1 to 50 characters.", - type=_validate_name, - ) - .add_argument( - "type", type=str, location="json", choices=Tag.TAG_TYPE_LIST, nullable=True, help="Invalid tag type." - ) - ) - args = parser.parse_args() + args = parser_tags.parse_args() tag = TagService.save_tags(args) response = {"id": tag.id, "name": tag.name, "type": tag.type, "binding_count": 0} @@ -60,8 +61,14 @@ class TagListApi(Resource): return response, 200 +parser_tag_id = reqparse.RequestParser().add_argument( + "name", nullable=False, required=True, help="Name must be between 1 to 50 characters.", type=_validate_name +) + + @console_ns.route("/tags/") class TagUpdateDeleteApi(Resource): + @api.expect(parser_tag_id) @setup_required @login_required @account_initialization_required @@ -72,10 +79,7 @@ class TagUpdateDeleteApi(Resource): if not (current_user.has_edit_permission or current_user.is_dataset_editor): raise Forbidden() - parser = reqparse.RequestParser().add_argument( - "name", nullable=False, required=True, help="Name must be between 1 to 50 characters.", type=_validate_name - ) - args = parser.parse_args() + args = parser_tag_id.parse_args() tag = TagService.update_tags(args, tag_id) binding_count = TagService.get_tag_binding_count(tag_id) @@ -99,8 +103,17 @@ class TagUpdateDeleteApi(Resource): return 204 +parser_create = ( + reqparse.RequestParser() + .add_argument("tag_ids", type=list, nullable=False, required=True, location="json", help="Tag IDs is required.") + .add_argument("target_id", type=str, nullable=False, required=True, location="json", help="Target ID is required.") + .add_argument("type", type=str, location="json", choices=Tag.TAG_TYPE_LIST, nullable=True, help="Invalid tag type.") +) + + @console_ns.route("/tag-bindings/create") class TagBindingCreateApi(Resource): + @api.expect(parser_create) @setup_required @login_required @account_initialization_required @@ -110,26 +123,23 @@ class TagBindingCreateApi(Resource): if not (current_user.has_edit_permission or current_user.is_dataset_editor): raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument( - "tag_ids", type=list, nullable=False, required=True, location="json", help="Tag IDs is required." - ) - .add_argument( - "target_id", type=str, nullable=False, required=True, location="json", help="Target ID is required." - ) - .add_argument( - "type", type=str, location="json", choices=Tag.TAG_TYPE_LIST, nullable=True, help="Invalid tag type." - ) - ) - args = parser.parse_args() + args = parser_create.parse_args() TagService.save_tag_binding(args) return {"result": "success"}, 200 +parser_remove = ( + reqparse.RequestParser() + .add_argument("tag_id", type=str, nullable=False, required=True, help="Tag ID is required.") + .add_argument("target_id", type=str, nullable=False, required=True, help="Target ID is required.") + .add_argument("type", type=str, location="json", choices=Tag.TAG_TYPE_LIST, nullable=True, help="Invalid tag type.") +) + + @console_ns.route("/tag-bindings/remove") class TagBindingDeleteApi(Resource): + @api.expect(parser_remove) @setup_required @login_required @account_initialization_required @@ -139,15 +149,7 @@ class TagBindingDeleteApi(Resource): if not (current_user.has_edit_permission or current_user.is_dataset_editor): raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument("tag_id", type=str, nullable=False, required=True, help="Tag ID is required.") - .add_argument("target_id", type=str, nullable=False, required=True, help="Target ID is required.") - .add_argument( - "type", type=str, location="json", choices=Tag.TAG_TYPE_LIST, nullable=True, help="Invalid tag type." - ) - ) - args = parser.parse_args() + args = parser_remove.parse_args() TagService.delete_tag_binding(args) return {"result": "success"}, 200 diff --git a/api/controllers/console/version.py b/api/controllers/console/version.py index 417486f59e..104a205fc8 100644 --- a/api/controllers/console/version.py +++ b/api/controllers/console/version.py @@ -11,16 +11,16 @@ from . import api, console_ns logger = logging.getLogger(__name__) +parser = reqparse.RequestParser().add_argument( + "current_version", type=str, required=True, location="args", help="Current application version" +) + @console_ns.route("/version") class VersionApi(Resource): @api.doc("check_version_update") @api.doc(description="Check for application version updates") - @api.expect( - api.parser().add_argument( - "current_version", type=str, required=True, location="args", help="Current application version" - ) - ) + @api.expect(parser) @api.response( 200, "Success", @@ -37,7 +37,6 @@ class VersionApi(Resource): ) def get(self): """Check for application version updates""" - parser = reqparse.RequestParser().add_argument("current_version", type=str, required=True, location="args") args = parser.parse_args() check_update_url = dify_config.CHECK_UPDATE_URL diff --git a/api/controllers/console/workspace/account.py b/api/controllers/console/workspace/account.py index 499a52370f..0833b39f41 100644 --- a/api/controllers/console/workspace/account.py +++ b/api/controllers/console/workspace/account.py @@ -8,7 +8,7 @@ from sqlalchemy.orm import Session from configs import dify_config from constants.languages import supported_language -from controllers.console import console_ns +from controllers.console import api, console_ns from controllers.console.auth.error import ( EmailAlreadyInUseError, EmailChangeLimitError, @@ -43,8 +43,19 @@ from services.billing_service import BillingService from services.errors.account import CurrentPasswordIncorrectError as ServiceCurrentPasswordIncorrectError +def _init_parser(): + parser = reqparse.RequestParser() + if dify_config.EDITION == "CLOUD": + parser.add_argument("invitation_code", type=str, location="json") + parser.add_argument("interface_language", type=supported_language, required=True, location="json").add_argument( + "timezone", type=timezone, required=True, location="json" + ) + return parser + + @console_ns.route("/account/init") class AccountInitApi(Resource): + @api.expect(_init_parser()) @setup_required @login_required def post(self): @@ -53,14 +64,7 @@ class AccountInitApi(Resource): if account.status == "active": raise AccountAlreadyInitedError() - parser = reqparse.RequestParser() - - if dify_config.EDITION == "CLOUD": - parser.add_argument("invitation_code", type=str, location="json") - parser.add_argument("interface_language", type=supported_language, required=True, location="json").add_argument( - "timezone", type=timezone, required=True, location="json" - ) - args = parser.parse_args() + args = _init_parser().parse_args() if dify_config.EDITION == "CLOUD": if not args["invitation_code"]: @@ -106,16 +110,19 @@ class AccountProfileApi(Resource): return current_user +parser_name = reqparse.RequestParser().add_argument("name", type=str, required=True, location="json") + + @console_ns.route("/account/name") class AccountNameApi(Resource): + @api.expect(parser_name) @setup_required @login_required @account_initialization_required @marshal_with(account_fields) def post(self): current_user, _ = current_account_with_tenant() - parser = reqparse.RequestParser().add_argument("name", type=str, required=True, location="json") - args = parser.parse_args() + args = parser_name.parse_args() # Validate account name length if len(args["name"]) < 3 or len(args["name"]) > 30: @@ -126,68 +133,80 @@ class AccountNameApi(Resource): return updated_account +parser_avatar = reqparse.RequestParser().add_argument("avatar", type=str, required=True, location="json") + + @console_ns.route("/account/avatar") class AccountAvatarApi(Resource): + @api.expect(parser_avatar) @setup_required @login_required @account_initialization_required @marshal_with(account_fields) def post(self): current_user, _ = current_account_with_tenant() - parser = reqparse.RequestParser().add_argument("avatar", type=str, required=True, location="json") - args = parser.parse_args() + args = parser_avatar.parse_args() updated_account = AccountService.update_account(current_user, avatar=args["avatar"]) return updated_account +parser_interface = reqparse.RequestParser().add_argument( + "interface_language", type=supported_language, required=True, location="json" +) + + @console_ns.route("/account/interface-language") class AccountInterfaceLanguageApi(Resource): + @api.expect(parser_interface) @setup_required @login_required @account_initialization_required @marshal_with(account_fields) def post(self): current_user, _ = current_account_with_tenant() - parser = reqparse.RequestParser().add_argument( - "interface_language", type=supported_language, required=True, location="json" - ) - args = parser.parse_args() + args = parser_interface.parse_args() updated_account = AccountService.update_account(current_user, interface_language=args["interface_language"]) return updated_account +parser_theme = reqparse.RequestParser().add_argument( + "interface_theme", type=str, choices=["light", "dark"], required=True, location="json" +) + + @console_ns.route("/account/interface-theme") class AccountInterfaceThemeApi(Resource): + @api.expect(parser_theme) @setup_required @login_required @account_initialization_required @marshal_with(account_fields) def post(self): current_user, _ = current_account_with_tenant() - parser = reqparse.RequestParser().add_argument( - "interface_theme", type=str, choices=["light", "dark"], required=True, location="json" - ) - args = parser.parse_args() + args = parser_theme.parse_args() updated_account = AccountService.update_account(current_user, interface_theme=args["interface_theme"]) return updated_account +parser_timezone = reqparse.RequestParser().add_argument("timezone", type=str, required=True, location="json") + + @console_ns.route("/account/timezone") class AccountTimezoneApi(Resource): + @api.expect(parser_timezone) @setup_required @login_required @account_initialization_required @marshal_with(account_fields) def post(self): current_user, _ = current_account_with_tenant() - parser = reqparse.RequestParser().add_argument("timezone", type=str, required=True, location="json") - args = parser.parse_args() + args = parser_timezone.parse_args() # Validate timezone string, e.g. America/New_York, Asia/Shanghai if args["timezone"] not in pytz.all_timezones: @@ -198,21 +217,24 @@ class AccountTimezoneApi(Resource): return updated_account +parser_pw = ( + reqparse.RequestParser() + .add_argument("password", type=str, required=False, location="json") + .add_argument("new_password", type=str, required=True, location="json") + .add_argument("repeat_new_password", type=str, required=True, location="json") +) + + @console_ns.route("/account/password") class AccountPasswordApi(Resource): + @api.expect(parser_pw) @setup_required @login_required @account_initialization_required @marshal_with(account_fields) def post(self): current_user, _ = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("password", type=str, required=False, location="json") - .add_argument("new_password", type=str, required=True, location="json") - .add_argument("repeat_new_password", type=str, required=True, location="json") - ) - args = parser.parse_args() + args = parser_pw.parse_args() if args["new_password"] != args["repeat_new_password"]: raise RepeatPasswordNotMatchError() @@ -294,20 +316,23 @@ class AccountDeleteVerifyApi(Resource): return {"result": "success", "data": token} +parser_delete = ( + reqparse.RequestParser() + .add_argument("token", type=str, required=True, location="json") + .add_argument("code", type=str, required=True, location="json") +) + + @console_ns.route("/account/delete") class AccountDeleteApi(Resource): + @api.expect(parser_delete) @setup_required @login_required @account_initialization_required def post(self): account, _ = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("token", type=str, required=True, location="json") - .add_argument("code", type=str, required=True, location="json") - ) - args = parser.parse_args() + args = parser_delete.parse_args() if not AccountService.verify_account_deletion_code(args["token"], args["code"]): raise InvalidAccountDeletionCodeError() @@ -317,16 +342,19 @@ class AccountDeleteApi(Resource): return {"result": "success"} +parser_feedback = ( + reqparse.RequestParser() + .add_argument("email", type=str, required=True, location="json") + .add_argument("feedback", type=str, required=True, location="json") +) + + @console_ns.route("/account/delete/feedback") class AccountDeleteUpdateFeedbackApi(Resource): + @api.expect(parser_feedback) @setup_required def post(self): - parser = ( - reqparse.RequestParser() - .add_argument("email", type=str, required=True, location="json") - .add_argument("feedback", type=str, required=True, location="json") - ) - args = parser.parse_args() + args = parser_feedback.parse_args() BillingService.update_account_deletion_feedback(args["email"], args["feedback"]) @@ -351,6 +379,14 @@ class EducationVerifyApi(Resource): return BillingService.EducationIdentity.verify(account.id, account.email) +parser_edu = ( + reqparse.RequestParser() + .add_argument("token", type=str, required=True, location="json") + .add_argument("institution", type=str, required=True, location="json") + .add_argument("role", type=str, required=True, location="json") +) + + @console_ns.route("/account/education") class EducationApi(Resource): status_fields = { @@ -360,6 +396,7 @@ class EducationApi(Resource): "allow_refresh": fields.Boolean, } + @api.expect(parser_edu) @setup_required @login_required @account_initialization_required @@ -368,13 +405,7 @@ class EducationApi(Resource): def post(self): account, _ = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("token", type=str, required=True, location="json") - .add_argument("institution", type=str, required=True, location="json") - .add_argument("role", type=str, required=True, location="json") - ) - args = parser.parse_args() + args = parser_edu.parse_args() return BillingService.EducationIdentity.activate(account, args["token"], args["institution"], args["role"]) @@ -394,6 +425,14 @@ class EducationApi(Resource): return res +parser_autocomplete = ( + reqparse.RequestParser() + .add_argument("keywords", type=str, required=True, location="args") + .add_argument("page", type=int, required=False, location="args", default=0) + .add_argument("limit", type=int, required=False, location="args", default=20) +) + + @console_ns.route("/account/education/autocomplete") class EducationAutoCompleteApi(Resource): data_fields = { @@ -402,6 +441,7 @@ class EducationAutoCompleteApi(Resource): "has_next": fields.Boolean, } + @api.expect(parser_autocomplete) @setup_required @login_required @account_initialization_required @@ -409,33 +449,30 @@ class EducationAutoCompleteApi(Resource): @cloud_edition_billing_enabled @marshal_with(data_fields) def get(self): - parser = ( - reqparse.RequestParser() - .add_argument("keywords", type=str, required=True, location="args") - .add_argument("page", type=int, required=False, location="args", default=0) - .add_argument("limit", type=int, required=False, location="args", default=20) - ) - args = parser.parse_args() + args = parser_autocomplete.parse_args() return BillingService.EducationIdentity.autocomplete(args["keywords"], args["page"], args["limit"]) +parser_change_email = ( + reqparse.RequestParser() + .add_argument("email", type=email, required=True, location="json") + .add_argument("language", type=str, required=False, location="json") + .add_argument("phase", type=str, required=False, location="json") + .add_argument("token", type=str, required=False, location="json") +) + + @console_ns.route("/account/change-email") class ChangeEmailSendEmailApi(Resource): + @api.expect(parser_change_email) @enable_change_email @setup_required @login_required @account_initialization_required def post(self): current_user, _ = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("email", type=email, required=True, location="json") - .add_argument("language", type=str, required=False, location="json") - .add_argument("phase", type=str, required=False, location="json") - .add_argument("token", type=str, required=False, location="json") - ) - args = parser.parse_args() + args = parser_change_email.parse_args() ip_address = extract_remote_ip(request) if AccountService.is_email_send_ip_limit(ip_address): @@ -470,20 +507,23 @@ class ChangeEmailSendEmailApi(Resource): return {"result": "success", "data": token} +parser_validity = ( + reqparse.RequestParser() + .add_argument("email", type=email, required=True, location="json") + .add_argument("code", type=str, required=True, location="json") + .add_argument("token", type=str, required=True, nullable=False, location="json") +) + + @console_ns.route("/account/change-email/validity") class ChangeEmailCheckApi(Resource): + @api.expect(parser_validity) @enable_change_email @setup_required @login_required @account_initialization_required def post(self): - parser = ( - reqparse.RequestParser() - .add_argument("email", type=email, required=True, location="json") - .add_argument("code", type=str, required=True, location="json") - .add_argument("token", type=str, required=True, nullable=False, location="json") - ) - args = parser.parse_args() + args = parser_validity.parse_args() user_email = args["email"] @@ -514,20 +554,23 @@ class ChangeEmailCheckApi(Resource): return {"is_valid": True, "email": token_data.get("email"), "token": new_token} +parser_reset = ( + reqparse.RequestParser() + .add_argument("new_email", type=email, required=True, location="json") + .add_argument("token", type=str, required=True, nullable=False, location="json") +) + + @console_ns.route("/account/change-email/reset") class ChangeEmailResetApi(Resource): + @api.expect(parser_reset) @enable_change_email @setup_required @login_required @account_initialization_required @marshal_with(account_fields) def post(self): - parser = ( - reqparse.RequestParser() - .add_argument("new_email", type=email, required=True, location="json") - .add_argument("token", type=str, required=True, nullable=False, location="json") - ) - args = parser.parse_args() + args = parser_reset.parse_args() if AccountService.is_account_in_freeze(args["new_email"]): raise AccountInFreezeError() @@ -555,12 +598,15 @@ class ChangeEmailResetApi(Resource): return updated_account +parser_check = reqparse.RequestParser().add_argument("email", type=email, required=True, location="json") + + @console_ns.route("/account/change-email/check-email-unique") class CheckEmailUnique(Resource): + @api.expect(parser_check) @setup_required def post(self): - parser = reqparse.RequestParser().add_argument("email", type=email, required=True, location="json") - args = parser.parse_args() + args = parser_check.parse_args() if AccountService.is_account_in_freeze(args["email"]): raise AccountInFreezeError() if not AccountService.check_email_unique(args["email"]): diff --git a/api/controllers/console/workspace/members.py b/api/controllers/console/workspace/members.py index d66f861799..3ca453f1da 100644 --- a/api/controllers/console/workspace/members.py +++ b/api/controllers/console/workspace/members.py @@ -5,7 +5,7 @@ from flask_restx import Resource, marshal_with, reqparse import services from configs import dify_config -from controllers.console import console_ns +from controllers.console import api, console_ns from controllers.console.auth.error import ( CannotTransferOwnerToSelfError, EmailCodeError, @@ -48,22 +48,25 @@ class MemberListApi(Resource): return {"result": "success", "accounts": members}, 200 +parser_invite = ( + reqparse.RequestParser() + .add_argument("emails", type=list, required=True, location="json") + .add_argument("role", type=str, required=True, default="admin", location="json") + .add_argument("language", type=str, required=False, location="json") +) + + @console_ns.route("/workspaces/current/members/invite-email") class MemberInviteEmailApi(Resource): """Invite a new member by email.""" + @api.expect(parser_invite) @setup_required @login_required @account_initialization_required @cloud_edition_billing_resource_check("members") def post(self): - parser = ( - reqparse.RequestParser() - .add_argument("emails", type=list, required=True, location="json") - .add_argument("role", type=str, required=True, default="admin", location="json") - .add_argument("language", type=str, required=False, location="json") - ) - args = parser.parse_args() + args = parser_invite.parse_args() invitee_emails = args["emails"] invitee_role = args["role"] @@ -143,16 +146,19 @@ class MemberCancelInviteApi(Resource): }, 200 +parser_update = reqparse.RequestParser().add_argument("role", type=str, required=True, location="json") + + @console_ns.route("/workspaces/current/members//update-role") class MemberUpdateRoleApi(Resource): """Update member role.""" + @api.expect(parser_update) @setup_required @login_required @account_initialization_required def put(self, member_id): - parser = reqparse.RequestParser().add_argument("role", type=str, required=True, location="json") - args = parser.parse_args() + args = parser_update.parse_args() new_role = args["role"] if not TenantAccountRole.is_valid_role(new_role): @@ -191,17 +197,20 @@ class DatasetOperatorMemberListApi(Resource): return {"result": "success", "accounts": members}, 200 +parser_send = reqparse.RequestParser().add_argument("language", type=str, required=False, location="json") + + @console_ns.route("/workspaces/current/members/send-owner-transfer-confirm-email") class SendOwnerTransferEmailApi(Resource): """Send owner transfer email.""" + @api.expect(parser_send) @setup_required @login_required @account_initialization_required @is_allow_transfer_owner def post(self): - parser = reqparse.RequestParser().add_argument("language", type=str, required=False, location="json") - args = parser.parse_args() + args = parser_send.parse_args() ip_address = extract_remote_ip(request) if AccountService.is_email_send_ip_limit(ip_address): raise EmailSendIpLimitError() @@ -229,19 +238,22 @@ class SendOwnerTransferEmailApi(Resource): return {"result": "success", "data": token} +parser_owner = ( + reqparse.RequestParser() + .add_argument("code", type=str, required=True, location="json") + .add_argument("token", type=str, required=True, nullable=False, location="json") +) + + @console_ns.route("/workspaces/current/members/owner-transfer-check") class OwnerTransferCheckApi(Resource): + @api.expect(parser_owner) @setup_required @login_required @account_initialization_required @is_allow_transfer_owner def post(self): - parser = ( - reqparse.RequestParser() - .add_argument("code", type=str, required=True, location="json") - .add_argument("token", type=str, required=True, nullable=False, location="json") - ) - args = parser.parse_args() + args = parser_owner.parse_args() # check if the current user is the owner of the workspace current_user, _ = current_account_with_tenant() if not current_user.current_tenant: @@ -276,17 +288,20 @@ class OwnerTransferCheckApi(Resource): return {"is_valid": True, "email": token_data.get("email"), "token": new_token} +parser_owner_transfer = reqparse.RequestParser().add_argument( + "token", type=str, required=True, nullable=False, location="json" +) + + @console_ns.route("/workspaces/current/members//owner-transfer") class OwnerTransfer(Resource): + @api.expect(parser_owner_transfer) @setup_required @login_required @account_initialization_required @is_allow_transfer_owner def post(self, member_id): - parser = reqparse.RequestParser().add_argument( - "token", type=str, required=True, nullable=False, location="json" - ) - args = parser.parse_args() + args = parser_owner_transfer.parse_args() # check if the current user is the owner of the workspace current_user, _ = current_account_with_tenant() diff --git a/api/controllers/console/workspace/model_providers.py b/api/controllers/console/workspace/model_providers.py index 04db975fc2..832ec8af0f 100644 --- a/api/controllers/console/workspace/model_providers.py +++ b/api/controllers/console/workspace/model_providers.py @@ -4,7 +4,7 @@ from flask import send_file from flask_restx import Resource, reqparse from werkzeug.exceptions import Forbidden -from controllers.console import console_ns +from controllers.console import api, console_ns from controllers.console.wraps import account_initialization_required, setup_required from core.model_runtime.entities.model_entities import ModelType from core.model_runtime.errors.validate import CredentialsValidateFailedError @@ -14,9 +14,19 @@ from libs.login import current_account_with_tenant, login_required from services.billing_service import BillingService from services.model_provider_service import ModelProviderService +parser_model = reqparse.RequestParser().add_argument( + "model_type", + type=str, + required=False, + nullable=True, + choices=[mt.value for mt in ModelType], + location="args", +) + @console_ns.route("/workspaces/current/model-providers") class ModelProviderListApi(Resource): + @api.expect(parser_model) @setup_required @login_required @account_initialization_required @@ -24,15 +34,7 @@ class ModelProviderListApi(Resource): _, current_tenant_id = current_account_with_tenant() tenant_id = current_tenant_id - parser = reqparse.RequestParser().add_argument( - "model_type", - type=str, - required=False, - nullable=True, - choices=[mt.value for mt in ModelType], - location="args", - ) - args = parser.parse_args() + args = parser_model.parse_args() model_provider_service = ModelProviderService() provider_list = model_provider_service.get_provider_list(tenant_id=tenant_id, model_type=args.get("model_type")) @@ -40,8 +42,30 @@ class ModelProviderListApi(Resource): return jsonable_encoder({"data": provider_list}) +parser_cred = reqparse.RequestParser().add_argument( + "credential_id", type=uuid_value, required=False, nullable=True, location="args" +) +parser_post_cred = ( + reqparse.RequestParser() + .add_argument("credentials", type=dict, required=True, nullable=False, location="json") + .add_argument("name", type=StrLen(30), required=False, nullable=True, location="json") +) + +parser_put_cred = ( + reqparse.RequestParser() + .add_argument("credential_id", type=uuid_value, required=True, nullable=False, location="json") + .add_argument("credentials", type=dict, required=True, nullable=False, location="json") + .add_argument("name", type=StrLen(30), required=False, nullable=True, location="json") +) + +parser_delete_cred = reqparse.RequestParser().add_argument( + "credential_id", type=uuid_value, required=True, nullable=False, location="json" +) + + @console_ns.route("/workspaces/current/model-providers//credentials") class ModelProviderCredentialApi(Resource): + @api.expect(parser_cred) @setup_required @login_required @account_initialization_required @@ -49,10 +73,7 @@ class ModelProviderCredentialApi(Resource): _, current_tenant_id = current_account_with_tenant() tenant_id = current_tenant_id # if credential_id is not provided, return current used credential - parser = reqparse.RequestParser().add_argument( - "credential_id", type=uuid_value, required=False, nullable=True, location="args" - ) - args = parser.parse_args() + args = parser_cred.parse_args() model_provider_service = ModelProviderService() credentials = model_provider_service.get_provider_credential( @@ -61,6 +82,7 @@ class ModelProviderCredentialApi(Resource): return {"credentials": credentials} + @api.expect(parser_post_cred) @setup_required @login_required @account_initialization_required @@ -69,12 +91,7 @@ class ModelProviderCredentialApi(Resource): if not current_user.is_admin_or_owner: raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument("credentials", type=dict, required=True, nullable=False, location="json") - .add_argument("name", type=StrLen(30), required=False, nullable=True, location="json") - ) - args = parser.parse_args() + args = parser_post_cred.parse_args() model_provider_service = ModelProviderService() @@ -90,6 +107,7 @@ class ModelProviderCredentialApi(Resource): return {"result": "success"}, 201 + @api.expect(parser_put_cred) @setup_required @login_required @account_initialization_required @@ -98,13 +116,7 @@ class ModelProviderCredentialApi(Resource): if not current_user.is_admin_or_owner: raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument("credential_id", type=uuid_value, required=True, nullable=False, location="json") - .add_argument("credentials", type=dict, required=True, nullable=False, location="json") - .add_argument("name", type=StrLen(30), required=False, nullable=True, location="json") - ) - args = parser.parse_args() + args = parser_put_cred.parse_args() model_provider_service = ModelProviderService() @@ -121,6 +133,7 @@ class ModelProviderCredentialApi(Resource): return {"result": "success"} + @api.expect(parser_delete_cred) @setup_required @login_required @account_initialization_required @@ -128,10 +141,8 @@ class ModelProviderCredentialApi(Resource): current_user, current_tenant_id = current_account_with_tenant() if not current_user.is_admin_or_owner: raise Forbidden() - parser = reqparse.RequestParser().add_argument( - "credential_id", type=uuid_value, required=True, nullable=False, location="json" - ) - args = parser.parse_args() + + args = parser_delete_cred.parse_args() model_provider_service = ModelProviderService() model_provider_service.remove_provider_credential( @@ -141,8 +152,14 @@ class ModelProviderCredentialApi(Resource): return {"result": "success"}, 204 +parser_switch = reqparse.RequestParser().add_argument( + "credential_id", type=str, required=True, nullable=False, location="json" +) + + @console_ns.route("/workspaces/current/model-providers//credentials/switch") class ModelProviderCredentialSwitchApi(Resource): + @api.expect(parser_switch) @setup_required @login_required @account_initialization_required @@ -150,10 +167,7 @@ class ModelProviderCredentialSwitchApi(Resource): current_user, current_tenant_id = current_account_with_tenant() if not current_user.is_admin_or_owner: raise Forbidden() - parser = reqparse.RequestParser().add_argument( - "credential_id", type=str, required=True, nullable=False, location="json" - ) - args = parser.parse_args() + args = parser_switch.parse_args() service = ModelProviderService() service.switch_active_provider_credential( @@ -164,17 +178,20 @@ class ModelProviderCredentialSwitchApi(Resource): return {"result": "success"} +parser_validate = reqparse.RequestParser().add_argument( + "credentials", type=dict, required=True, nullable=False, location="json" +) + + @console_ns.route("/workspaces/current/model-providers//credentials/validate") class ModelProviderValidateApi(Resource): + @api.expect(parser_validate) @setup_required @login_required @account_initialization_required def post(self, provider: str): _, current_tenant_id = current_account_with_tenant() - parser = reqparse.RequestParser().add_argument( - "credentials", type=dict, required=True, nullable=False, location="json" - ) - args = parser.parse_args() + args = parser_validate.parse_args() tenant_id = current_tenant_id @@ -218,8 +235,19 @@ class ModelProviderIconApi(Resource): return send_file(io.BytesIO(icon), mimetype=mimetype) +parser_preferred = reqparse.RequestParser().add_argument( + "preferred_provider_type", + type=str, + required=True, + nullable=False, + choices=["system", "custom"], + location="json", +) + + @console_ns.route("/workspaces/current/model-providers//preferred-provider-type") class PreferredProviderTypeUpdateApi(Resource): + @api.expect(parser_preferred) @setup_required @login_required @account_initialization_required @@ -230,15 +258,7 @@ class PreferredProviderTypeUpdateApi(Resource): tenant_id = current_tenant_id - parser = reqparse.RequestParser().add_argument( - "preferred_provider_type", - type=str, - required=True, - nullable=False, - choices=["system", "custom"], - location="json", - ) - args = parser.parse_args() + args = parser_preferred.parse_args() model_provider_service = ModelProviderService() model_provider_service.switch_preferred_provider( diff --git a/api/controllers/console/workspace/models.py b/api/controllers/console/workspace/models.py index 5ab958d585..d6aad129a6 100644 --- a/api/controllers/console/workspace/models.py +++ b/api/controllers/console/workspace/models.py @@ -3,7 +3,7 @@ import logging from flask_restx import Resource, reqparse from werkzeug.exceptions import Forbidden -from controllers.console import console_ns +from controllers.console import api, console_ns from controllers.console.wraps import account_initialization_required, setup_required from core.model_runtime.entities.model_entities import ModelType from core.model_runtime.errors.validate import CredentialsValidateFailedError @@ -16,23 +16,29 @@ from services.model_provider_service import ModelProviderService logger = logging.getLogger(__name__) +parser_get_default = reqparse.RequestParser().add_argument( + "model_type", + type=str, + required=True, + nullable=False, + choices=[mt.value for mt in ModelType], + location="args", +) +parser_post_default = reqparse.RequestParser().add_argument( + "model_settings", type=list, required=True, nullable=False, location="json" +) + + @console_ns.route("/workspaces/current/default-model") class DefaultModelApi(Resource): + @api.expect(parser_get_default) @setup_required @login_required @account_initialization_required def get(self): _, tenant_id = current_account_with_tenant() - parser = reqparse.RequestParser().add_argument( - "model_type", - type=str, - required=True, - nullable=False, - choices=[mt.value for mt in ModelType], - location="args", - ) - args = parser.parse_args() + args = parser_get_default.parse_args() model_provider_service = ModelProviderService() default_model_entity = model_provider_service.get_default_model_of_model_type( @@ -41,6 +47,7 @@ class DefaultModelApi(Resource): return jsonable_encoder({"data": default_model_entity}) + @api.expect(parser_post_default) @setup_required @login_required @account_initialization_required @@ -50,10 +57,7 @@ class DefaultModelApi(Resource): if not current_user.is_admin_or_owner: raise Forbidden() - parser = reqparse.RequestParser().add_argument( - "model_settings", type=list, required=True, nullable=False, location="json" - ) - args = parser.parse_args() + args = parser_post_default.parse_args() model_provider_service = ModelProviderService() model_settings = args["model_settings"] for model_setting in model_settings: @@ -84,6 +88,35 @@ class DefaultModelApi(Resource): return {"result": "success"} +parser_post_models = ( + reqparse.RequestParser() + .add_argument("model", type=str, required=True, nullable=False, location="json") + .add_argument( + "model_type", + type=str, + required=True, + nullable=False, + choices=[mt.value for mt in ModelType], + location="json", + ) + .add_argument("load_balancing", type=dict, required=False, nullable=True, location="json") + .add_argument("config_from", type=str, required=False, nullable=True, location="json") + .add_argument("credential_id", type=uuid_value, required=False, nullable=True, location="json") +) +parser_delete_models = ( + reqparse.RequestParser() + .add_argument("model", type=str, required=True, nullable=False, location="json") + .add_argument( + "model_type", + type=str, + required=True, + nullable=False, + choices=[mt.value for mt in ModelType], + location="json", + ) +) + + @console_ns.route("/workspaces/current/model-providers//models") class ModelProviderModelApi(Resource): @setup_required @@ -97,6 +130,7 @@ class ModelProviderModelApi(Resource): return jsonable_encoder({"data": models}) + @api.expect(parser_post_models) @setup_required @login_required @account_initialization_required @@ -106,23 +140,7 @@ class ModelProviderModelApi(Resource): if not current_user.is_admin_or_owner: raise Forbidden() - - parser = ( - reqparse.RequestParser() - .add_argument("model", type=str, required=True, nullable=False, location="json") - .add_argument( - "model_type", - type=str, - required=True, - nullable=False, - choices=[mt.value for mt in ModelType], - location="json", - ) - .add_argument("load_balancing", type=dict, required=False, nullable=True, location="json") - .add_argument("config_from", type=str, required=False, nullable=True, location="json") - .add_argument("credential_id", type=uuid_value, required=False, nullable=True, location="json") - ) - args = parser.parse_args() + args = parser_post_models.parse_args() if args.get("config_from", "") == "custom-model": if not args.get("credential_id"): @@ -160,6 +178,7 @@ class ModelProviderModelApi(Resource): return {"result": "success"}, 200 + @api.expect(parser_delete_models) @setup_required @login_required @account_initialization_required @@ -169,19 +188,7 @@ class ModelProviderModelApi(Resource): if not current_user.is_admin_or_owner: raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument("model", type=str, required=True, nullable=False, location="json") - .add_argument( - "model_type", - type=str, - required=True, - nullable=False, - choices=[mt.value for mt in ModelType], - location="json", - ) - ) - args = parser.parse_args() + args = parser_delete_models.parse_args() model_provider_service = ModelProviderService() model_provider_service.remove_model( @@ -191,29 +198,76 @@ class ModelProviderModelApi(Resource): return {"result": "success"}, 204 +parser_get_credentials = ( + reqparse.RequestParser() + .add_argument("model", type=str, required=True, nullable=False, location="args") + .add_argument( + "model_type", + type=str, + required=True, + nullable=False, + choices=[mt.value for mt in ModelType], + location="args", + ) + .add_argument("config_from", type=str, required=False, nullable=True, location="args") + .add_argument("credential_id", type=uuid_value, required=False, nullable=True, location="args") +) + + +parser_post_cred = ( + reqparse.RequestParser() + .add_argument("model", type=str, required=True, nullable=False, location="json") + .add_argument( + "model_type", + type=str, + required=True, + nullable=False, + choices=[mt.value for mt in ModelType], + location="json", + ) + .add_argument("name", type=StrLen(30), required=False, nullable=True, location="json") + .add_argument("credentials", type=dict, required=True, nullable=False, location="json") +) +parser_put_cred = ( + reqparse.RequestParser() + .add_argument("model", type=str, required=True, nullable=False, location="json") + .add_argument( + "model_type", + type=str, + required=True, + nullable=False, + choices=[mt.value for mt in ModelType], + location="json", + ) + .add_argument("credential_id", type=uuid_value, required=True, nullable=False, location="json") + .add_argument("credentials", type=dict, required=True, nullable=False, location="json") + .add_argument("name", type=StrLen(30), required=False, nullable=True, location="json") +) +parser_delete_cred = ( + reqparse.RequestParser() + .add_argument("model", type=str, required=True, nullable=False, location="json") + .add_argument( + "model_type", + type=str, + required=True, + nullable=False, + choices=[mt.value for mt in ModelType], + location="json", + ) + .add_argument("credential_id", type=uuid_value, required=True, nullable=False, location="json") +) + + @console_ns.route("/workspaces/current/model-providers//models/credentials") class ModelProviderModelCredentialApi(Resource): + @api.expect(parser_get_credentials) @setup_required @login_required @account_initialization_required def get(self, provider: str): _, tenant_id = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("model", type=str, required=True, nullable=False, location="args") - .add_argument( - "model_type", - type=str, - required=True, - nullable=False, - choices=[mt.value for mt in ModelType], - location="args", - ) - .add_argument("config_from", type=str, required=False, nullable=True, location="args") - .add_argument("credential_id", type=uuid_value, required=False, nullable=True, location="args") - ) - args = parser.parse_args() + args = parser_get_credentials.parse_args() model_provider_service = ModelProviderService() current_credential = model_provider_service.get_model_credential( @@ -257,6 +311,7 @@ class ModelProviderModelCredentialApi(Resource): } ) + @api.expect(parser_post_cred) @setup_required @login_required @account_initialization_required @@ -266,21 +321,7 @@ class ModelProviderModelCredentialApi(Resource): if not current_user.is_admin_or_owner: raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument("model", type=str, required=True, nullable=False, location="json") - .add_argument( - "model_type", - type=str, - required=True, - nullable=False, - choices=[mt.value for mt in ModelType], - location="json", - ) - .add_argument("name", type=StrLen(30), required=False, nullable=True, location="json") - .add_argument("credentials", type=dict, required=True, nullable=False, location="json") - ) - args = parser.parse_args() + args = parser_post_cred.parse_args() model_provider_service = ModelProviderService() @@ -304,6 +345,7 @@ class ModelProviderModelCredentialApi(Resource): return {"result": "success"}, 201 + @api.expect(parser_put_cred) @setup_required @login_required @account_initialization_required @@ -313,22 +355,7 @@ class ModelProviderModelCredentialApi(Resource): if not current_user.is_admin_or_owner: raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument("model", type=str, required=True, nullable=False, location="json") - .add_argument( - "model_type", - type=str, - required=True, - nullable=False, - choices=[mt.value for mt in ModelType], - location="json", - ) - .add_argument("credential_id", type=uuid_value, required=True, nullable=False, location="json") - .add_argument("credentials", type=dict, required=True, nullable=False, location="json") - .add_argument("name", type=StrLen(30), required=False, nullable=True, location="json") - ) - args = parser.parse_args() + args = parser_put_cred.parse_args() model_provider_service = ModelProviderService() @@ -347,6 +374,7 @@ class ModelProviderModelCredentialApi(Resource): return {"result": "success"} + @api.expect(parser_delete_cred) @setup_required @login_required @account_initialization_required @@ -355,20 +383,7 @@ class ModelProviderModelCredentialApi(Resource): if not current_user.is_admin_or_owner: raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument("model", type=str, required=True, nullable=False, location="json") - .add_argument( - "model_type", - type=str, - required=True, - nullable=False, - choices=[mt.value for mt in ModelType], - location="json", - ) - .add_argument("credential_id", type=uuid_value, required=True, nullable=False, location="json") - ) - args = parser.parse_args() + args = parser_delete_cred.parse_args() model_provider_service = ModelProviderService() model_provider_service.remove_model_credential( @@ -382,8 +397,24 @@ class ModelProviderModelCredentialApi(Resource): return {"result": "success"}, 204 +parser_switch = ( + reqparse.RequestParser() + .add_argument("model", type=str, required=True, nullable=False, location="json") + .add_argument( + "model_type", + type=str, + required=True, + nullable=False, + choices=[mt.value for mt in ModelType], + location="json", + ) + .add_argument("credential_id", type=str, required=True, nullable=False, location="json") +) + + @console_ns.route("/workspaces/current/model-providers//models/credentials/switch") class ModelProviderModelCredentialSwitchApi(Resource): + @api.expect(parser_switch) @setup_required @login_required @account_initialization_required @@ -392,20 +423,7 @@ class ModelProviderModelCredentialSwitchApi(Resource): if not current_user.is_admin_or_owner: raise Forbidden() - parser = ( - reqparse.RequestParser() - .add_argument("model", type=str, required=True, nullable=False, location="json") - .add_argument( - "model_type", - type=str, - required=True, - nullable=False, - choices=[mt.value for mt in ModelType], - location="json", - ) - .add_argument("credential_id", type=str, required=True, nullable=False, location="json") - ) - args = parser.parse_args() + args = parser_switch.parse_args() service = ModelProviderService() service.add_model_credential_to_model_list( @@ -418,29 +436,32 @@ class ModelProviderModelCredentialSwitchApi(Resource): return {"result": "success"} +parser_model_enable_disable = ( + reqparse.RequestParser() + .add_argument("model", type=str, required=True, nullable=False, location="json") + .add_argument( + "model_type", + type=str, + required=True, + nullable=False, + choices=[mt.value for mt in ModelType], + location="json", + ) +) + + @console_ns.route( "/workspaces/current/model-providers//models/enable", endpoint="model-provider-model-enable" ) class ModelProviderModelEnableApi(Resource): + @api.expect(parser_model_enable_disable) @setup_required @login_required @account_initialization_required def patch(self, provider: str): _, tenant_id = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("model", type=str, required=True, nullable=False, location="json") - .add_argument( - "model_type", - type=str, - required=True, - nullable=False, - choices=[mt.value for mt in ModelType], - location="json", - ) - ) - args = parser.parse_args() + args = parser_model_enable_disable.parse_args() model_provider_service = ModelProviderService() model_provider_service.enable_model( @@ -454,25 +475,14 @@ class ModelProviderModelEnableApi(Resource): "/workspaces/current/model-providers//models/disable", endpoint="model-provider-model-disable" ) class ModelProviderModelDisableApi(Resource): + @api.expect(parser_model_enable_disable) @setup_required @login_required @account_initialization_required def patch(self, provider: str): _, tenant_id = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("model", type=str, required=True, nullable=False, location="json") - .add_argument( - "model_type", - type=str, - required=True, - nullable=False, - choices=[mt.value for mt in ModelType], - location="json", - ) - ) - args = parser.parse_args() + args = parser_model_enable_disable.parse_args() model_provider_service = ModelProviderService() model_provider_service.disable_model( @@ -482,28 +492,31 @@ class ModelProviderModelDisableApi(Resource): return {"result": "success"} +parser_validate = ( + reqparse.RequestParser() + .add_argument("model", type=str, required=True, nullable=False, location="json") + .add_argument( + "model_type", + type=str, + required=True, + nullable=False, + choices=[mt.value for mt in ModelType], + location="json", + ) + .add_argument("credentials", type=dict, required=True, nullable=False, location="json") +) + + @console_ns.route("/workspaces/current/model-providers//models/credentials/validate") class ModelProviderModelValidateApi(Resource): + @api.expect(parser_validate) @setup_required @login_required @account_initialization_required def post(self, provider: str): _, tenant_id = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("model", type=str, required=True, nullable=False, location="json") - .add_argument( - "model_type", - type=str, - required=True, - nullable=False, - choices=[mt.value for mt in ModelType], - location="json", - ) - .add_argument("credentials", type=dict, required=True, nullable=False, location="json") - ) - args = parser.parse_args() + args = parser_validate.parse_args() model_provider_service = ModelProviderService() @@ -530,16 +543,19 @@ class ModelProviderModelValidateApi(Resource): return response +parser_parameter = reqparse.RequestParser().add_argument( + "model", type=str, required=True, nullable=False, location="args" +) + + @console_ns.route("/workspaces/current/model-providers//models/parameter-rules") class ModelProviderModelParameterRuleApi(Resource): + @api.expect(parser_parameter) @setup_required @login_required @account_initialization_required def get(self, provider: str): - parser = reqparse.RequestParser().add_argument( - "model", type=str, required=True, nullable=False, location="args" - ) - args = parser.parse_args() + args = parser_parameter.parse_args() _, tenant_id = current_account_with_tenant() model_provider_service = ModelProviderService() diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py index 021dc96000..bb8c02b99a 100644 --- a/api/controllers/console/workspace/plugin.py +++ b/api/controllers/console/workspace/plugin.py @@ -5,7 +5,7 @@ from flask_restx import Resource, reqparse from werkzeug.exceptions import Forbidden from configs import dify_config -from controllers.console import console_ns +from controllers.console import api, console_ns from controllers.console.workspace import plugin_permission_required from controllers.console.wraps import account_initialization_required, setup_required from core.model_runtime.utils.encoders import jsonable_encoder @@ -37,19 +37,22 @@ class PluginDebuggingKeyApi(Resource): raise ValueError(e) +parser_list = ( + reqparse.RequestParser() + .add_argument("page", type=int, required=False, location="args", default=1) + .add_argument("page_size", type=int, required=False, location="args", default=256) +) + + @console_ns.route("/workspaces/current/plugin/list") class PluginListApi(Resource): + @api.expect(parser_list) @setup_required @login_required @account_initialization_required def get(self): _, tenant_id = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("page", type=int, required=False, location="args", default=1) - .add_argument("page_size", type=int, required=False, location="args", default=256) - ) - args = parser.parse_args() + args = parser_list.parse_args() try: plugins_with_total = PluginService.list_with_total(tenant_id, args["page"], args["page_size"]) except PluginDaemonClientSideError as e: @@ -58,14 +61,17 @@ class PluginListApi(Resource): return jsonable_encoder({"plugins": plugins_with_total.list, "total": plugins_with_total.total}) +parser_latest = reqparse.RequestParser().add_argument("plugin_ids", type=list, required=True, location="json") + + @console_ns.route("/workspaces/current/plugin/list/latest-versions") class PluginListLatestVersionsApi(Resource): + @api.expect(parser_latest) @setup_required @login_required @account_initialization_required def post(self): - req = reqparse.RequestParser().add_argument("plugin_ids", type=list, required=True, location="json") - args = req.parse_args() + args = parser_latest.parse_args() try: versions = PluginService.list_latest_versions(args["plugin_ids"]) @@ -75,16 +81,19 @@ class PluginListLatestVersionsApi(Resource): return jsonable_encoder({"versions": versions}) +parser_ids = reqparse.RequestParser().add_argument("plugin_ids", type=list, required=True, location="json") + + @console_ns.route("/workspaces/current/plugin/list/installations/ids") class PluginListInstallationsFromIdsApi(Resource): + @api.expect(parser_ids) @setup_required @login_required @account_initialization_required def post(self): _, tenant_id = current_account_with_tenant() - parser = reqparse.RequestParser().add_argument("plugin_ids", type=list, required=True, location="json") - args = parser.parse_args() + args = parser_ids.parse_args() try: plugins = PluginService.list_installations_from_ids(tenant_id, args["plugin_ids"]) @@ -94,16 +103,19 @@ class PluginListInstallationsFromIdsApi(Resource): return jsonable_encoder({"plugins": plugins}) +parser_icon = ( + reqparse.RequestParser() + .add_argument("tenant_id", type=str, required=True, location="args") + .add_argument("filename", type=str, required=True, location="args") +) + + @console_ns.route("/workspaces/current/plugin/icon") class PluginIconApi(Resource): + @api.expect(parser_icon) @setup_required def get(self): - req = ( - reqparse.RequestParser() - .add_argument("tenant_id", type=str, required=True, location="args") - .add_argument("filename", type=str, required=True, location="args") - ) - args = req.parse_args() + args = parser_icon.parse_args() try: icon_bytes, mimetype = PluginService.get_asset(args["tenant_id"], args["filename"]) @@ -157,8 +169,17 @@ class PluginUploadFromPkgApi(Resource): return jsonable_encoder(response) +parser_github = ( + reqparse.RequestParser() + .add_argument("repo", type=str, required=True, location="json") + .add_argument("version", type=str, required=True, location="json") + .add_argument("package", type=str, required=True, location="json") +) + + @console_ns.route("/workspaces/current/plugin/upload/github") class PluginUploadFromGithubApi(Resource): + @api.expect(parser_github) @setup_required @login_required @account_initialization_required @@ -166,13 +187,7 @@ class PluginUploadFromGithubApi(Resource): def post(self): _, tenant_id = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("repo", type=str, required=True, location="json") - .add_argument("version", type=str, required=True, location="json") - .add_argument("package", type=str, required=True, location="json") - ) - args = parser.parse_args() + args = parser_github.parse_args() try: response = PluginService.upload_pkg_from_github(tenant_id, args["repo"], args["version"], args["package"]) @@ -206,19 +221,21 @@ class PluginUploadFromBundleApi(Resource): return jsonable_encoder(response) +parser_pkg = reqparse.RequestParser().add_argument( + "plugin_unique_identifiers", type=list, required=True, location="json" +) + + @console_ns.route("/workspaces/current/plugin/install/pkg") class PluginInstallFromPkgApi(Resource): + @api.expect(parser_pkg) @setup_required @login_required @account_initialization_required @plugin_permission_required(install_required=True) def post(self): _, tenant_id = current_account_with_tenant() - - parser = reqparse.RequestParser().add_argument( - "plugin_unique_identifiers", type=list, required=True, location="json" - ) - args = parser.parse_args() + args = parser_pkg.parse_args() # check if all plugin_unique_identifiers are valid string for plugin_unique_identifier in args["plugin_unique_identifiers"]: @@ -233,8 +250,18 @@ class PluginInstallFromPkgApi(Resource): return jsonable_encoder(response) +parser_githubapi = ( + reqparse.RequestParser() + .add_argument("repo", type=str, required=True, location="json") + .add_argument("version", type=str, required=True, location="json") + .add_argument("package", type=str, required=True, location="json") + .add_argument("plugin_unique_identifier", type=str, required=True, location="json") +) + + @console_ns.route("/workspaces/current/plugin/install/github") class PluginInstallFromGithubApi(Resource): + @api.expect(parser_githubapi) @setup_required @login_required @account_initialization_required @@ -242,14 +269,7 @@ class PluginInstallFromGithubApi(Resource): def post(self): _, tenant_id = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("repo", type=str, required=True, location="json") - .add_argument("version", type=str, required=True, location="json") - .add_argument("package", type=str, required=True, location="json") - .add_argument("plugin_unique_identifier", type=str, required=True, location="json") - ) - args = parser.parse_args() + args = parser_githubapi.parse_args() try: response = PluginService.install_from_github( @@ -265,8 +285,14 @@ class PluginInstallFromGithubApi(Resource): return jsonable_encoder(response) +parser_marketplace = reqparse.RequestParser().add_argument( + "plugin_unique_identifiers", type=list, required=True, location="json" +) + + @console_ns.route("/workspaces/current/plugin/install/marketplace") class PluginInstallFromMarketplaceApi(Resource): + @api.expect(parser_marketplace) @setup_required @login_required @account_initialization_required @@ -274,10 +300,7 @@ class PluginInstallFromMarketplaceApi(Resource): def post(self): _, tenant_id = current_account_with_tenant() - parser = reqparse.RequestParser().add_argument( - "plugin_unique_identifiers", type=list, required=True, location="json" - ) - args = parser.parse_args() + args = parser_marketplace.parse_args() # check if all plugin_unique_identifiers are valid string for plugin_unique_identifier in args["plugin_unique_identifiers"]: @@ -292,19 +315,21 @@ class PluginInstallFromMarketplaceApi(Resource): return jsonable_encoder(response) +parser_pkgapi = reqparse.RequestParser().add_argument( + "plugin_unique_identifier", type=str, required=True, location="args" +) + + @console_ns.route("/workspaces/current/plugin/marketplace/pkg") class PluginFetchMarketplacePkgApi(Resource): + @api.expect(parser_pkgapi) @setup_required @login_required @account_initialization_required @plugin_permission_required(install_required=True) def get(self): _, tenant_id = current_account_with_tenant() - - parser = reqparse.RequestParser().add_argument( - "plugin_unique_identifier", type=str, required=True, location="args" - ) - args = parser.parse_args() + args = parser_pkgapi.parse_args() try: return jsonable_encoder( @@ -319,8 +344,14 @@ class PluginFetchMarketplacePkgApi(Resource): raise ValueError(e) +parser_fetch = reqparse.RequestParser().add_argument( + "plugin_unique_identifier", type=str, required=True, location="args" +) + + @console_ns.route("/workspaces/current/plugin/fetch-manifest") class PluginFetchManifestApi(Resource): + @api.expect(parser_fetch) @setup_required @login_required @account_initialization_required @@ -328,10 +359,7 @@ class PluginFetchManifestApi(Resource): def get(self): _, tenant_id = current_account_with_tenant() - parser = reqparse.RequestParser().add_argument( - "plugin_unique_identifier", type=str, required=True, location="args" - ) - args = parser.parse_args() + args = parser_fetch.parse_args() try: return jsonable_encoder( @@ -345,8 +373,16 @@ class PluginFetchManifestApi(Resource): raise ValueError(e) +parser_tasks = ( + reqparse.RequestParser() + .add_argument("page", type=int, required=True, location="args") + .add_argument("page_size", type=int, required=True, location="args") +) + + @console_ns.route("/workspaces/current/plugin/tasks") class PluginFetchInstallTasksApi(Resource): + @api.expect(parser_tasks) @setup_required @login_required @account_initialization_required @@ -354,12 +390,7 @@ class PluginFetchInstallTasksApi(Resource): def get(self): _, tenant_id = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("page", type=int, required=True, location="args") - .add_argument("page_size", type=int, required=True, location="args") - ) - args = parser.parse_args() + args = parser_tasks.parse_args() try: return jsonable_encoder( @@ -429,8 +460,16 @@ class PluginDeleteInstallTaskItemApi(Resource): raise ValueError(e) +parser_marketplace_api = ( + reqparse.RequestParser() + .add_argument("original_plugin_unique_identifier", type=str, required=True, location="json") + .add_argument("new_plugin_unique_identifier", type=str, required=True, location="json") +) + + @console_ns.route("/workspaces/current/plugin/upgrade/marketplace") class PluginUpgradeFromMarketplaceApi(Resource): + @api.expect(parser_marketplace_api) @setup_required @login_required @account_initialization_required @@ -438,12 +477,7 @@ class PluginUpgradeFromMarketplaceApi(Resource): def post(self): _, tenant_id = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("original_plugin_unique_identifier", type=str, required=True, location="json") - .add_argument("new_plugin_unique_identifier", type=str, required=True, location="json") - ) - args = parser.parse_args() + args = parser_marketplace_api.parse_args() try: return jsonable_encoder( @@ -455,8 +489,19 @@ class PluginUpgradeFromMarketplaceApi(Resource): raise ValueError(e) +parser_github_post = ( + reqparse.RequestParser() + .add_argument("original_plugin_unique_identifier", type=str, required=True, location="json") + .add_argument("new_plugin_unique_identifier", type=str, required=True, location="json") + .add_argument("repo", type=str, required=True, location="json") + .add_argument("version", type=str, required=True, location="json") + .add_argument("package", type=str, required=True, location="json") +) + + @console_ns.route("/workspaces/current/plugin/upgrade/github") class PluginUpgradeFromGithubApi(Resource): + @api.expect(parser_github_post) @setup_required @login_required @account_initialization_required @@ -464,15 +509,7 @@ class PluginUpgradeFromGithubApi(Resource): def post(self): _, tenant_id = current_account_with_tenant() - parser = ( - reqparse.RequestParser() - .add_argument("original_plugin_unique_identifier", type=str, required=True, location="json") - .add_argument("new_plugin_unique_identifier", type=str, required=True, location="json") - .add_argument("repo", type=str, required=True, location="json") - .add_argument("version", type=str, required=True, location="json") - .add_argument("package", type=str, required=True, location="json") - ) - args = parser.parse_args() + args = parser_github_post.parse_args() try: return jsonable_encoder( @@ -489,15 +526,20 @@ class PluginUpgradeFromGithubApi(Resource): raise ValueError(e) +parser_uninstall = reqparse.RequestParser().add_argument( + "plugin_installation_id", type=str, required=True, location="json" +) + + @console_ns.route("/workspaces/current/plugin/uninstall") class PluginUninstallApi(Resource): + @api.expect(parser_uninstall) @setup_required @login_required @account_initialization_required @plugin_permission_required(install_required=True) def post(self): - req = reqparse.RequestParser().add_argument("plugin_installation_id", type=str, required=True, location="json") - args = req.parse_args() + args = parser_uninstall.parse_args() _, tenant_id = current_account_with_tenant() @@ -507,8 +549,16 @@ class PluginUninstallApi(Resource): raise ValueError(e) +parser_change_post = ( + reqparse.RequestParser() + .add_argument("install_permission", type=str, required=True, location="json") + .add_argument("debug_permission", type=str, required=True, location="json") +) + + @console_ns.route("/workspaces/current/plugin/permission/change") class PluginChangePermissionApi(Resource): + @api.expect(parser_change_post) @setup_required @login_required @account_initialization_required @@ -518,12 +568,7 @@ class PluginChangePermissionApi(Resource): if not user.is_admin_or_owner: raise Forbidden() - req = ( - reqparse.RequestParser() - .add_argument("install_permission", type=str, required=True, location="json") - .add_argument("debug_permission", type=str, required=True, location="json") - ) - args = req.parse_args() + args = parser_change_post.parse_args() install_permission = TenantPluginPermission.InstallPermission(args["install_permission"]) debug_permission = TenantPluginPermission.DebugPermission(args["debug_permission"]) @@ -558,8 +603,20 @@ class PluginFetchPermissionApi(Resource): ) +parser_dynamic = ( + reqparse.RequestParser() + .add_argument("plugin_id", type=str, required=True, location="args") + .add_argument("provider", type=str, required=True, location="args") + .add_argument("action", type=str, required=True, location="args") + .add_argument("parameter", type=str, required=True, location="args") + .add_argument("credential_id", type=str, required=False, location="args") + .add_argument("provider_type", type=str, required=True, location="args") +) + + @console_ns.route("/workspaces/current/plugin/parameters/dynamic-options") class PluginFetchDynamicSelectOptionsApi(Resource): + @api.expect(parser_dynamic) @setup_required @login_required @account_initialization_required @@ -571,16 +628,7 @@ class PluginFetchDynamicSelectOptionsApi(Resource): user_id = current_user.id - parser = ( - reqparse.RequestParser() - .add_argument("plugin_id", type=str, required=True, location="args") - .add_argument("provider", type=str, required=True, location="args") - .add_argument("action", type=str, required=True, location="args") - .add_argument("parameter", type=str, required=True, location="args") - .add_argument("credential_id", type=str, required=False, location="args") - .add_argument("provider_type", type=str, required=True, location="args") - ) - args = parser.parse_args() + args = parser_dynamic.parse_args() try: options = PluginParameterService.get_dynamic_select_options( @@ -599,8 +647,16 @@ class PluginFetchDynamicSelectOptionsApi(Resource): return jsonable_encoder({"options": options}) +parser_change = ( + reqparse.RequestParser() + .add_argument("permission", type=dict, required=True, location="json") + .add_argument("auto_upgrade", type=dict, required=True, location="json") +) + + @console_ns.route("/workspaces/current/plugin/preferences/change") class PluginChangePreferencesApi(Resource): + @api.expect(parser_change) @setup_required @login_required @account_initialization_required @@ -609,12 +665,7 @@ class PluginChangePreferencesApi(Resource): if not user.is_admin_or_owner: raise Forbidden() - req = ( - reqparse.RequestParser() - .add_argument("permission", type=dict, required=True, location="json") - .add_argument("auto_upgrade", type=dict, required=True, location="json") - ) - args = req.parse_args() + args = parser_change.parse_args() permission = args["permission"] @@ -694,8 +745,12 @@ class PluginFetchPreferencesApi(Resource): return jsonable_encoder({"permission": permission_dict, "auto_upgrade": auto_upgrade_dict}) +parser_exclude = reqparse.RequestParser().add_argument("plugin_id", type=str, required=True, location="json") + + @console_ns.route("/workspaces/current/plugin/preferences/autoupgrade/exclude") class PluginAutoUpgradeExcludePluginApi(Resource): + @api.expect(parser_exclude) @setup_required @login_required @account_initialization_required @@ -703,8 +758,7 @@ class PluginAutoUpgradeExcludePluginApi(Resource): # exclude one single plugin _, tenant_id = current_account_with_tenant() - req = reqparse.RequestParser().add_argument("plugin_id", type=str, required=True, location="json") - args = req.parse_args() + args = parser_exclude.parse_args() return jsonable_encoder({"success": PluginAutoUpgradeService.exclude_plugin(tenant_id, args["plugin_id"])}) diff --git a/api/controllers/console/workspace/tool_providers.py b/api/controllers/console/workspace/tool_providers.py index 2d123106f3..1c9d438ca6 100644 --- a/api/controllers/console/workspace/tool_providers.py +++ b/api/controllers/console/workspace/tool_providers.py @@ -10,7 +10,7 @@ from sqlalchemy.orm import Session from werkzeug.exceptions import Forbidden from configs import dify_config -from controllers.console import console_ns +from controllers.console import api, console_ns from controllers.console.wraps import ( account_initialization_required, enterprise_license_required, @@ -52,8 +52,19 @@ def is_valid_url(url: str) -> bool: return False +parser_tool = reqparse.RequestParser().add_argument( + "type", + type=str, + choices=["builtin", "model", "api", "workflow", "mcp"], + required=False, + nullable=True, + location="args", +) + + @console_ns.route("/workspaces/current/tool-providers") class ToolProviderListApi(Resource): + @api.expect(parser_tool) @setup_required @login_required @account_initialization_required @@ -62,15 +73,7 @@ class ToolProviderListApi(Resource): user_id = user.id - req = reqparse.RequestParser().add_argument( - "type", - type=str, - choices=["builtin", "model", "api", "workflow", "mcp"], - required=False, - nullable=True, - location="args", - ) - args = req.parse_args() + args = parser_tool.parse_args() return ToolCommonService.list_tool_providers(user_id, tenant_id, args.get("type", None)) @@ -102,8 +105,14 @@ class ToolBuiltinProviderInfoApi(Resource): return jsonable_encoder(BuiltinToolManageService.get_builtin_tool_provider_info(tenant_id, provider)) +parser_delete = reqparse.RequestParser().add_argument( + "credential_id", type=str, required=True, nullable=False, location="json" +) + + @console_ns.route("/workspaces/current/tool-provider/builtin//delete") class ToolBuiltinProviderDeleteApi(Resource): + @api.expect(parser_delete) @setup_required @login_required @account_initialization_required @@ -112,10 +121,7 @@ class ToolBuiltinProviderDeleteApi(Resource): if not user.is_admin_or_owner: raise Forbidden() - req = reqparse.RequestParser().add_argument( - "credential_id", type=str, required=True, nullable=False, location="json" - ) - args = req.parse_args() + args = parser_delete.parse_args() return BuiltinToolManageService.delete_builtin_tool_provider( tenant_id, @@ -124,8 +130,17 @@ class ToolBuiltinProviderDeleteApi(Resource): ) +parser_add = ( + reqparse.RequestParser() + .add_argument("credentials", type=dict, required=True, nullable=False, location="json") + .add_argument("name", type=StrLen(30), required=False, nullable=False, location="json") + .add_argument("type", type=str, required=True, nullable=False, location="json") +) + + @console_ns.route("/workspaces/current/tool-provider/builtin//add") class ToolBuiltinProviderAddApi(Resource): + @api.expect(parser_add) @setup_required @login_required @account_initialization_required @@ -134,13 +149,7 @@ class ToolBuiltinProviderAddApi(Resource): user_id = user.id - parser = ( - reqparse.RequestParser() - .add_argument("credentials", type=dict, required=True, nullable=False, location="json") - .add_argument("name", type=StrLen(30), required=False, nullable=False, location="json") - .add_argument("type", type=str, required=True, nullable=False, location="json") - ) - args = parser.parse_args() + args = parser_add.parse_args() if args["type"] not in CredentialType.values(): raise ValueError(f"Invalid credential type: {args['type']}") @@ -155,8 +164,17 @@ class ToolBuiltinProviderAddApi(Resource): ) +parser_update = ( + reqparse.RequestParser() + .add_argument("credential_id", type=str, required=True, nullable=False, location="json") + .add_argument("credentials", type=dict, required=False, nullable=True, location="json") + .add_argument("name", type=StrLen(30), required=False, nullable=True, location="json") +) + + @console_ns.route("/workspaces/current/tool-provider/builtin//update") class ToolBuiltinProviderUpdateApi(Resource): + @api.expect(parser_update) @setup_required @login_required @account_initialization_required @@ -168,14 +186,7 @@ class ToolBuiltinProviderUpdateApi(Resource): user_id = user.id - parser = ( - reqparse.RequestParser() - .add_argument("credential_id", type=str, required=True, nullable=False, location="json") - .add_argument("credentials", type=dict, required=False, nullable=True, location="json") - .add_argument("name", type=StrLen(30), required=False, nullable=True, location="json") - ) - - args = parser.parse_args() + args = parser_update.parse_args() result = BuiltinToolManageService.update_builtin_tool_provider( user_id=user_id, @@ -213,8 +224,22 @@ class ToolBuiltinProviderIconApi(Resource): return send_file(io.BytesIO(icon_bytes), mimetype=mimetype, max_age=icon_cache_max_age) +parser_api_add = ( + reqparse.RequestParser() + .add_argument("credentials", type=dict, required=True, nullable=False, location="json") + .add_argument("schema_type", type=str, required=True, nullable=False, location="json") + .add_argument("schema", type=str, required=True, nullable=False, location="json") + .add_argument("provider", type=str, required=True, nullable=False, location="json") + .add_argument("icon", type=dict, required=True, nullable=False, location="json") + .add_argument("privacy_policy", type=str, required=False, nullable=True, location="json") + .add_argument("labels", type=list[str], required=False, nullable=True, location="json", default=[]) + .add_argument("custom_disclaimer", type=str, required=False, nullable=True, location="json") +) + + @console_ns.route("/workspaces/current/tool-provider/api/add") class ToolApiProviderAddApi(Resource): + @api.expect(parser_api_add) @setup_required @login_required @account_initialization_required @@ -226,19 +251,7 @@ class ToolApiProviderAddApi(Resource): user_id = user.id - parser = ( - reqparse.RequestParser() - .add_argument("credentials", type=dict, required=True, nullable=False, location="json") - .add_argument("schema_type", type=str, required=True, nullable=False, location="json") - .add_argument("schema", type=str, required=True, nullable=False, location="json") - .add_argument("provider", type=str, required=True, nullable=False, location="json") - .add_argument("icon", type=dict, required=True, nullable=False, location="json") - .add_argument("privacy_policy", type=str, required=False, nullable=True, location="json") - .add_argument("labels", type=list[str], required=False, nullable=True, location="json", default=[]) - .add_argument("custom_disclaimer", type=str, required=False, nullable=True, location="json") - ) - - args = parser.parse_args() + args = parser_api_add.parse_args() return ApiToolManageService.create_api_tool_provider( user_id, @@ -254,8 +267,12 @@ class ToolApiProviderAddApi(Resource): ) +parser_remote = reqparse.RequestParser().add_argument("url", type=str, required=True, nullable=False, location="args") + + @console_ns.route("/workspaces/current/tool-provider/api/remote") class ToolApiProviderGetRemoteSchemaApi(Resource): + @api.expect(parser_remote) @setup_required @login_required @account_initialization_required @@ -264,9 +281,7 @@ class ToolApiProviderGetRemoteSchemaApi(Resource): user_id = user.id - parser = reqparse.RequestParser().add_argument("url", type=str, required=True, nullable=False, location="args") - - args = parser.parse_args() + args = parser_remote.parse_args() return ApiToolManageService.get_api_tool_provider_remote_schema( user_id, @@ -275,8 +290,14 @@ class ToolApiProviderGetRemoteSchemaApi(Resource): ) +parser_tools = reqparse.RequestParser().add_argument( + "provider", type=str, required=True, nullable=False, location="args" +) + + @console_ns.route("/workspaces/current/tool-provider/api/tools") class ToolApiProviderListToolsApi(Resource): + @api.expect(parser_tools) @setup_required @login_required @account_initialization_required @@ -285,11 +306,7 @@ class ToolApiProviderListToolsApi(Resource): user_id = user.id - parser = reqparse.RequestParser().add_argument( - "provider", type=str, required=True, nullable=False, location="args" - ) - - args = parser.parse_args() + args = parser_tools.parse_args() return jsonable_encoder( ApiToolManageService.list_api_tool_provider_tools( @@ -300,8 +317,23 @@ class ToolApiProviderListToolsApi(Resource): ) +parser_api_update = ( + reqparse.RequestParser() + .add_argument("credentials", type=dict, required=True, nullable=False, location="json") + .add_argument("schema_type", type=str, required=True, nullable=False, location="json") + .add_argument("schema", type=str, required=True, nullable=False, location="json") + .add_argument("provider", type=str, required=True, nullable=False, location="json") + .add_argument("original_provider", type=str, required=True, nullable=False, location="json") + .add_argument("icon", type=dict, required=True, nullable=False, location="json") + .add_argument("privacy_policy", type=str, required=True, nullable=True, location="json") + .add_argument("labels", type=list[str], required=False, nullable=True, location="json") + .add_argument("custom_disclaimer", type=str, required=True, nullable=True, location="json") +) + + @console_ns.route("/workspaces/current/tool-provider/api/update") class ToolApiProviderUpdateApi(Resource): + @api.expect(parser_api_update) @setup_required @login_required @account_initialization_required @@ -313,20 +345,7 @@ class ToolApiProviderUpdateApi(Resource): user_id = user.id - parser = ( - reqparse.RequestParser() - .add_argument("credentials", type=dict, required=True, nullable=False, location="json") - .add_argument("schema_type", type=str, required=True, nullable=False, location="json") - .add_argument("schema", type=str, required=True, nullable=False, location="json") - .add_argument("provider", type=str, required=True, nullable=False, location="json") - .add_argument("original_provider", type=str, required=True, nullable=False, location="json") - .add_argument("icon", type=dict, required=True, nullable=False, location="json") - .add_argument("privacy_policy", type=str, required=True, nullable=True, location="json") - .add_argument("labels", type=list[str], required=False, nullable=True, location="json") - .add_argument("custom_disclaimer", type=str, required=True, nullable=True, location="json") - ) - - args = parser.parse_args() + args = parser_api_update.parse_args() return ApiToolManageService.update_api_tool_provider( user_id, @@ -343,8 +362,14 @@ class ToolApiProviderUpdateApi(Resource): ) +parser_api_delete = reqparse.RequestParser().add_argument( + "provider", type=str, required=True, nullable=False, location="json" +) + + @console_ns.route("/workspaces/current/tool-provider/api/delete") class ToolApiProviderDeleteApi(Resource): + @api.expect(parser_api_delete) @setup_required @login_required @account_initialization_required @@ -356,11 +381,7 @@ class ToolApiProviderDeleteApi(Resource): user_id = user.id - parser = reqparse.RequestParser().add_argument( - "provider", type=str, required=True, nullable=False, location="json" - ) - - args = parser.parse_args() + args = parser_api_delete.parse_args() return ApiToolManageService.delete_api_tool_provider( user_id, @@ -369,8 +390,12 @@ class ToolApiProviderDeleteApi(Resource): ) +parser_get = reqparse.RequestParser().add_argument("provider", type=str, required=True, nullable=False, location="args") + + @console_ns.route("/workspaces/current/tool-provider/api/get") class ToolApiProviderGetApi(Resource): + @api.expect(parser_get) @setup_required @login_required @account_initialization_required @@ -379,11 +404,7 @@ class ToolApiProviderGetApi(Resource): user_id = user.id - parser = reqparse.RequestParser().add_argument( - "provider", type=str, required=True, nullable=False, location="args" - ) - - args = parser.parse_args() + args = parser_get.parse_args() return ApiToolManageService.get_api_tool_provider( user_id, @@ -407,40 +428,44 @@ class ToolBuiltinProviderCredentialsSchemaApi(Resource): ) +parser_schema = reqparse.RequestParser().add_argument( + "schema", type=str, required=True, nullable=False, location="json" +) + + @console_ns.route("/workspaces/current/tool-provider/api/schema") class ToolApiProviderSchemaApi(Resource): + @api.expect(parser_schema) @setup_required @login_required @account_initialization_required def post(self): - parser = reqparse.RequestParser().add_argument( - "schema", type=str, required=True, nullable=False, location="json" - ) - - args = parser.parse_args() + args = parser_schema.parse_args() return ApiToolManageService.parser_api_schema( schema=args["schema"], ) +parser_pre = ( + reqparse.RequestParser() + .add_argument("tool_name", type=str, required=True, nullable=False, location="json") + .add_argument("provider_name", type=str, required=False, nullable=False, location="json") + .add_argument("credentials", type=dict, required=True, nullable=False, location="json") + .add_argument("parameters", type=dict, required=True, nullable=False, location="json") + .add_argument("schema_type", type=str, required=True, nullable=False, location="json") + .add_argument("schema", type=str, required=True, nullable=False, location="json") +) + + @console_ns.route("/workspaces/current/tool-provider/api/test/pre") class ToolApiProviderPreviousTestApi(Resource): + @api.expect(parser_pre) @setup_required @login_required @account_initialization_required def post(self): - parser = ( - reqparse.RequestParser() - .add_argument("tool_name", type=str, required=True, nullable=False, location="json") - .add_argument("provider_name", type=str, required=False, nullable=False, location="json") - .add_argument("credentials", type=dict, required=True, nullable=False, location="json") - .add_argument("parameters", type=dict, required=True, nullable=False, location="json") - .add_argument("schema_type", type=str, required=True, nullable=False, location="json") - .add_argument("schema", type=str, required=True, nullable=False, location="json") - ) - - args = parser.parse_args() + args = parser_pre.parse_args() _, current_tenant_id = current_account_with_tenant() return ApiToolManageService.test_api_tool_preview( current_tenant_id, @@ -453,8 +478,22 @@ class ToolApiProviderPreviousTestApi(Resource): ) +parser_create = ( + reqparse.RequestParser() + .add_argument("workflow_app_id", type=uuid_value, required=True, nullable=False, location="json") + .add_argument("name", type=alphanumeric, required=True, nullable=False, location="json") + .add_argument("label", type=str, required=True, nullable=False, location="json") + .add_argument("description", type=str, required=True, nullable=False, location="json") + .add_argument("icon", type=dict, required=True, nullable=False, location="json") + .add_argument("parameters", type=list[dict], required=True, nullable=False, location="json") + .add_argument("privacy_policy", type=str, required=False, nullable=True, location="json", default="") + .add_argument("labels", type=list[str], required=False, nullable=True, location="json") +) + + @console_ns.route("/workspaces/current/tool-provider/workflow/create") class ToolWorkflowProviderCreateApi(Resource): + @api.expect(parser_create) @setup_required @login_required @account_initialization_required @@ -466,19 +505,7 @@ class ToolWorkflowProviderCreateApi(Resource): user_id = user.id - reqparser = ( - reqparse.RequestParser() - .add_argument("workflow_app_id", type=uuid_value, required=True, nullable=False, location="json") - .add_argument("name", type=alphanumeric, required=True, nullable=False, location="json") - .add_argument("label", type=str, required=True, nullable=False, location="json") - .add_argument("description", type=str, required=True, nullable=False, location="json") - .add_argument("icon", type=dict, required=True, nullable=False, location="json") - .add_argument("parameters", type=list[dict], required=True, nullable=False, location="json") - .add_argument("privacy_policy", type=str, required=False, nullable=True, location="json", default="") - .add_argument("labels", type=list[str], required=False, nullable=True, location="json") - ) - - args = reqparser.parse_args() + args = parser_create.parse_args() return WorkflowToolManageService.create_workflow_tool( user_id=user_id, @@ -494,8 +521,22 @@ class ToolWorkflowProviderCreateApi(Resource): ) +parser_workflow_update = ( + reqparse.RequestParser() + .add_argument("workflow_tool_id", type=uuid_value, required=True, nullable=False, location="json") + .add_argument("name", type=alphanumeric, required=True, nullable=False, location="json") + .add_argument("label", type=str, required=True, nullable=False, location="json") + .add_argument("description", type=str, required=True, nullable=False, location="json") + .add_argument("icon", type=dict, required=True, nullable=False, location="json") + .add_argument("parameters", type=list[dict], required=True, nullable=False, location="json") + .add_argument("privacy_policy", type=str, required=False, nullable=True, location="json", default="") + .add_argument("labels", type=list[str], required=False, nullable=True, location="json") +) + + @console_ns.route("/workspaces/current/tool-provider/workflow/update") class ToolWorkflowProviderUpdateApi(Resource): + @api.expect(parser_workflow_update) @setup_required @login_required @account_initialization_required @@ -507,19 +548,7 @@ class ToolWorkflowProviderUpdateApi(Resource): user_id = user.id - reqparser = ( - reqparse.RequestParser() - .add_argument("workflow_tool_id", type=uuid_value, required=True, nullable=False, location="json") - .add_argument("name", type=alphanumeric, required=True, nullable=False, location="json") - .add_argument("label", type=str, required=True, nullable=False, location="json") - .add_argument("description", type=str, required=True, nullable=False, location="json") - .add_argument("icon", type=dict, required=True, nullable=False, location="json") - .add_argument("parameters", type=list[dict], required=True, nullable=False, location="json") - .add_argument("privacy_policy", type=str, required=False, nullable=True, location="json", default="") - .add_argument("labels", type=list[str], required=False, nullable=True, location="json") - ) - - args = reqparser.parse_args() + args = parser_workflow_update.parse_args() if not args["workflow_tool_id"]: raise ValueError("incorrect workflow_tool_id") @@ -538,8 +567,14 @@ class ToolWorkflowProviderUpdateApi(Resource): ) +parser_workflow_delete = reqparse.RequestParser().add_argument( + "workflow_tool_id", type=uuid_value, required=True, nullable=False, location="json" +) + + @console_ns.route("/workspaces/current/tool-provider/workflow/delete") class ToolWorkflowProviderDeleteApi(Resource): + @api.expect(parser_workflow_delete) @setup_required @login_required @account_initialization_required @@ -551,11 +586,7 @@ class ToolWorkflowProviderDeleteApi(Resource): user_id = user.id - reqparser = reqparse.RequestParser().add_argument( - "workflow_tool_id", type=uuid_value, required=True, nullable=False, location="json" - ) - - args = reqparser.parse_args() + args = parser_workflow_delete.parse_args() return WorkflowToolManageService.delete_workflow_tool( user_id, @@ -564,8 +595,16 @@ class ToolWorkflowProviderDeleteApi(Resource): ) +parser_wf_get = ( + reqparse.RequestParser() + .add_argument("workflow_tool_id", type=uuid_value, required=False, nullable=True, location="args") + .add_argument("workflow_app_id", type=uuid_value, required=False, nullable=True, location="args") +) + + @console_ns.route("/workspaces/current/tool-provider/workflow/get") class ToolWorkflowProviderGetApi(Resource): + @api.expect(parser_wf_get) @setup_required @login_required @account_initialization_required @@ -574,13 +613,7 @@ class ToolWorkflowProviderGetApi(Resource): user_id = user.id - parser = ( - reqparse.RequestParser() - .add_argument("workflow_tool_id", type=uuid_value, required=False, nullable=True, location="args") - .add_argument("workflow_app_id", type=uuid_value, required=False, nullable=True, location="args") - ) - - args = parser.parse_args() + args = parser_wf_get.parse_args() if args.get("workflow_tool_id"): tool = WorkflowToolManageService.get_workflow_tool_by_tool_id( @@ -600,8 +633,14 @@ class ToolWorkflowProviderGetApi(Resource): return jsonable_encoder(tool) +parser_wf_tools = reqparse.RequestParser().add_argument( + "workflow_tool_id", type=uuid_value, required=True, nullable=False, location="args" +) + + @console_ns.route("/workspaces/current/tool-provider/workflow/tools") class ToolWorkflowProviderListToolApi(Resource): + @api.expect(parser_wf_tools) @setup_required @login_required @account_initialization_required @@ -610,11 +649,7 @@ class ToolWorkflowProviderListToolApi(Resource): user_id = user.id - parser = reqparse.RequestParser().add_argument( - "workflow_tool_id", type=uuid_value, required=True, nullable=False, location="args" - ) - - args = parser.parse_args() + args = parser_wf_tools.parse_args() return jsonable_encoder( WorkflowToolManageService.list_single_workflow_tools( @@ -790,32 +825,40 @@ class ToolOAuthCallback(Resource): return redirect(f"{dify_config.CONSOLE_WEB_URL}/oauth-callback") +parser_default_cred = reqparse.RequestParser().add_argument( + "id", type=str, required=True, nullable=False, location="json" +) + + @console_ns.route("/workspaces/current/tool-provider/builtin//default-credential") class ToolBuiltinProviderSetDefaultApi(Resource): + @api.expect(parser_default_cred) @setup_required @login_required @account_initialization_required def post(self, provider): current_user, current_tenant_id = current_account_with_tenant() - parser = reqparse.RequestParser().add_argument("id", type=str, required=True, nullable=False, location="json") - args = parser.parse_args() + args = parser_default_cred.parse_args() return BuiltinToolManageService.set_default_provider( tenant_id=current_tenant_id, user_id=current_user.id, provider=provider, id=args["id"] ) +parser_custom = ( + reqparse.RequestParser() + .add_argument("client_params", type=dict, required=False, nullable=True, location="json") + .add_argument("enable_oauth_custom_client", type=bool, required=False, nullable=True, location="json") +) + + @console_ns.route("/workspaces/current/tool-provider/builtin//oauth/custom-client") class ToolOAuthCustomClient(Resource): + @api.expect(parser_custom) @setup_required @login_required @account_initialization_required def post(self, provider): - parser = ( - reqparse.RequestParser() - .add_argument("client_params", type=dict, required=False, nullable=True, location="json") - .add_argument("enable_oauth_custom_client", type=bool, required=False, nullable=True, location="json") - ) - args = parser.parse_args() + args = parser_custom.parse_args() user, tenant_id = current_account_with_tenant() @@ -878,25 +921,44 @@ class ToolBuiltinProviderGetCredentialInfoApi(Resource): ) +parser_mcp = ( + reqparse.RequestParser() + .add_argument("server_url", type=str, required=True, nullable=False, location="json") + .add_argument("name", type=str, required=True, nullable=False, location="json") + .add_argument("icon", type=str, required=True, nullable=False, location="json") + .add_argument("icon_type", type=str, required=True, nullable=False, location="json") + .add_argument("icon_background", type=str, required=False, nullable=True, location="json", default="") + .add_argument("server_identifier", type=str, required=True, nullable=False, location="json") + .add_argument("configuration", type=dict, required=False, nullable=True, location="json", default={}) + .add_argument("headers", type=dict, required=False, nullable=True, location="json", default={}) + .add_argument("authentication", type=dict, required=False, nullable=True, location="json", default={}) +) +parser_mcp_put = ( + reqparse.RequestParser() + .add_argument("server_url", type=str, required=True, nullable=False, location="json") + .add_argument("name", type=str, required=True, nullable=False, location="json") + .add_argument("icon", type=str, required=True, nullable=False, location="json") + .add_argument("icon_type", type=str, required=True, nullable=False, location="json") + .add_argument("icon_background", type=str, required=False, nullable=True, location="json") + .add_argument("provider_id", type=str, required=True, nullable=False, location="json") + .add_argument("server_identifier", type=str, required=True, nullable=False, location="json") + .add_argument("configuration", type=dict, required=False, nullable=True, location="json", default={}) + .add_argument("headers", type=dict, required=False, nullable=True, location="json", default={}) + .add_argument("authentication", type=dict, required=False, nullable=True, location="json", default={}) +) +parser_mcp_delete = reqparse.RequestParser().add_argument( + "provider_id", type=str, required=True, nullable=False, location="json" +) + + @console_ns.route("/workspaces/current/tool-provider/mcp") class ToolProviderMCPApi(Resource): + @api.expect(parser_mcp) @setup_required @login_required @account_initialization_required def post(self): - parser = ( - reqparse.RequestParser() - .add_argument("server_url", type=str, required=True, nullable=False, location="json") - .add_argument("name", type=str, required=True, nullable=False, location="json") - .add_argument("icon", type=str, required=True, nullable=False, location="json") - .add_argument("icon_type", type=str, required=True, nullable=False, location="json") - .add_argument("icon_background", type=str, required=False, nullable=True, location="json", default="") - .add_argument("server_identifier", type=str, required=True, nullable=False, location="json") - .add_argument("configuration", type=dict, required=False, nullable=True, location="json", default={}) - .add_argument("headers", type=dict, required=False, nullable=True, location="json", default={}) - .add_argument("authentication", type=dict, required=False, nullable=True, location="json", default={}) - ) - args = parser.parse_args() + args = parser_mcp.parse_args() user, tenant_id = current_account_with_tenant() # Parse and validate models @@ -921,24 +983,12 @@ class ToolProviderMCPApi(Resource): ) return jsonable_encoder(result) + @api.expect(parser_mcp_put) @setup_required @login_required @account_initialization_required def put(self): - parser = ( - reqparse.RequestParser() - .add_argument("server_url", type=str, required=True, nullable=False, location="json") - .add_argument("name", type=str, required=True, nullable=False, location="json") - .add_argument("icon", type=str, required=True, nullable=False, location="json") - .add_argument("icon_type", type=str, required=True, nullable=False, location="json") - .add_argument("icon_background", type=str, required=False, nullable=True, location="json") - .add_argument("provider_id", type=str, required=True, nullable=False, location="json") - .add_argument("server_identifier", type=str, required=True, nullable=False, location="json") - .add_argument("configuration", type=dict, required=False, nullable=True, location="json", default={}) - .add_argument("headers", type=dict, required=False, nullable=True, location="json", default={}) - .add_argument("authentication", type=dict, required=False, nullable=True, location="json", default={}) - ) - args = parser.parse_args() + args = parser_mcp_put.parse_args() configuration = MCPConfiguration.model_validate(args["configuration"]) authentication = MCPAuthentication.model_validate(args["authentication"]) if args["authentication"] else None _, current_tenant_id = current_account_with_tenant() @@ -972,14 +1022,12 @@ class ToolProviderMCPApi(Resource): ) return {"result": "success"} + @api.expect(parser_mcp_delete) @setup_required @login_required @account_initialization_required def delete(self): - parser = reqparse.RequestParser().add_argument( - "provider_id", type=str, required=True, nullable=False, location="json" - ) - args = parser.parse_args() + args = parser_mcp_delete.parse_args() _, current_tenant_id = current_account_with_tenant() with Session(db.engine) as session, session.begin(): @@ -988,18 +1036,21 @@ class ToolProviderMCPApi(Resource): return {"result": "success"} +parser_auth = ( + reqparse.RequestParser() + .add_argument("provider_id", type=str, required=True, nullable=False, location="json") + .add_argument("authorization_code", type=str, required=False, nullable=True, location="json") +) + + @console_ns.route("/workspaces/current/tool-provider/mcp/auth") class ToolMCPAuthApi(Resource): + @api.expect(parser_auth) @setup_required @login_required @account_initialization_required def post(self): - parser = ( - reqparse.RequestParser() - .add_argument("provider_id", type=str, required=True, nullable=False, location="json") - .add_argument("authorization_code", type=str, required=False, nullable=True, location="json") - ) - args = parser.parse_args() + args = parser_auth.parse_args() provider_id = args["provider_id"] _, tenant_id = current_account_with_tenant() @@ -1097,15 +1148,18 @@ class ToolMCPUpdateApi(Resource): return jsonable_encoder(tools) +parser_cb = ( + reqparse.RequestParser() + .add_argument("code", type=str, required=True, nullable=False, location="args") + .add_argument("state", type=str, required=True, nullable=False, location="args") +) + + @console_ns.route("/mcp/oauth/callback") class ToolMCPCallbackApi(Resource): + @api.expect(parser_cb) def get(self): - parser = ( - reqparse.RequestParser() - .add_argument("code", type=str, required=True, nullable=False, location="args") - .add_argument("state", type=str, required=True, nullable=False, location="args") - ) - args = parser.parse_args() + args = parser_cb.parse_args() state_key = args["state"] authorization_code = args["code"] diff --git a/api/controllers/console/workspace/workspace.py b/api/controllers/console/workspace/workspace.py index 70d5ef242e..f10c30db2e 100644 --- a/api/controllers/console/workspace/workspace.py +++ b/api/controllers/console/workspace/workspace.py @@ -13,7 +13,7 @@ from controllers.common.errors import ( TooManyFilesError, UnsupportedFileTypeError, ) -from controllers.console import console_ns +from controllers.console import api, console_ns from controllers.console.admin import admin_required from controllers.console.error import AccountNotLinkTenantError from controllers.console.wraps import ( @@ -150,15 +150,18 @@ class TenantApi(Resource): return WorkspaceService.get_tenant_info(tenant), 200 +parser_switch = reqparse.RequestParser().add_argument("tenant_id", type=str, required=True, location="json") + + @console_ns.route("/workspaces/switch") class SwitchWorkspaceApi(Resource): + @api.expect(parser_switch) @setup_required @login_required @account_initialization_required def post(self): current_user, _ = current_account_with_tenant() - parser = reqparse.RequestParser().add_argument("tenant_id", type=str, required=True, location="json") - args = parser.parse_args() + args = parser_switch.parse_args() # check if tenant_id is valid, 403 if not try: @@ -242,16 +245,19 @@ class WebappLogoWorkspaceApi(Resource): return {"id": upload_file.id}, 201 +parser_info = reqparse.RequestParser().add_argument("name", type=str, required=True, location="json") + + @console_ns.route("/workspaces/info") class WorkspaceInfoApi(Resource): + @api.expect(parser_info) @setup_required @login_required @account_initialization_required # Change workspace name def post(self): _, current_tenant_id = current_account_with_tenant() - parser = reqparse.RequestParser().add_argument("name", type=str, required=True, location="json") - args = parser.parse_args() + args = parser_info.parse_args() if not current_tenant_id: raise ValueError("No current tenant")