mirror of
https://github.com/langgenius/dify.git
synced 2026-11-10 21:35:45 +08:00
ci: enable Ruff E402 (#44123)
Co-authored-by: Codex <codex@openai.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
63 files changed
+700
-370
No files matched your search
@@ -58,7 +58,6 @@ select = [
|
||||
]
|
||||
|
||||
ignore = [
|
||||
"E402", # module-import-not-at-top-of-file
|
||||
"F841", # unused-variable
|
||||
"B901", # allow return in yield
|
||||
"B904", # raise-without-from-inside-except
|
||||
|
||||
@@ -8,6 +8,7 @@ pscycogreen_gevent.patch_psycopg()
|
||||
print("psycopg2 patched with gevent.", flush=True) # noqa: T201
|
||||
|
||||
|
||||
from app import app, celery
|
||||
# Import after gevent patching.
|
||||
from app import app, celery # noqa: E402
|
||||
|
||||
__all__ = ["app", "celery"]
|
||||
@@ -5,14 +5,13 @@ from typing import Any, override
|
||||
|
||||
from pydantic.fields import FieldInfo
|
||||
|
||||
from .http_request import NacosHttpClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from configs.remote_settings_sources.base import RemoteSettingsSource
|
||||
|
||||
from .http_request import NacosHttpClient
|
||||
from .utils import parse_config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NacosSettingsSource(RemoteSettingsSource):
|
||||
def __init__(self, configs: Mapping[str, Any]):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from collections import OrderedDict
|
||||
from http import HTTPStatus
|
||||
from importlib import import_module
|
||||
from types import ModuleType
|
||||
from typing import cast
|
||||
|
||||
from flask import Blueprint, current_app, got_request_exception
|
||||
@@ -128,9 +129,224 @@ RESOURCE_MODULES = (
|
||||
for module_name in RESOURCE_MODULES:
|
||||
import_module(module_name)
|
||||
|
||||
# Ensure resource modules are imported so route decorators are evaluated.
|
||||
# Import other controllers
|
||||
from . import (
|
||||
|
||||
def _register_controllers() -> tuple[ModuleType, ...]:
|
||||
"""Load route modules after their namespaces and schema models exist."""
|
||||
|
||||
# Ensure resource modules are imported so route decorators are evaluated.
|
||||
# Import other controllers
|
||||
from . import (
|
||||
apikey,
|
||||
extension,
|
||||
feature,
|
||||
human_input_form,
|
||||
init_validate,
|
||||
notification,
|
||||
onboarding,
|
||||
resource_access_token,
|
||||
setup,
|
||||
spec,
|
||||
system,
|
||||
workflow_run_archive,
|
||||
)
|
||||
from .agent import composer as agent_composer
|
||||
from .agent import roster as agent_roster
|
||||
|
||||
# Import app controllers
|
||||
from .app import (
|
||||
advanced_prompt_template,
|
||||
agent,
|
||||
agent_app_access,
|
||||
agent_app_feature,
|
||||
agent_app_sandbox,
|
||||
agent_config_inspector,
|
||||
annotation,
|
||||
app,
|
||||
audio,
|
||||
completion,
|
||||
conversation,
|
||||
conversation_variables,
|
||||
generator,
|
||||
mcp_server,
|
||||
message,
|
||||
model_config,
|
||||
ops_trace,
|
||||
site,
|
||||
statistic,
|
||||
workflow,
|
||||
workflow_app_log,
|
||||
workflow_comment,
|
||||
workflow_draft_variable,
|
||||
workflow_node_output_inspector,
|
||||
workflow_run,
|
||||
workflow_statistic,
|
||||
workflow_trigger,
|
||||
)
|
||||
|
||||
# Import auth controllers
|
||||
from .auth import (
|
||||
activate,
|
||||
data_source_bearer_auth,
|
||||
data_source_oauth,
|
||||
email_register,
|
||||
forgot_password,
|
||||
login,
|
||||
oauth,
|
||||
oauth_server,
|
||||
)
|
||||
|
||||
# Import billing controllers
|
||||
from .billing import billing, compliance
|
||||
|
||||
# Import datasets controllers
|
||||
from .datasets import (
|
||||
data_source,
|
||||
datasets,
|
||||
datasets_document,
|
||||
datasets_segments,
|
||||
external,
|
||||
hit_testing,
|
||||
metadata,
|
||||
website,
|
||||
)
|
||||
from .datasets.rag_pipeline import (
|
||||
datasource_auth,
|
||||
datasource_content_preview,
|
||||
rag_pipeline,
|
||||
rag_pipeline_datasets,
|
||||
rag_pipeline_draft_variable,
|
||||
rag_pipeline_import,
|
||||
rag_pipeline_workflow,
|
||||
)
|
||||
|
||||
# Import explore controllers
|
||||
from .explore import (
|
||||
banner,
|
||||
installed_app,
|
||||
parameter,
|
||||
recommended_app,
|
||||
saved_message,
|
||||
trial,
|
||||
)
|
||||
from .snippets import snippet_workflow, snippet_workflow_draft_variable
|
||||
from .socketio import workflow as socketio_workflow
|
||||
|
||||
# Import tag controllers
|
||||
from .tag import tags
|
||||
|
||||
# Import workspace controllers
|
||||
from .workspace import (
|
||||
account,
|
||||
agent_providers,
|
||||
endpoint,
|
||||
load_balancing_config,
|
||||
members,
|
||||
model_providers,
|
||||
models,
|
||||
plugin,
|
||||
rbac,
|
||||
skills,
|
||||
snippets,
|
||||
tool_providers,
|
||||
trigger_providers,
|
||||
workspace,
|
||||
)
|
||||
|
||||
return (
|
||||
apikey,
|
||||
extension,
|
||||
feature,
|
||||
human_input_form,
|
||||
init_validate,
|
||||
notification,
|
||||
onboarding,
|
||||
resource_access_token,
|
||||
setup,
|
||||
spec,
|
||||
system,
|
||||
workflow_run_archive,
|
||||
agent_composer,
|
||||
agent_roster,
|
||||
advanced_prompt_template,
|
||||
agent,
|
||||
agent_app_access,
|
||||
agent_app_feature,
|
||||
agent_app_sandbox,
|
||||
agent_config_inspector,
|
||||
annotation,
|
||||
app,
|
||||
audio,
|
||||
completion,
|
||||
conversation,
|
||||
conversation_variables,
|
||||
generator,
|
||||
mcp_server,
|
||||
message,
|
||||
model_config,
|
||||
ops_trace,
|
||||
site,
|
||||
statistic,
|
||||
workflow,
|
||||
workflow_app_log,
|
||||
workflow_comment,
|
||||
workflow_draft_variable,
|
||||
workflow_node_output_inspector,
|
||||
workflow_run,
|
||||
workflow_statistic,
|
||||
workflow_trigger,
|
||||
activate,
|
||||
data_source_bearer_auth,
|
||||
data_source_oauth,
|
||||
email_register,
|
||||
forgot_password,
|
||||
login,
|
||||
oauth,
|
||||
oauth_server,
|
||||
billing,
|
||||
compliance,
|
||||
data_source,
|
||||
datasets,
|
||||
datasets_document,
|
||||
datasets_segments,
|
||||
external,
|
||||
hit_testing,
|
||||
metadata,
|
||||
website,
|
||||
datasource_auth,
|
||||
datasource_content_preview,
|
||||
rag_pipeline,
|
||||
rag_pipeline_datasets,
|
||||
rag_pipeline_draft_variable,
|
||||
rag_pipeline_import,
|
||||
rag_pipeline_workflow,
|
||||
banner,
|
||||
installed_app,
|
||||
parameter,
|
||||
recommended_app,
|
||||
saved_message,
|
||||
trial,
|
||||
snippet_workflow,
|
||||
snippet_workflow_draft_variable,
|
||||
socketio_workflow,
|
||||
tags,
|
||||
account,
|
||||
agent_providers,
|
||||
endpoint,
|
||||
load_balancing_config,
|
||||
members,
|
||||
model_providers,
|
||||
models,
|
||||
plugin,
|
||||
rbac,
|
||||
skills,
|
||||
snippets,
|
||||
tool_providers,
|
||||
trigger_providers,
|
||||
workspace,
|
||||
)
|
||||
|
||||
|
||||
(
|
||||
apikey,
|
||||
extension,
|
||||
feature,
|
||||
@@ -143,12 +359,8 @@ from . import (
|
||||
spec,
|
||||
system,
|
||||
workflow_run_archive,
|
||||
)
|
||||
from .agent import composer as agent_composer
|
||||
from .agent import roster as agent_roster
|
||||
|
||||
# Import app controllers
|
||||
from .app import (
|
||||
agent_composer,
|
||||
agent_roster,
|
||||
advanced_prompt_template,
|
||||
agent,
|
||||
agent_app_access,
|
||||
@@ -176,10 +388,6 @@ from .app import (
|
||||
workflow_run,
|
||||
workflow_statistic,
|
||||
workflow_trigger,
|
||||
)
|
||||
|
||||
# Import auth controllers
|
||||
from .auth import (
|
||||
activate,
|
||||
data_source_bearer_auth,
|
||||
data_source_oauth,
|
||||
@@ -188,13 +396,8 @@ from .auth import (
|
||||
login,
|
||||
oauth,
|
||||
oauth_server,
|
||||
)
|
||||
|
||||
# Import billing controllers
|
||||
from .billing import billing, compliance
|
||||
|
||||
# Import datasets controllers
|
||||
from .datasets import (
|
||||
billing,
|
||||
compliance,
|
||||
data_source,
|
||||
datasets,
|
||||
datasets_document,
|
||||
@@ -203,8 +406,6 @@ from .datasets import (
|
||||
hit_testing,
|
||||
metadata,
|
||||
website,
|
||||
)
|
||||
from .datasets.rag_pipeline import (
|
||||
datasource_auth,
|
||||
datasource_content_preview,
|
||||
rag_pipeline,
|
||||
@@ -212,25 +413,16 @@ from .datasets.rag_pipeline import (
|
||||
rag_pipeline_draft_variable,
|
||||
rag_pipeline_import,
|
||||
rag_pipeline_workflow,
|
||||
)
|
||||
|
||||
# Import explore controllers
|
||||
from .explore import (
|
||||
banner,
|
||||
installed_app,
|
||||
parameter,
|
||||
recommended_app,
|
||||
saved_message,
|
||||
trial,
|
||||
)
|
||||
from .snippets import snippet_workflow, snippet_workflow_draft_variable
|
||||
from .socketio import workflow as socketio_workflow
|
||||
|
||||
# Import tag controllers
|
||||
from .tag import tags
|
||||
|
||||
# Import workspace controllers
|
||||
from .workspace import (
|
||||
snippet_workflow,
|
||||
snippet_workflow_draft_variable,
|
||||
socketio_workflow,
|
||||
tags,
|
||||
account,
|
||||
agent_providers,
|
||||
endpoint,
|
||||
@@ -245,7 +437,7 @@ from .workspace import (
|
||||
tool_providers,
|
||||
trigger_providers,
|
||||
workspace,
|
||||
)
|
||||
) = _register_controllers()
|
||||
|
||||
api.add_namespace(console_ns)
|
||||
|
||||
|
||||
@@ -355,7 +355,6 @@ class AnnotationApi(Resource):
|
||||
@console_ns.response(204, "Annotations deleted successfully")
|
||||
@with_session
|
||||
def delete(self, session: Session, app_id: UUID):
|
||||
|
||||
# Use request.args.getlist to get annotation_ids array directly
|
||||
annotation_ids = request.args.getlist("annotation_id")
|
||||
|
||||
|
||||
@@ -222,7 +222,6 @@ class CompletionMessageStopApi(Resource):
|
||||
@with_current_user_id
|
||||
@get_app_model(mode=AppMode.COMPLETION)
|
||||
def post(self, current_user_id: str, app_model: App, task_id: str):
|
||||
|
||||
AppTaskService.stop_task(
|
||||
task_id=task_id,
|
||||
invoke_from=InvokeFrom.DEBUGGER,
|
||||
|
||||
@@ -111,7 +111,6 @@ class CompletionConversationApi(Resource):
|
||||
@get_app_model(mode=AppMode.COMPLETION)
|
||||
@model_validate(CompletionConversationQuery)
|
||||
def get(self, req_data: CompletionConversationQuery, session: Session, current_user: Account, app_model: App):
|
||||
|
||||
query = sa.select(Conversation).where(
|
||||
Conversation.app_id == app_model.id, Conversation.mode == "completion", Conversation.is_deleted.is_(False)
|
||||
)
|
||||
@@ -241,7 +240,6 @@ class ChatConversationApi(Resource):
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
||||
@model_validate(ChatConversationQuery)
|
||||
def get(self, req_data: ChatConversationQuery, session: Session, current_user: Account, app_model: App):
|
||||
|
||||
subquery = (
|
||||
sa.select(Conversation.id.label("conversation_id"), EndUser.session_id.label("from_end_user_session_id"))
|
||||
.outerjoin(EndUser, Conversation.from_end_user_id == EndUser.id)
|
||||
|
||||
@@ -274,7 +274,6 @@ class RuleGenerateApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(RuleGeneratePayload)
|
||||
def post(self, req_data: RuleGeneratePayload, current_tenant_id: str):
|
||||
|
||||
try:
|
||||
rules = LLMGenerator.generate_rule_config(tenant_id=current_tenant_id, args=req_data)
|
||||
except ProviderTokenNotInitError as ex:
|
||||
@@ -308,7 +307,6 @@ class RuleCodeGenerateApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(RuleCodeGeneratePayload)
|
||||
def post(self, req_data: RuleCodeGeneratePayload, current_tenant_id: str):
|
||||
|
||||
try:
|
||||
code_result = LLMGenerator.generate_code(
|
||||
tenant_id=current_tenant_id,
|
||||
@@ -349,7 +347,6 @@ class RuleStructuredOutputGenerateApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(RuleStructuredOutputPayload)
|
||||
def post(self, req_data: RuleStructuredOutputPayload, current_tenant_id: str):
|
||||
|
||||
try:
|
||||
structured_output = LLMGenerator.generate_structured_output(
|
||||
tenant_id=current_tenant_id,
|
||||
@@ -563,7 +560,6 @@ class WorkflowGenerateApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(WorkflowGeneratePayload)
|
||||
def post(self, req_data: WorkflowGeneratePayload, current_tenant_id: str):
|
||||
|
||||
# Reject empty / over-length instructions at the boundary (shared with
|
||||
# the streaming endpoint) before spending a planner+builder roundtrip.
|
||||
guard = _workflow_instruction_guard(req_data)
|
||||
@@ -656,7 +652,6 @@ class WorkflowGenerateStreamApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(WorkflowGeneratePayload)
|
||||
def post(self, req_data: WorkflowGeneratePayload, current_tenant_id: str):
|
||||
|
||||
# Same boundary guards as the blocking endpoint — return a normal 400
|
||||
# JSON for these BEFORE opening the stream.
|
||||
guard = _workflow_instruction_guard(req_data)
|
||||
|
||||
@@ -517,7 +517,6 @@ def _update_message_feedback(
|
||||
def _get_message_suggested_questions(
|
||||
*, context: RequestContext, app_id: str, app_mode: str, message_id: UUID
|
||||
) -> dict[str, object]:
|
||||
|
||||
try:
|
||||
questions = application_services().message_suggested_questions.get_suggested_questions(
|
||||
app_id=app_id,
|
||||
|
||||
@@ -545,7 +545,6 @@ class ConversationVariableCollectionApi(Resource):
|
||||
@get_app_model(mode=AppMode.ADVANCED_CHAT)
|
||||
@model_validate(ConversationVariableUpdatePayload)
|
||||
def post(self, req_data: ConversationVariableUpdatePayload, current_user: Account, app_model: App):
|
||||
|
||||
workflow_service = WorkflowService()
|
||||
|
||||
conversation_variables_list = [
|
||||
@@ -647,7 +646,6 @@ class EnvironmentVariableCollectionApi(Resource):
|
||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||
@model_validate(EnvironmentVariableUpdatePayload)
|
||||
def post(self, req_data: EnvironmentVariableUpdatePayload, current_user: Account, app_model: App):
|
||||
|
||||
workflow_service = WorkflowService()
|
||||
|
||||
environment_variables_list = [
|
||||
|
||||
@@ -1168,7 +1168,6 @@ class RagPipelineRecommendedPluginApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(RagPipelineRecommendedPluginQuery)
|
||||
def get(self, req_data: RagPipelineRecommendedPluginQuery, current_tenant_id: str, current_user: Account):
|
||||
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
recommended_plugins = rag_pipeline_service.get_recommended_plugins(
|
||||
req_data.type, current_user, current_tenant_id
|
||||
|
||||
@@ -300,7 +300,6 @@ class EndpointListApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@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,
|
||||
@@ -330,7 +329,6 @@ class EndpointListForSinglePluginApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@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,
|
||||
|
||||
@@ -216,7 +216,6 @@ class DefaultModelApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@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=req_data.model_type
|
||||
@@ -323,7 +322,6 @@ class ModelProviderModelApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@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=req_data.model, model_type=req_data.model_type
|
||||
@@ -349,7 +347,6 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@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,
|
||||
@@ -412,7 +409,6 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserCreateCredential)
|
||||
def post(self, req_data: ParserCreateCredential, tenant_id: str, provider: str):
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
|
||||
try:
|
||||
@@ -445,7 +441,6 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserUpdateCredential)
|
||||
def put(self, req_data: ParserUpdateCredential, current_tenant_id: str, provider: str):
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
|
||||
try:
|
||||
@@ -473,7 +468,6 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@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,
|
||||
@@ -498,7 +492,6 @@ class ModelProviderModelCredentialSwitchApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@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,
|
||||
@@ -523,7 +516,6 @@ class ModelProviderModelEnableApi(Resource):
|
||||
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||
@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=req_data.model, model_type=req_data.model_type
|
||||
@@ -545,7 +537,6 @@ class ModelProviderModelDisableApi(Resource):
|
||||
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||
@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=req_data.model, model_type=req_data.model_type
|
||||
@@ -577,7 +568,6 @@ class ModelProviderModelValidateApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserValidate)
|
||||
def post(self, req_data: ParserValidate, tenant_id: str, provider: str):
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
|
||||
result = True
|
||||
@@ -615,7 +605,6 @@ class ModelProviderModelParameterRuleApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@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=req_data.model
|
||||
|
||||
@@ -688,7 +688,6 @@ class PluginListLatestVersionsApi(Resource):
|
||||
@account_initialization_required
|
||||
@model_validate(ParserLatest)
|
||||
def post(self, req_data: ParserLatest):
|
||||
|
||||
try:
|
||||
versions = PluginService.list_latest_versions(req_data.plugin_ids)
|
||||
except PluginDaemonClientSideError as e:
|
||||
@@ -708,7 +707,6 @@ class PluginListInstallationsFromIdsApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserLatest)
|
||||
def post(self, req_data: ParserLatest, tenant_id: str):
|
||||
|
||||
try:
|
||||
plugins = PluginService.list_installations_from_ids(tenant_id, req_data.plugin_ids)
|
||||
except PluginDaemonClientSideError as e:
|
||||
@@ -725,7 +723,6 @@ class PluginIconApi(Resource):
|
||||
@setup_required
|
||||
@model_validate(ParserIcon)
|
||||
def get(self, req_data: ParserIcon):
|
||||
|
||||
try:
|
||||
icon_bytes, mimetype = PluginService.get_asset(req_data.tenant_id, req_data.filename)
|
||||
except PluginDaemonClientSideError as e:
|
||||
@@ -746,7 +743,6 @@ class PluginAssetApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserAsset)
|
||||
def get(self, req_data: ParserAsset, tenant_id: str):
|
||||
|
||||
try:
|
||||
binary = PluginService.extract_asset(tenant_id, req_data.plugin_unique_identifier, req_data.file_name)
|
||||
return send_file(io.BytesIO(binary), mimetype="application/octet-stream")
|
||||
@@ -789,7 +785,6 @@ class PluginUploadFromGithubApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserGithubUpload)
|
||||
def post(self, req_data: ParserGithubUpload, tenant_id: str):
|
||||
|
||||
try:
|
||||
response = PluginService.upload_pkg_from_github(
|
||||
tenant_id, req_data.repo, req_data.version, req_data.package
|
||||
@@ -833,7 +828,6 @@ class PluginInstallFromPkgApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserPluginIdentifiers)
|
||||
def post(self, req_data: ParserPluginIdentifiers, tenant_id: str):
|
||||
|
||||
try:
|
||||
response = PluginService.install_from_local_pkg(tenant_id, req_data.plugin_unique_identifiers)
|
||||
except PluginDaemonClientSideError as e:
|
||||
@@ -855,7 +849,6 @@ class PluginInstallFromGithubApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserGithubInstall)
|
||||
def post(self, req_data: ParserGithubInstall, tenant_id: str):
|
||||
|
||||
try:
|
||||
response = PluginService.install_from_github(
|
||||
tenant_id,
|
||||
@@ -883,7 +876,6 @@ class PluginInstallFromMarketplaceApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserPluginIdentifiers)
|
||||
def post(self, req_data: ParserPluginIdentifiers, tenant_id: str):
|
||||
|
||||
try:
|
||||
response = PluginService.install_from_marketplace_pkg(tenant_id, req_data.plugin_unique_identifiers)
|
||||
except PluginDaemonClientSideError as e:
|
||||
@@ -905,7 +897,6 @@ class PluginFetchMarketplacePkgApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserPluginIdentifierQuery)
|
||||
def get(self, req_data: ParserPluginIdentifierQuery, tenant_id: str):
|
||||
|
||||
try:
|
||||
return jsonable_encoder(
|
||||
{
|
||||
@@ -932,7 +923,6 @@ class PluginFetchManifestApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserPluginIdentifierQuery)
|
||||
def get(self, req_data: ParserPluginIdentifierQuery, tenant_id: str):
|
||||
|
||||
try:
|
||||
return jsonable_encoder(
|
||||
{
|
||||
@@ -957,7 +947,6 @@ class PluginFetchInstallTasksApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserTasks)
|
||||
def get(self, req_data: ParserTasks, tenant_id: str):
|
||||
|
||||
try:
|
||||
return jsonable_encoder(
|
||||
{"tasks": PluginService.fetch_install_tasks(tenant_id, req_data.page, req_data.page_size)}
|
||||
@@ -1043,7 +1032,6 @@ class PluginUpgradeFromMarketplaceApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserMarketplaceUpgrade)
|
||||
def post(self, req_data: ParserMarketplaceUpgrade, tenant_id: str):
|
||||
|
||||
try:
|
||||
return jsonable_encoder(
|
||||
PluginService.upgrade_plugin_with_marketplace(
|
||||
@@ -1067,7 +1055,6 @@ class PluginUpgradeFromGithubApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserGithubUpgrade)
|
||||
def post(self, req_data: ParserGithubUpgrade, tenant_id: str):
|
||||
|
||||
try:
|
||||
return jsonable_encoder(
|
||||
PluginService.upgrade_plugin_with_github(
|
||||
@@ -1096,7 +1083,6 @@ class PluginUninstallApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserUninstall)
|
||||
def post(self, req_data: ParserUninstall, tenant_id: str):
|
||||
|
||||
try:
|
||||
return {
|
||||
"success": PluginService.uninstall(
|
||||
@@ -1171,7 +1157,6 @@ class PluginFetchDynamicSelectOptionsApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserDynamicOptions)
|
||||
def get(self, req_data: ParserDynamicOptions, tenant_id: str, current_user: Account):
|
||||
|
||||
try:
|
||||
options = PluginParameterService.get_dynamic_select_options(
|
||||
tenant_id=tenant_id,
|
||||
|
||||
@@ -527,7 +527,6 @@ class ToolBuiltinProviderListToolsApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def get(self, tenant_id: str, provider: str):
|
||||
|
||||
return dump_response(
|
||||
ToolApiListResponse,
|
||||
BuiltinToolManageService.list_builtin_tool_provider_tools(
|
||||
@@ -549,7 +548,6 @@ class ToolBuiltinProviderInfoApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def get(self, tenant_id: str, provider: str):
|
||||
|
||||
return _dump_tool_provider_payload(
|
||||
BuiltinToolManageService.get_builtin_tool_provider_info(tenant_id, provider).to_dict()
|
||||
)
|
||||
@@ -571,7 +569,6 @@ class ToolBuiltinProviderDeleteApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@model_validate(BuiltinToolCredentialDeletePayload)
|
||||
def post(self, req_data: BuiltinToolCredentialDeletePayload, tenant_id: str, provider: str):
|
||||
|
||||
return dump_response(
|
||||
SimpleResultResponse,
|
||||
BuiltinToolManageService.delete_builtin_tool_provider(
|
||||
@@ -855,7 +852,6 @@ class ToolBuiltinProviderCredentialsSchemaApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def get(self, tenant_id: str, provider, credential_type):
|
||||
|
||||
return dump_response(
|
||||
ProviderConfigListResponse,
|
||||
BuiltinToolManageService.list_builtin_provider_credentials_schema(
|
||||
@@ -1075,7 +1071,6 @@ class ToolApiListApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def get(self, tenant_id: str):
|
||||
|
||||
return _dump_tool_provider_payload_list(
|
||||
[
|
||||
provider.to_dict()
|
||||
@@ -1604,7 +1599,6 @@ class ToolMCPListAllApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def get(self, tenant_id: str):
|
||||
|
||||
with sessionmaker(db.engine).begin() as session:
|
||||
service = MCPToolManageService(session=session)
|
||||
# Skip sensitive data decryption for list view to improve performance
|
||||
|
||||
@@ -402,7 +402,6 @@ def knowledge_pipeline_publish_enabled[**P, R](view: Callable[P, R]) -> Callable
|
||||
def edit_permission_required[**P, R](f: Callable[P, R]) -> Callable[P, R]:
|
||||
@wraps(f)
|
||||
def decorated_function(*args: P.args, **kwargs: P.kwargs):
|
||||
|
||||
from libs.login import current_user
|
||||
|
||||
if not dify_config.RBAC_ENABLED:
|
||||
@@ -419,7 +418,6 @@ def edit_permission_required[**P, R](f: Callable[P, R]) -> Callable[P, R]:
|
||||
def is_admin_or_owner_required[**P, R](f: Callable[P, R]) -> Callable[P, R]:
|
||||
@wraps(f)
|
||||
def decorated_function(*args: P.args, **kwargs: P.kwargs):
|
||||
|
||||
from libs.login import current_user
|
||||
|
||||
if not dify_config.RBAC_ENABLED:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from types import ModuleType
|
||||
|
||||
from flask import Blueprint
|
||||
from flask_restx import Namespace
|
||||
|
||||
@@ -14,7 +16,26 @@ api = ExternalApi(
|
||||
|
||||
files_ns = Namespace("files", description="File operations", path="/")
|
||||
|
||||
from . import appdeploy_files, plugin_file_upload, tool_files, upload_file_delivery
|
||||
|
||||
def _register_controllers() -> tuple[ModuleType, ...]:
|
||||
"""Load route modules after their namespaces and schema models exist."""
|
||||
|
||||
from . import appdeploy_files, plugin_file_upload, tool_files, upload_file_delivery
|
||||
|
||||
return (
|
||||
appdeploy_files,
|
||||
plugin_file_upload,
|
||||
tool_files,
|
||||
upload_file_delivery,
|
||||
)
|
||||
|
||||
|
||||
(
|
||||
appdeploy_files,
|
||||
plugin_file_upload,
|
||||
tool_files,
|
||||
upload_file_delivery,
|
||||
) = _register_controllers()
|
||||
|
||||
api.add_namespace(files_ns)
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from types import ModuleType
|
||||
|
||||
from flask import Blueprint
|
||||
from flask_restx import Namespace
|
||||
|
||||
@@ -24,20 +26,51 @@ api = ExternalApi(
|
||||
# Create namespace
|
||||
inner_api_ns = Namespace("inner_api", description="Internal API operations", path="/")
|
||||
|
||||
from . import mail as _mail
|
||||
from . import runtime_credentials as _runtime_credentials
|
||||
from .agent import files as _agent_files
|
||||
from .agent import llm as _agent_llm
|
||||
from .agent import runtime_usage as _agent_runtime_usage
|
||||
from .agent import tools as _agent_tools
|
||||
from .app import app as _app_app
|
||||
from .app import dsl as _app_dsl
|
||||
from .app import file_grants as _app_file_grants
|
||||
from .knowledge import retrieval as _knowledge_retrieval
|
||||
from .plugin import agent_config as _agent_config
|
||||
from .plugin import plugin as _plugin
|
||||
from .plugin import skills as _skills
|
||||
from .workspace import workspace as _workspace
|
||||
|
||||
def _register_controllers() -> tuple[ModuleType, ...]:
|
||||
"""Load route modules after their namespace exists."""
|
||||
|
||||
from . import mail, runtime_credentials
|
||||
from .agent import files, llm, runtime_usage, tools
|
||||
from .app import app, dsl, file_grants
|
||||
from .knowledge import retrieval
|
||||
from .plugin import agent_config, plugin, skills
|
||||
from .workspace import workspace
|
||||
|
||||
return (
|
||||
mail,
|
||||
runtime_credentials,
|
||||
files,
|
||||
llm,
|
||||
runtime_usage,
|
||||
tools,
|
||||
app,
|
||||
dsl,
|
||||
file_grants,
|
||||
retrieval,
|
||||
agent_config,
|
||||
plugin,
|
||||
skills,
|
||||
workspace,
|
||||
)
|
||||
|
||||
|
||||
(
|
||||
_mail,
|
||||
_runtime_credentials,
|
||||
_agent_files,
|
||||
_agent_llm,
|
||||
_agent_runtime_usage,
|
||||
_agent_tools,
|
||||
_app_app,
|
||||
_app_dsl,
|
||||
_app_file_grants,
|
||||
_knowledge_retrieval,
|
||||
_agent_config,
|
||||
_plugin,
|
||||
_skills,
|
||||
_workspace,
|
||||
) = _register_controllers()
|
||||
|
||||
api.add_namespace(inner_api_ns)
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from types import ModuleType
|
||||
|
||||
from flask import Blueprint
|
||||
from flask_restx import Namespace
|
||||
|
||||
@@ -14,7 +16,16 @@ api = ExternalApi(
|
||||
|
||||
mcp_ns = Namespace("mcp", description="MCP operations", path="/")
|
||||
|
||||
from . import mcp
|
||||
|
||||
def _register_controllers() -> tuple[ModuleType, ...]:
|
||||
"""Load route modules after their namespaces and schema models exist."""
|
||||
|
||||
from . import mcp
|
||||
|
||||
return (mcp,)
|
||||
|
||||
|
||||
(mcp,) = _register_controllers()
|
||||
|
||||
api.add_namespace(mcp_ns)
|
||||
|
||||
|
||||
@@ -1,51 +1,15 @@
|
||||
from types import ModuleType
|
||||
from typing import override
|
||||
|
||||
from flask import Blueprint
|
||||
from flask_restx import Namespace
|
||||
|
||||
from controllers.openapi._catalog import attach_catalog
|
||||
from controllers.openapi._errors import ErrorBody, OpenApiErrorCode, OpenApiErrorFormatter
|
||||
from controllers.openapi._upload import describe_multipart_bodies
|
||||
from libs.device_flow_security import attach_anti_framing
|
||||
from libs.external_api import ErrorSpec, ExternalApi
|
||||
|
||||
bp = Blueprint("openapi", __name__, url_prefix="/openapi/v1")
|
||||
attach_anti_framing(bp)
|
||||
attach_catalog(bp)
|
||||
|
||||
|
||||
class _OpenApi(ExternalApi):
|
||||
"""The surface's document also names the multipart form its file-bearing bodies accept."""
|
||||
|
||||
error_response_model = ErrorBody
|
||||
|
||||
@override
|
||||
def finish_document(self, document: dict[str, object]) -> dict[str, object]:
|
||||
return super().finish_document(describe_multipart_bodies(document))
|
||||
|
||||
@override
|
||||
def error_code_for(self, spec: ErrorSpec) -> str:
|
||||
return OpenApiErrorFormatter.code_for(spec["status"], spec["error_code"])
|
||||
|
||||
@override
|
||||
def implicit_error_codes(self, status: int) -> set[str]:
|
||||
return {OpenApiErrorFormatter.code_for(status, None)}
|
||||
|
||||
|
||||
api = _OpenApi(
|
||||
bp,
|
||||
version="1.0",
|
||||
title="OpenAPI",
|
||||
description="User-scoped programmatic API (bearer auth)",
|
||||
error_body_formatter=OpenApiErrorFormatter(),
|
||||
)
|
||||
|
||||
openapi_ns = Namespace("openapi", description="User-scoped operations", path="/")
|
||||
|
||||
# Register response/query models BEFORE importing controller modules so that
|
||||
# @openapi_ns.response / @openapi_ns.expect decorators can resolve model names.
|
||||
from controllers.common.fields import EventStreamResponse, SimpleResultResponse
|
||||
from controllers.common.schema import register_enum_models, register_response_schema_models, register_schema_models
|
||||
from controllers.openapi._catalog import attach_catalog
|
||||
from controllers.openapi._errors import ErrorBody, OpenApiErrorCode, OpenApiErrorFormatter
|
||||
from controllers.openapi._models import (
|
||||
AccountPayload,
|
||||
AccountResponse,
|
||||
@@ -184,14 +148,50 @@ from controllers.openapi._models import (
|
||||
WorkspacePayload,
|
||||
WorkspaceSummaryResponse,
|
||||
)
|
||||
from controllers.openapi._upload import describe_multipart_bodies
|
||||
from fields.file_fields import FileResponse
|
||||
from fields.workflow_run_fields import (
|
||||
WorkflowRunDetailResponse,
|
||||
WorkflowRunNodeExecutionListResponse,
|
||||
WorkflowRunNodeExecutionResponse,
|
||||
)
|
||||
from libs.device_flow_security import attach_anti_framing
|
||||
from libs.external_api import ErrorSpec, ExternalApi
|
||||
from services.entities.dsl_entities import CheckDependenciesResult, Import
|
||||
|
||||
bp = Blueprint("openapi", __name__, url_prefix="/openapi/v1")
|
||||
attach_anti_framing(bp)
|
||||
attach_catalog(bp)
|
||||
|
||||
|
||||
class _OpenApi(ExternalApi):
|
||||
"""The surface's document also names the multipart form its file-bearing bodies accept."""
|
||||
|
||||
error_response_model = ErrorBody
|
||||
|
||||
@override
|
||||
def finish_document(self, document: dict[str, object]) -> dict[str, object]:
|
||||
return super().finish_document(describe_multipart_bodies(document))
|
||||
|
||||
@override
|
||||
def error_code_for(self, spec: ErrorSpec) -> str:
|
||||
return OpenApiErrorFormatter.code_for(spec["status"], spec["error_code"])
|
||||
|
||||
@override
|
||||
def implicit_error_codes(self, status: int) -> set[str]:
|
||||
return {OpenApiErrorFormatter.code_for(status, None)}
|
||||
|
||||
|
||||
api = _OpenApi(
|
||||
bp,
|
||||
version="1.0",
|
||||
title="OpenAPI",
|
||||
description="User-scoped programmatic API (bearer auth)",
|
||||
error_body_formatter=OpenApiErrorFormatter(),
|
||||
)
|
||||
|
||||
openapi_ns = Namespace("openapi", description="User-scoped operations", path="/")
|
||||
|
||||
register_schema_models(
|
||||
openapi_ns,
|
||||
AdvancedChatNodeRunPayload,
|
||||
@@ -347,7 +347,62 @@ register_response_schema_models(
|
||||
# string on the wire so old clients keep parsing future codes.
|
||||
register_enum_models(openapi_ns, OpenApiErrorCode)
|
||||
|
||||
from . import (
|
||||
|
||||
def _register_controllers() -> tuple[ModuleType, ...]:
|
||||
"""Load route modules after their namespaces and schema models exist."""
|
||||
|
||||
from . import (
|
||||
account,
|
||||
app_create,
|
||||
app_dsl,
|
||||
app_info,
|
||||
app_release,
|
||||
app_run,
|
||||
app_webapp,
|
||||
app_workflow,
|
||||
apps,
|
||||
apps_permitted_external,
|
||||
files,
|
||||
human_input_form,
|
||||
index,
|
||||
knowledge_bases,
|
||||
model_providers,
|
||||
node_types,
|
||||
oauth_device,
|
||||
oauth_device_sso,
|
||||
plugins,
|
||||
tool_providers,
|
||||
workflow_events,
|
||||
workspaces,
|
||||
)
|
||||
|
||||
return (
|
||||
account,
|
||||
app_create,
|
||||
app_dsl,
|
||||
app_info,
|
||||
app_release,
|
||||
app_run,
|
||||
app_webapp,
|
||||
app_workflow,
|
||||
apps,
|
||||
apps_permitted_external,
|
||||
files,
|
||||
human_input_form,
|
||||
index,
|
||||
knowledge_bases,
|
||||
model_providers,
|
||||
node_types,
|
||||
oauth_device,
|
||||
oauth_device_sso,
|
||||
plugins,
|
||||
tool_providers,
|
||||
workflow_events,
|
||||
workspaces,
|
||||
)
|
||||
|
||||
|
||||
(
|
||||
account,
|
||||
app_create,
|
||||
app_dsl,
|
||||
@@ -370,7 +425,7 @@ from . import (
|
||||
tool_providers,
|
||||
workflow_events,
|
||||
workspaces,
|
||||
)
|
||||
) = _register_controllers()
|
||||
|
||||
# Request models are imported from _models.py and registered above.
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from types import ModuleType
|
||||
|
||||
from flask import Blueprint
|
||||
from flask_restx import Namespace
|
||||
|
||||
@@ -27,8 +29,63 @@ api.document_generic_errors(InvalidArgumentError, NotFoundError, UnauthorizedErr
|
||||
|
||||
service_api_ns = Namespace("service_api", description="Service operations", path="/")
|
||||
|
||||
from . import index
|
||||
from .app import (
|
||||
|
||||
def _register_controllers() -> tuple[ModuleType, ...]:
|
||||
"""Load route modules after their namespaces and schema models exist."""
|
||||
|
||||
from . import index
|
||||
from .app import (
|
||||
annotation,
|
||||
app,
|
||||
audio,
|
||||
completion,
|
||||
conversation,
|
||||
file,
|
||||
file_preview,
|
||||
human_input_form,
|
||||
message,
|
||||
site,
|
||||
workflow,
|
||||
workflow_events,
|
||||
)
|
||||
from .dataset import (
|
||||
dataset,
|
||||
document,
|
||||
hit_testing,
|
||||
metadata,
|
||||
segment,
|
||||
)
|
||||
from .dataset.rag_pipeline import rag_pipeline_workflow
|
||||
from .end_user import end_user
|
||||
from .workspace import models
|
||||
|
||||
return (
|
||||
index,
|
||||
annotation,
|
||||
app,
|
||||
audio,
|
||||
completion,
|
||||
conversation,
|
||||
file,
|
||||
file_preview,
|
||||
human_input_form,
|
||||
message,
|
||||
site,
|
||||
workflow,
|
||||
workflow_events,
|
||||
dataset,
|
||||
document,
|
||||
hit_testing,
|
||||
metadata,
|
||||
segment,
|
||||
rag_pipeline_workflow,
|
||||
end_user,
|
||||
models,
|
||||
)
|
||||
|
||||
|
||||
(
|
||||
index,
|
||||
annotation,
|
||||
app,
|
||||
audio,
|
||||
@@ -41,17 +98,15 @@ from .app import (
|
||||
site,
|
||||
workflow,
|
||||
workflow_events,
|
||||
)
|
||||
from .dataset import (
|
||||
dataset,
|
||||
document,
|
||||
hit_testing,
|
||||
metadata,
|
||||
segment,
|
||||
)
|
||||
from .dataset.rag_pipeline import rag_pipeline_workflow
|
||||
from .end_user import end_user
|
||||
from .workspace import models
|
||||
rag_pipeline_workflow,
|
||||
end_user,
|
||||
models,
|
||||
) = _register_controllers()
|
||||
|
||||
__all__ = [
|
||||
"annotation",
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
from types import ModuleType
|
||||
|
||||
from flask import Blueprint
|
||||
|
||||
# Create trigger blueprint
|
||||
bp = Blueprint("trigger", __name__, url_prefix="/triggers")
|
||||
|
||||
# Import routes after blueprint creation to avoid circular imports
|
||||
from . import trigger, webhook
|
||||
|
||||
def _register_controllers() -> tuple[ModuleType, ...]:
|
||||
"""Load route modules after their namespaces and schema models exist."""
|
||||
|
||||
# Import routes after blueprint creation to avoid circular imports
|
||||
from . import trigger, webhook
|
||||
|
||||
return (
|
||||
trigger,
|
||||
webhook,
|
||||
)
|
||||
|
||||
|
||||
(
|
||||
trigger,
|
||||
webhook,
|
||||
) = _register_controllers()
|
||||
|
||||
__all__ = [
|
||||
"trigger",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from types import ModuleType
|
||||
|
||||
from flask import Blueprint
|
||||
from flask_restx import Namespace
|
||||
|
||||
@@ -38,7 +40,52 @@ api.document_generic_errors(
|
||||
# Create namespace
|
||||
web_ns = Namespace("web", description="Web application API operations", path="/")
|
||||
|
||||
from . import (
|
||||
|
||||
def _register_controllers() -> tuple[ModuleType, ...]:
|
||||
"""Load route modules after their namespaces and schema models exist."""
|
||||
|
||||
from . import (
|
||||
app,
|
||||
audio,
|
||||
completion,
|
||||
conversation,
|
||||
feature,
|
||||
files,
|
||||
forgot_password,
|
||||
human_input_file_upload,
|
||||
human_input_form,
|
||||
login,
|
||||
message,
|
||||
passport,
|
||||
remote_files,
|
||||
saved_message,
|
||||
site,
|
||||
workflow,
|
||||
workflow_events,
|
||||
)
|
||||
|
||||
return (
|
||||
app,
|
||||
audio,
|
||||
completion,
|
||||
conversation,
|
||||
feature,
|
||||
files,
|
||||
forgot_password,
|
||||
human_input_file_upload,
|
||||
human_input_form,
|
||||
login,
|
||||
message,
|
||||
passport,
|
||||
remote_files,
|
||||
saved_message,
|
||||
site,
|
||||
workflow,
|
||||
workflow_events,
|
||||
)
|
||||
|
||||
|
||||
(
|
||||
app,
|
||||
audio,
|
||||
completion,
|
||||
@@ -56,7 +103,7 @@ from . import (
|
||||
site,
|
||||
workflow,
|
||||
workflow_events,
|
||||
)
|
||||
) = _register_controllers()
|
||||
|
||||
api.add_namespace(web_ns)
|
||||
|
||||
|
||||
@@ -383,7 +383,8 @@ class RagPipelineGenerateEntity(WorkflowAppGenerateEntity):
|
||||
start_node_id: str | None = None
|
||||
|
||||
|
||||
from core.ops.ops_trace_manager import TraceQueueManager
|
||||
# Resolve circular model references after model definitions.
|
||||
from core.ops.ops_trace_manager import TraceQueueManager # noqa: E402
|
||||
|
||||
AppGenerateEntity.model_rebuild()
|
||||
EasyUIBasedAppGenerateEntity.model_rebuild()
|
||||
|
||||
@@ -18,6 +18,9 @@ from core.file import remote_fetcher
|
||||
from core.workflow.file_reference import build_file_reference
|
||||
from extensions.ext_storage import storage
|
||||
from graphon.file import File, FileTransferMethod, get_file_type_by_mime_type
|
||||
|
||||
# init tool_file_parser
|
||||
from graphon.file.tool_file_parser import set_tool_file_manager_factory
|
||||
from models.model import MessageFile
|
||||
from models.tools import ToolFile
|
||||
|
||||
@@ -214,10 +217,6 @@ class ToolFileManager:
|
||||
return stream, self._build_graph_file_reference(tool_file)
|
||||
|
||||
|
||||
# init tool_file_parser
|
||||
from graphon.file.tool_file_parser import set_tool_file_manager_factory
|
||||
|
||||
|
||||
def _factory() -> ToolFileManager:
|
||||
return ToolFileManager()
|
||||
|
||||
|
||||
@@ -13,8 +13,11 @@ API_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(API_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(API_ROOT))
|
||||
|
||||
from dev.generate_swagger_specs import apply_runtime_defaults
|
||||
from libs.flask_restx_compat import sort_openapi_arrays
|
||||
# Import after script path setup.
|
||||
from dev.generate_swagger_specs import apply_runtime_defaults # noqa: E402
|
||||
|
||||
# Import after script path setup.
|
||||
from libs.flask_restx_compat import sort_openapi_arrays # noqa: E402
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -20,8 +20,11 @@ API_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(API_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(API_ROOT))
|
||||
|
||||
from dev.generate_fastopenapi_specs import FASTOPENAPI_SPEC_TARGETS, generate_fastopenapi_specs
|
||||
from dev.generate_swagger_specs import SPEC_TARGETS, generate_specs
|
||||
# Import after script path setup.
|
||||
from dev.generate_fastopenapi_specs import FASTOPENAPI_SPEC_TARGETS, generate_fastopenapi_specs # noqa: E402
|
||||
|
||||
# Import after script path setup.
|
||||
from dev.generate_swagger_specs import SPEC_TARGETS, generate_specs # noqa: E402
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ API_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(API_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(API_ROOT))
|
||||
|
||||
from libs.flask_restx_compat import finalize_openapi_payload
|
||||
# Import after script path setup.
|
||||
from libs.flask_restx_compat import finalize_openapi_payload # noqa: E402
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import override
|
||||
|
||||
"""Custom OTEL ID Generator for correlation-based trace/span ID derivation.
|
||||
|
||||
Uses contextvars for thread-safe correlation_id -> trace_id mapping.
|
||||
@@ -11,6 +9,7 @@ without depending on span creation order.
|
||||
import random
|
||||
import uuid
|
||||
from contextvars import ContextVar
|
||||
from typing import override
|
||||
|
||||
from opentelemetry.sdk.trace.id_generator import IdGenerator
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
from core.hosting_configuration import HostingConfiguration
|
||||
from dify_app import DifyApp
|
||||
|
||||
hosting_configuration = HostingConfiguration()
|
||||
|
||||
|
||||
from dify_app import DifyApp
|
||||
|
||||
|
||||
def init_app(app: DifyApp):
|
||||
hosting_configuration.init_app(app)
|
||||
@@ -27,6 +27,7 @@ from models.enums import (
|
||||
DocumentCreatedFrom,
|
||||
DocumentDocType,
|
||||
IndexingStatus,
|
||||
PermissionEnum,
|
||||
ProcessRuleMode,
|
||||
SegmentStatus,
|
||||
SegmentType,
|
||||
@@ -141,8 +142,6 @@ class DocumentDict(TypedDict):
|
||||
hit_count: int | None
|
||||
|
||||
|
||||
from models.enums import PermissionEnum
|
||||
|
||||
# Backward-compatible alias — new code should import PermissionEnum from models.enums
|
||||
DatasetPermissionEnum = PermissionEnum
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
from typing import override
|
||||
|
||||
"""Provider ID entities for plugin system."""
|
||||
|
||||
import re
|
||||
from typing import override
|
||||
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
|
||||
+1
-3
@@ -32,15 +32,13 @@ from core.ops.entities.trace_entity import (
|
||||
from graphon.enums import BuiltinNodeTypes
|
||||
from models import EndUser
|
||||
from models.enums import EndUserType, MessageStatus
|
||||
from tests.unit_tests.core.ops.trace_fixtures import message_trace_info, tool_trace_info, workflow_trace_info
|
||||
|
||||
|
||||
def _dt() -> datetime:
|
||||
return datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC)
|
||||
|
||||
|
||||
from tests.unit_tests.core.ops.trace_fixtures import message_trace_info, tool_trace_info, workflow_trace_info
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def langfuse_config():
|
||||
return LangfuseConfig(public_key="pk-123", secret_key="sk-123", host="https://cloud.langfuse.com")
|
||||
|
||||
@@ -25,15 +25,13 @@ from core.ops.entities.trace_entity import (
|
||||
from graphon.enums import BuiltinNodeTypes, WorkflowNodeExecutionMetadataKey
|
||||
from models import EndUser
|
||||
from models.enums import EndUserType, MessageStatus
|
||||
from tests.unit_tests.core.ops.trace_fixtures import message_trace_info, tool_trace_info, workflow_trace_info
|
||||
|
||||
|
||||
def _dt() -> datetime:
|
||||
return datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC)
|
||||
|
||||
|
||||
from tests.unit_tests.core.ops.trace_fixtures import message_trace_info, tool_trace_info, workflow_trace_info
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def opik_config():
|
||||
return OpikConfig(
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from typing import override
|
||||
|
||||
"""Tencent APM tracing with idempotent client cleanup."""
|
||||
|
||||
import inspect
|
||||
import logging
|
||||
from typing import override
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
+4
-4
@@ -3,15 +3,15 @@ from typing import Any, TypedDict
|
||||
|
||||
from pydantic import BaseModel, model_validator
|
||||
|
||||
from core.rag.datasource.vdb.field import parse_metadata_json
|
||||
from core.rag.models.document import Document
|
||||
from extensions.ext_redis import redis_client
|
||||
|
||||
_import_err_msg = (
|
||||
"`alibabacloud_gpdb20160503` and `alibabacloud_tea_openapi` packages not found, "
|
||||
"please run `pip install alibabacloud_gpdb20160503 alibabacloud_tea_openapi`"
|
||||
)
|
||||
|
||||
from core.rag.datasource.vdb.field import parse_metadata_json
|
||||
from core.rag.models.document import Document
|
||||
from extensions.ext_redis import redis_client
|
||||
|
||||
|
||||
class AnalyticdbClientParamsDict(TypedDict):
|
||||
access_key_id: str
|
||||
|
||||
+3
-3
@@ -8,9 +8,6 @@ from typing import TYPE_CHECKING, Any, override
|
||||
|
||||
import httpx
|
||||
import qdrant_client
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from flask import current_app
|
||||
from httpx import DigestAuth
|
||||
from pydantic import BaseModel
|
||||
@@ -25,6 +22,7 @@ from qdrant_client.http.models import (
|
||||
)
|
||||
from qdrant_client.local.qdrant_local import QdrantLocal
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from configs import dify_config
|
||||
from core.rag.datasource.vdb.field import Field
|
||||
@@ -39,6 +37,8 @@ from extensions.ext_redis import redis_client
|
||||
from models.dataset import Dataset, TidbAuthBinding
|
||||
from models.enums import TidbAuthBindingStatus
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qdrant_client import grpc # noqa
|
||||
from qdrant_client.conversions import common_types
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import override
|
||||
|
||||
"""Unit tests for Weaviate vector database implementation.
|
||||
|
||||
Focuses on verifying that doc_type is properly handled in:
|
||||
@@ -13,6 +11,7 @@ import datetime
|
||||
import json
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from typing import override
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -5,6 +5,7 @@ This service restores archived workflow run data from S3-compatible storage
|
||||
back to the database.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import logging
|
||||
import time
|
||||
@@ -19,21 +20,6 @@ import click
|
||||
from pydantic import TypeAdapter
|
||||
from sqlalchemy.dialects.postgresql import insert as pg_insert
|
||||
from sqlalchemy.engine import CursorResult
|
||||
|
||||
|
||||
class _TableInfo(TypedDict, total=False):
|
||||
row_count: int
|
||||
|
||||
|
||||
class ArchiveManifest(TypedDict, total=False):
|
||||
tables: dict[str, _TableInfo]
|
||||
schema_version: str
|
||||
|
||||
|
||||
_manifest_adapter: TypeAdapter[ArchiveManifest] = TypeAdapter(ArchiveManifest)
|
||||
|
||||
import contextlib
|
||||
|
||||
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
|
||||
|
||||
from extensions.ext_database import db
|
||||
@@ -56,6 +42,18 @@ from repositories.api_workflow_run_repository import APIWorkflowRunRepository
|
||||
from repositories.factory import DifyAPIRepositoryFactory
|
||||
from services.retention.workflow_run.constants import ARCHIVE_BUNDLE_NAME
|
||||
|
||||
|
||||
class _TableInfo(TypedDict, total=False):
|
||||
row_count: int
|
||||
|
||||
|
||||
class ArchiveManifest(TypedDict, total=False):
|
||||
tables: dict[str, _TableInfo]
|
||||
schema_version: str
|
||||
|
||||
|
||||
_manifest_adapter: TypeAdapter[ArchiveManifest] = TypeAdapter(ArchiveManifest)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import Protocol, override
|
||||
|
||||
"""
|
||||
Queue dispatcher system for async workflow execution.
|
||||
|
||||
@@ -8,6 +6,7 @@ with appropriate queue routing and priority assignment.
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
from typing import Protocol, override
|
||||
|
||||
from configs import dify_config
|
||||
from enums import DeploymentEdition
|
||||
|
||||
@@ -94,7 +94,22 @@ from services.errors.app import (
|
||||
WorkflowHashNotEqualError,
|
||||
WorkflowNotFoundError,
|
||||
)
|
||||
from services.human_input_service import HumanInputService
|
||||
from services.system_feature_service import SystemFeatureService
|
||||
from services.workflow.workflow_converter import WorkflowConverter
|
||||
from services.workflow_ref_service import WorkflowRef
|
||||
from services.workflow_version_number_service import allocate_version_number
|
||||
|
||||
from .errors.workflow_service import DraftWorkflowDeletionError, WorkflowInUseError
|
||||
from .human_input_delivery_test_service import (
|
||||
DeliveryTestContext,
|
||||
DeliveryTestEmailRecipient,
|
||||
DeliveryTestError,
|
||||
DeliveryTestUnsupportedError,
|
||||
HumanInputDeliveryTestService,
|
||||
)
|
||||
from .workflow_draft_variable_service import DraftVariableSaver, DraftVarLoader, WorkflowDraftVariableService
|
||||
from .workflow_restore import apply_published_workflow_snapshot_to_draft
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -140,21 +155,6 @@ class _DebugHumanInputNode:
|
||||
|
||||
|
||||
HumanInputNode = _DebugHumanInputNode
|
||||
from services.human_input_service import HumanInputService
|
||||
from services.workflow.workflow_converter import WorkflowConverter
|
||||
from services.workflow_ref_service import WorkflowRef
|
||||
from services.workflow_version_number_service import allocate_version_number
|
||||
|
||||
from .errors.workflow_service import DraftWorkflowDeletionError, WorkflowInUseError
|
||||
from .human_input_delivery_test_service import (
|
||||
DeliveryTestContext,
|
||||
DeliveryTestEmailRecipient,
|
||||
DeliveryTestError,
|
||||
DeliveryTestUnsupportedError,
|
||||
HumanInputDeliveryTestService,
|
||||
)
|
||||
from .workflow_draft_variable_service import DraftVariableSaver, DraftVarLoader, WorkflowDraftVariableService
|
||||
from .workflow_restore import apply_published_workflow_snapshot_to_draft
|
||||
|
||||
_file_access_controller = DatabaseFileAccessController()
|
||||
|
||||
|
||||
@@ -5,10 +5,6 @@ import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
API_PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(API_PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(API_PROJECT_ROOT))
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from tests.helpers.legacy_model_type_migration import (
|
||||
@@ -18,6 +14,10 @@ from tests.helpers.legacy_model_type_migration import (
|
||||
seed_legacy_model_type_dirty_data,
|
||||
)
|
||||
|
||||
API_PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(API_PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(API_PROJECT_ROOT))
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
from repositories.knowledge.dataset_read_repository import (
|
||||
get_dataset_available_segment_count,
|
||||
get_document_hit_count,
|
||||
get_document_segment_count,
|
||||
)
|
||||
|
||||
"""
|
||||
Integration tests for Dataset and Document model properties using testcontainers.
|
||||
|
||||
@@ -21,8 +15,11 @@ from models.dataset import Dataset, Document, DocumentSegment
|
||||
from models.enums import DataSourceType, DocumentCreatedFrom, IndexingStatus, SegmentStatus
|
||||
from repositories.knowledge.dataset_read_repository import (
|
||||
get_dataset_available_document_count,
|
||||
get_dataset_available_segment_count,
|
||||
get_dataset_document_count,
|
||||
get_dataset_word_count,
|
||||
get_document_hit_count,
|
||||
get_document_segment_count,
|
||||
get_next_segment,
|
||||
get_previous_segment,
|
||||
get_segment_dataset,
|
||||
|
||||
@@ -8,6 +8,11 @@ from tempfile import TemporaryDirectory
|
||||
from typing import TYPE_CHECKING
|
||||
from unittest.mock import MagicMock, create_autospec, patch
|
||||
|
||||
# Ensure OpenDAL fs writes to tmp to avoid polluting workspace
|
||||
os.environ.setdefault("OPENDAL_SCHEME", "fs")
|
||||
os.environ.setdefault("OPENDAL_FS_ROOT", "/tmp/dify-storage")
|
||||
os.environ.setdefault("STORAGE_TYPE", "opendal")
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
from redis import Redis
|
||||
@@ -15,6 +20,13 @@ from sqlalchemy import create_engine
|
||||
from sqlalchemy.engine import URL, Engine
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
import core.db.session_factory as session_factory_module
|
||||
from extensions import ext_redis
|
||||
from models.account import Account, Tenant, TenantAccountJoin, TenantAccountRole
|
||||
from models.base import TypeBase
|
||||
from tests.unit_tests.config_override import apply_config_overrides
|
||||
from tests.unit_tests.rbac_fakes import RBACDomain, build_rbac_domain
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from extensions.ext_application_services import ApplicationServices
|
||||
from tests.unit_tests.account_domain import AccountDomain
|
||||
@@ -43,18 +55,6 @@ redis_mock.hgetall = MagicMock(return_value={})
|
||||
redis_mock.hdel = MagicMock()
|
||||
redis_mock.incr = MagicMock(return_value=1)
|
||||
|
||||
# Ensure OpenDAL fs writes to tmp to avoid polluting workspace
|
||||
os.environ.setdefault("OPENDAL_SCHEME", "fs")
|
||||
os.environ.setdefault("OPENDAL_FS_ROOT", "/tmp/dify-storage")
|
||||
os.environ.setdefault("STORAGE_TYPE", "opendal")
|
||||
|
||||
import core.db.session_factory as session_factory_module
|
||||
from extensions import ext_redis
|
||||
from models.account import Account, Tenant, TenantAccountJoin, TenantAccountRole
|
||||
from models.base import TypeBase
|
||||
from tests.unit_tests.config_override import apply_config_overrides
|
||||
from tests.unit_tests.rbac_fakes import RBACDomain, build_rbac_domain
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from extensions.application_services.app import AppServices
|
||||
from services.tag_application_service import TagApplicationService
|
||||
|
||||
@@ -3,13 +3,14 @@ from unittest.mock import patch
|
||||
|
||||
from flask.views import MethodView as FlaskMethodView
|
||||
|
||||
from controllers.common.fields import Parameters, Site
|
||||
from core.app.app_config.common.parameters_mapping import get_parameters_from_feature_dict
|
||||
from models.model import IconType
|
||||
|
||||
_NEEDS_METHOD_VIEW_CLEANUP = False
|
||||
if not hasattr(builtins, "MethodView"):
|
||||
builtins.MethodView = FlaskMethodView
|
||||
_NEEDS_METHOD_VIEW_CLEANUP = True
|
||||
from controllers.common.fields import Parameters, Site
|
||||
from core.app.app_config.common.parameters_mapping import get_parameters_from_feature_dict
|
||||
from models.model import IconType
|
||||
|
||||
|
||||
def test_parameters_model_round_trip():
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import annotations
|
||||
|
||||
"""
|
||||
Unit tests for the external dataset controller payload schemas.
|
||||
|
||||
@@ -8,6 +6,8 @@ in request constraints (e.g. max length changes) without exercising the
|
||||
full Flask/RESTX request stack.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
|
||||
@@ -12,13 +12,6 @@ from flask import Flask
|
||||
from flask.views import MethodView as FlaskMethodView
|
||||
from werkzeug.exceptions import UnprocessableEntity
|
||||
|
||||
from tests.unit_tests.config_override import apply_config_overrides
|
||||
|
||||
_NEEDS_METHOD_VIEW_CLEANUP = False
|
||||
if not hasattr(builtins, "MethodView"):
|
||||
builtins.__dict__["MethodView"] = FlaskMethodView
|
||||
_NEEDS_METHOD_VIEW_CLEANUP = True
|
||||
|
||||
import controllers.console.extension as module
|
||||
from constants import HIDDEN_VALUE
|
||||
from controllers.console.extension import (
|
||||
@@ -28,9 +21,6 @@ from controllers.console.extension import (
|
||||
CodeBasedExtensionAPI,
|
||||
)
|
||||
from enums import DeploymentEdition
|
||||
|
||||
if _NEEDS_METHOD_VIEW_CLEANUP:
|
||||
del builtins.__dict__["MethodView"]
|
||||
from machinery.context import RequestContext
|
||||
from models.account import Account, AccountStatus, Tenant
|
||||
from services.api_based_extension_application_service import (
|
||||
@@ -40,6 +30,15 @@ from services.api_based_extension_application_service import (
|
||||
APIBasedExtensionRecord,
|
||||
APIBasedExtensionUpdate,
|
||||
)
|
||||
from tests.unit_tests.config_override import apply_config_overrides
|
||||
|
||||
_NEEDS_METHOD_VIEW_CLEANUP = False
|
||||
if not hasattr(builtins, "MethodView"):
|
||||
builtins.__dict__["MethodView"] = FlaskMethodView
|
||||
_NEEDS_METHOD_VIEW_CLEANUP = True
|
||||
|
||||
if _NEEDS_METHOD_VIEW_CLEANUP:
|
||||
del builtins.__dict__["MethodView"]
|
||||
|
||||
|
||||
def unwrap(func: Callable[..., object]) -> Callable[..., object]:
|
||||
|
||||
@@ -1756,7 +1756,6 @@ def _run_case(
|
||||
token_rows: dict[str, ResolvedRow],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> TestResponse:
|
||||
|
||||
edition = scenario.edition or (
|
||||
DeploymentEdition.ENTERPRISE if Trait.ENTERPRISE_ONLY in route.traits else DeploymentEdition.COMMUNITY
|
||||
)
|
||||
|
||||
@@ -228,7 +228,6 @@ class TestAnnotationReplyActionStatusApi:
|
||||
handler(api, app_model=app_model, job_id="j1", action="enable")
|
||||
|
||||
def test_error(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
||||
def _get(key):
|
||||
if "error" in key:
|
||||
return b"oops"
|
||||
|
||||
@@ -13,6 +13,15 @@ Focus on:
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
# =============================================================================
|
||||
# API Endpoint Tests
|
||||
#
|
||||
# ``FileApi.post`` is wrapped by ``@validate_app_token(fetch_user_arg=...)``
|
||||
# which preserves ``__wrapped__`` via ``functools.wraps``. We call the
|
||||
# unwrapped method directly to bypass the decorator.
|
||||
# =============================================================================
|
||||
from inspect import unwrap
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
@@ -196,17 +205,6 @@ class TestFileUploadResponse:
|
||||
assert hasattr(mock_file, "created_at")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# API Endpoint Tests
|
||||
#
|
||||
# ``FileApi.post`` is wrapped by ``@validate_app_token(fetch_user_arg=...)``
|
||||
# which preserves ``__wrapped__`` via ``functools.wraps``. We call the
|
||||
# unwrapped method directly to bypass the decorator.
|
||||
# =============================================================================
|
||||
|
||||
from inspect import unwrap
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_app_model():
|
||||
from models import App
|
||||
|
||||
@@ -16,6 +16,55 @@ from pydantic import ValidationError
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.app import AppAccessModeQuery
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# audio.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.audio import TextToAudioPayload
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# completion.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.completion import ChatMessagePayload, CompletionMessagePayload
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# conversation.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.conversation import ConversationListQuery, ConversationRenamePayload
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# forgot_password.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.forgot_password import (
|
||||
ForgotPasswordCheckPayload,
|
||||
ForgotPasswordResetPayload,
|
||||
ForgotPasswordSendPayload,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# login.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.login import EmailCodeLoginSendPayload, EmailCodeLoginVerifyPayload, LoginPayload
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# message.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.message import MessageFeedbackPayload, MessageListQuery, MessageMoreLikeThisQuery
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# remote_files.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.remote_files import RemoteFileUploadPayload
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# saved_message.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.saved_message import SavedMessageCreatePayload, SavedMessageListQuery
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# workflow.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.workflow import WorkflowRunPayload
|
||||
|
||||
|
||||
class TestAppAccessModeQuery:
|
||||
def test_alias_resolution(self) -> None:
|
||||
@@ -34,12 +83,6 @@ class TestAppAccessModeQuery:
|
||||
assert q.app_code == "code1"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# audio.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.audio import TextToAudioPayload
|
||||
|
||||
|
||||
class TestTextToAudioPayload:
|
||||
def test_defaults(self) -> None:
|
||||
p = TextToAudioPayload.model_validate({})
|
||||
@@ -62,12 +105,6 @@ class TestTextToAudioPayload:
|
||||
TextToAudioPayload(message_id="not-a-uuid")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# completion.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.completion import ChatMessagePayload, CompletionMessagePayload
|
||||
|
||||
|
||||
class TestCompletionMessagePayload:
|
||||
def test_defaults(self) -> None:
|
||||
p = CompletionMessagePayload(inputs={})
|
||||
@@ -117,12 +154,6 @@ class TestChatMessagePayload:
|
||||
ChatMessagePayload(inputs={})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# conversation.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.conversation import ConversationListQuery, ConversationRenamePayload
|
||||
|
||||
|
||||
class TestConversationListQuery:
|
||||
def test_defaults(self) -> None:
|
||||
q = ConversationListQuery()
|
||||
@@ -183,12 +214,6 @@ class TestConversationRenamePayload:
|
||||
assert p.name == "test"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# message.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.message import MessageFeedbackPayload, MessageListQuery, MessageMoreLikeThisQuery
|
||||
|
||||
|
||||
class TestMessageListQuery:
|
||||
def test_valid_query(self) -> None:
|
||||
cid = str(uuid4())
|
||||
@@ -249,12 +274,6 @@ class TestMessageMoreLikeThisQuery:
|
||||
MessageMoreLikeThisQuery()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# remote_files.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.remote_files import RemoteFileUploadPayload
|
||||
|
||||
|
||||
class TestRemoteFileUploadPayload:
|
||||
def test_valid_url(self) -> None:
|
||||
p = RemoteFileUploadPayload(url="https://example.com/file.pdf")
|
||||
@@ -270,12 +289,6 @@ class TestRemoteFileUploadPayload:
|
||||
RemoteFileUploadPayload()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# saved_message.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.saved_message import SavedMessageCreatePayload, SavedMessageListQuery
|
||||
|
||||
|
||||
class TestSavedMessageListQuery:
|
||||
def test_defaults(self) -> None:
|
||||
q = SavedMessageListQuery()
|
||||
@@ -309,12 +322,6 @@ class TestSavedMessageCreatePayload:
|
||||
SavedMessageCreatePayload()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# workflow.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.workflow import WorkflowRunPayload
|
||||
|
||||
|
||||
class TestWorkflowRunPayload:
|
||||
def test_defaults(self) -> None:
|
||||
p = WorkflowRunPayload(inputs={})
|
||||
@@ -330,16 +337,6 @@ class TestWorkflowRunPayload:
|
||||
WorkflowRunPayload()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# forgot_password.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.forgot_password import (
|
||||
ForgotPasswordCheckPayload,
|
||||
ForgotPasswordResetPayload,
|
||||
ForgotPasswordSendPayload,
|
||||
)
|
||||
|
||||
|
||||
class TestForgotPasswordSendPayload:
|
||||
def test_valid_email(self) -> None:
|
||||
p = ForgotPasswordSendPayload(email="user@example.com")
|
||||
@@ -384,12 +381,6 @@ class TestForgotPasswordResetPayload:
|
||||
ForgotPasswordResetPayload(token="tok", new_password="123456789", password_confirm="123456789")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# login.py models
|
||||
# ---------------------------------------------------------------------------
|
||||
from controllers.web.login import EmailCodeLoginSendPayload, EmailCodeLoginVerifyPayload, LoginPayload
|
||||
|
||||
|
||||
class TestLoginPayload:
|
||||
def test_valid(self) -> None:
|
||||
p = LoginPayload(email="a@b.com", password="Valid1234")
|
||||
|
||||
@@ -40,6 +40,7 @@ from services.remote_file_service import RemoteFileInvalidUrlError as RemoteFile
|
||||
from services.remote_file_service import RemoteFileNotFoundError as RemoteFileNotFoundServiceError
|
||||
from services.remote_file_service import RemoteFileUnavailableError as RemoteFileUnavailableServiceError
|
||||
from services.remote_file_service import RemoteFileUrlBlockedError as RemoteFileUrlBlockedServiceError
|
||||
from tests.unit_tests.model_factories import make_end_user
|
||||
|
||||
REMOTE_FILE_ERROR_CASES: tuple[tuple[type[RemoteFileError], type[BaseHTTPException], str, int], ...] = (
|
||||
(RemoteFileInvalidUrlServiceError, RemoteFileInvalidUrlError, "remote_file_invalid_url", 400),
|
||||
@@ -54,7 +55,6 @@ REMOTE_FILE_ERROR_CASES: tuple[tuple[type[RemoteFileError], type[BaseHTTPExcepti
|
||||
502,
|
||||
),
|
||||
)
|
||||
from tests.unit_tests.model_factories import make_end_user
|
||||
|
||||
|
||||
def _app_model() -> App:
|
||||
|
||||
@@ -20,6 +20,8 @@ from sqlalchemy.orm import Session, sessionmaker
|
||||
from core.model_manager import ModelManager
|
||||
from core.tools.entities.tool_entities import ToolProviderType
|
||||
from core.tools.utils.model_invocation_utils import InvokeModelError, ModelInvocationUtils
|
||||
from graphon.model_runtime.entities.llm_entities import LLMResult, LLMUsage
|
||||
from graphon.model_runtime.entities.message_entities import AssistantPromptMessage
|
||||
from graphon.model_runtime.entities.model_entities import ModelPropertyKey
|
||||
from graphon.model_runtime.errors.invoke import (
|
||||
InvokeAuthorizationError,
|
||||
@@ -204,7 +206,3 @@ def test_invoke_error_mappings(exc, expected, sqlite_session: Session):
|
||||
assert persisted.prompt_tokens == 5
|
||||
assert persisted.answer_tokens == 0
|
||||
mock_factory.assert_called_once_with(tenant_id=TENANT_ID, user_id=USER_ID)
|
||||
|
||||
|
||||
from graphon.model_runtime.entities.llm_entities import LLMResult, LLMUsage
|
||||
from graphon.model_runtime.entities.message_entities import AssistantPromptMessage
|
||||
@@ -64,7 +64,6 @@ def test_declares_variable():
|
||||
|
||||
|
||||
def test_collect_container_errors():
|
||||
|
||||
# Not a container error
|
||||
nodes = [{"id": "n1", "data": {"type": "llm"}}, {"id": "n2", "data": {"type": "code", "parentId": "n1"}}]
|
||||
errors = WorkflowGenerator._collect_container_errors(nodes=nodes)
|
||||
@@ -80,7 +79,6 @@ def test_collect_container_errors():
|
||||
|
||||
|
||||
def test_collect_unknown_tools():
|
||||
|
||||
# Missing tool/provider info
|
||||
nodes = [
|
||||
{"id": "n1", "data": {"type": "tool", "provider_id": "", "tool_name": ""}},
|
||||
@@ -95,7 +93,6 @@ def test_collect_unknown_tools():
|
||||
|
||||
|
||||
def test_collect_unresolved_refs():
|
||||
|
||||
# Missing node ref
|
||||
nodes = [{"id": "n1", "data": {"type": "llm", "prompt_template": [{"text": "{#unknown.var#}"}]}}]
|
||||
# To trigger the parsing we need to mock _collect_refs_in_data behavior or let it parse naturally
|
||||
@@ -118,7 +115,6 @@ def test_collect_unresolved_refs():
|
||||
|
||||
|
||||
def test_collect_edge_cycle_errors():
|
||||
|
||||
# Self-loop
|
||||
graph = {"nodes": [{"id": "n1"}], "edges": [{"source": "n1", "target": "n1"}]}
|
||||
errors = WorkflowGenerator._collect_edge_cycle_errors(graph=graph, known_ids={"n1"})
|
||||
@@ -127,7 +123,6 @@ def test_collect_edge_cycle_errors():
|
||||
|
||||
|
||||
def test_collect_container_errors_empty_container():
|
||||
|
||||
# Empty container
|
||||
nodes = [
|
||||
{"id": "n1", "data": {"type": "iteration"}},
|
||||
@@ -138,7 +133,6 @@ def test_collect_container_errors_empty_container():
|
||||
|
||||
|
||||
def test_collect_container_errors_cycle():
|
||||
|
||||
# Ancestor cycle
|
||||
nodes = [
|
||||
{"id": "n1", "data": {"type": "iteration", "parentId": "n2"}},
|
||||
@@ -150,7 +144,6 @@ def test_collect_container_errors_cycle():
|
||||
|
||||
|
||||
def test_postprocess_graph_edges():
|
||||
|
||||
# Try calling _postprocess_graph directly to trigger _sanitize_node_ids
|
||||
graph = {
|
||||
"nodes": [{"id": "sys", "data": {"type": "start"}}, {"id": "bad id", "data": {"type": "llm"}}],
|
||||
@@ -177,7 +170,6 @@ def test_document_extractor_start_vars():
|
||||
|
||||
|
||||
def test_missing_terminal_mode_auto():
|
||||
|
||||
# Empty graph, should get MISSING_TERMINAL
|
||||
graph = {"nodes": [{"id": "n1", "data": {"type": "start"}}], "edges": []}
|
||||
|
||||
|
||||
@@ -29,7 +29,9 @@ from graphon.node_events import NodeRunResult, StreamChunkEvent, StreamCompleted
|
||||
from graphon.nodes.code import CodeNode
|
||||
from graphon.nodes.document_extractor import DocumentExtractorNode
|
||||
from graphon.nodes.http_request import HttpRequestNode
|
||||
from graphon.nodes.iteration import IterationNode
|
||||
from graphon.nodes.llm import LLMNode
|
||||
from graphon.nodes.loop import LoopNode
|
||||
from graphon.nodes.parameter_extractor import ParameterExtractorNode
|
||||
from graphon.nodes.question_classifier import QuestionClassifierNode
|
||||
from graphon.nodes.template_transform import TemplateTransformNode
|
||||
@@ -611,10 +613,6 @@ class MockDocumentExtractorNode(MockNodeMixin, DocumentExtractorNode):
|
||||
)
|
||||
|
||||
|
||||
from graphon.nodes.iteration import IterationNode
|
||||
from graphon.nodes.loop import LoopNode
|
||||
|
||||
|
||||
class MockIterationNode(MockNodeMixin, IterationNode):
|
||||
"""Mock implementation of IterationNode that preserves mock configuration."""
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from repositories.knowledge.keyword_table_repository import load_keyword_table
|
||||
|
||||
"""
|
||||
Comprehensive unit tests for Dataset models.
|
||||
|
||||
@@ -77,6 +75,7 @@ from repositories.knowledge.dataset_read_repository import (
|
||||
get_segment_dataset,
|
||||
get_segment_document,
|
||||
)
|
||||
from repositories.knowledge.keyword_table_repository import load_keyword_table
|
||||
from repositories.knowledge.segment_read_adapter import get_segment_attachments
|
||||
from services.knowledge.dataset_read_service import get_dataset_queries
|
||||
from tests.unit_tests.config_override import apply_config_overrides
|
||||
|
||||
@@ -134,7 +134,6 @@ def test_writes_commit_and_other_accounts_cannot_mutate(
|
||||
def test_write_rolls_back_on_commit_failure(
|
||||
seeded: WorkspaceRepository, sqlite_session_factory: sessionmaker[Session]
|
||||
) -> None:
|
||||
|
||||
def fail_flush(_session: Session, _flush_context: object, _instances: object) -> None:
|
||||
raise RuntimeError("write failed")
|
||||
|
||||
|
||||
@@ -103,7 +103,6 @@ def test_send_code_normalizes_new_account_email_and_language(
|
||||
service: AccountEmailRegistrationService,
|
||||
boundaries: dict[str, MagicMock],
|
||||
) -> None:
|
||||
|
||||
service.send_code(
|
||||
remote_ip="127.0.0.1",
|
||||
requested_email="New@Example.com",
|
||||
@@ -260,7 +259,6 @@ def test_register_rejects_password_mismatch_before_reading_token(
|
||||
service: AccountEmailRegistrationService,
|
||||
boundaries: dict[str, MagicMock],
|
||||
) -> None:
|
||||
|
||||
with pytest.raises(EmailRegistrationPasswordMismatchError):
|
||||
service.register(
|
||||
remote_ip="127.0.0.1",
|
||||
|
||||
@@ -147,7 +147,6 @@ def fixture() -> ServiceFixture:
|
||||
|
||||
|
||||
def test_estimate_new_sources_resolves_each_variant_and_normalizes_rule(fixture: ServiceFixture) -> None:
|
||||
|
||||
result = fixture.service.estimate_new_sources(
|
||||
_context(),
|
||||
NewSourcesEstimateCommand(
|
||||
@@ -258,7 +257,6 @@ def test_estimate_new_notion_source_maps_actor_credential_failure_without_runnin
|
||||
|
||||
|
||||
def test_estimate_empty_new_sources_returns_zero_without_runner_call(fixture: ServiceFixture) -> None:
|
||||
|
||||
result = fixture.service.estimate_new_sources(
|
||||
_context(),
|
||||
NewSourcesEstimateCommand(sources=(), process_rule={"mode": "automatic"}),
|
||||
@@ -309,7 +307,6 @@ def test_estimate_rejects_missing_saved_process_rule(fixture: ServiceFixture) ->
|
||||
|
||||
|
||||
def test_estimate_empty_batch_raises_not_found_without_runner_call(fixture: ServiceFixture) -> None:
|
||||
|
||||
with pytest.raises(EstimateDocumentNotFoundError):
|
||||
fixture.service.estimate_batch(_context(), dataset_id="dataset-1", batch="batch-1")
|
||||
|
||||
|
||||
@@ -292,7 +292,6 @@ def _application(
|
||||
def test_mutations_use_request_actor_without_a_flask_request(
|
||||
sqlite_session_factory: sessionmaker[Session],
|
||||
) -> None:
|
||||
|
||||
with sqlite_session_factory.begin() as session:
|
||||
session.add(_dataset("dataset-1", "workspace-1"))
|
||||
session.add(_document("document-1", "workspace-1", "dataset-1"))
|
||||
@@ -389,7 +388,6 @@ def test_redis_state_uses_existing_keys_and_decodes_status() -> None:
|
||||
def test_child_chunk_mutations_preserve_explicit_actors(
|
||||
sqlite_session_factory: sessionmaker[Session],
|
||||
) -> None:
|
||||
|
||||
with sqlite_session_factory.begin() as session:
|
||||
session.add(_dataset("dataset-1", "workspace-1"))
|
||||
document = _document("document-1", "workspace-1", "dataset-1")
|
||||
|
||||
@@ -1151,7 +1151,6 @@ def test_get_default_block_configs_success(rag_pipeline_service: RagPipelineServ
|
||||
|
||||
|
||||
def test_get_default_block_config_success(rag_pipeline_service: RagPipelineServiceTestContext) -> None:
|
||||
|
||||
result = rag_pipeline_service.service.get_default_block_config(BuiltinNodeTypes.LLM)
|
||||
assert result is not None
|
||||
assert result["type"] == "llm"
|
||||
@@ -1172,7 +1171,6 @@ def test_publish_workflow_raises_when_draft_workflow_missing(
|
||||
def test_get_default_block_config_returns_none_when_mapped_type_missing(
|
||||
mocker: MockerFixture, rag_pipeline_service: RagPipelineServiceTestContext
|
||||
) -> None:
|
||||
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.get_node_type_classes_mapping", return_value={})
|
||||
|
||||
assert rag_pipeline_service.service.get_default_block_config(BuiltinNodeTypes.START) is None
|
||||
@@ -1181,7 +1179,6 @@ def test_get_default_block_config_returns_none_when_mapped_type_missing(
|
||||
def test_get_default_block_config_injects_http_request_filter(
|
||||
mocker: MockerFixture, rag_pipeline_service: RagPipelineServiceTestContext
|
||||
) -> None:
|
||||
|
||||
fake_node_cls = mocker.Mock()
|
||||
fake_node_cls.get_default_config.return_value = {"type": "http-request"}
|
||||
mocker.patch(
|
||||
@@ -1613,7 +1610,6 @@ def test_set_datasource_variables_raises_when_node_id_missing(
|
||||
def test_get_default_block_configs_skips_empty_configs(
|
||||
mocker: MockerFixture, rag_pipeline_service: RagPipelineServiceTestContext
|
||||
) -> None:
|
||||
|
||||
http_node = mocker.Mock()
|
||||
http_node.get_default_config.return_value = {"type": "http-request"}
|
||||
empty_node = mocker.Mock()
|
||||
@@ -2076,7 +2072,6 @@ def test_publish_workflow_skips_dataset_update_for_non_knowledge_nodes(
|
||||
def test_get_default_block_config_returns_none_when_default_empty(
|
||||
mocker: MockerFixture, rag_pipeline_service: RagPipelineServiceTestContext
|
||||
) -> None:
|
||||
|
||||
node_cls = mocker.Mock()
|
||||
node_cls.get_default_config.return_value = None
|
||||
mocker.patch(
|
||||
|
||||
@@ -101,7 +101,6 @@ def test_send_old_email_code_coerces_unexpected_phase_to_initial_state(
|
||||
service: AccountChangeEmailService,
|
||||
boundaries: dict[str, MagicMock],
|
||||
) -> None:
|
||||
|
||||
token = service.send_code(
|
||||
_context(),
|
||||
requested_email="OLD@example.com",
|
||||
|
||||
@@ -444,7 +444,6 @@ def test_submit_form_by_token_missing_inputs(
|
||||
sample_form_record: HumanInputFormRecord,
|
||||
unbound_session_factory: sessionmaker[Session],
|
||||
) -> None:
|
||||
|
||||
definition_with_input = FormDefinition(
|
||||
form_content="hello",
|
||||
inputs=[ParagraphInputConfig(output_variable_name="content")],
|
||||
|
||||
Reference in new issue
Block a user