Parcourir la source

feat: mypy for all type check (#10921)

yihong il y a 4 mois
Parent
commit
56e15d09a9
100 fichiers modifiés avec 346 ajouts et 316 suppressions
  1. 6 0
      .github/workflows/api-tests.yml
  2. 8 5
      api/commands.py
  3. 6 8
      api/configs/feature/__init__.py
  4. 0 4
      api/configs/middleware/__init__.py
  5. 3 2
      api/configs/remote_settings_sources/apollo/client.py
  6. 2 1
      api/constants/model_template.py
  7. 1 1
      api/controllers/common/fields.py
  8. 90 5
      api/controllers/console/__init__.py
  9. 1 1
      api/controllers/console/admin.py
  10. 14 9
      api/controllers/console/apikey.py
  11. 1 1
      api/controllers/console/app/advanced_prompt_template.py
  12. 1 1
      api/controllers/console/app/agent.py
  13. 3 3
      api/controllers/console/app/annotation.py
  14. 2 2
      api/controllers/console/app/app.py
  15. 2 2
      api/controllers/console/app/app_import.py
  16. 1 1
      api/controllers/console/app/audio.py
  17. 2 2
      api/controllers/console/app/completion.py
  18. 8 7
      api/controllers/console/app/conversation.py
  19. 1 1
      api/controllers/console/app/conversation_variables.py
  20. 2 2
      api/controllers/console/app/generator.py
  21. 3 3
      api/controllers/console/app/message.py
  22. 9 4
      api/controllers/console/app/model_config.py
  23. 1 1
      api/controllers/console/app/ops_trace.py
  24. 3 3
      api/controllers/console/app/site.py
  25. 2 2
      api/controllers/console/app/statistic.py
  26. 1 1
      api/controllers/console/app/workflow.py
  27. 2 2
      api/controllers/console/app/workflow_app_log.py
  28. 2 2
      api/controllers/console/app/workflow_run.py
  29. 2 2
      api/controllers/console/app/workflow_statistic.py
  30. 1 1
      api/controllers/console/app/wraps.py
  31. 3 3
      api/controllers/console/auth/activate.py
  32. 2 2
      api/controllers/console/auth/data_source_bearer_auth.py
  33. 4 4
      api/controllers/console/auth/data_source_oauth.py
  34. 3 3
      api/controllers/console/auth/forgot_password.py
  35. 2 2
      api/controllers/console/auth/login.py
  36. 4 3
      api/controllers/console/auth/oauth.py
  37. 2 2
      api/controllers/console/billing/billing.py
  38. 2 2
      api/controllers/console/datasets/data_source.py
  39. 3 3
      api/controllers/console/datasets/datasets.py
  40. 5 5
      api/controllers/console/datasets/datasets_document.py
  41. 2 2
      api/controllers/console/datasets/datasets_segments.py
  42. 2 2
      api/controllers/console/datasets/external.py
  43. 1 1
      api/controllers/console/datasets/hit_testing.py
  44. 2 2
      api/controllers/console/datasets/hit_testing_base.py
  45. 1 1
      api/controllers/console/datasets/website.py
  46. 1 8
      api/controllers/console/explore/audio.py
  47. 2 21
      api/controllers/console/explore/completion.py
  48. 3 29
      api/controllers/console/explore/conversation.py
  49. 6 5
      api/controllers/console/explore/installed_app.py
  50. 3 22
      api/controllers/console/explore/message.py
  51. 1 1
      api/controllers/console/explore/parameter.py
  52. 2 2
      api/controllers/console/explore/recommended_app.py
  53. 3 3
      api/controllers/console/explore/saved_message.py
  54. 1 8
      api/controllers/console/explore/workflow.py
  55. 2 2
      api/controllers/console/explore/wraps.py
  56. 2 2
      api/controllers/console/extension.py
  57. 2 2
      api/controllers/console/feature.py
  58. 6 3
      api/controllers/console/files.py
  59. 1 1
      api/controllers/console/init_validate.py
  60. 1 1
      api/controllers/console/ping.py
  61. 2 2
      api/controllers/console/remote_files.py
  62. 1 1
      api/controllers/console/setup.py
  63. 3 3
      api/controllers/console/tag/tags.py
  64. 1 1
      api/controllers/console/version.py
  65. 2 2
      api/controllers/console/workspace/account.py
  66. 3 3
      api/controllers/console/workspace/load_balancing_config.py
  67. 16 15
      api/controllers/console/workspace/members.py
  68. 5 4
      api/controllers/console/workspace/model_providers.py
  69. 3 3
      api/controllers/console/workspace/models.py
  70. 2 2
      api/controllers/console/workspace/tool_providers.py
  71. 6 8
      api/controllers/console/workspace/workspace.py
  72. 3 3
      api/controllers/console/wraps.py
  73. 1 1
      api/controllers/files/image_preview.py
  74. 1 1
      api/controllers/files/tool_files.py
  75. 1 1
      api/controllers/inner_api/workspace/workspace.py
  76. 3 3
      api/controllers/inner_api/wraps.py
  77. 1 1
      api/controllers/service_api/app/app.py
  78. 2 2
      api/controllers/service_api/app/audio.py
  79. 1 1
      api/controllers/service_api/app/completion.py
  80. 2 2
      api/controllers/service_api/app/conversation.py
  81. 1 1
      api/controllers/service_api/app/file.py
  82. 2 2
      api/controllers/service_api/app/message.py
  83. 2 2
      api/controllers/service_api/app/workflow.py
  84. 1 1
      api/controllers/service_api/dataset/dataset.py
  85. 1 1
      api/controllers/service_api/dataset/document.py
  86. 2 2
      api/controllers/service_api/dataset/segment.py
  87. 1 1
      api/controllers/service_api/index.py
  88. 6 4
      api/controllers/service_api/wraps.py
  89. 1 1
      api/controllers/web/app.py
  90. 2 2
      api/controllers/web/audio.py
  91. 1 1
      api/controllers/web/completion.py
  92. 2 2
      api/controllers/web/conversation.py
  93. 1 1
      api/controllers/web/feature.py
  94. 2 2
      api/controllers/web/files.py
  95. 2 2
      api/controllers/web/message.py
  96. 1 1
      api/controllers/web/passport.py
  97. 1 1
      api/controllers/web/remote_files.py
  98. 2 2
      api/controllers/web/saved_message.py
  99. 1 1
      api/controllers/web/site.py
  100. 1 1
      api/controllers/web/workflow.py

+ 6 - 0
.github/workflows/api-tests.yml

@@ -56,6 +56,12 @@ jobs:
       - 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

+ 8 - 5
api/commands.py

@@ -159,8 +159,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)
             )
