Merge branch 'main' into fix/chore-fix

This commit is contained in:
Yeuoly 2024-12-24 21:28:56 +08:00
commit 4199998c7e
734 changed files with 7911 additions and 5007 deletions

View File

@ -50,9 +50,18 @@ jobs:
- name: Run ModelRuntime
run: poetry run -C api bash dev/pytest/pytest_model_runtime.sh
- name: Run dify config tests
run: poetry run -C api python dev/pytest/pytest_config_tests.py
- name: Run Tool
run: poetry run -C api bash dev/pytest/pytest_tools.sh
- name: Run mypy
run: |
pushd api
poetry run python -m mypy --install-types --non-interactive .
popd
- name: Set up dotenvs
run: |
cp docker/.env.example docker/.env

View File

@ -9,5 +9,6 @@ yq eval '.services["pgvecto-rs"].ports += ["5431:5432"]' -i docker/docker-compos
yq eval '.services["elasticsearch"].ports += ["9200:9200"]' -i docker/docker-compose.yaml
yq eval '.services.couchbase-server.ports += ["8091-8096:8091-8096"]' -i docker/docker-compose.yaml
yq eval '.services.couchbase-server.ports += ["11210:11210"]' -i docker/docker-compose.yaml
yq eval '.services.tidb.ports += ["4000:4000"]' -i docker/docker-compose.yaml
echo "Ports exposed for sandbox, weaviate, qdrant, chroma, milvus, pgvector, pgvecto-rs, elasticsearch, couchbase"
echo "Ports exposed for sandbox, weaviate, tidb, qdrant, chroma, milvus, pgvector, pgvecto-rs, elasticsearch, couchbase"

View File

@ -60,17 +60,8 @@ DB_DATABASE=dify
STORAGE_TYPE=opendal
# Apache OpenDAL storage configuration, refer to https://github.com/apache/opendal
STORAGE_OPENDAL_SCHEME=fs
# OpenDAL FS
OPENDAL_SCHEME=fs
OPENDAL_FS_ROOT=storage
# OpenDAL S3
OPENDAL_S3_ROOT=/
OPENDAL_S3_BUCKET=your-bucket-name
OPENDAL_S3_ENDPOINT=https://s3.amazonaws.com
OPENDAL_S3_ACCESS_KEY_ID=your-access-key
OPENDAL_S3_SECRET_ACCESS_KEY=your-secret-key
OPENDAL_S3_REGION=your-region
OPENDAL_S3_SERVER_SIDE_ENCRYPTION=
# S3 Storage configuration
S3_USE_AWS_MANAGED_IAM=false
@ -313,8 +304,7 @@ UPLOAD_VIDEO_FILE_SIZE_LIMIT=100
UPLOAD_AUDIO_FILE_SIZE_LIMIT=50
# Model configuration
MULTIMODAL_SEND_IMAGE_FORMAT=base64
MULTIMODAL_SEND_VIDEO_FORMAT=base64
MULTIMODAL_SEND_FORMAT=base64
PROMPT_GENERATION_MAX_TOKENS=512
CODE_GENERATION_MAX_TOKENS=1024
@ -409,6 +399,7 @@ INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=4000
WORKFLOW_MAX_EXECUTION_STEPS=500
WORKFLOW_MAX_EXECUTION_TIME=1200
WORKFLOW_CALL_MAX_DEPTH=5
WORKFLOW_PARALLEL_DEPTH_LIMIT=3
MAX_VARIABLE_SIZE=204800
# App configuration
@ -446,3 +437,5 @@ CREATE_TIDB_SERVICE_JOB_ENABLED=false
# Maximum number of submitted thread count in a ThreadPool for parallel node execution
MAX_SUBMIT_COUNT=100
# Lockout duration in seconds
LOGIN_LOCKOUT_DURATION=86400

View File

@ -70,7 +70,6 @@ ignore = [
"SIM113", # eumerate-for-loop
"SIM117", # multiple-with-statements
"SIM210", # if-expr-with-true-false
"SIM300", # yoda-conditions,
]
[lint.per-file-ignores]

View File

@ -1,13 +1,30 @@
from app_factory import create_app
from libs import threadings_utils, version_utils
from libs import version_utils
# preparation before creating app
version_utils.check_supported_python_version()
threadings_utils.apply_gevent_threading_patch()
def is_db_command():
import sys
if len(sys.argv) > 1 and sys.argv[0].endswith("flask") and sys.argv[1] == "db":
return True
return False
# create app
app = create_app()
celery = app.extensions["celery"]
if is_db_command():
from app_factory import create_migrations_app
app = create_migrations_app()
else:
from app_factory import create_app
from libs import threadings_utils
threadings_utils.apply_gevent_threading_patch()
app = create_app()
celery = app.extensions["celery"]
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5001)

View File

@ -1,5 +1,4 @@
import logging
import os
import time
from configs import dify_config
@ -17,15 +16,6 @@ def create_flask_app_with_configs() -> DifyApp:
dify_app = DifyApp(__name__)
dify_app.config.from_mapping(dify_config.model_dump())
# populate configs into system environment variables
for key, value in dify_app.config.items():
if isinstance(value, str):
os.environ[key] = value
elif isinstance(value, int | float | bool):
os.environ[key] = str(value)
elif value is None:
os.environ[key] = ""
return dify_app
@ -98,3 +88,14 @@ def initialize_extensions(app: DifyApp):
end_time = time.perf_counter()
if dify_config.DEBUG:
logging.info(f"Loaded {short_name} ({round((end_time - start_time) * 1000, 2)} ms)")
def create_migrations_app():
app = create_flask_app_with_configs()
from extensions import ext_database, ext_migrate
# Initialize only required extensions
ext_database.init_app(app)
ext_migrate.init_app(app)
return app

View File

