refactor: replace manual model_validate with @model_validate in workspace remaining controllers (#40239)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Likalikali
2026-08-09 08:23:51 +00:00
committed by GitHub
co-authored by autofix-ci[bot]
parent 5ebb389d26
commit b692ddd80c
10 changed files with 303 additions and 172 deletions
+4 -3
View File
@@ -41,6 +41,7 @@ from controllers.console.wraps import (
cloud_edition_billing_enabled,
enable_change_email,
enterprise_license_required,
model_validate,
only_edition_cloud,
setup_required,
with_current_user,
@@ -332,9 +333,9 @@ class AccountAvatarApi(Resource):
@login_required
@account_initialization_required
@with_current_user
def get(self, current_user: Account):
args = AccountAvatarQuery.model_validate(request.args.to_dict(flat=True))
avatar = args.avatar
@model_validate(AccountAvatarQuery)
def get(self, req_data: AccountAvatarQuery, current_user: Account):
avatar = req_data.avatar
if avatar.startswith(("http://", "https://")):
return AvatarUrlResponse(avatar_url=avatar).model_dump(mode="json")
+9 -10
View File
@@ -11,7 +11,6 @@ from enum import StrEnum
from http import HTTPStatus
from typing import Any
from flask import request
from flask_restx import Resource
from pydantic import BaseModel, Field
@@ -294,14 +293,14 @@ class EndpointListApi(Resource):
@account_initialization_required
@with_current_user_id
@with_current_tenant_id
def get(self, tenant_id: str, user_id: str):
args = EndpointListQuery.model_validate(request.args.to_dict(flat=True))
@model_validate(EndpointListQuery)
def get(self, req_data: EndpointListQuery, tenant_id: str, user_id: str):
endpoints = EndpointService.list_endpoints(
tenant_id=tenant_id,
user_id=user_id,
page=args.page,
page_size=args.page_size,
page=req_data.page,
page_size=req_data.page_size,
)
return EndpointListResponse(endpoints=endpoints).model_dump(mode="json")
@@ -322,15 +321,15 @@ class EndpointListForSinglePluginApi(Resource):
@account_initialization_required
@with_current_user_id
@with_current_tenant_id
def get(self, tenant_id: str, user_id: str):
args = EndpointListForPluginQuery.model_validate(request.args.to_dict(flat=True))
@model_validate(EndpointListForPluginQuery)
def get(self, req_data: EndpointListForPluginQuery, tenant_id: str, user_id: str):
endpoints = EndpointService.list_endpoints_for_single_plugin(
tenant_id=tenant_id,
user_id=user_id,
plugin_id=args.plugin_id,
page=args.page,
page_size=args.page_size,
plugin_id=req_data.plugin_id,
page=req_data.page,
page_size=req_data.page_size,
)
return EndpointListResponse(endpoints=endpoints).model_dump(mode="json")
+75 -75
View File
@@ -1,7 +1,6 @@
import logging
from typing import Any, cast
from flask import request
from flask_restx import Resource
from pydantic import BaseModel, Field, field_validator
@@ -18,6 +17,7 @@ from controllers.console.wraps import (
RBACResourceScope,
account_initialization_required,
is_admin_or_owner_required,
model_validate,
rbac_permission_required,
setup_required,
with_current_tenant_id,
@@ -212,12 +212,12 @@ class DefaultModelApi(Resource):
@login_required
@account_initialization_required
@with_current_tenant_id
def get(self, tenant_id: str):
args = ParserGetDefault.model_validate(request.args.to_dict(flat=True))
@model_validate(ParserGetDefault)
def get(self, req_data: ParserGetDefault, tenant_id: str):
model_provider_service = ModelProviderService()
default_model_entity = model_provider_service.get_default_model_of_model_type(
tenant_id=tenant_id, model_type=args.model_type
tenant_id=tenant_id, model_type=req_data.model_type
)
return DefaultModelDataResponse(data=default_model_entity).model_dump(mode="json")
@@ -230,10 +230,10 @@ class DefaultModelApi(Resource):
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
@account_initialization_required
@with_current_tenant_id
def post(self, tenant_id: str):
args = ParserPostDefault.model_validate(console_ns.payload)
@model_validate(ParserPostDefault)
def post(self, req_data: ParserPostDefault, tenant_id: str):
model_provider_service = ModelProviderService()
model_settings = args.model_settings
model_settings = req_data.model_settings
for model_setting in model_settings:
if model_setting.provider is None:
continue
@@ -279,43 +279,43 @@ class ModelProviderModelApi(Resource):
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
@account_initialization_required
@with_current_tenant_id
def post(self, tenant_id: str, provider: str):
@model_validate(ParserPostModels)
def post(self, req_data: ParserPostModels, tenant_id: str, provider: str):
# To save the model's load balance configs
args = ParserPostModels.model_validate(console_ns.payload)
if args.config_from == "custom-model":
if not args.credential_id:
if req_data.config_from == "custom-model":
if not req_data.credential_id:
raise ValueError("credential_id is required when configuring a custom-model")
service = ModelProviderService()
service.switch_active_custom_model_credential(
tenant_id=tenant_id,
provider=provider,
model_type=args.model_type,
model=args.model,
credential_id=args.credential_id,
model_type=req_data.model_type,
model=req_data.model,
credential_id=req_data.credential_id,
)
model_load_balancing_service = ModelLoadBalancingService()
if args.load_balancing and args.load_balancing.configs:
if req_data.load_balancing and req_data.load_balancing.configs:
# save load balancing configs
model_load_balancing_service.update_load_balancing_configs(
tenant_id=tenant_id,
provider=provider,
model=args.model,
model_type=args.model_type,
configs=args.load_balancing.configs,
config_from=args.config_from or "",
model=req_data.model,
model_type=req_data.model_type,
configs=req_data.load_balancing.configs,
config_from=req_data.config_from or "",
session=db.session(),
)
if args.load_balancing.enabled:
if req_data.load_balancing.enabled:
model_load_balancing_service.enable_model_load_balancing(
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
tenant_id=tenant_id, provider=provider, model=req_data.model, model_type=req_data.model_type
)
else:
model_load_balancing_service.disable_model_load_balancing(
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
tenant_id=tenant_id, provider=provider, model=req_data.model, model_type=req_data.model_type
)
return SimpleResultResponse(result="success").model_dump(mode="json"), 200
@@ -328,12 +328,12 @@ class ModelProviderModelApi(Resource):
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
@account_initialization_required
@with_current_tenant_id
def delete(self, tenant_id: str, provider: str):
args = ParserDeleteModels.model_validate(console_ns.payload)
@model_validate(ParserDeleteModels)
def delete(self, req_data: ParserDeleteModels, tenant_id: str, provider: str):
model_provider_service = ModelProviderService()
model_provider_service.remove_model(
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
tenant_id=tenant_id, provider=provider, model=req_data.model, model_type=req_data.model_type
)
return "", 204
@@ -352,29 +352,29 @@ class ModelProviderModelCredentialApi(Resource):
@account_initialization_required
@with_current_user
@with_current_tenant_id
def get(self, tenant_id: str, user: Account, provider: str):
args = ParserGetCredentials.model_validate(request.args.to_dict(flat=True))
@model_validate(ParserGetCredentials)
def get(self, req_data: ParserGetCredentials, tenant_id: str, user: Account, provider: str):
model_provider_service = ModelProviderService()
current_credential = model_provider_service.get_model_credential(
tenant_id=tenant_id,
provider=provider,
model_type=args.model_type,
model=args.model,
credential_id=args.credential_id,
model_type=req_data.model_type,
model=req_data.model,
credential_id=req_data.credential_id,
)
model_load_balancing_service = ModelLoadBalancingService()
is_load_balancing_enabled, load_balancing_configs = model_load_balancing_service.get_load_balancing_configs(
tenant_id=tenant_id,
provider=provider,
model=args.model,
model_type=args.model_type,
model=req_data.model,
model_type=req_data.model_type,
session=db.session(),
config_from=args.config_from or "",
config_from=req_data.config_from or "",
)
if args.config_from == "predefined-model":
if req_data.config_from == "predefined-model":
# Only the predefined-model branch needs visibility filtering by user.
# The account is injected once by the handler and only passed into the
# service branch that needs user-scoped credential visibility.
@@ -387,8 +387,8 @@ class ModelProviderModelCredentialApi(Resource):
available_credentials = model_provider_service.get_provider_model_available_credentials(
tenant_id=tenant_id,
provider=provider,
model_type=args.model_type,
model=args.model,
model_type=req_data.model_type,
model=req_data.model,
)
credentials: dict[str, Any] = {}
@@ -414,8 +414,8 @@ class ModelProviderModelCredentialApi(Resource):
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_CREATE, resource_required=False)
@account_initialization_required
@with_current_tenant_id
def post(self, tenant_id: str, provider: str):
args = ParserCreateCredential.model_validate(console_ns.payload)
@model_validate(ParserCreateCredential)
def post(self, req_data: ParserCreateCredential, tenant_id: str, provider: str):
model_provider_service = ModelProviderService()
@@ -423,17 +423,17 @@ class ModelProviderModelCredentialApi(Resource):
model_provider_service.create_model_credential(
tenant_id=tenant_id,
provider=provider,
model=args.model,
model_type=args.model_type,
credentials=args.credentials,
credential_name=args.name,
model=req_data.model,
model_type=req_data.model_type,
credentials=req_data.credentials,
credential_name=req_data.name,
)
except CredentialsValidateFailedError as ex:
logger.exception(
"Failed to save model credentials, tenant_id: %s, model: %s, model_type: %s",
tenant_id,
args.model,
args.model_type,
req_data.model,
req_data.model_type,
)
raise ValueError(str(ex))
@@ -447,8 +447,8 @@ class ModelProviderModelCredentialApi(Resource):
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
@account_initialization_required
@with_current_tenant_id
def put(self, current_tenant_id: str, provider: str):
args = ParserUpdateCredential.model_validate(console_ns.payload)
@model_validate(ParserUpdateCredential)
def put(self, req_data: ParserUpdateCredential, current_tenant_id: str, provider: str):
model_provider_service = ModelProviderService()
@@ -456,11 +456,11 @@ class ModelProviderModelCredentialApi(Resource):
model_provider_service.update_model_credential(
tenant_id=current_tenant_id,
provider=provider,
model_type=args.model_type,
model=args.model,
credentials=args.credentials,
credential_id=args.credential_id,
credential_name=args.name,
model_type=req_data.model_type,
model=req_data.model,
credentials=req_data.credentials,
credential_id=req_data.credential_id,
credential_name=req_data.name,
)
except CredentialsValidateFailedError as ex:
raise ValueError(str(ex))
@@ -475,16 +475,16 @@ class ModelProviderModelCredentialApi(Resource):
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
@account_initialization_required
@with_current_tenant_id
def delete(self, current_tenant_id: str, provider: str):
args = ParserDeleteCredential.model_validate(console_ns.payload)
@model_validate(ParserDeleteCredential)
def delete(self, req_data: ParserDeleteCredential, current_tenant_id: str, provider: str):
model_provider_service = ModelProviderService()
model_provider_service.remove_model_credential(
tenant_id=current_tenant_id,
provider=provider,
model_type=args.model_type,
model=args.model,
credential_id=args.credential_id,
model_type=req_data.model_type,
model=req_data.model,
credential_id=req_data.credential_id,
)
return "", 204
@@ -500,16 +500,16 @@ class ModelProviderModelCredentialSwitchApi(Resource):
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_USE, resource_required=False)
@account_initialization_required
@with_current_tenant_id
def post(self, current_tenant_id: str, provider: str):
args = ParserSwitch.model_validate(console_ns.payload)
@model_validate(ParserSwitch)
def post(self, req_data: ParserSwitch, current_tenant_id: str, provider: str):
service = ModelProviderService()
service.add_model_credential_to_model_list(
tenant_id=current_tenant_id,
provider=provider,
model_type=args.model_type,
model=args.model,
credential_id=args.credential_id,
model_type=req_data.model_type,
model=req_data.model,
credential_id=req_data.credential_id,
)
return SimpleResultResponse(result="success").model_dump(mode="json")
@@ -525,12 +525,12 @@ class ModelProviderModelEnableApi(Resource):
@account_initialization_required
@with_current_tenant_id
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
def patch(self, tenant_id: str, provider: str):
args = ParserDeleteModels.model_validate(console_ns.payload)
@model_validate(ParserDeleteModels)
def patch(self, req_data: ParserDeleteModels, tenant_id: str, provider: str):
model_provider_service = ModelProviderService()
model_provider_service.enable_model(
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
tenant_id=tenant_id, provider=provider, model=req_data.model, model_type=req_data.model_type
)
return SimpleResultResponse(result="success").model_dump(mode="json")
@@ -547,12 +547,12 @@ class ModelProviderModelDisableApi(Resource):
@account_initialization_required
@with_current_tenant_id
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
def patch(self, tenant_id: str, provider: str):
args = ParserDeleteModels.model_validate(console_ns.payload)
@model_validate(ParserDeleteModels)
def patch(self, req_data: ParserDeleteModels, tenant_id: str, provider: str):
model_provider_service = ModelProviderService()
model_provider_service.disable_model(
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
tenant_id=tenant_id, provider=provider, model=req_data.model, model_type=req_data.model_type
)
return SimpleResultResponse(result="success").model_dump(mode="json")
@@ -579,8 +579,8 @@ class ModelProviderModelValidateApi(Resource):
@login_required
@account_initialization_required
@with_current_tenant_id
def post(self, tenant_id: str, provider: str):
args = ParserValidate.model_validate(console_ns.payload)
@model_validate(ParserValidate)
def post(self, req_data: ParserValidate, tenant_id: str, provider: str):
model_provider_service = ModelProviderService()
@@ -591,9 +591,9 @@ class ModelProviderModelValidateApi(Resource):
model_provider_service.validate_model_credentials(
tenant_id=tenant_id,
provider=provider,
model=args.model,
model_type=args.model_type,
credentials=args.credentials,
model=req_data.model,
model_type=req_data.model_type,
credentials=req_data.credentials,
)
except CredentialsValidateFailedError as ex:
result = False
@@ -617,12 +617,12 @@ class ModelProviderModelParameterRuleApi(Resource):
@login_required
@account_initialization_required
@with_current_tenant_id
def get(self, tenant_id: str, provider: str):
args = ParserParameter.model_validate(request.args.to_dict(flat=True))
@model_validate(ParserParameter)
def get(self, req_data: ParserParameter, tenant_id: str, provider: str):
model_provider_service = ModelProviderService()
parameter_rules = model_provider_service.get_model_parameter_rules(
tenant_id=tenant_id, provider=provider, model=args.model
tenant_id=tenant_id, provider=provider, model=req_data.model
)
return ModelParameterRuleListResponse(data=parameter_rules).model_dump(mode="json")
+6 -6
View File
@@ -11,7 +11,7 @@ from werkzeug.exceptions import NotFound
from configs import dify_config
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
from controllers.console import console_ns
from controllers.console.wraps import RBACPermission, RBACResourceScope, rbac_permission_required
from controllers.console.wraps import RBACPermission, RBACResourceScope, model_validate, rbac_permission_required
from core.db.session_factory import session_factory
from core.rbac import RBACResourceWhitelistScope
from extensions.ext_database import db
@@ -310,19 +310,19 @@ class RBACRolesApi(Resource):
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
)
@console_ns.response(200, "Success", console_ns.models[_RBACRoleList.__name__])
def get(self):
@model_validate(_RolesListQuery)
def get(self, req_data: _RolesListQuery):
tenant_id, account_id = _current_ids()
query = _RolesListQuery.model_validate(request.args.to_dict(flat=True))
options = query.to_inner_options()
options = req_data.to_inner_options()
if not dify_config.RBAC_ENABLED:
result = _legacy_workspace_roles(
options, include_owner=query.include_owner, billing_enabled=dify_config.BILLING_ENABLED
options, include_owner=req_data.include_owner, billing_enabled=dify_config.BILLING_ENABLED
)
else:
result = svc.RBACService.Roles.list(
tenant_id,
account_id,
include_owner=query.include_owner,
include_owner=req_data.include_owner,
biiling_enabled=dify_config.BILLING_ENABLED,
options=options,
)
@@ -39,6 +39,7 @@ from ..wraps import (
account_initialization_required,
edit_permission_required,
is_admin_or_owner_required,
model_validate,
rbac_permission_required,
setup_required,
with_current_tenant_id,
@@ -233,13 +234,12 @@ class TriggerSubscriptionBuilderCreateApi(Resource):
@account_initialization_required
@with_current_user
@with_current_tenant_id
def post(self, tenant_id: str, user: Account, provider: str):
@model_validate(TriggerSubscriptionBuilderCreatePayload)
def post(self, req_data: TriggerSubscriptionBuilderCreatePayload, tenant_id: str, user: Account, provider: str):
"""Add a new subscription instance for a trigger provider"""
payload = TriggerSubscriptionBuilderCreatePayload.model_validate(console_ns.payload or {})
try:
credential_type = CredentialType.of(payload.credential_type)
credential_type = CredentialType.of(req_data.credential_type)
subscription_builder = TriggerSubscriptionBuilderService.create_trigger_subscription_builder(
tenant_id=tenant_id,
user_id=user.id,
@@ -298,11 +298,17 @@ class TriggerSubscriptionBuilderVerifyApi(Resource):
@account_initialization_required
@with_current_user
@with_current_tenant_id
def post(self, tenant_id: str, user: Account, provider: str, subscription_builder_id: str):
@model_validate(TriggerSubscriptionBuilderVerifyPayload)
def post(
self,
req_data: TriggerSubscriptionBuilderVerifyPayload,
tenant_id: str,
user: Account,
provider: str,
subscription_builder_id: str,
):
"""Verify and update a subscription instance for a trigger provider"""
payload = TriggerSubscriptionBuilderVerifyPayload.model_validate(console_ns.payload or {})
try:
# Use atomic update_and_verify to prevent race conditions
result = TriggerSubscriptionBuilderService.update_and_verify_builder(
@@ -311,7 +317,7 @@ class TriggerSubscriptionBuilderVerifyApi(Resource):
provider_id=TriggerProviderID(provider),
subscription_builder_id=subscription_builder_id,
subscription_builder_updater=SubscriptionBuilderUpdater(
credentials=payload.credentials,
credentials=req_data.credentials,
),
)
return dump_response(TriggerVerificationResponse, result)
@@ -337,10 +343,17 @@ class TriggerSubscriptionBuilderUpdateApi(Resource):
@account_initialization_required
@with_current_user
@with_current_tenant_id
def post(self, tenant_id: str, user: Account, provider: str, subscription_builder_id: str):
@model_validate(TriggerSubscriptionBuilderUpdatePayload)
def post(
self,
req_data: TriggerSubscriptionBuilderUpdatePayload,
tenant_id: str,
user: Account,
provider: str,
subscription_builder_id: str,
):
"""Update a subscription instance for a trigger provider"""
payload = TriggerSubscriptionBuilderUpdatePayload.model_validate(console_ns.payload or {})
try:
return TriggerSubscriptionBuilderService.update_trigger_subscription_builder(
tenant_id=tenant_id,
@@ -348,10 +361,10 @@ class TriggerSubscriptionBuilderUpdateApi(Resource):
provider_id=TriggerProviderID(provider),
subscription_builder_id=subscription_builder_id,
subscription_builder_updater=SubscriptionBuilderUpdater(
name=payload.name,
parameters=payload.parameters,
properties=payload.properties,
credentials=payload.credentials,
name=req_data.name,
parameters=req_data.parameters,
properties=req_data.properties,
credentials=req_data.credentials,
),
).model_dump(mode="json")
except Exception as e:
@@ -406,9 +419,16 @@ class TriggerSubscriptionBuilderBuildApi(Resource):
@account_initialization_required
@with_current_user
@with_current_tenant_id
def post(self, tenant_id: str, user: Account, provider: str, subscription_builder_id: str):
@model_validate(TriggerSubscriptionBuilderUpdatePayload)
def post(
self,
req_data: TriggerSubscriptionBuilderUpdatePayload,
tenant_id: str,
user: Account,
provider: str,
subscription_builder_id: str,
):
"""Build a subscription instance for a trigger provider"""
payload = TriggerSubscriptionBuilderUpdatePayload.model_validate(console_ns.payload or {})
try:
# Use atomic update_and_build to prevent race conditions
TriggerSubscriptionBuilderService.update_and_build_builder(
@@ -417,9 +437,9 @@ class TriggerSubscriptionBuilderBuildApi(Resource):
provider_id=TriggerProviderID(provider),
subscription_builder_id=subscription_builder_id,
subscription_builder_updater=SubscriptionBuilderUpdater(
name=payload.name,
parameters=payload.parameters,
properties=payload.properties,
name=req_data.name,
parameters=req_data.parameters,
properties=req_data.properties,
),
)
return SimpleResultResponse(result="success").model_dump(mode="json")
@@ -442,11 +462,10 @@ class TriggerSubscriptionUpdateApi(Resource):
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
@account_initialization_required
@with_current_tenant_id
def post(self, tenant_id: str, subscription_id: str):
@model_validate(TriggerSubscriptionBuilderUpdatePayload)
def post(self, req_data: TriggerSubscriptionBuilderUpdatePayload, tenant_id: str, subscription_id: str):
"""Update a subscription instance"""
request = TriggerSubscriptionBuilderUpdatePayload.model_validate(console_ns.payload or {})
subscription = TriggerProviderService.get_subscription_by_id(
tenant_id=tenant_id,
subscription_id=subscription_id,
@@ -458,7 +477,9 @@ class TriggerSubscriptionUpdateApi(Resource):
try:
# For rename only, just update the name
rename = request.name is not None and not any((request.credentials, request.parameters, request.properties))
rename = req_data.name is not None and not any(
(req_data.credentials, req_data.parameters, req_data.properties)
)
# When credential type is UNAUTHORIZED, it indicates the subscription was manually created
# For Manually created subscription, they dont have credentials, parameters
# They only have name and properties(which is input by user)
@@ -467,8 +488,8 @@ class TriggerSubscriptionUpdateApi(Resource):
TriggerProviderService.update_trigger_subscription(
tenant_id=tenant_id,
subscription_id=subscription_id,
name=request.name,
properties=request.properties,
name=req_data.name,
properties=req_data.properties,
)
return SimpleResultResponse(result="success").model_dump(mode="json")
@@ -476,11 +497,11 @@ class TriggerSubscriptionUpdateApi(Resource):
# we need to call third party provider(e.g. GitHub) to rebuild the subscription
TriggerProviderService.rebuild_trigger_subscription(
tenant_id=tenant_id,
name=request.name,
name=req_data.name,
provider_id=provider_id,
subscription_id=subscription_id,
credentials=request.credentials or subscription.credentials,
parameters=request.parameters or subscription.parameters,
credentials=req_data.credentials or subscription.credentials,
parameters=req_data.parameters or subscription.parameters,
)
return SimpleResultResponse(result="success").model_dump(mode="json")
except ValueError as e:
@@ -740,18 +761,17 @@ class TriggerOAuthClientManageApi(Resource):
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
@account_initialization_required
@with_current_tenant_id
def post(self, tenant_id: str, provider: str):
@model_validate(TriggerOAuthClientPayload)
def post(self, req_data: TriggerOAuthClientPayload, tenant_id: str, provider: str):
"""Configure custom OAuth client for a provider"""
payload = TriggerOAuthClientPayload.model_validate(console_ns.payload or {})
try:
provider_id = TriggerProviderID(provider)
result = TriggerProviderService.save_custom_oauth_client_params(
tenant_id=tenant_id,
provider_id=provider_id,
client_params=payload.client_params,
enabled=payload.enabled,
client_params=req_data.client_params,
enabled=req_data.enabled,
)
return dump_response(SimpleResultResponse, result)
@@ -805,18 +825,24 @@ class TriggerSubscriptionVerifyApi(Resource):
@account_initialization_required
@with_current_user
@with_current_tenant_id
def post(self, tenant_id: str, user: Account, provider: str, subscription_id: str):
@model_validate(TriggerSubscriptionBuilderVerifyPayload)
def post(
self,
req_data: TriggerSubscriptionBuilderVerifyPayload,
tenant_id: str,
user: Account,
provider: str,
subscription_id: str,
):
"""Verify credentials for an existing subscription (edit mode only)"""
verify_request = TriggerSubscriptionBuilderVerifyPayload.model_validate(console_ns.payload or {})
try:
result = TriggerProviderService.verify_subscription_credentials(
tenant_id=tenant_id,
user_id=user.id,
provider_id=TriggerProviderID(provider),
subscription_id=subscription_id,
credentials=verify_request.credentials,
credentials=req_data.credentials,
)
return dump_response(TriggerVerificationResponse, result)
except ValueError as e:
@@ -16,6 +16,7 @@ from controllers.console.auth.error import (
from controllers.console.error import AccountInFreezeError
from controllers.console.workspace.account import (
AccountAvatarApi,
AccountAvatarQuery,
AccountDeleteApi,
AccountDeleteVerifyApi,
AccountInitApi,
@@ -216,7 +217,7 @@ class TestAccountAvatarApiGet:
return_value="https://signed/example",
) as sign_mock,
):
result = method(api, user)
result = method(api, AccountAvatarQuery(avatar=file_id), user)
assert result == {"avatar_url": "https://signed/example"}
sign_mock.assert_called_once_with(upload_file_id=file_id)
@@ -256,7 +257,7 @@ class TestAccountAvatarApiGet:
) as sign_mock,
):
with pytest.raises(NotFound):
method(api, user)
method(api, AccountAvatarQuery(avatar=file_id), user)
sign_mock.assert_not_called()
@@ -289,7 +290,7 @@ class TestAccountAvatarApiGet:
return_value="https://signed/example",
) as sign_mock,
):
result = method(api, user)
result = method(api, AccountAvatarQuery(avatar=file_id), user)
assert result == {"avatar_url": "https://signed/example"}
sign_mock.assert_called_once_with(upload_file_id=file_id)
@@ -308,7 +309,7 @@ class TestAccountAvatarApiGet:
return_value="https://signed/should-not-use",
) as sign_mock,
):
result = method(api, user)
result = method(api, AccountAvatarQuery(avatar=external), user)
assert result == {"avatar_url": external}
sign_mock.assert_not_called()
@@ -17,7 +17,9 @@ from controllers.console.workspace.endpoint import (
EndpointIdPayload,
EndpointItemApi,
EndpointListApi,
EndpointListForPluginQuery,
EndpointListForSinglePluginApi,
EndpointListQuery,
EndpointUpdatePayload,
LegacyEndpointUpdatePayload,
)
@@ -146,7 +148,7 @@ class TestEndpointListApi:
return_value=[endpoint_entity],
),
):
result = method(api, "t1", "u1")
result = method(api, EndpointListQuery(page=1, page_size=10), "t1", "u1")
endpoint = result["endpoints"][0]
assert endpoint["id"] == "e1"
@@ -180,7 +182,7 @@ class TestEndpointListApi:
app.test_request_context("/?page=0&page_size=10"),
):
with pytest.raises(ValueError):
method(api, "t1", "u1")
method(api, EndpointListQuery(page=0, page_size=10), "t1", "u1")
class TestEndpointListForSinglePluginApi:
@@ -195,7 +197,7 @@ class TestEndpointListForSinglePluginApi:
return_value=[_endpoint_entity()],
),
):
result = method(api, "t1", "u1")
result = method(api, EndpointListForPluginQuery(page=1, page_size=10, plugin_id="p1"), "t1", "u1")
assert result["endpoints"][0]["id"] == "e1"
assert result["endpoints"][0]["settings"]["api_key"] == "pl********et"
@@ -209,7 +211,7 @@ class TestEndpointListForSinglePluginApi:
app.test_request_context("/?page=1&page_size=10"),
):
with pytest.raises(ValueError):
method(api, "t1", "u1")
method(api, EndpointListForPluginQuery(page=1, page_size=10), "t1", "u1")
class TestEndpointItemApi:
@@ -15,6 +15,16 @@ from controllers.console.workspace.models import (
ModelProviderModelEnableApi,
ModelProviderModelParameterRuleApi,
ModelProviderModelValidateApi,
ParserCreateCredential,
ParserDeleteCredential,
ParserDeleteModels,
ParserGetCredentials,
ParserGetDefault,
ParserParameter,
ParserPostDefault,
ParserPostModels,
ParserSwitch,
ParserValidate,
)
from graphon.model_runtime.entities.model_entities import ModelType
from graphon.model_runtime.errors.validate import CredentialsValidateFailedError
@@ -43,7 +53,7 @@ class TestDefaultModelApi:
},
}
result = method(api, "tenant1")
result = method(api, ParserGetDefault(model_type=ModelType.LLM), "tenant1")
assert "data" in result
@@ -65,7 +75,7 @@ class TestDefaultModelApi:
app.test_request_context("/", json=payload),
patch("controllers.console.workspace.models.ModelProviderService"),
):
result = method(api, "tenant1")
result = method(api, ParserPostDefault.model_validate(payload), "tenant1")
assert result["result"] == "success"
@@ -79,7 +89,7 @@ class TestDefaultModelApi:
):
service.return_value.get_default_model_of_model_type.return_value = None
result = method(api, "t1")
result = method(api, ParserGetDefault(model_type=ModelType.LLM), "t1")
assert "data" in result
@@ -117,7 +127,7 @@ class TestModelProviderModelApi:
patch("controllers.console.workspace.models.ModelProviderService"),
patch("controllers.console.workspace.models.ModelLoadBalancingService"),
):
result, status = method(api, "tenant1", "openai")
result, status = method(api, ParserPostModels.model_validate(payload), "tenant1", "openai")
assert status == 200
@@ -134,7 +144,7 @@ class TestModelProviderModelApi:
app.test_request_context("/", json=payload),
patch("controllers.console.workspace.models.ModelProviderService"),
):
result, status = method(api, "tenant1", "openai")
result, status = method(api, ParserDeleteModels.model_validate(payload), "tenant1", "openai")
assert status == 204
@@ -177,7 +187,13 @@ class TestModelProviderModelCredentialApi:
provider_service.return_value.provider_manager.get_provider_model_available_credentials.return_value = []
lb_service.return_value.get_load_balancing_configs.return_value = (False, [])
result = method(api, "tenant1", SimpleNamespace(id="u1"), "openai")
result = method(
api,
ParserGetCredentials(model="gpt-4", model_type=ModelType.LLM),
"tenant1",
SimpleNamespace(id="u1"),
"openai",
)
assert "credentials" in result
@@ -195,7 +211,7 @@ class TestModelProviderModelCredentialApi:
app.test_request_context("/", json=payload),
patch("controllers.console.workspace.models.ModelProviderService"),
):
result, status = method(api, "tenant1", "openai")
result, status = method(api, ParserCreateCredential.model_validate(payload), "tenant1", "openai")
assert status == 201
@@ -212,7 +228,13 @@ class TestModelProviderModelCredentialApi:
service.return_value.provider_manager.get_provider_model_available_credentials.return_value = []
lb.return_value.get_load_balancing_configs.return_value = (False, [])
result = method(api, "t1", SimpleNamespace(id="u1"), "openai")
result = method(
api,
ParserGetCredentials(model="gpt", model_type=ModelType.LLM),
"t1",
SimpleNamespace(id="u1"),
"openai",
)
assert result["credentials"] == {}
@@ -230,7 +252,7 @@ class TestModelProviderModelCredentialApi:
app.test_request_context("/", json=payload),
patch("controllers.console.workspace.models.ModelProviderService"),
):
result, status = method(api, "t1", "openai")
result, status = method(api, ParserDeleteCredential.model_validate(payload), "t1", "openai")
assert status == 204
@@ -250,7 +272,7 @@ class TestModelProviderModelCredentialSwitchApi:
app.test_request_context("/", json=payload),
patch("controllers.console.workspace.models.ModelProviderService"),
):
result = method(api, "tenant1", "openai")
result = method(api, ParserSwitch.model_validate(payload), "tenant1", "openai")
assert result["result"] == "success"
@@ -269,7 +291,7 @@ class TestModelEnableDisableApis:
app.test_request_context("/", json=payload),
patch("controllers.console.workspace.models.ModelProviderService"),
):
result = method(api, "tenant1", "openai")
result = method(api, ParserDeleteModels.model_validate(payload), "tenant1", "openai")
assert result["result"] == "success"
@@ -286,7 +308,7 @@ class TestModelEnableDisableApis:
app.test_request_context("/", json=payload),
patch("controllers.console.workspace.models.ModelProviderService"),
):
result = method(api, "tenant1", "openai")
result = method(api, ParserDeleteModels.model_validate(payload), "tenant1", "openai")
assert result["result"] == "success"
@@ -306,7 +328,7 @@ class TestModelProviderModelValidateApi:
app.test_request_context("/", json=payload),
patch("controllers.console.workspace.models.ModelProviderService"),
):
result = method(api, "tenant1", "openai")
result = method(api, ParserValidate.model_validate(payload), "tenant1", "openai")
assert result["result"] == "success"
@@ -327,7 +349,7 @@ class TestModelProviderModelValidateApi:
):
service_mock.return_value.validate_model_credentials.side_effect = CredentialsValidateFailedError("invalid")
result = method(api, "tenant1", "openai")
result = method(api, ParserValidate.model_validate(payload), "tenant1", "openai")
assert result["result"] == "error"
@@ -343,7 +365,7 @@ class TestParameterAndAvailableModels:
):
service_mock.return_value.get_model_parameter_rules.return_value = []
result = method(api, "tenant1", "openai")
result = method(api, ParserParameter(model="gpt-4"), "tenant1", "openai")
assert "data" in result
@@ -371,7 +393,7 @@ class TestParameterAndAvailableModels:
):
service.return_value.get_model_parameter_rules.return_value = []
result = method(api, "t1", "openai")
result = method(api, ParserParameter(model="gpt"), "t1", "openai")
assert result["data"] == []
@@ -26,6 +26,7 @@ from werkzeug.exceptions import Forbidden, NotFound
from configs import dify_config
from controllers.console.workspace import rbac as rbac_mod
from controllers.console.workspace.rbac import _RolesListQuery
@pytest.fixture
@@ -175,7 +176,10 @@ class TestPaginationMapping:
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")),
patch("controllers.console.workspace.rbac.svc.RBACService.Roles.list") as mock_list,
):
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi())
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(
rbac_mod.RBACRolesApi(),
_RolesListQuery.model_validate({"page": 1, "limit": 2, "include_owner": 1}),
)
owner_permission_keys = rbac_mod._LEGACY_ROLE_PERMISSION_KEYS["owner"]
valid_owner_permission_keys = []
@@ -230,7 +234,7 @@ class TestPaginationMapping:
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")),
patch("controllers.console.workspace.rbac.svc.RBACService.Roles.list"),
):
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi())
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi(), _RolesListQuery())
names = [r["name"] for r in response["data"]]
assert "owner" not in names
@@ -242,7 +246,10 @@ class TestPaginationMapping:
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")),
patch("controllers.console.workspace.rbac.svc.RBACService.Roles.list"),
):
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi())
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(
rbac_mod.RBACRolesApi(),
_RolesListQuery.model_validate({"include_owner": 1}),
)
names = [r["name"] for r in response["data"]]
assert "owner" in names
@@ -254,7 +261,7 @@ class TestPaginationMapping:
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")),
patch("controllers.console.workspace.rbac.svc.RBACService.Roles.list"),
):
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi())
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi(), _RolesListQuery())
names = [r["name"] for r in response["data"]]
assert "owner" not in names
@@ -267,7 +274,10 @@ class TestPaginationMapping:
patch("controllers.console.workspace.rbac.svc.RBACService.Roles.list") as mock_list,
patch("controllers.console.workspace.rbac._dump", return_value={}),
):
inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi())
inspect.unwrap(rbac_mod.RBACRolesApi.get)(
rbac_mod.RBACRolesApi(),
_RolesListQuery.model_validate({"page": 2, "limit": 50, "reverse": True, "include_owner": 1}),
)
_, kwargs = mock_list.call_args
options = kwargs["options"]
@@ -15,15 +15,19 @@ from controllers.console.workspace.trigger_providers import (
TriggerOAuthAuthorizeApi,
TriggerOAuthCallbackApi,
TriggerOAuthClientManageApi,
TriggerOAuthClientPayload,
TriggerProviderIconApi,
TriggerProviderInfoApi,
TriggerProviderListApi,
TriggerSubscriptionBuilderBuildApi,
TriggerSubscriptionBuilderCreateApi,
TriggerSubscriptionBuilderCreatePayload,
TriggerSubscriptionBuilderGetApi,
TriggerSubscriptionBuilderLogsApi,
TriggerSubscriptionBuilderUpdateApi,
TriggerSubscriptionBuilderUpdatePayload,
TriggerSubscriptionBuilderVerifyApi,
TriggerSubscriptionBuilderVerifyPayload,
TriggerSubscriptionListApi,
TriggerSubscriptionUpdateApi,
TriggerSubscriptionVerifyApi,
@@ -163,7 +167,13 @@ class TestTriggerSubscriptionBuilderApis:
return_value=subscription_builder(),
),
):
result = method(api, "t1", mock_user(), "github")
result = method(
api,
TriggerSubscriptionBuilderCreatePayload(credential_type="UNAUTHORIZED"),
"t1",
mock_user(),
"github",
)
assert result["subscription_builder"]["id"] == "b1"
def test_get_builder(self, app: Flask) -> None:
@@ -196,7 +206,14 @@ class TestTriggerSubscriptionBuilderApis:
return_value={"verified": True},
),
):
assert method(api, "t1", mock_user(), "github", "b1") == {"verified": True}
assert method(
api,
TriggerSubscriptionBuilderVerifyPayload(credentials={"a": 1}),
"t1",
mock_user(),
"github",
"b1",
) == {"verified": True}
def test_verify_builder_error(self, app: Flask) -> None:
api = TriggerSubscriptionBuilderVerifyApi()
@@ -210,7 +227,14 @@ class TestTriggerSubscriptionBuilderApis:
),
):
with pytest.raises(ValueError):
method(api, "t1", mock_user(), "github", "b1")
method(
api,
TriggerSubscriptionBuilderVerifyPayload(credentials={}),
"t1",
mock_user(),
"github",
"b1",
)
def test_update_builder(self, app: Flask) -> None:
api = TriggerSubscriptionBuilderUpdateApi()
@@ -223,7 +247,17 @@ class TestTriggerSubscriptionBuilderApis:
return_value=subscription_builder(),
) as mock_update_builder,
):
assert method(api, "t1", mock_user(), "github", "b1")["id"] == "b1"
assert (
method(
api,
TriggerSubscriptionBuilderUpdatePayload(name="n"),
"t1",
mock_user(),
"github",
"b1",
)["id"]
== "b1"
)
mock_update_builder.assert_called_once_with(
tenant_id="t1",
user_id="u1",
@@ -263,7 +297,14 @@ class TestTriggerSubscriptionBuilderApis:
return_value=None,
),
):
assert method(api, "t1", mock_user(), "github", "b1") == {"result": "success"}
assert method(
api,
TriggerSubscriptionBuilderUpdatePayload(name="x"),
"t1",
mock_user(),
"github",
"b1",
) == {"result": "success"}
class TestTriggerSubscriptionCrud:
@@ -283,7 +324,12 @@ class TestTriggerSubscriptionCrud:
),
patch("controllers.console.workspace.trigger_providers.TriggerProviderService.update_trigger_subscription"),
):
assert method(api, "t1", "s1") == {"result": "success"}
assert method(
api,
TriggerSubscriptionBuilderUpdatePayload(name="x"),
"t1",
"s1",
) == {"result": "success"}
def test_update_not_found(self, app: Flask) -> None:
api = TriggerSubscriptionUpdateApi()
@@ -297,7 +343,7 @@ class TestTriggerSubscriptionCrud:
),
):
with pytest.raises(NotFoundError):
method(api, "t1", "x")
method(api, TriggerSubscriptionBuilderUpdatePayload(name="x"), "t1", "x")
def test_update_rebuild(self, app: Flask) -> None:
api = TriggerSubscriptionUpdateApi()
@@ -319,7 +365,12 @@ class TestTriggerSubscriptionCrud:
"controllers.console.workspace.trigger_providers.TriggerProviderService.rebuild_trigger_subscription"
),
):
assert method(api, "t1", "s1") == {"result": "success"}
assert method(
api,
TriggerSubscriptionBuilderUpdatePayload(credentials={}),
"t1",
"s1",
) == {"result": "success"}
class TestTriggerOAuthApis:
@@ -499,7 +550,12 @@ class TestTriggerOAuthClientManageApi:
return_value={"result": "success"},
),
):
assert method(api, "t1", "github") == {"result": "success"}
assert method(
api,
TriggerOAuthClientPayload(enabled=True),
"t1",
"github",
) == {"result": "success"}
def test_delete_client(self, app: Flask) -> None:
api = TriggerOAuthClientManageApi()
@@ -526,7 +582,7 @@ class TestTriggerOAuthClientManageApi:
),
):
with pytest.raises(BadRequest):
method(api, "t1", "github")
method(api, TriggerOAuthClientPayload(enabled=True), "t1", "github")
class TestTriggerSubscriptionVerifyApi:
@@ -541,7 +597,14 @@ class TestTriggerSubscriptionVerifyApi:
return_value={"verified": True},
),
):
assert method(api, "t1", mock_user(), "github", "s1") == {"verified": True}
assert method(
api,
TriggerSubscriptionBuilderVerifyPayload(credentials={}),
"t1",
mock_user(),
"github",
"s1",
) == {"verified": True}
@pytest.mark.parametrize("raised_exception", [ValueError("bad"), Exception("boom")])
def test_verify_errors(self, app: Flask, raised_exception: Exception) -> None:
@@ -556,4 +619,11 @@ class TestTriggerSubscriptionVerifyApi:
),
):
with pytest.raises(BadRequest):
method(api, "t1", mock_user(), "github", "s1")
method(
api,
TriggerSubscriptionBuilderVerifyPayload(credentials={}),
"t1",
mock_user(),
"github",
"s1",
)