@@ -285,8 +284,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)
             )
@@ -450,7 +448,8 @@ 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()
-                    apps.append(app)
+                    if app is not None:
+                        apps.append(app)
 
             if len(apps) == 0:
                 break
@@ -621,6 +620,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()

+ 6 - 8
api/configs/feature/__init__.py

@@ -239,7 +239,6 @@ class HttpConfig(BaseSettings):
     )
 
     @computed_field
-    @property
     def CONSOLE_CORS_ALLOW_ORIGINS(self) -> list[str]:
         return self.inner_CONSOLE_CORS_ALLOW_ORIGINS.split(",")
 
@@ -250,7 +249,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(",")
 
@@ -715,27 +713,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() != ""}
 

+ 0 - 4
api/configs/middleware/__init__.py

@@ -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

+ 3 - 2
api/configs/remote_settings_sources/apollo/client.py

@@ -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)]

+ 2 - 1
api/constants/model_template.py

@@ -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": {

+ 1 - 1
api/controllers/common/fields.py

@@ -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,

+ 90 - 5
api/controllers/console/__init__.py

@@ -3,6 +3,25 @@ from flask import Blueprint
 from libs.external_api import ExternalApi
 
 from .app.app_import import AppImportApi, 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
 
@@ -66,15 +85,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

+ 1 - 1
api/controllers/console/admin.py

@@ -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 werkzeug.exceptions import NotFound, Unauthorized
 
 from configs import dify_config

+ 14 - 9
api/controllers/console/apikey.py

@@ -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 werkzeug.exceptions import Forbidden
 