@ -160,8 +160,7 @@ def migrate_annotation_vector_database():
try:
# get apps info
apps = (
db.session.query(App)
.filter(App.status == "normal")
App.query.filter(App.status == "normal")
.order_by(App.created_at.desc())
.paginate(page=page, per_page=50)
)
@ -286,8 +285,7 @@ def migrate_knowledge_vector_database():
while True:
try:
datasets = (
db.session.query(Dataset)
.filter(Dataset.indexing_technique == "high_quality")
Dataset.query.filter(Dataset.indexing_technique == "high_quality")
.order_by(Dataset.created_at.desc())
.paginate(page=page, per_page=50)
)
@ -451,6 +449,7 @@ def convert_to_agent_apps():
if app_id not in proceeded_app_ids:
proceeded_app_ids.append(app_id)
app = db.session.query(App).filter(App.id == app_id).first()
if app is not None:
apps.append(app)
if len(apps) == 0:
@ -556,7 +555,8 @@ def create_tenant(email: str, language: Optional[str] = None, name: Optional[str
if language not in languages:
language = "en-US"
name = name.strip()
# Validates name encoding for non-Latin characters.
name = name.strip().encode("utf-8").decode("utf-8") if name else None
# generate random password
new_password = secrets.token_urlsafe(16)
@ -621,6 +621,10 @@ where sites.id is null limit 1000"""
try:
app = db.session.query(App).filter(App.id == app_id).first()
if not app:
print(f"App {app_id} not found")
continue
tenant = app.tenant
if tenant:
accounts = tenant.get_accounts()

View File

@ -297,7 +297,6 @@ class HttpConfig(BaseSettings):
)
@computed_field
@property
def CONSOLE_CORS_ALLOW_ORIGINS(self) -> list[str]:
return self.inner_CONSOLE_CORS_ALLOW_ORIGINS.split(",")
@ -308,7 +307,6 @@ class HttpConfig(BaseSettings):
)
@computed_field
@property
def WEB_API_CORS_ALLOW_ORIGINS(self) -> list[str]:
return self.inner_WEB_API_CORS_ALLOW_ORIGINS.split(",")
@ -491,6 +489,11 @@ class WorkflowConfig(BaseSettings):
default=5,
)
WORKFLOW_PARALLEL_DEPTH_LIMIT: PositiveInt = Field(
description="Maximum allowed depth for nested parallel executions",
default=3,
)
MAX_VARIABLE_SIZE: PositiveInt = Field(
description="Maximum size in bytes for a single variable in workflows. Default to 200 KB.",
default=200 * 1024,
@ -543,6 +546,11 @@ class AuthConfig(BaseSettings):
default=60,
)
LOGIN_LOCKOUT_DURATION: PositiveInt = Field(
description="Time (in seconds) a user must wait before retrying login after exceeding the rate limit.",
default=86400,
)
class ModerationConfig(BaseSettings):
"""
@ -718,14 +726,9 @@ class IndexingConfig(BaseSettings):
)
class VisionFormatConfig(BaseSettings):
MULTIMODAL_SEND_IMAGE_FORMAT: Literal["base64", "url"] = Field(
description="Format for sending images in multimodal contexts ('base64' or 'url'), default is base64",
default="base64",
)
MULTIMODAL_SEND_VIDEO_FORMAT: Literal["base64", "url"] = Field(
description="Format for sending videos in multimodal contexts ('base64' or 'url'), default is base64",
class MultiModalTransferConfig(BaseSettings):
MULTIMODAL_SEND_FORMAT: Literal["base64", "url"] = Field(
description="Format for sending files in multimodal contexts ('base64' or 'url'), default is base64",
default="base64",
)
@ -768,27 +771,27 @@ class PositionConfig(BaseSettings):
default="",
)
@computed_field
@property
def POSITION_PROVIDER_PINS_LIST(self) -> list[str]:
return [item.strip() for item in self.POSITION_PROVIDER_PINS.split(",") if item.strip() != ""]
@computed_field
@property
def POSITION_PROVIDER_INCLUDES_SET(self) -> set[str]:
return {item.strip() for item in self.POSITION_PROVIDER_INCLUDES.split(",") if item.strip() != ""}
@computed_field
@property
def POSITION_PROVIDER_EXCLUDES_SET(self) -> set[str]:
return {item.strip() for item in self.POSITION_PROVIDER_EXCLUDES.split(",") if item.strip() != ""}
@computed_field
@property
def POSITION_TOOL_PINS_LIST(self) -> list[str]:
return [item.strip() for item in self.POSITION_TOOL_PINS.split(",") if item.strip() != ""]
@computed_field
@property
def POSITION_TOOL_INCLUDES_SET(self) -> set[str]:
return {item.strip() for item in self.POSITION_TOOL_INCLUDES.split(",") if item.strip() != ""}
@computed_field
@property
def POSITION_TOOL_EXCLUDES_SET(self) -> set[str]:
return {item.strip() for item in self.POSITION_TOOL_EXCLUDES.split(",") if item.strip() != ""}
@ -833,13 +836,13 @@ class FeatureConfig(
FileAccessConfig,
FileUploadConfig,
HttpConfig,
VisionFormatConfig,
InnerAPIConfig,
IndexingConfig,
LoggingConfig,
MailConfig,
ModelLoadBalanceConfig,
ModerationConfig,
MultiModalTransferConfig,
PositionConfig,
RagEtlConfig,
SecurityConfig,

View File

@ -130,7 +130,6 @@ class DatabaseConfig(BaseSettings):
)
@computed_field
@property
def SQLALCHEMY_DATABASE_URI(self) -> str:
db_extras = (
f"{self.DB_EXTRAS}&client_encoding={self.DB_CHARSET}" if self.DB_CHARSET else self.DB_EXTRAS
@ -168,7 +167,6 @@ class DatabaseConfig(BaseSettings):
)
@computed_field
@property
def SQLALCHEMY_ENGINE_OPTIONS(self) -> dict[str, Any]:
return {
"pool_size": self.SQLALCHEMY_POOL_SIZE,
@ -206,7 +204,6 @@ class CeleryConfig(DatabaseConfig):
)
@computed_field
@property
def CELERY_RESULT_BACKEND(self) -> str | None:
return (
"db+{}".format(self.SQLALCHEMY_DATABASE_URI)
@ -214,7 +211,6 @@ class CeleryConfig(DatabaseConfig):
else self.CELERY_BROKER_URL
)
@computed_field
@property
def BROKER_USE_SSL(self) -> bool:
return self.CELERY_BROKER_URL.startswith("rediss://") if self.CELERY_BROKER_URL else False

View File

@ -1,51 +1,9 @@
from enum import StrEnum
from typing import Literal
from pydantic import Field
from pydantic_settings import BaseSettings
class OpenDALScheme(StrEnum):
FS = "fs"
S3 = "s3"
class OpenDALStorageConfig(BaseSettings):
STORAGE_OPENDAL_SCHEME: str = Field(
default=OpenDALScheme.FS.value,
OPENDAL_SCHEME: str = Field(
default="fs",
description="OpenDAL scheme.",
)
# FS
OPENDAL_FS_ROOT: str = Field(
default="storage",
description="Root path for local storage.",
)
# S3
OPENDAL_S3_ROOT: str = Field(
default="/",
description="Root path for S3 storage.",
)
OPENDAL_S3_BUCKET: str = Field(
default="",
description="S3 bucket name.",
)
OPENDAL_S3_ENDPOINT: str = Field(
default="https://s3.amazonaws.com",
description="S3 endpoint URL.",
)
OPENDAL_S3_ACCESS_KEY_ID: str = Field(
default="",
description="S3 access key ID.",
)
OPENDAL_S3_SECRET_ACCESS_KEY: str = Field(
default="",
description="S3 secret access key.",
)
OPENDAL_S3_REGION: str = Field(
default="",
description="S3 region.",
)
OPENDAL_S3_SERVER_SIDE_ENCRYPTION: Literal["aws:kms", ""] = Field(
default="",
description="S3 server-side encryption.",
)

View File

@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
CURRENT_VERSION: str = Field(
description="Dify version",
default="0.13.2",
default="0.14.2",
)
COMMIT_SHA: str = Field(

View File

@ -4,6 +4,7 @@ import logging
import os
import threading
import time
from collections.abc import Mapping
from pathlib import Path
from .python_3x import http_request, makedirs_wrapper
@ -255,8 +256,8 @@ class ApolloClient:
logger.info("stopped, long_poll")
# add the need for endorsement to the header
def _sign_headers(self, url):
headers = {}
def _sign_headers(self, url: str) -> Mapping[str, str]:
headers: dict[str, str] = {}
if self.secret == "":
return headers
uri = url[len(self.config_url) : len(url)]

View File

@ -1,8 +1,9 @@
import json
from collections.abc import Mapping
from models.model import AppMode
default_app_templates = {
default_app_templates: Mapping[AppMode, Mapping] = {
# workflow default mode
AppMode.WORKFLOW: {
"app": {

View File

@ -4,3 +4,8 @@ from werkzeug.exceptions import HTTPException
class FilenameNotExistsError(HTTPException):
code = 400
description = "The specified filename does not exist."
class RemoteFileUploadError(HTTPException):
code = 400
description = "Error uploading remote file."

View File

@ -1,4 +1,4 @@
from flask_restful import fields
from flask_restful import fields # type: ignore
parameters__system_parameters = {
"image_file_size_limit": fields.Integer,

View File

@ -3,6 +3,25 @@ from flask import Blueprint
from libs.external_api import ExternalApi
from .app.app_import import AppImportApi, AppImportCheckDependenciesApi, AppImportConfirmApi
from .explore.audio import ChatAudioApi, ChatTextApi
from .explore.completion import ChatApi, ChatStopApi, CompletionApi, CompletionStopApi
from .explore.conversation import (
ConversationApi,
ConversationListApi,
ConversationPinApi,
ConversationRenameApi,
ConversationUnPinApi,
)
from .explore.message import (
MessageFeedbackApi,
MessageListApi,
MessageMoreLikeThisApi,
MessageSuggestedQuestionApi,
)
from .explore.workflow import (
InstalledAppWorkflowRunApi,
InstalledAppWorkflowTaskStopApi,
)
from .files import FileApi, FilePreviewApi, FileSupportTypeApi
from .remote_files import RemoteFileInfoApi, RemoteFileUploadApi
@ -67,15 +86,81 @@ from .datasets import (
# Import explore controllers
from .explore import (
audio,
completion,
conversation,
installed_app,
message,
parameter,
recommended_app,
saved_message,
workflow,
)
# Explore Audio
api.add_resource(ChatAudioApi, "/installed-apps/<uuid:installed_app_id>/audio-to-text", endpoint="installed_app_audio")
api.add_resource(ChatTextApi, "/installed-apps/<uuid:installed_app_id>/text-to-audio", endpoint="installed_app_text")
# Explore Completion
api.add_resource(
CompletionApi, "/installed-apps/<uuid:installed_app_id>/completion-messages", endpoint="installed_app_completion"
)
api.add_resource(
CompletionStopApi,
"/installed-apps/<uuid:installed_app_id>/completion-messages/<string:task_id>/stop",
endpoint="installed_app_stop_completion",
)
api.add_resource(
ChatApi, "/installed-apps/<uuid:installed_app_id>/chat-messages", endpoint="installed_app_chat_completion"
)
api.add_resource(
ChatStopApi,
"/installed-apps/<uuid:installed_app_id>/chat-messages/<string:task_id>/stop",
endpoint="installed_app_stop_chat_completion",
)
# Explore Conversation
api.add_resource(
ConversationRenameApi,
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>/name",
endpoint="installed_app_conversation_rename",
)
api.add_resource(
ConversationListApi, "/installed-apps/<uuid:installed_app_id>/conversations", endpoint="installed_app_conversations"
)
api.add_resource(
ConversationApi,
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>",
endpoint="installed_app_conversation",
)
api.add_resource(
ConversationPinApi,
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>/pin",
endpoint="installed_app_conversation_pin",
)
api.add_resource(
ConversationUnPinApi,
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>/unpin",
endpoint="installed_app_conversation_unpin",
)
# Explore Message
api.add_resource(MessageListApi, "/installed-apps/<uuid:installed_app_id>/messages", endpoint="installed_app_messages")
api.add_resource(
MessageFeedbackApi,
"/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/feedbacks",
endpoint="installed_app_message_feedback",
)
api.add_resource(
MessageMoreLikeThisApi,
"/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/more-like-this",
endpoint="installed_app_more_like_this",
)
api.add_resource(
MessageSuggestedQuestionApi,
"/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/suggested-questions",
endpoint="installed_app_suggested_question",
)
# Explore Workflow
api.add_resource(InstalledAppWorkflowRunApi, "/installed-apps/<uuid:installed_app_id>/workflows/run")
api.add_resource(
InstalledAppWorkflowTaskStopApi, "/installed-apps/<uuid:installed_app_id>/workflows/tasks/<string:task_id>/stop"
)
# Import tag controllers

View File

@ -1,7 +1,7 @@
from functools import wraps
from flask import request
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
from sqlalchemy import select
from sqlalchemy.orm import Session
from werkzeug.exceptions import NotFound, Unauthorized
@ -33,7 +33,7 @@ def admin_required(view):
if auth_scheme != "bearer":
raise Unauthorized("Invalid Authorization header format. Expected 'Bearer <api-key>' format.")
if dify_config.ADMIN_API_KEY != auth_token:
if auth_token != dify_config.ADMIN_API_KEY:
raise Unauthorized("API key is invalid.")
return view(*args, **kwargs)

View File

@ -1,5 +1,7 @@
import flask_restful
from flask_login import current_user
from typing import Any
import flask_restful # type: ignore
from flask_login import current_user # type: ignore
from flask_restful import Resource, fields, marshal_with
from sqlalchemy import select
from sqlalchemy.orm import Session
@ -46,14 +48,15 @@ def _get_resource(resource_id, tenant_id, resource_model):
class BaseApiKeyListResource(Resource):
method_decorators = [account_initialization_required, login_required, setup_required]
resource_type = None
resource_model = None
resource_id_field = None
token_prefix = None
resource_type: str | None = None
resource_model: Any = None
resource_id_field: str | None = None
token_prefix: str | None = None
max_keys = 10
@marshal_with(api_key_list)
def get(self, resource_id):
assert self.resource_id_field is not None, "resource_id_field must be set"
resource_id = str(resource_id)
_get_resource(resource_id, current_user.current_tenant_id, self.resource_model)
keys = (
@ -65,6 +68,7 @@ class BaseApiKeyListResource(Resource):
@marshal_with(api_key_fields)
def post(self, resource_id):
assert self.resource_id_field is not None, "resource_id_field must be set"
resource_id = str(resource_id)
_get_resource(resource_id, current_user.current_tenant_id, self.resource_model)
if not current_user.is_editor:
@ -97,11 +101,12 @@ class BaseApiKeyListResource(Resource):
class BaseApiKeyResource(Resource):
method_decorators = [account_initialization_required, login_required, setup_required]
resource_type = None
resource_model = None
resource_id_field = None
resource_type: str | None = None
resource_model: Any = None
resource_id_field: str | None = None
def delete(self, resource_id, api_key_id):
assert self.resource_id_field is not None, "resource_id_field must be set"
resource_id = str(resource_id)
api_key_id = str(api_key_id)
_get_resource(resource_id, current_user.current_tenant_id, self.resource_model)

View File

@ -1,4 +1,4 @@
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
from controllers.console import api
from controllers.console.wraps import account_initialization_required, setup_required

View File

@ -1,4 +1,4 @@
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
from controllers.console import api
from controllers.console.app.wraps import get_app_model

View File

@ -1,6 +1,6 @@
from flask import request
from flask_login import current_user
from flask_restful import Resource, marshal, marshal_with, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, marshal, marshal_with, reqparse # type: ignore
from werkzeug.exceptions import Forbidden
from controllers.console import api
@ -110,7 +110,7 @@ class AnnotationListApi(Resource):
page = request.args.get("page", default=1, type=int)
limit = request.args.get("limit", default=20, type=int)
keyword = request.args.get("keyword", default=None, type=str)
keyword = request.args.get("keyword", default="", type=str)
app_id = str(app_id)
annotation_list, total = AppAnnotationService.get_annotation_list_by_app_id(app_id, page, limit, keyword)

View File

@ -1,8 +1,8 @@
import uuid
from typing import cast
from flask_login import current_user
from flask_restful import Resource, inputs, marshal, marshal_with, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, inputs, marshal, marshal_with, reqparse # type: ignore
from sqlalchemy import select
from sqlalchemy.orm import Session
from werkzeug.exceptions import BadRequest, Forbidden, abort

View File

@ -1,7 +1,7 @@
from typing import cast
from flask_login import current_user
from flask_restful import Resource, marshal_with, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, marshal_with, reqparse # type: ignore
from sqlalchemy.orm import Session
from werkzeug.exceptions import Forbidden

View File

@ -1,7 +1,7 @@
import logging
from flask import request
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
from werkzeug.exceptions import InternalServerError
import services

View File

@ -1,7 +1,7 @@
import logging
import flask_login
from flask_restful import Resource, reqparse
import flask_login # type: ignore
from flask_restful import Resource, reqparse # type: ignore
from werkzeug.exceptions import InternalServerError, NotFound
import services

View File

@ -1,9 +1,9 @@
from datetime import UTC, datetime
import pytz
from flask_login import current_user
from flask_restful import Resource, marshal_with, reqparse
from flask_restful.inputs import int_range
import pytz # pip install pytz
from flask_login import current_user # type: ignore
from flask_restful import Resource, marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from sqlalchemy import func, or_
from sqlalchemy.orm import joinedload
from werkzeug.exceptions import Forbidden, NotFound
@ -77,8 +77,9 @@ class CompletionConversationApi(Resource):
query = query.where(Conversation.created_at < end_datetime_utc)
# FIXME, the type ignore in this file
if args["annotation_status"] == "annotated":
query = query.options(joinedload(Conversation.message_annotations)).join(
query = query.options(joinedload(Conversation.message_annotations)).join( # type: ignore
MessageAnnotation, MessageAnnotation.conversation_id == Conversation.id
)
elif args["annotation_status"] == "not_annotated":
@ -222,7 +223,7 @@ class ChatConversationApi(Resource):
query = query.where(Conversation.created_at <= end_datetime_utc)
if args["annotation_status"] == "annotated":
query = query.options(joinedload(Conversation.message_annotations)).join(
query = query.options(joinedload(Conversation.message_annotations)).join( # type: ignore
MessageAnnotation, MessageAnnotation.conversation_id == Conversation.id
)
elif args["annotation_status"] == "not_annotated":
@ -234,7 +235,7 @@ class ChatConversationApi(Resource):
if args["message_count_gte"] and args["message_count_gte"] >= 1:
query = (
query.options(joinedload(Conversation.messages))
query.options(joinedload(Conversation.messages)) # type: ignore
.join(Message, Message.conversation_id == Conversation.id)
.group_by(Conversation.id)
.having(func.count(Message.id) >= args["message_count_gte"])

View File

@ -1,4 +1,4 @@
from flask_restful import Resource, marshal_with, reqparse
from flask_restful import Resource, marshal_with, reqparse # type: ignore
from sqlalchemy import select
from sqlalchemy.orm import Session

View File

@ -1,7 +1,7 @@
import os
from flask_login import current_user
from flask_restful import Resource, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, reqparse # type: ignore
from controllers.console import api
from controllers.console.app.error import (

View File

@ -1,8 +1,8 @@
import logging
from flask_login import current_user
from flask_restful import Resource, fields, marshal_with, reqparse
from flask_restful.inputs import int_range
from flask_login import current_user # type: ignore
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
from controllers.console import api

View File

@ -1,8 +1,9 @@
import json
from typing import cast
from flask import request
from flask_login import current_user
from flask_restful import Resource
from flask_login import current_user # type: ignore
from flask_restful import Resource # type: ignore
from controllers.console import api
from controllers.console.app.wraps import get_app_model
@ -26,7 +27,9 @@ class ModelConfigResource(Resource):
"""Modify app model config"""
# validate config
model_configuration = AppModelConfigService.validate_configuration(
tenant_id=current_user.current_tenant_id, config=request.json, app_mode=AppMode.value_of(app_model.mode)
tenant_id=current_user.current_tenant_id,
config=cast(dict, request.json),
app_mode=AppMode.value_of(app_model.mode),
)
new_app_model_config = AppModelConfig(
@ -38,9 +41,11 @@ class ModelConfigResource(Resource):
if app_model.mode == AppMode.AGENT_CHAT.value or app_model.is_agent:
# get original app model config
original_app_model_config: AppModelConfig = (
original_app_model_config = (
db.session.query(AppModelConfig).filter(AppModelConfig.id == app_model.app_model_config_id).first()
)
if original_app_model_config is None:
raise ValueError("Original app model config not found")
agent_mode = original_app_model_config.agent_mode_dict
# decrypt agent tool parameters if it's secret-input
parameter_map = {}
@ -65,7 +70,7 @@ class ModelConfigResource(Resource):
provider_type=agent_tool_entity.provider_type,
identity_id=f"AGENT.{app_model.id}",
)
except Exception as e:
except Exception:
continue
# get decrypted parameters
@ -97,7 +102,7 @@ class ModelConfigResource(Resource):
app_id=app_model.id,
agent_tool=agent_tool_entity,
)
except Exception as e:
except Exception:
continue
manager = ToolParameterConfigurationManager(

View File

@ -1,4 +1,5 @@
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
from werkzeug.exceptions import BadRequest
from controllers.console import api
from controllers.console.app.error import TracingConfigCheckError, TracingConfigIsExist, TracingConfigNotExist
@ -26,7 +27,7 @@ class TraceAppConfigApi(Resource):
return {"has_not_configured": True}
return trace_config
except Exception as e:
raise e
raise BadRequest(str(e))
@setup_required
@login_required
@ -48,7 +49,7 @@ class TraceAppConfigApi(Resource):
raise TracingConfigCheckError()
return result
except Exception as e:
raise e
raise BadRequest(str(e))
@setup_required
@login_required
@ -68,7 +69,7 @@ class TraceAppConfigApi(Resource):
raise TracingConfigNotExist()
return {"result": "success"}
except Exception as e:
raise e
raise BadRequest(str(e))
@setup_required
@login_required
@ -85,7 +86,7 @@ class TraceAppConfigApi(Resource):
raise TracingConfigNotExist()
return {"result": "success"}
except Exception as e:
raise e
raise BadRequest(str(e))
api.add_resource(TraceAppConfigApi, "/apps/<uuid:app_id>/trace-config")

View File

@ -1,7 +1,7 @@
from datetime import UTC, datetime
from flask_login import current_user
from flask_restful import Resource, marshal_with, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, marshal_with, reqparse # type: ignore
from werkzeug.exceptions import Forbidden, NotFound
from constants.languages import supported_language
@ -50,7 +50,7 @@ class AppSite(Resource):
if not current_user.is_editor:
raise Forbidden()
site = db.session.query(Site).filter(Site.app_id == app_model.id).one_or_404()
site = Site.query.filter(Site.app_id == app_model.id).one_or_404()
for attr_name in [
"title",

View File

@ -3,8 +3,8 @@ from decimal import Decimal
import pytz
from flask import jsonify
from flask_login import current_user
from flask_restful import Resource, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, reqparse # type: ignore
from controllers.console import api
from controllers.console.app.wraps import get_app_model

View File

@ -2,10 +2,11 @@ import json
import logging
from flask import abort, request
from flask_restful import Resource, marshal_with, reqparse
from flask_restful import Resource, marshal_with, reqparse # type: ignore
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
import services
from configs import dify_config
from controllers.console import api
from controllers.console.app.error import ConversationCompletedError, DraftWorkflowNotExist, DraftWorkflowNotSync
from controllers.console.app.wraps import get_app_model
@ -460,7 +461,21 @@ class ConvertToWorkflowApi(Resource):
}
class WorkflowConfigApi(Resource):
"""Resource for workflow configuration."""
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
def get(self, app_model: App):
return {
"parallel_depth_limit": dify_config.WORKFLOW_PARALLEL_DEPTH_LIMIT,
}
api.add_resource(DraftWorkflowApi, "/apps/<uuid:app_id>/workflows/draft")
api.add_resource(WorkflowConfigApi, "/apps/<uuid:app_id>/workflows/draft/config")
api.add_resource(AdvancedChatDraftWorkflowRunApi, "/apps/<uuid:app_id>/advanced-chat/workflows/draft/run")
api.add_resource(DraftWorkflowRunApi, "/apps/<uuid:app_id>/workflows/draft/run")
api.add_resource(WorkflowTaskStopApi, "/apps/<uuid:app_id>/workflow-runs/tasks/<string:task_id>/stop")

View File

@ -1,5 +1,5 @@
from flask_restful import Resource, marshal_with, reqparse
from flask_restful.inputs import int_range
from flask_restful import Resource, marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from controllers.console import api
from controllers.console.app.wraps import get_app_model

View File

@ -1,5 +1,5 @@
from flask_restful import Resource, marshal_with, reqparse
from flask_restful.inputs import int_range
from flask_restful import Resource, marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from controllers.console import api
from controllers.console.app.wraps import get_app_model

View File

@ -3,8 +3,8 @@ from decimal import Decimal
import pytz
from flask import jsonify
from flask_login import current_user
from flask_restful import Resource, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, reqparse # type: ignore
from controllers.console import api
from controllers.console.app.wraps import get_app_model

View File

@ -5,8 +5,7 @@ from typing import Optional, Union
from controllers.console.app.error import AppNotFoundError
from extensions.ext_database import db
from libs.login import current_user
from models import App
from models.model import AppMode
from models import App, AppMode
def get_app_model(view: Optional[Callable] = None, *, mode: Union[AppMode, list[AppMode], None] = None):

View File

@ -1,14 +1,14 @@
import datetime
from flask import request
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
from constants.languages import supported_language
from controllers.console import api
from controllers.console.error import AlreadyActivateError
from extensions.ext_database import db
from libs.helper import StrLen, email, extract_remote_ip, timezone
from models.account import AccountStatus, Tenant
from models.account import AccountStatus
from services.account_service import AccountService, RegisterService
@ -27,7 +27,7 @@ class ActivateCheckApi(Resource):
invitation = RegisterService.get_invitation_if_token_valid(workspaceId, reg_email, token)
if invitation:
data = invitation.get("data", {})
tenant: Tenant = invitation.get("tenant", None)
tenant = invitation.get("tenant", None)
workspace_name = tenant.name if tenant else None
workspace_id = tenant.id if tenant else None
invitee_email = data.get("email") if data else None

View File

@ -1,5 +1,5 @@
from flask_login import current_user
from flask_restful import Resource, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, reqparse # type: ignore
from werkzeug.exceptions import Forbidden
from controllers.console import api

View File

@ -2,8 +2,8 @@ import logging
import requests
from flask import current_app, redirect, request
from flask_login import current_user
from flask_restful import Resource
from flask_login import current_user # type: ignore
from flask_restful import Resource # type: ignore
from werkzeug.exceptions import Forbidden
from configs import dify_config
@ -17,8 +17,8 @@ from ..wraps import account_initialization_required, setup_required
def get_oauth_providers():
with current_app.app_context():
notion_oauth = NotionOAuth(
client_id=dify_config.NOTION_CLIENT_ID,
client_secret=dify_config.NOTION_CLIENT_SECRET,
client_id=dify_config.NOTION_CLIENT_ID or "",
client_secret=dify_config.NOTION_CLIENT_SECRET or "",
redirect_uri=dify_config.CONSOLE_API_URL + "/console/api/oauth/data-source/callback/notion",
)

View File

@ -126,8 +126,8 @@ class ForgotPasswordResetApi(Resource):
else:
try:
account = AccountService.create_account_and_tenant(
email=reset_data.get("email"),
name=reset_data.get("email"),
email=reset_data.get("email", ""),
name=reset_data.get("email", ""),
password=password_confirm,
interface_language=languages[0],
)

View File

@ -1,8 +1,8 @@
from typing import cast
import flask_login
import flask_login # type: ignore
from flask import request
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
import services
from constants.languages import languages

View File

@ -78,8 +78,9 @@ class OAuthCallback(Resource):
try:
token = oauth_provider.get_access_token(code)
user_info = oauth_provider.get_user_info(token)
except requests.exceptions.HTTPError as e:
logging.exception(f"An error occurred during the OAuth process with {provider}: {e.response.text}")
except requests.exceptions.RequestException as e:
error_text = e.response.text if e.response else str(e)
logging.exception(f"An error occurred during the OAuth process with {provider}: {error_text}")
return {"error": "OAuth process failed"}, 400
if invite_token and RegisterService.is_valid_invite_token(invite_token):
@ -131,7 +132,7 @@ class OAuthCallback(Resource):
def _get_account_by_openid_or_email(provider: str, user_info: OAuthUserInfo) -> Optional[Account]:
account = Account.get_by_openid(provider, user_info.id)
account: Optional[Account] = Account.get_by_openid(provider, user_info.id)
if not account:
with Session(db.engine) as session:

View File

@ -1,5 +1,5 @@
from flask_login import current_user
from flask_restful import Resource, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, reqparse # type: ignore
from controllers.console import api
from controllers.console.wraps import account_initialization_required, only_edition_cloud, setup_required

View File

@ -1,7 +1,7 @@
import flask_restful
import flask_restful # type: ignore
from flask import request
from flask_login import current_user
from flask_restful import Resource, marshal, marshal_with, reqparse
from flask_login import current_user # type: ignore # type: ignore
from flask_restful import Resource, marshal, marshal_with, reqparse # type: ignore
from werkzeug.exceptions import Forbidden, NotFound
import services

View File

@ -1,10 +1,11 @@
import logging
from argparse import ArgumentTypeError
from datetime import UTC, datetime
from typing import cast
from flask import request
from flask_login import current_user
from flask_restful import Resource, fields, marshal, marshal_with, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, fields, marshal, marshal_with, reqparse # type: ignore
from sqlalchemy import asc, desc
from werkzeug.exceptions import Forbidden, NotFound
@ -161,7 +162,7 @@ class DatasetDocumentListApi(Resource):
f"Truthy value expected: got {fetch_val} but expected one of yes/no, true/false, t/f, y/n, 1/0 "
f"(case insensitive)."
)
except (ArgumentTypeError, ValueError, Exception) as e:
except (ArgumentTypeError, ValueError, Exception):
fetch = False
dataset = DatasetService.get_dataset(dataset_id)
if not dataset:
@ -749,8 +750,7 @@ class DocumentMetadataApi(DocumentResource):
if not isinstance(doc_metadata, dict):
raise ValueError("doc_metadata must be a dictionary.")
metadata_schema = DocumentService.DOCUMENT_METADATA_SCHEMA[doc_type]
metadata_schema: dict = cast(dict, DocumentService.DOCUMENT_METADATA_SCHEMA[doc_type])
document.doc_metadata = {}
if doc_type == "others":
@ -964,7 +964,7 @@ class DocumentRetryApi(DocumentResource):
if document.indexing_status == "completed":
raise DocumentAlreadyFinishedError()
retry_documents.append(document)
except Exception as e:
except Exception:
logging.exception(f"Failed to retry document, document id: {document_id}")
continue
# retry document

View File

@ -3,8 +3,8 @@ from datetime import UTC, datetime
import pandas as pd
from flask import request
from flask_login import current_user
from flask_restful import Resource, marshal, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, marshal, reqparse # type: ignore
from werkzeug.exceptions import Forbidden, NotFound
import services

View File

@ -1,6 +1,6 @@
from flask import request
from flask_login import current_user
from flask_restful import Resource, marshal, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, marshal, reqparse # type: ignore
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
import services

View File

@ -1,4 +1,4 @@
from flask_restful import Resource
from flask_restful import Resource # type: ignore
from controllers.console import api
from controllers.console.datasets.hit_testing_base import DatasetsHitTestingBase

View File

@ -1,7 +1,7 @@
import logging
from flask_login import current_user
from flask_restful import marshal, reqparse
from flask_login import current_user # type: ignore
from flask_restful import marshal, reqparse # type: ignore
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
import services.dataset_service

View File

@ -1,4 +1,4 @@
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
from controllers.console import api
from controllers.console.datasets.error import WebsiteCrawlError

View File

@ -4,7 +4,6 @@ from flask import request
from werkzeug.exceptions import InternalServerError
import services
from controllers.console import api
from controllers.console.app.error import (
AppUnavailableError,
AudioTooLargeError,
@ -67,7 +66,7 @@ class ChatAudioApi(InstalledAppResource):
class ChatTextApi(InstalledAppResource):
def post(self, installed_app):
from flask_restful import reqparse
from flask_restful import reqparse # type: ignore
app_model = installed_app.app
try:
@ -118,9 +117,3 @@ class ChatTextApi(InstalledAppResource):
except Exception as e:
logging.exception("internal server error.")
raise InternalServerError()
api.add_resource(ChatAudioApi, "/installed-apps/<uuid:installed_app_id>/audio-to-text", endpoint="installed_app_audio")
api.add_resource(ChatTextApi, "/installed-apps/<uuid:installed_app_id>/text-to-audio", endpoint="installed_app_text")
# api.add_resource(ChatTextApiWithMessageId, '/installed-apps/<uuid:installed_app_id>/text-to-audio/message-id',
# endpoint='installed_app_text_with_message_id')

View File

@ -1,12 +1,11 @@
import logging
from datetime import UTC, datetime
from flask_login import current_user
from flask_restful import reqparse
from flask_login import current_user # type: ignore
from flask_restful import reqparse # type: ignore
from werkzeug.exceptions import InternalServerError, NotFound
import services
from controllers.console import api
from controllers.console.app.error import (
AppUnavailableError,
CompletionRequestError,
@ -147,21 +146,3 @@ class ChatStopApi(InstalledAppResource):
AppQueueManager.set_stop_flag(task_id, InvokeFrom.EXPLORE, current_user.id)
return {"result": "success"}, 200
api.add_resource(
CompletionApi, "/installed-apps/<uuid:installed_app_id>/completion-messages", endpoint="installed_app_completion"
)
api.add_resource(
CompletionStopApi,
"/installed-apps/<uuid:installed_app_id>/completion-messages/<string:task_id>/stop",
endpoint="installed_app_stop_completion",
)
api.add_resource(
ChatApi, "/installed-apps/<uuid:installed_app_id>/chat-messages", endpoint="installed_app_chat_completion"
)
api.add_resource(
ChatStopApi,
"/installed-apps/<uuid:installed_app_id>/chat-messages/<string:task_id>/stop",
endpoint="installed_app_stop_chat_completion",
)

View File

@ -1,12 +1,13 @@
from flask_login import current_user
from flask_restful import marshal_with, reqparse
from flask_restful.inputs import int_range
from flask_login import current_user # type: ignore
from flask_restful import marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from sqlalchemy.orm import Session
from werkzeug.exceptions import NotFound
from controllers.console import api
from controllers.console.explore.error import NotChatAppError
from controllers.console.explore.wraps import InstalledAppResource
from core.app.entities.app_invoke_entities import InvokeFrom
from extensions.ext_database import db
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
from libs.helper import uuid_value
from models.model import AppMode
@ -34,7 +35,9 @@ class ConversationListApi(InstalledAppResource):
pinned = True if args["pinned"] == "true" else False
try:
with Session(db.engine) as session:
return WebConversationService.pagination_by_last_id(
session=session,
app_model=app_model,
user=current_user,
last_id=args["last_id"],
@ -114,28 +117,3 @@ class ConversationUnPinApi(InstalledAppResource):
WebConversationService.unpin(app_model, conversation_id, current_user)
return {"result": "success"}
api.add_resource(
ConversationRenameApi,
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>/name",
endpoint="installed_app_conversation_rename",
)
api.add_resource(
ConversationListApi, "/installed-apps/<uuid:installed_app_id>/conversations", endpoint="installed_app_conversations"
)
api.add_resource(
ConversationApi,
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>",
endpoint="installed_app_conversation",
)
api.add_resource(
ConversationPinApi,
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>/pin",
endpoint="installed_app_conversation_pin",
)
api.add_resource(
ConversationUnPinApi,
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>/unpin",
endpoint="installed_app_conversation_unpin",
)

View File

@ -1,8 +1,9 @@
from datetime import UTC, datetime
from typing import Any
from flask import request
from flask_login import current_user
from flask_restful import Resource, inputs, marshal_with, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, inputs, marshal_with, reqparse # type: ignore
from sqlalchemy import and_
from werkzeug.exceptions import BadRequest, Forbidden, NotFound
@ -34,7 +35,7 @@ class InstalledAppsListApi(Resource):
installed_apps = db.session.query(InstalledApp).filter(InstalledApp.tenant_id == current_tenant_id).all()
current_user.role = TenantService.get_user_role(current_user, current_user.current_tenant)
installed_apps = [
installed_app_list: list[dict[str, Any]] = [
{
"id": installed_app.id,
"app": installed_app.app,
@ -47,7 +48,7 @@ class InstalledAppsListApi(Resource):
for installed_app in installed_apps
if installed_app.app is not None
]
installed_apps.sort(
installed_app_list.sort(
key=lambda app: (
-app["is_pinned"],
app["last_used_at"] is None,
@ -55,7 +56,7 @@ class InstalledAppsListApi(Resource):
)
)
return {"installed_apps": installed_apps}
return {"installed_apps": installed_app_list}
@login_required
@account_initialization_required

View File

@ -1,12 +1,11 @@
import logging
from flask_login import current_user
from flask_restful import marshal_with, reqparse
from flask_restful.inputs import int_range
from flask_login import current_user # type: ignore
from flask_restful import marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from werkzeug.exceptions import InternalServerError, NotFound
import services
from controllers.console import api
from controllers.console.app.error import (
AppMoreLikeThisDisabledError,
CompletionRequestError,
@ -70,7 +69,7 @@ class MessageFeedbackApi(InstalledAppResource):
args = parser.parse_args()
try:
MessageService.create_feedback(app_model, message_id, current_user, args["rating"])
MessageService.create_feedback(app_model, message_id, current_user, args["rating"], args["content"])
except services.errors.message.MessageNotExistsError:
raise NotFound("Message Not Exists.")
@ -153,21 +152,3 @@ class MessageSuggestedQuestionApi(InstalledAppResource):
raise InternalServerError()
return {"data": questions}
api.add_resource(MessageListApi, "/installed-apps/<uuid:installed_app_id>/messages", endpoint="installed_app_messages")
api.add_resource(
MessageFeedbackApi,
"/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/feedbacks",
endpoint="installed_app_message_feedback",
)
api.add_resource(
MessageMoreLikeThisApi,
"/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/more-like-this",
endpoint="installed_app_more_like_this",
)
api.add_resource(
MessageSuggestedQuestionApi,
"/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/suggested-questions",
endpoint="installed_app_suggested_question",
)

View File

@ -1,4 +1,4 @@
from flask_restful import marshal_with
from flask_restful import marshal_with # type: ignore
from controllers.common import fields
from controllers.common import helpers as controller_helpers

View File

@ -1,9 +1,10 @@
from flask_login import current_user
from flask_restful import Resource, fields, marshal_with, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore
from constants.languages import languages
from controllers.console import api
from controllers.console.wraps import account_initialization_required
from libs.helper import AppIconUrlField
from libs.login import login_required
from services.recommended_app_service import RecommendedAppService
@ -12,6 +13,8 @@ app_fields = {
"name": fields.String,
"mode": fields.String,
"icon": fields.String,
"icon_type": fields.String,
"icon_url": AppIconUrlField,
"icon_background": fields.String,
}

View File

@ -1,6 +1,6 @@
from flask_login import current_user
from flask_restful import fields, marshal_with, reqparse
from flask_restful.inputs import int_range
from flask_login import current_user # type: ignore
from flask_restful import fields, marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from werkzeug.exceptions import NotFound
from controllers.console import api

View File

@ -1,9 +1,8 @@
import logging
from flask_restful import reqparse
from flask_restful import reqparse # type: ignore
from werkzeug.exceptions import InternalServerError
from controllers.console import api
from controllers.console.app.error import (
CompletionRequestError,
ProviderModelCurrentlyNotSupportError,
@ -73,9 +72,3 @@ class InstalledAppWorkflowTaskStopApi(InstalledAppResource):
AppQueueManager.set_stop_flag(task_id, InvokeFrom.EXPLORE, current_user.id)
return {"result": "success"}
api.add_resource(InstalledAppWorkflowRunApi, "/installed-apps/<uuid:installed_app_id>/workflows/run")
api.add_resource(
InstalledAppWorkflowTaskStopApi, "/installed-apps/<uuid:installed_app_id>/workflows/tasks/<string:task_id>/stop"
)

View File

@ -1,7 +1,7 @@
from functools import wraps
from flask_login import current_user
from flask_restful import Resource
from flask_login import current_user # type: ignore
from flask_restful import Resource # type: ignore
from werkzeug.exceptions import NotFound
from controllers.console.wraps import account_initialization_required

View File

@ -1,5 +1,5 @@
from flask_login import current_user
from flask_restful import Resource, marshal_with, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, marshal_with, reqparse # type: ignore
from constants import HIDDEN_VALUE
from controllers.console import api

View File

@ -1,5 +1,5 @@
from flask_login import current_user
from flask_restful import Resource
from flask_login import current_user # type: ignore
from flask_restful import Resource # type: ignore
from libs.login import login_required
from services.feature_service import FeatureService

View File

@ -1,6 +1,9 @@
from typing import Literal
from flask import request
from flask_login import current_user
from flask_restful import Resource, marshal_with
from flask_login import current_user # type: ignore
from flask_restful import Resource, marshal_with # type: ignore
from werkzeug.exceptions import Forbidden
import services
from configs import dify_config
@ -47,7 +50,8 @@ class FileApi(Resource):
@cloud_edition_billing_resource_check("documents")
def post(self):
file = request.files["file"]
source = request.form.get("source")
source_str = request.form.get("source")
source: Literal["datasets"] | None = "datasets" if source_str == "datasets" else None
if "file" not in request.files:
raise NoFileUploadedError()
@ -58,6 +62,9 @@ class FileApi(Resource):
if not file.filename:
raise FilenameNotExistsError
if source == "datasets" and not current_user.is_dataset_editor:
raise Forbidden()
if source not in ("datasets", None):
source = None

View File

@ -1,4 +1,4 @@
from flask_restful import Resource
from flask_restful import Resource # type: ignore
from controllers.console import api

View File

@ -2,11 +2,12 @@ import urllib.parse
from typing import cast
import httpx
from flask_login import current_user
from flask_restful import Resource, marshal_with, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, marshal_with, reqparse # type: ignore
import services
from controllers.common import helpers
from controllers.common.errors import RemoteFileUploadError
from core.file import helpers as file_helpers
from core.helper import ssrf_proxy
from fields.file_fields import file_fields_with_signed_url, remote_file_info_fields
@ -43,10 +44,14 @@ class RemoteFileUploadApi(Resource):
url = args["url"]
try:
resp = ssrf_proxy.head(url=url)
if resp.status_code != httpx.codes.OK:
resp = ssrf_proxy.get(url=url, timeout=3, follow_redirects=True)
resp.raise_for_status()
if resp.status_code != httpx.codes.OK:
raise RemoteFileUploadError(f"Failed to fetch file from {url}: {resp.text}")
except httpx.RequestError as e:
raise RemoteFileUploadError(f"Failed to fetch file from {url}: {str(e)}")
file_info = helpers.guess_file_info_from_response(resp)

View File

@ -1,5 +1,5 @@
from flask import request
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
from configs import dify_config
from libs.helper import StrLen, email, extract_remote_ip

View File

@ -1,6 +1,6 @@
from flask import request
from flask_login import current_user
from flask_restful import Resource, marshal_with, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, marshal_with, reqparse # type: ignore
from werkzeug.exceptions import Forbidden
from controllers.console import api
@ -23,7 +23,7 @@ class TagListApi(Resource):
@account_initialization_required
@marshal_with(tag_fields)
def get(self):
tag_type = request.args.get("type", type=str)
tag_type = request.args.get("type", type=str, default="")
keyword = request.args.get("keyword", default=None, type=str)
tags = TagService.get_tags(tag_type, current_user.current_tenant_id, keyword)

View File

@ -2,7 +2,7 @@ import json
import logging
import requests
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
from packaging import version
from configs import dify_config

View File

@ -2,8 +2,8 @@ import datetime
import pytz
from flask import request
from flask_login import current_user
from flask_restful import Resource, fields, marshal_with, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore
from configs import dify_config
from constants.languages import supported_language

View File

@ -1,4 +1,4 @@
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
from werkzeug.exceptions import Forbidden
from controllers.console import api
@ -37,7 +37,7 @@ class LoadBalancingCredentialsValidateApi(Resource):
model_load_balancing_service = ModelLoadBalancingService()
result = True
error = None
error = ""
try:
model_load_balancing_service.validate_load_balancing_credentials(
@ -86,7 +86,7 @@ class LoadBalancingConfigCredentialsValidateApi(Resource):
model_load_balancing_service = ModelLoadBalancingService()
result = True
error = None
error = ""
try:
model_load_balancing_service.validate_load_balancing_credentials(

View File

@ -1,7 +1,7 @@
from urllib import parse
from flask_login import current_user
from flask_restful import Resource, abort, marshal_with, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, abort, marshal_with, reqparse # type: ignore
import services
from configs import dify_config
@ -89,9 +89,9 @@ class MemberCancelInviteApi(Resource):
@account_initialization_required
def delete(self, member_id):
member = db.session.query(Account).filter(Account.id == str(member_id)).first()
if not member:
if member is None:
abort(404)
else:
try:
TenantService.remove_member_from_tenant(current_user.current_tenant, member, current_user)
except services.errors.account.CannotOperateSelfError as e:
@ -122,10 +122,11 @@ class MemberUpdateRoleApi(Resource):
return {"code": "invalid-role", "message": "Invalid role"}, 400
member = db.session.get(Account, str(member_id))
if not member:
if member:
abort(404)
try:
assert member is not None, "Member not found"
TenantService.update_member_role(current_user.current_tenant, member, new_role, current_user)
except Exception as e:
raise ValueError(str(e))

View File

@ -1,8 +1,8 @@
import io
from flask import send_file
from flask_login import current_user
from flask_restful import Resource, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, reqparse # type: ignore
from werkzeug.exceptions import Forbidden
from controllers.console import api
@ -66,7 +66,7 @@ class ModelProviderValidateApi(Resource):
model_provider_service = ModelProviderService()
result = True
error = None
error = ""
try:
model_provider_service.provider_credentials_validate(
@ -133,10 +133,8 @@ class ModelProviderIconApi(Resource):
icon_type=icon_type,
lang=lang,
)
if not icon:
return {"message": "Icon not found"}, 404
if icon is None:
raise ValueError(f"icon not found for provider {provider}, icon_type {icon_type}, lang {lang}")
return send_file(io.BytesIO(icon), mimetype=mimetype)

View File

@ -1,7 +1,7 @@
import logging
from flask_login import current_user
from flask_restful import Resource, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, reqparse # type: ignore
from werkzeug.exceptions import Forbidden
from controllers.console import api
@ -308,7 +308,7 @@ class ModelProviderModelValidateApi(Resource):
model_provider_service = ModelProviderService()
result = True
error = None
error = ""
try:
model_provider_service.model_credentials_validate(

View File

@ -1,14 +1,16 @@
import io
from flask import send_file
from flask_login import current_user
from flask_restful import Resource, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, reqparse # type: ignore
from sqlalchemy.orm import Session
from werkzeug.exceptions import Forbidden
from configs import dify_config
from controllers.console import api
from controllers.console.wraps import account_initialization_required, enterprise_license_required, setup_required
from core.model_runtime.utils.encoders import jsonable_encoder
from extensions.ext_database import db
from libs.helper import alphanumeric, uuid_value
from libs.login import login_required
from services.tools.api_tools_manage_service import ApiToolManageService
@ -112,12 +114,16 @@ class ToolBuiltinProviderUpdateApi(Resource):
args = parser.parse_args()
return BuiltinToolManageService.update_builtin_tool_provider(
user_id,
tenant_id,
provider,
args["credentials"],
with Session(db.engine) as session:
result = BuiltinToolManageService.update_builtin_tool_provider(
session=session,
user_id=user_id,
tenant_id=tenant_id,
provider_name=provider,
credentials=args["credentials"],
)
session.commit()
return result
class ToolBuiltinProviderGetCredentialsApi(Resource):
@ -125,15 +131,11 @@ class ToolBuiltinProviderGetCredentialsApi(Resource):
@login_required
@account_initialization_required
def get(self, provider):
user = current_user
user_id = user.id
tenant_id = user.current_tenant_id
tenant_id = current_user.current_tenant_id
return BuiltinToolManageService.get_builtin_tool_provider_credentials(
user_id,
tenant_id,
provider,
tenant_id=tenant_id,
provider_name=provider,
)
@ -329,7 +331,6 @@ class ToolBuiltinProviderCredentialsSchemaApi(Resource):
def get(self, provider):
user = current_user
user_id = user.id
tenant_id = user.current_tenant_id
return BuiltinToolManageService.list_builtin_provider_credentials_schema(provider, tenant_id)

View File

@ -1,8 +1,8 @@
import logging
from flask import request
from flask_login import current_user
from flask_restful import Resource, fields, inputs, marshal, marshal_with, reqparse
from flask_login import current_user # type: ignore
from flask_restful import Resource, fields, inputs, marshal, marshal_with, reqparse # type: ignore
from werkzeug.exceptions import Unauthorized
import services
@ -82,11 +82,7 @@ class WorkspaceListApi(Resource):
parser.add_argument("limit", type=inputs.int_range(1, 100), required=False, default=20, location="args")
args = parser.parse_args()
tenants = (
db.session.query(Tenant)
.order_by(Tenant.created_at.desc())
.paginate(page=args["page"], per_page=args["limit"])
)
tenants = Tenant.query.order_by(Tenant.created_at.desc()).paginate(page=args["page"], per_page=args["limit"])
has_more = False
if len(tenants.items) == args["limit"]:
@ -151,6 +147,8 @@ class SwitchWorkspaceApi(Resource):
raise AccountNotLinkTenantError("Account not link tenant")
new_tenant = db.session.query(Tenant).get(args["tenant_id"]) # Get new tenant
if new_tenant is None:
raise ValueError("Tenant not found")
return {"result": "success", "new_tenant": marshal(WorkspaceService.get_tenant_info(new_tenant), tenant_fields)}
@ -166,7 +164,7 @@ class CustomConfigWorkspaceApi(Resource):
parser.add_argument("replace_webapp_logo", type=str, location="json")
args = parser.parse_args()
tenant = db.session.query(Tenant).filter(Tenant.id == current_user.current_tenant_id).one_or_404()
tenant = Tenant.query.filter(Tenant.id == current_user.current_tenant_id).one_or_404()
custom_config_dict = {
"remove_webapp_brand": args["remove_webapp_brand"],

View File

@ -3,7 +3,7 @@ import os
from functools import wraps
from flask import abort, request
from flask_login import current_user
from flask_login import current_user # type: ignore
from configs import dify_config
from controllers.console.workspace.error import AccountNotInitializedError
@ -122,8 +122,8 @@ def cloud_utm_record(view):
utm_info = request.cookies.get("utm_info")
if utm_info:
utm_info = json.loads(utm_info)
OperationService.record_utm(current_user.current_tenant_id, utm_info)
utm_info_dict: dict = json.loads(utm_info)
OperationService.record_utm(current_user.current_tenant_id, utm_info_dict)
except Exception as e:
pass
return view(*args, **kwargs)

View File

@ -1,5 +1,5 @@
from flask import Response, request
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
from werkzeug.exceptions import NotFound
import services

View File

@ -1,5 +1,5 @@
from flask import Response
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
from werkzeug.exceptions import Forbidden, NotFound
from controllers.files import api

View File

@ -1,4 +1,4 @@
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
from controllers.console.wraps import setup_required
from controllers.inner_api import api

View File

@ -45,16 +45,14 @@ def enterprise_inner_api_user_auth(view):
if " " in user_id:
user_id = user_id.split(" ")[1]
inner_api_key = request.headers.get("X-Inner-Api-Key")
if not inner_api_key:
raise ValueError("inner api key not found")
inner_api_key = request.headers.get("X-Inner-Api-Key", "")
data_to_sign = f"DIFY {user_id}"
signature = hmac_new(inner_api_key.encode("utf-8"), data_to_sign.encode("utf-8"), sha1)
signature = b64encode(signature.digest()).decode("utf-8")
signature_base64 = b64encode(signature.digest()).decode("utf-8")
if signature != token:
if signature_base64 != token:
return view(*args, **kwargs)
kwargs["user"] = db.session.query(EndUser).filter(EndUser.id == user_id).first()

View File

@ -1,4 +1,4 @@
from flask_restful import Resource, marshal_with
from flask_restful import Resource, marshal_with # type: ignore
from controllers.common import fields
from controllers.common import helpers as controller_helpers

View File

@ -1,7 +1,7 @@
import logging
from flask import request
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
from werkzeug.exceptions import InternalServerError
import services
@ -83,7 +83,7 @@ class TextApi(Resource):
and app_model.workflow
and app_model.workflow.features_dict
):
text_to_speech = app_model.workflow.features_dict.get("text_to_speech")
text_to_speech = app_model.workflow.features_dict.get("text_to_speech", {})
voice = args.get("voice") or text_to_speech.get("voice")
else:
try:

View File

@ -1,6 +1,6 @@
import logging
from flask_restful import Resource, reqparse
from flask_restful import Resource, reqparse # type: ignore
from werkzeug.exceptions import InternalServerError, NotFound
import services

View File

@ -1,5 +1,6 @@
from flask_restful import Resource, marshal_with, reqparse
from flask_restful.inputs import int_range
from flask_restful import Resource, marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from sqlalchemy.orm import Session
from werkzeug.exceptions import NotFound
import services
@ -7,6 +8,7 @@ from controllers.service_api import api
from controllers.service_api.app.error import NotChatAppError
from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from core.app.entities.app_invoke_entities import InvokeFrom
from extensions.ext_database import db
from fields.conversation_fields import (
conversation_delete_fields,
conversation_infinite_scroll_pagination_fields,
@ -39,7 +41,9 @@ class ConversationApi(Resource):
args = parser.parse_args()
try:
with Session(db.engine) as session:
return ConversationService.pagination_by_last_id(
session=session,
app_model=app_model,
user=end_user,
last_id=args["last_id"],

View File

@ -1,5 +1,5 @@
from flask import request
from flask_restful import Resource, marshal_with
from flask_restful import Resource, marshal_with # type: ignore
import services
from controllers.common.errors import FilenameNotExistsError

View File

@ -1,7 +1,7 @@
import logging
from flask_restful import Resource, fields, marshal_with, reqparse
from flask_restful.inputs import int_range
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from werkzeug.exceptions import BadRequest, InternalServerError, NotFound
import services
@ -104,10 +104,11 @@ class MessageFeedbackApi(Resource):
parser = reqparse.RequestParser()
parser.add_argument("rating", type=str, choices=["like", "dislike", None], location="json")
parser.add_argument("content", type=str, location="json")
args = parser.parse_args()
try:
MessageService.create_feedback(app_model, message_id, end_user, args["rating"])
MessageService.create_feedback(app_model, message_id, end_user, args["rating"], args["content"])
except services.errors.message.MessageNotExistsError:
raise NotFound("Message Not Exists.")

View File

@ -1,7 +1,7 @@
import logging
from flask_restful import Resource, fields, marshal_with, reqparse
from flask_restful.inputs import int_range
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from werkzeug.exceptions import InternalServerError
from controllers.service_api import api

View File

@ -1,5 +1,5 @@
from flask import request
from flask_restful import marshal, reqparse
from flask_restful import marshal, reqparse # type: ignore
from werkzeug.exceptions import NotFound
import services.dataset_service

View File

@ -1,7 +1,7 @@
import json
from flask import request
from flask_restful import marshal, reqparse
from flask_restful import marshal, reqparse # type: ignore
from sqlalchemy import desc
from werkzeug.exceptions import NotFound

View File

@ -1,5 +1,5 @@
from flask_login import current_user
from flask_restful import marshal, reqparse
from flask_login import current_user # type: ignore
from flask_restful import marshal, reqparse # type: ignore
from werkzeug.exceptions import NotFound
from controllers.service_api import api

View File

@ -1,4 +1,4 @@
from flask_restful import Resource
from flask_restful import Resource # type: ignore
from configs import dify_config
from controllers.service_api import api

View File

@ -5,8 +5,8 @@ from functools import wraps
from typing import Optional
from flask import current_app, request
from flask_login import user_logged_in
from flask_restful import Resource
from flask_login import user_logged_in # type: ignore
from flask_restful import Resource # type: ignore
from pydantic import BaseModel
from werkzeug.exceptions import Forbidden, Unauthorized
@ -49,6 +49,8 @@ def validate_app_token(view: Optional[Callable] = None, *, fetch_user_arg: Optio
raise Forbidden("The app's API service has been disabled.")
tenant = db.session.query(Tenant).filter(Tenant.id == app_model.tenant_id).first()
if tenant is None:
raise ValueError("Tenant does not exist.")
if tenant.status == TenantStatus.ARCHIVE:
raise Forbidden("The workspace's status is archived.")
@ -154,8 +156,8 @@ def validate_dataset_token(view=None):
# Login admin
if account:
account.current_tenant = tenant
current_app.login_manager._update_request_context_with_user(account)
user_logged_in.send(current_app._get_current_object(), user=_get_user())
current_app.login_manager._update_request_context_with_user(account) # type: ignore
user_logged_in.send(current_app._get_current_object(), user=_get_user()) # type: ignore
else:
raise Unauthorized("Tenant owner account does not exist.")
else:

View File

@ -1,4 +1,4 @@
from flask_restful import marshal_with
from flask_restful import marshal_with # type: ignore
from controllers.common import fields
from controllers.common import helpers as controller_helpers

View File

@ -65,7 +65,7 @@ class AudioApi(WebApiResource):
class TextApi(WebApiResource):
def post(self, app_model: App, end_user):
from flask_restful import reqparse
from flask_restful import reqparse # type: ignore
try:
parser = reqparse.RequestParser()
@ -82,7 +82,7 @@ class TextApi(WebApiResource):
and app_model.workflow
and app_model.workflow.features_dict
):
text_to_speech = app_model.workflow.features_dict.get("text_to_speech")
text_to_speech = app_model.workflow.features_dict.get("text_to_speech", {})
voice = args.get("voice") or text_to_speech.get("voice")
else:
try:

View File

@ -1,6 +1,6 @@
import logging
from flask_restful import reqparse
from flask_restful import reqparse # type: ignore
from werkzeug.exceptions import InternalServerError, NotFound
import services

View File

@ -1,11 +1,13 @@
from flask_restful import marshal_with, reqparse
from flask_restful.inputs import int_range
from flask_restful import marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from sqlalchemy.orm import Session
from werkzeug.exceptions import NotFound
from controllers.web import api
from controllers.web.error import NotChatAppError
from controllers.web.wraps import WebApiResource
from core.app.entities.app_invoke_entities import InvokeFrom
from extensions.ext_database import db
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
from libs.helper import uuid_value
from models.model import AppMode
@ -40,7 +42,9 @@ class ConversationListApi(WebApiResource):
pinned = True if args["pinned"] == "true" else False
try:
with Session(db.engine) as session:
return WebConversationService.pagination_by_last_id(
session=session,
app_model=app_model,
user=end_user,
last_id=args["last_id"],

View File

@ -1,4 +1,4 @@
from flask_restful import Resource
from flask_restful import Resource # type: ignore
from controllers.web import api
from services.feature_service import FeatureService

View File

@ -1,5 +1,5 @@
from flask import request
from flask_restful import marshal_with
from flask_restful import marshal_with # type: ignore
import services
from controllers.common.errors import FilenameNotExistsError
@ -33,7 +33,7 @@ class FileApi(WebApiResource):
content=file.read(),
mimetype=file.mimetype,
user=end_user,
source=source,
source="datasets" if source == "datasets" else None,
)
except services.errors.file.FileTooLargeError as file_too_large_error:
raise FileTooLargeError(file_too_large_error.description)

Some files were not shown because too many files have changed in this diff Show More