@@ -35,14 +37,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 = (
@@ -54,6 +57,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:
@@ -86,11 +90,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)

+ 1 - 1
api/controllers/console/app/advanced_prompt_template.py

@@ -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

+ 1 - 1
api/controllers/console/app/agent.py

@@ -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

+ 3 - 3
api/controllers/console/app/annotation.py

@@ -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)

+ 2 - 2
api/controllers/console/app/app.py

@@ -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

+ 2 - 2
api/controllers/console/app/app_import.py

@@ -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
 

+ 1 - 1
api/controllers/console/app/audio.py

@@ -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

+ 2 - 2
api/controllers/console/app/completion.py

@@ -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

+ 8 - 7
api/controllers/console/app/conversation.py

@@ -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"])

+ 1 - 1
api/controllers/console/app/conversation_variables.py

@@ -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
 

+ 2 - 2
api/controllers/console/app/generator.py

@@ -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 (

+ 3 - 3
api/controllers/console/app/message.py

@@ -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

+ 9 - 4
api/controllers/console/app/model_config.py

@@ -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 = {}

+ 1 - 1
api/controllers/console/app/ops_trace.py

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

+ 3 - 3
api/controllers/console/app/site.py

@@ -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",

+ 2 - 2
api/controllers/console/app/statistic.py

@@ -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

+ 1 - 1
api/controllers/console/app/workflow.py

@@ -2,7 +2,7 @@ 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

+ 2 - 2
api/controllers/console/app/workflow_app_log.py

@@ -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

+ 2 - 2
api/controllers/console/app/workflow_run.py

@@ -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

+ 2 - 2
api/controllers/console/app/workflow_statistic.py

@@ -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

+ 1 - 1
api/controllers/console/app/wraps.py

@@ -8,7 +8,7 @@ from libs.login import current_user
 from models import App, AppMode
 
 
-def get_app_model(view: Optional[Callable] = None, *, mode: Union[AppMode, list[AppMode]] = None):
+def get_app_model(view: Optional[Callable] = None, *, mode: Union[AppMode, list[AppMode], None] = None):
     def decorator(view_func):
         @wraps(view_func)
         def decorated_view(*args, **kwargs):

+ 3 - 3
api/controllers/console/auth/activate.py

@@ -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

+ 2 - 2
api/controllers/console/auth/data_source_bearer_auth.py

@@ -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

+ 4 - 4
api/controllers/console/auth/data_source_oauth.py

@@ -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",
         )
 

+ 3 - 3
api/controllers/console/auth/forgot_password.py

@@ -2,7 +2,7 @@ import base64
 import secrets
 
 from flask import request
-from flask_restful import Resource, reqparse
+from flask_restful import Resource, reqparse  # type: ignore
 
 from constants.languages import languages
 from controllers.console import api
@@ -122,8 +122,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],
                 )

+ 2 - 2
api/controllers/console/auth/login.py

@@ -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

+ 4 - 3
api/controllers/console/auth/oauth.py

@@ -4,7 +4,7 @@ from typing import Optional
 
 import requests
 from flask import current_app, redirect, request
-from flask_restful import Resource
+from flask_restful import Resource  # type: ignore
 from werkzeug.exceptions import Unauthorized
 
 from configs import dify_config
@@ -77,7 +77,8 @@ class OAuthCallback(Resource):
             token = oauth_provider.get_access_token(code)
             user_info = oauth_provider.get_user_info(token)
         except requests.exceptions.RequestException as e:
-            logging.exception(f"An error occurred during the OAuth process with {provider}: {e.response.text}")
+            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):
@@ -129,7 +130,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:
         account = Account.query.filter_by(email=user_info.email).first()

+ 2 - 2
api/controllers/console/billing/billing.py

@@ -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

+ 2 - 2
api/controllers/console/datasets/data_source.py

@@ -2,8 +2,8 @@ import datetime
 import json
 
 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 NotFound
 
 from controllers.console import api

+ 3 - 3
api/controllers/console/datasets/datasets.py

@@ -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

+ 5 - 5
api/controllers/console/datasets/datasets_document.py

@@ -1,12 +1,13 @@
 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 transformers.hf_argparser import string_to_bool
+from transformers.hf_argparser import string_to_bool  # type: ignore
 from werkzeug.exceptions import Forbidden, NotFound
 
 import services
@@ -733,8 +734,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":

+ 2 - 2
api/controllers/console/datasets/datasets_segments.py

@@ -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

+ 2 - 2
api/controllers/console/datasets/external.py

@@ -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

+ 1 - 1
api/controllers/console/datasets/hit_testing.py

@@ -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

+ 2 - 2
api/controllers/console/datasets/hit_testing_base.py

@@ -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

+ 1 - 1
api/controllers/console/datasets/website.py

@@ -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

+ 1 - 8
api/controllers/console/explore/audio.py

@@ -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')

+ 2 - 21
api/controllers/console/explore/completion.py

@@ -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",
-)

+ 3 - 29
api/controllers/console/explore/conversation.py

@@ -1,10 +1,9 @@
-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
@@ -118,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",
-)

+ 6 - 5
api/controllers/console/explore/installed_app.py

@@ -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

+ 3 - 22
api/controllers/console/explore/message.py

@@ -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,
@@ -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",
-)

+ 1 - 1
api/controllers/console/explore/parameter.py

@@ -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

+ 2 - 2
api/controllers/console/explore/recommended_app.py

@@ -1,5 +1,5 @@
-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

+ 3 - 3
api/controllers/console/explore/saved_message.py

@@ -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

+ 1 - 8
api/controllers/console/explore/workflow.py

@@ -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"
-)

+ 2 - 2
api/controllers/console/explore/wraps.py

@@ -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

+ 2 - 2
api/controllers/console/extension.py

@@ -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

+ 2 - 2
api/controllers/console/feature.py

@@ -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

+ 6 - 3
api/controllers/console/files.py

@@ -1,6 +1,8 @@
+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
@@ -48,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()

+ 1 - 1
api/controllers/console/init_validate.py

@@ -1,7 +1,7 @@
 import os
 
 from flask import session
-from flask_restful import Resource, reqparse
+from flask_restful import Resource, reqparse  # type: ignore
 
 from configs import dify_config
 from libs.helper import StrLen

+ 1 - 1
api/controllers/console/ping.py

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

+ 2 - 2
api/controllers/console/remote_files.py

@@ -2,8 +2,8 @@ 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

+ 1 - 1
api/controllers/console/setup.py

@@ -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

+ 3 - 3
api/controllers/console/tag/tags.py

@@ -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)
 

+ 1 - 1
api/controllers/console/version.py

@@ -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

+ 2 - 2
api/controllers/console/workspace/account.py

@@ -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

+ 3 - 3
api/controllers/console/workspace/load_balancing_config.py

@@ -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(

+ 16 - 15
api/controllers/console/workspace/members.py

@@ -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,19 +89,19 @@ 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)
-
-        try:
-            TenantService.remove_member_from_tenant(current_user.current_tenant, member, current_user)
-        except services.errors.account.CannotOperateSelfError as e:
-            return {"code": "cannot-operate-self", "message": str(e)}, 400
-        except services.errors.account.NoPermissionError as e:
-            return {"code": "forbidden", "message": str(e)}, 403
-        except services.errors.account.MemberNotInTenantError as e:
-            return {"code": "member-not-found", "message": str(e)}, 404
-        except Exception as e:
-            raise ValueError(str(e))
+        else:
+            try:
+                TenantService.remove_member_from_tenant(current_user.current_tenant, member, current_user)
+            except services.errors.account.CannotOperateSelfError as e:
+                return {"code": "cannot-operate-self", "message": str(e)}, 400
+            except services.errors.account.NoPermissionError as e:
+                return {"code": "forbidden", "message": str(e)}, 403
+            except services.errors.account.MemberNotInTenantError as e:
+                return {"code": "member-not-found", "message": str(e)}, 404
+            except Exception as e:
+                raise ValueError(str(e))
 
         return {"result": "success"}, 204
 
@@ -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))

+ 5 - 4
api/controllers/console/workspace/model_providers.py

@@ -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(
@@ -132,7 +132,8 @@ class ModelProviderIconApi(Resource):
             icon_type=icon_type,
             lang=lang,
         )
-
+        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)
 
 

+ 3 - 3
api/controllers/console/workspace/models.py

@@ -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(

+ 2 - 2
api/controllers/console/workspace/tool_providers.py

@@ -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 sqlalchemy.orm import Session
 from werkzeug.exceptions import Forbidden
 

+ 6 - 8
api/controllers/console/workspace/workspace.py

@@ -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"],

+ 3 - 3
api/controllers/console/wraps.py

@@ -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
@@ -121,8 +121,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)

+ 1 - 1
api/controllers/files/image_preview.py

@@ -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

+ 1 - 1
api/controllers/files/tool_files.py

@@ -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

+ 1 - 1
api/controllers/inner_api/workspace/workspace.py

@@ -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

+ 3 - 3
api/controllers/inner_api/wraps.py

@@ -45,14 +45,14 @@ def 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")
+        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()

+ 1 - 1
api/controllers/service_api/app/app.py

@@ -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

+ 2 - 2
api/controllers/service_api/app/audio.py

@@ -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:

+ 1 - 1
api/controllers/service_api/app/completion.py

@@ -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

+ 2 - 2
api/controllers/service_api/app/conversation.py

@@ -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 sqlalchemy.orm import Session
 from werkzeug.exceptions import NotFound
 

+ 1 - 1
api/controllers/service_api/app/file.py

@@ -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

+ 2 - 2
api/controllers/service_api/app/message.py

@@ -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

+ 2 - 2
api/controllers/service_api/app/workflow.py

@@ -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

+ 1 - 1
api/controllers/service_api/dataset/dataset.py

@@ -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

+ 1 - 1
api/controllers/service_api/dataset/document.py

@@ -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
 

+ 2 - 2
api/controllers/service_api/dataset/segment.py

@@ -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

+ 1 - 1
api/controllers/service_api/index.py

@@ -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

+ 6 - 4
api/controllers/service_api/wraps.py

@@ -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:

+ 1 - 1
api/controllers/web/app.py

@@ -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

+ 2 - 2
api/controllers/web/audio.py

@@ -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:

+ 1 - 1
api/controllers/web/completion.py

@@ -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

+ 2 - 2
api/controllers/web/conversation.py

@@ -1,5 +1,5 @@
-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
 

+ 1 - 1
api/controllers/web/feature.py

@@ -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

+ 2 - 2
api/controllers/web/files.py

@@ -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)

+ 2 - 2
api/controllers/web/message.py

@@ -1,7 +1,7 @@
 import logging
 
-from flask_restful import fields, marshal_with, reqparse
-from flask_restful.inputs import int_range
+from flask_restful import fields, marshal_with, reqparse  # type: ignore
+from flask_restful.inputs import int_range  # type: ignore
 from werkzeug.exceptions import InternalServerError, NotFound
 
 import services

+ 1 - 1
api/controllers/web/passport.py

@@ -1,7 +1,7 @@
 import uuid
 
 from flask import request
-from flask_restful import Resource
+from flask_restful import Resource  # type: ignore
 from werkzeug.exceptions import NotFound, Unauthorized
 
 from controllers.web import api

+ 1 - 1
api/controllers/web/remote_files.py

@@ -1,7 +1,7 @@
 import urllib.parse
 
 import httpx
-from flask_restful import marshal_with, reqparse
+from flask_restful import marshal_with, reqparse  # type: ignore
 
 import services
 from controllers.common import helpers

+ 2 - 2
api/controllers/web/saved_message.py

@@ -1,5 +1,5 @@
-from flask_restful import fields, marshal_with, reqparse
-from flask_restful.inputs import int_range
+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.web import api

+ 1 - 1
api/controllers/web/site.py

@@ -1,4 +1,4 @@
-from flask_restful import fields, marshal_with
+from flask_restful import fields, marshal_with  # type: ignore
 from werkzeug.exceptions import Forbidden
 
 from configs import dify_config

+ 1 - 1
api/controllers/web/workflow.py

@@ -1,6 +1,6 @@
 import logging
 
-from flask_restful import reqparse
+from flask_restful import reqparse  # type: ignore
 from werkzeug.exceptions import InternalServerError
 
 from controllers.web import api

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff