diff --git a/Makefile b/Makefile index 3b5024683f..d65209c2ff 100644 --- a/Makefile +++ b/Makefile @@ -83,16 +83,15 @@ lint: @echo "✅ Linting complete" type-check: - @echo "📝 Running type checks (basedpyright + pyrefly + mypy)..." - @./dev/basedpyright-check $(PATH_TO_CHECK) - @./dev/pyrefly-check-local - @uv --directory api run mypy --exclude-gitignore --exclude 'tests/' --exclude 'migrations/' --exclude 'dev/generate_swagger_specs.py' --check-untyped-defs --disable-error-code=import-untyped . + @echo "📝 Running type checks (pyrefly + mypy)..." + @./dev/pyrefly-check-local $(PATH_TO_CHECK) + @uv --directory api run mypy --exclude-gitignore --exclude 'tests/' --exclude 'migrations/' --exclude 'dev/generate_swagger_specs.py' --exclude 'dev/generate_fastopenapi_specs.py' --check-untyped-defs --disable-error-code=import-untyped . @echo "✅ Type checks complete" type-check-core: - @echo "📝 Running core type checks (basedpyright + mypy)..." - @./dev/basedpyright-check $(PATH_TO_CHECK) - @uv --directory api run mypy --exclude-gitignore --exclude 'tests/' --exclude 'migrations/' --exclude 'dev/generate_swagger_specs.py' --exclude 'dev/generate_fastopenapi_specs.py' --check-untyped-defs --disable-error-code=import-untyped . + @echo "📝 Running core type checks (pyrefly + mypy)..." + @./dev/pyrefly-check-local $(PATH_TO_CHECK) + @uv --directory api run mypy --exclude-gitignore --exclude 'tests/' --exclude 'migrations/' --exclude 'dev/generate_swagger_specs.py' --exclude 'dev/generate_fastopenapi_specs.py' --check-untyped-defs --disable-error-code=import-untyped . @echo "✅ Core type checks complete" test: @@ -153,8 +152,8 @@ help: @echo " make format - Format code with ruff" @echo " make check - Check code with ruff" @echo " make lint - Format, fix, and lint code (ruff, imports, dotenv)" - @echo " make type-check - Run type checks (basedpyright, pyrefly, mypy)" - @echo " make type-check-core - Run core type checks (basedpyright, mypy)" + @echo " make type-check - Run type checks (pyrefly, mypy)" + @echo " make type-check-core - Run core type checks (pyrefly, mypy)" @echo " make test - Run backend unit tests (or TARGET_TESTS=./api/tests/)" @echo "" @echo "Docker Build Targets:" diff --git a/api/README.md b/api/README.md index a075bc0fa9..b41ee99b09 100644 --- a/api/README.md +++ b/api/README.md @@ -99,7 +99,7 @@ The scripts resolve paths relative to their location, so you can run them from a ./dev/reformat # Run all formatters and linters uv run ruff check --fix ./ # Fix linting issues uv run ruff format ./ # Format code - uv run basedpyright . # Type checking + uv run pyrefly check # Type checking ``` ## Generate TS stub diff --git a/api/app_factory.py b/api/app_factory.py index 5583071980..565c7fefd8 100644 --- a/api/app_factory.py +++ b/api/app_factory.py @@ -117,7 +117,7 @@ def create_flask_app_with_configs() -> DifyApp: logger.warning("Failed to add trace headers to response", exc_info=True) return response - # Capture the decorator's return value to avoid pyright reportUnusedFunction + # Capture the decorator return values so static checkers do not treat the hooks as unused. _ = before_request _ = add_trace_headers diff --git a/api/commands/plugin.py b/api/commands/plugin.py index 8bd5392d7b..8ad2321b07 100644 --- a/api/commands/plugin.py +++ b/api/commands/plugin.py @@ -185,9 +185,9 @@ def transform_datasource_credentials(environment: str): firecrawl_plugin_id = "langgenius/firecrawl_datasource" jina_plugin_id = "langgenius/jina_datasource" if environment == "online": - notion_plugin_unique_identifier = plugin_migration._fetch_plugin_unique_identifier(notion_plugin_id) # pyright: ignore[reportPrivateUsage] - firecrawl_plugin_unique_identifier = plugin_migration._fetch_plugin_unique_identifier(firecrawl_plugin_id) # pyright: ignore[reportPrivateUsage] - jina_plugin_unique_identifier = plugin_migration._fetch_plugin_unique_identifier(jina_plugin_id) # pyright: ignore[reportPrivateUsage] + notion_plugin_unique_identifier = plugin_migration._fetch_plugin_unique_identifier(notion_plugin_id) + firecrawl_plugin_unique_identifier = plugin_migration._fetch_plugin_unique_identifier(firecrawl_plugin_id) + jina_plugin_unique_identifier = plugin_migration._fetch_plugin_unique_identifier(jina_plugin_id) else: notion_plugin_unique_identifier = None firecrawl_plugin_unique_identifier = None diff --git a/api/configs/middleware/__init__.py b/api/configs/middleware/__init__.py index ee8b93aa9f..865bb48c67 100644 --- a/api/configs/middleware/__init__.py +++ b/api/configs/middleware/__init__.py @@ -1,5 +1,5 @@ import os -from typing import Any, Literal, TypedDict +from typing import Any, Literal, TypedDict, cast from urllib.parse import parse_qsl, quote_plus from pydantic import Field, NonNegativeFloat, NonNegativeInt, PositiveFloat, PositiveInt, computed_field @@ -50,28 +50,30 @@ from .vdb.vastbase_vector_config import VastbaseVectorConfig from .vdb.vikingdb_config import VikingDBConfig from .vdb.weaviate_config import WeaviateConfig +_VALID_STORAGE_TYPE = Literal[ + "opendal", + "s3", + "aliyun-oss", + "azure-blob", + "baidu-obs", + "clickzetta-volume", + "google-storage", + "huawei-obs", + "oci-storage", + "tencent-cos", + "volcengine-tos", + "supabase", + "local", +] + class StorageConfig(BaseSettings): - STORAGE_TYPE: Literal[ - "opendal", - "s3", - "aliyun-oss", - "azure-blob", - "baidu-obs", - "clickzetta-volume", - "google-storage", - "huawei-obs", - "oci-storage", - "tencent-cos", - "volcengine-tos", - "supabase", - "local", - ] = Field( + STORAGE_TYPE: _VALID_STORAGE_TYPE = Field( description="Type of storage to use." " Options: 'opendal', '(deprecated) local', 's3', 'aliyun-oss', 'azure-blob', 'baidu-obs', " "'clickzetta-volume', 'google-storage', 'huawei-obs', 'oci-storage', 'tencent-cos', " "'volcengine-tos', 'supabase'. Default is 'opendal'.", - default="opendal", + default=cast(_VALID_STORAGE_TYPE, "opendal"), ) STORAGE_LOCAL_PATH: str = Field( diff --git a/api/controllers/console/__init__.py b/api/controllers/console/__init__.py index 16fa07a6c2..f5aeb17ba2 100644 --- a/api/controllers/console/__init__.py +++ b/api/controllers/console/__init__.py @@ -116,7 +116,7 @@ from .explore import ( saved_message, trial, ) -from .socketio import workflow as socketio_workflow # pyright: ignore[reportUnusedImport] +from .socketio import workflow as socketio_workflow # Import tag controllers from .tag import tags diff --git a/api/core/app/apps/agent_chat/app_config_manager.py b/api/core/app/apps/agent_chat/app_config_manager.py index f0d81e0c59..e269b98bad 100644 --- a/api/core/app/apps/agent_chat/app_config_manager.py +++ b/api/core/app/apps/agent_chat/app_config_manager.py @@ -178,7 +178,7 @@ class AgentChatAppConfigManager(BaseAppConfigManager): if not isinstance(agent_mode, dict): raise ValueError("agent_mode must be of object type") - # FIXME(-LAN-): Cast needed due to basedpyright limitation with dict type narrowing + # FIXME(-LAN-): Cast needed because static checkers do not narrow this dict value. agent_mode = cast(dict[str, Any], agent_mode) if "enabled" not in agent_mode or not agent_mode["enabled"]: diff --git a/api/core/llm_generator/llm_generator.py b/api/core/llm_generator/llm_generator.py index af2611bb0b..30b9523146 100644 --- a/api/core/llm_generator/llm_generator.py +++ b/api/core/llm_generator/llm_generator.py @@ -435,7 +435,7 @@ class LLMGenerator: stream=False, ) - # Runtime type check since pyright has issues with the overload + # Runtime type check for overload narrowing. if not isinstance(result, LLMResult): raise TypeError("Expected LLMResult when stream=False") response = result diff --git a/api/core/rag/extractor/unstructured/unstructured_doc_extractor.py b/api/core/rag/extractor/unstructured/unstructured_doc_extractor.py index f9fbfbc409..da370f70e7 100644 --- a/api/core/rag/extractor/unstructured/unstructured_doc_extractor.py +++ b/api/core/rag/extractor/unstructured/unstructured_doc_extractor.py @@ -19,7 +19,7 @@ class UnstructuredWordExtractor(BaseExtractor): def extract(self) -> list[Document]: from unstructured.__version__ import __version__ as __unstructured_version__ - from unstructured.file_utils.filetype import ( # pyright: ignore[reportPrivateImportUsage] + from unstructured.file_utils.filetype import ( FileType, detect_filetype, ) @@ -27,7 +27,7 @@ class UnstructuredWordExtractor(BaseExtractor): unstructured_version = tuple(int(x) for x in __unstructured_version__.split(".")) # check the file extension try: - import magic # noqa: F401 # pyright: ignore[reportUnusedImport] + import magic # noqa: F401 is_doc = detect_filetype(self._file_path) == FileType.DOC except ImportError: diff --git a/api/core/rag/retrieval/router/multi_dataset_function_call_router.py b/api/core/rag/retrieval/router/multi_dataset_function_call_router.py index dd17545c86..ef56567a5a 100644 --- a/api/core/rag/retrieval/router/multi_dataset_function_call_router.py +++ b/api/core/rag/retrieval/router/multi_dataset_function_call_router.py @@ -28,10 +28,10 @@ class FunctionCallMultiDatasetRouter: SystemPromptMessage(content="You are a helpful AI assistant."), UserPromptMessage(content=query), ] - result: LLMResult = model_instance.invoke_llm( # pyright: ignore[reportCallIssue, reportArgumentType] + result: LLMResult = model_instance.invoke_llm( # pyrefly: ignore[no-matching-overload] prompt_messages=prompt_messages, tools=dataset_tools, - stream=False, # pyright: ignore[reportArgumentType] + stream=False, model_parameters={"temperature": 0.2, "top_p": 0.3, "max_tokens": 1500}, ) usage = result.usage or LLMUsage.empty_usage() diff --git a/api/extensions/ext_app_metrics.py b/api/extensions/ext_app_metrics.py index 914baaadaf..53490c06a9 100644 --- a/api/extensions/ext_app_metrics.py +++ b/api/extensions/ext_app_metrics.py @@ -11,14 +11,14 @@ from dify_app import DifyApp def init_app(app: DifyApp): @app.after_request - def after_request(response): # pyright: ignore[reportUnusedFunction] + def after_request(response): """Add Version headers to the response.""" response.headers.add("X-Version", dify_config.project.version) response.headers.add("X-Env", dify_config.DEPLOY_ENV) return response @app.route("/health") - def health(): # pyright: ignore[reportUnusedFunction] + def health(): return Response( json.dumps({"pid": os.getpid(), "status": "ok", "version": dify_config.project.version}), status=200, @@ -27,7 +27,7 @@ def init_app(app: DifyApp): @app.route("/threads") @admin_required - def threads(): # pyright: ignore[reportUnusedFunction] + def threads(): num_threads = threading.active_count() threads = threading.enumerate() @@ -53,7 +53,7 @@ def init_app(app: DifyApp): @app.route("/db-pool-stat") @admin_required - def pool_stat(): # pyright: ignore[reportUnusedFunction] + def pool_stat(): from extensions.ext_database import db engine = db.engine diff --git a/api/extensions/ext_database.py b/api/extensions/ext_database.py index 2e0d4c889a..466af99b95 100644 --- a/api/extensions/ext_database.py +++ b/api/extensions/ext_database.py @@ -33,7 +33,7 @@ def _setup_gevent_compatibility(): return @event.listens_for(Pool, "reset") - def _safe_reset(dbapi_connection, connection_record, reset_state): # pyright: ignore[reportUnusedFunction] + def _safe_reset(dbapi_connection, connection_record, reset_state): if reset_state.terminate_only: return diff --git a/api/extensions/ext_import_modules.py b/api/extensions/ext_import_modules.py index 4eb363ff93..9566f430b6 100644 --- a/api/extensions/ext_import_modules.py +++ b/api/extensions/ext_import_modules.py @@ -2,4 +2,4 @@ from dify_app import DifyApp def init_app(app: DifyApp): - from events import event_handlers # noqa: F401 # pyright: ignore[reportUnusedImport] + from events import event_handlers # noqa: F401 diff --git a/api/extensions/ext_socketio.py b/api/extensions/ext_socketio.py index 5ed82bac8d..2fe2369e9f 100644 --- a/api/extensions/ext_socketio.py +++ b/api/extensions/ext_socketio.py @@ -1,5 +1,9 @@ +from typing import Any, cast + import socketio # type: ignore[reportMissingTypeStubs] from configs import dify_config -sio = socketio.Server(async_mode="gevent", cors_allowed_origins=dify_config.CONSOLE_CORS_ALLOW_ORIGINS) +# TODO: FIXME(chariri) - Casting to any because app_factory attaches the +# current app as the `app` attribute on this - Bad. +sio = cast(Any, socketio.Server(async_mode="gevent", cors_allowed_origins=dify_config.CONSOLE_CORS_ALLOW_ORIGINS)) diff --git a/api/extensions/logstore/aliyun_logstore.py b/api/extensions/logstore/aliyun_logstore.py index f6a4765f14..9869e1eaf7 100644 --- a/api/extensions/logstore/aliyun_logstore.py +++ b/api/extensions/logstore/aliyun_logstore.py @@ -198,7 +198,7 @@ class AliyunLogStore: ) # Append Dify identification to the existing user agent - original_user_agent = self.client._user_agent # pyright: ignore[reportPrivateUsage] + original_user_agent = self.client._user_agent dify_version = dify_config.project.version enhanced_user_agent = f"Dify,Dify-{dify_version},{original_user_agent}" self.client.set_user_agent(enhanced_user_agent) diff --git a/api/providers/trace/trace-tencent/tests/unit_tests/tencent_trace/test_client.py b/api/providers/trace/trace-tencent/tests/unit_tests/tencent_trace/test_client.py index 3cd918f408..b6bc5b8606 100644 --- a/api/providers/trace/trace-tencent/tests/unit_tests/tencent_trace/test_client.py +++ b/api/providers/trace/trace-tencent/tests/unit_tests/tencent_trace/test_client.py @@ -94,29 +94,29 @@ class PatchedCoreComponents(TypedDict): def _add_stub_modules(monkeypatch: pytest.MonkeyPatch) -> None: """Drop fake metric modules into sys.modules so the client imports resolve.""" - metrics_module = types.ModuleType("opentelemetry.sdk.metrics") + metrics_module = cast(Any, types.ModuleType("opentelemetry.sdk.metrics")) metrics_module.Histogram = DummyHistogram metrics_module.MeterProvider = DummyMeterProvider monkeypatch.setitem(sys.modules, "opentelemetry.sdk.metrics", metrics_module) - metrics_export_module = types.ModuleType("opentelemetry.sdk.metrics.export") + metrics_export_module = cast(Any, types.ModuleType("opentelemetry.sdk.metrics.export")) metrics_export_module.AggregationTemporality = AggregationTemporality metrics_export_module.PeriodicExportingMetricReader = DummyMetricReader monkeypatch.setitem(sys.modules, "opentelemetry.sdk.metrics.export", metrics_export_module) - grpc_module = types.ModuleType("opentelemetry.exporter.otlp.proto.grpc.metric_exporter") + grpc_module = cast(Any, types.ModuleType("opentelemetry.exporter.otlp.proto.grpc.metric_exporter")) grpc_module.OTLPMetricExporter = DummyGrpcMetricExporter monkeypatch.setitem(sys.modules, "opentelemetry.exporter.otlp.proto.grpc.metric_exporter", grpc_module) - http_module = types.ModuleType("opentelemetry.exporter.otlp.proto.http.metric_exporter") + http_module = cast(Any, types.ModuleType("opentelemetry.exporter.otlp.proto.http.metric_exporter")) http_module.OTLPMetricExporter = DummyHttpMetricExporter monkeypatch.setitem(sys.modules, "opentelemetry.exporter.otlp.proto.http.metric_exporter", http_module) - http_json_module = types.ModuleType("opentelemetry.exporter.otlp.http.json.metric_exporter") + http_json_module = cast(Any, types.ModuleType("opentelemetry.exporter.otlp.http.json.metric_exporter")) http_json_module.OTLPMetricExporter = DummyJsonMetricExporter monkeypatch.setitem(sys.modules, "opentelemetry.exporter.otlp.http.json.metric_exporter", http_json_module) - legacy_json_module = types.ModuleType("opentelemetry.exporter.otlp.json.metric_exporter") + legacy_json_module = cast(Any, types.ModuleType("opentelemetry.exporter.otlp.json.metric_exporter")) legacy_json_module.OTLPMetricExporter = DummyJsonMetricExporter monkeypatch.setitem(sys.modules, "opentelemetry.exporter.otlp.json.metric_exporter", legacy_json_module) diff --git a/api/providers/vdb/vdb-chroma/src/dify_vdb_chroma/chroma_vector.py b/api/providers/vdb/vdb-chroma/src/dify_vdb_chroma/chroma_vector.py index 5b0cfbea15..754b1e8a89 100644 --- a/api/providers/vdb/vdb-chroma/src/dify_vdb_chroma/chroma_vector.py +++ b/api/providers/vdb/vdb-chroma/src/dify_vdb_chroma/chroma_vector.py @@ -2,7 +2,7 @@ import json from typing import Any, TypedDict import chromadb -from chromadb import QueryResult, Settings # pyright: ignore[reportPrivateImportUsage] +from chromadb import QueryResult, Settings from pydantic import BaseModel from configs import dify_config @@ -166,8 +166,8 @@ class ChromaVectorFactory(AbstractVectorFactory): config=ChromaConfig( host=dify_config.CHROMA_HOST or "", port=dify_config.CHROMA_PORT, - tenant=dify_config.CHROMA_TENANT or chromadb.DEFAULT_TENANT, # pyright: ignore[reportPrivateImportUsage] - database=dify_config.CHROMA_DATABASE or chromadb.DEFAULT_DATABASE, # pyright: ignore[reportPrivateImportUsage] + tenant=dify_config.CHROMA_TENANT or chromadb.DEFAULT_TENANT, + database=dify_config.CHROMA_DATABASE or chromadb.DEFAULT_DATABASE, auth_provider=dify_config.CHROMA_AUTH_PROVIDER, auth_credentials=dify_config.CHROMA_AUTH_CREDENTIALS, ), diff --git a/api/providers/vdb/vdb-couchbase/src/dify_vdb_couchbase/couchbase_vector.py b/api/providers/vdb/vdb-couchbase/src/dify_vdb_couchbase/couchbase_vector.py index bab176e285..5deac59dc9 100644 --- a/api/providers/vdb/vdb-couchbase/src/dify_vdb_couchbase/couchbase_vector.py +++ b/api/providers/vdb/vdb-couchbase/src/dify_vdb_couchbase/couchbase_vector.py @@ -59,7 +59,7 @@ class CouchbaseVector(BaseVector): auth = PasswordAuthenticator(config.user, config.password) options = ClusterOptions(auth) - self._cluster = Cluster(config.connection_string, options) # pyright: ignore[reportArgumentType] + self._cluster = Cluster(config.connection_string, options) self._bucket = self._cluster.bucket(config.bucket_name) self._scope = self._bucket.scope(config.scope_name) self._bucket_name = config.bucket_name @@ -306,7 +306,7 @@ class CouchbaseVector(BaseVector): def search_by_full_text(self, query: str, **kwargs: Any) -> list[Document]: top_k = kwargs.get("top_k", 4) try: - CBrequest = search.SearchRequest.create(search.QueryStringQuery("text:" + query)) # pyright: ignore[reportCallIssue] + CBrequest = search.SearchRequest.create(search.QueryStringQuery("text:" + query)) # pyrefly: ignore[bad-argument-count] search_iter = self._scope.search( self._collection_name + "_search", CBrequest, SearchOptions(limit=top_k, fields=["*"]) ) diff --git a/api/providers/vdb/vdb-qdrant/src/dify_vdb_qdrant/qdrant_vector.py b/api/providers/vdb/vdb-qdrant/src/dify_vdb_qdrant/qdrant_vector.py index b5ff87fc5d..6b0216441b 100644 --- a/api/providers/vdb/vdb-qdrant/src/dify_vdb_qdrant/qdrant_vector.py +++ b/api/providers/vdb/vdb-qdrant/src/dify_vdb_qdrant/qdrant_vector.py @@ -471,7 +471,7 @@ class QdrantVector(BaseVector): def _reload_if_needed(self): if isinstance(self._client, QdrantLocal): - self._client._load() # pyright: ignore[reportPrivateUsage] + self._client._load() @classmethod def _document_from_scored_point( diff --git a/api/pyproject.toml b/api/pyproject.toml index 0744ddd6f8..e3711f1558 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -118,7 +118,6 @@ dev = [ "dotenv-linter>=0.7.0", "faker>=40.15.0", "lxml-stubs>=0.5.1", - "basedpyright>=1.39.3", "ruff>=0.15.12", "pytest>=9.0.3", "pytest-benchmark>=5.2.3", diff --git a/api/pyrightconfig.json b/api/pyrightconfig.json deleted file mode 100644 index ac0e2a3a53..0000000000 --- a/api/pyrightconfig.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "include": ["."], - "exclude": [ - "tests/", - ".venv", - "migrations/", - "core/rag", - "providers/vdb/", - "providers/trace/*/tests", - ], - "typeCheckingMode": "strict", - "allowedUntypedLibraries": [ - "fastopenapi", - "flask_restx", - "flask_login", - "opentelemetry.instrumentation.celery", - "opentelemetry.instrumentation.flask", - "opentelemetry.instrumentation.httpx", - "opentelemetry.instrumentation.requests", - "opentelemetry.instrumentation.sqlalchemy", - "opentelemetry.instrumentation.redis", - "langfuse", - "cloudscraper", - "readabilipy", - "pypandoc", - "pypdfium2", - "webvtt", - "flask_compress", - "oss2", - "baidubce.auth.bce_credentials", - "baidubce.bce_client_configuration", - "baidubce.services.bos.bos_client", - "clickzetta", - "google.cloud", - "obs", - "qcloud_cos", - "tos", - "gmpy2", - "sendgrid", - "sendgrid.helpers.mail", - "holo_search_sdk.types", - "dify_vdb_qdrant", - "dify_vdb_tidb_on_qdrant" - ], - "reportUnknownMemberType": "hint", - "reportUnknownParameterType": "hint", - "reportUnknownArgumentType": "hint", - "reportUnknownVariableType": "hint", - "reportUnknownLambdaType": "hint", - "reportMissingParameterType": "hint", - "reportMissingTypeArgument": "hint", - "reportUnnecessaryComparison": "hint", - "reportUnnecessaryIsInstance": "hint", - "reportUnnecessaryTypeIgnoreComment": "hint", - "reportAttributeAccessIssue": "hint", - "pythonVersion": "3.12", - "pythonPlatform": "All" -} diff --git a/api/services/dataset_service.py b/api/services/dataset_service.py index 4f5a95dcde..ea8896a5aa 100644 --- a/api/services/dataset_service.py +++ b/api/services/dataset_service.py @@ -750,7 +750,7 @@ class DatasetService: knowledge_index_node_data["embedding_model_provider"] = dataset.embedding_model_provider knowledge_index_node_data["retrieval_model"] = dataset.retrieval_model knowledge_index_node_data["chunk_structure"] = dataset.chunk_structure - knowledge_index_node_data["indexing_technique"] = dataset.indexing_technique # pyright: ignore[reportAttributeAccessIssue] + knowledge_index_node_data["indexing_technique"] = dataset.indexing_technique knowledge_index_node_data["keyword_number"] = dataset.keyword_number knowledge_index_node_data["summary_index_setting"] = dataset.summary_index_setting node["data"] = knowledge_index_node_data diff --git a/api/services/model_load_balancing_service.py b/api/services/model_load_balancing_service.py index c269346f5f..15be7d5af3 100644 --- a/api/services/model_load_balancing_service.py +++ b/api/services/model_load_balancing_service.py @@ -1,6 +1,6 @@ import json import logging -from typing import Any, TypedDict +from typing import Any, TypedDict, cast from sqlalchemy import or_, select @@ -620,7 +620,7 @@ class ModelLoadBalancingService: for key, value in credentials.items(): if key in provider_credential_secret_variables: - credentials[key] = encrypter.encrypt_token(tenant_id, value) + credentials[key] = encrypter.encrypt_token(tenant_id, cast(str, value)) return credentials diff --git a/api/services/retention/conversation/message_export_service.py b/api/services/retention/conversation/message_export_service.py index fbe0d2795d..8d7fedf2c1 100644 --- a/api/services/retention/conversation/message_export_service.py +++ b/api/services/retention/conversation/message_export_service.py @@ -228,7 +228,7 @@ class AppMessageExportService: Message.conversation_id, Message.query, Message.answer, - Message._inputs, # pyright: ignore[reportPrivateUsage] + Message._inputs, Message.message_metadata, Message.created_at, ) diff --git a/api/tests/unit_tests/configs/test_dify_config.py b/api/tests/unit_tests/configs/test_dify_config.py index 919ebbc656..81f3f92a7c 100644 --- a/api/tests/unit_tests/configs/test_dify_config.py +++ b/api/tests/unit_tests/configs/test_dify_config.py @@ -130,7 +130,7 @@ def test_flask_configs(monkeypatch: pytest.MonkeyPatch): monkeypatch.setenv("CODE_EXECUTION_ENDPOINT", "http://127.0.0.1:8194/") # Disable `.env` loading to ensure test stability across environments - flask_app.config.from_mapping(DifyConfig(_env_file=None).model_dump()) # pyright: ignore + flask_app.config.from_mapping(DifyConfig(_env_file=None).model_dump()) config = flask_app.config # configs read from pydantic-settings diff --git a/api/tests/unit_tests/core/workflow/nodes/parameter_extractor/test_parameter_extractor_node.py b/api/tests/unit_tests/core/workflow/nodes/parameter_extractor/test_parameter_extractor_node.py index 8f8ec49f14..cd3b0b130b 100644 --- a/api/tests/unit_tests/core/workflow/nodes/parameter_extractor/test_parameter_extractor_node.py +++ b/api/tests/unit_tests/core/workflow/nodes/parameter_extractor/test_parameter_extractor_node.py @@ -122,7 +122,7 @@ class TestValidateResult: parameters=[ ParameterConfig( name="status", - type="select", # pyright: ignore[reportArgumentType] + type="select", description="Status", required=True, options=["active", "inactive"], diff --git a/api/tests/unit_tests/libs/test_token.py b/api/tests/unit_tests/libs/test_token.py index 734568d37b..6bd794b24d 100644 --- a/api/tests/unit_tests/libs/test_token.py +++ b/api/tests/unit_tests/libs/test_token.py @@ -1,6 +1,8 @@ +from typing import cast from unittest.mock import MagicMock import pytest +from flask import Request from werkzeug.wrappers import Response from constants import COOKIE_NAME_ACCESS_TOKEN, COOKIE_NAME_WEBAPP_ACCESS_TOKEN @@ -16,8 +18,8 @@ class MockRequest: def test_extract_access_token(): - def _mock_request(headers: dict[str, str], cookies: dict[str, str], args: dict[str, str]): - return MockRequest(headers, cookies, args) + def _mock_request(headers: dict[str, str], cookies: dict[str, str], args: dict[str, str]) -> Request: + return cast(Request, MockRequest(headers, cookies, args)) test_cases = [ (_mock_request({"Authorization": "Bearer 123"}, {}, {}), "123", "123"), @@ -27,8 +29,8 @@ def test_extract_access_token(): (_mock_request({}, {COOKIE_NAME_WEBAPP_ACCESS_TOKEN: "123"}, {}), None, "123"), ] for request, expected_console, expected_webapp in test_cases: - assert extract_access_token(request) == expected_console # pyright: ignore[reportArgumentType] - assert extract_webapp_access_token(request) == expected_webapp # pyright: ignore[reportArgumentType] + assert extract_access_token(request) == expected_console + assert extract_webapp_access_token(request) == expected_webapp def test_real_cookie_name_uses_host_prefix_without_domain(monkeypatch: pytest.MonkeyPatch): diff --git a/api/uv.lock b/api/uv.lock index 7af7fe62fe..628a128ade 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -470,18 +470,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/2f/babd02c9fc4ca35376ada7c291193a208165c7be2455f0f98bc1e1243f31/backports_zstd-1.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:f6843ecb181480e423b02f60fe29e393cbc31a95fb532acdf0d3a2c87bd50ce3", size = 288927, upload-time = "2025-12-29T17:26:40.923Z" }, ] -[[package]] -name = "basedpyright" -version = "1.39.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nodejs-wheel-binaries" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/04/19/5a5b9b9197973da732638957be3a65cf514d2f5a4964eeedbf33b6c65bbd/basedpyright-1.39.3.tar.gz", hash = "sha256:2f794e6b5f4260fb89f614ca6cd23c6f305373bb6b50c4ed7794ff2ae647fb14", size = 25503187, upload-time = "2026-04-20T22:14:47.424Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/5c/f950c1239ad26f3bb453e665428a2cf1893995de725a5eb0b64a2520b366/basedpyright-1.39.3-py3-none-any.whl", hash = "sha256:aba760dc83307727554f936d6b4381caa14482f30dbc2173167710e217c1f7ab", size = 12419181, upload-time = "2026-04-20T22:14:51.975Z" }, -] - [[package]] name = "bce-python-sdk" version = "0.9.71" @@ -1338,7 +1326,6 @@ dependencies = [ [package.dev-dependencies] dev = [ - { name = "basedpyright" }, { name = "boto3-stubs" }, { name = "celery-types" }, { name = "coverage" }, @@ -1621,7 +1608,6 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "basedpyright", specifier = ">=1.39.3" }, { name = "boto3-stubs", specifier = ">=1.43.2" }, { name = "celery-types", specifier = ">=0.23.0" }, { name = "coverage", specifier = ">=7.13.4" }, @@ -4026,22 +4012,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9d/91/04e965f8e717ba0ab4bdca5c112deeab11c9e750d94c4d4602f050295d39/nltk-3.9.4-py3-none-any.whl", hash = "sha256:f2fa301c3a12718ce4a0e9305c5675299da5ad9e26068218b69d692fda84828f", size = 1552087, upload-time = "2026-03-24T06:13:38.47Z" }, ] -[[package]] -name = "nodejs-wheel-binaries" -version = "24.11.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/89/da307731fdbb05a5f640b26de5b8ac0dc463fef059162accfc89e32f73bc/nodejs_wheel_binaries-24.11.1.tar.gz", hash = "sha256:413dfffeadfb91edb4d8256545dea797c237bba9b3faefea973cde92d96bb922", size = 8059, upload-time = "2025-11-18T18:21:58.207Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/5f/be5a4112e678143d4c15264d918f9a2dc086905c6426eb44515cf391a958/nodejs_wheel_binaries-24.11.1-py2.py3-none-macosx_13_0_arm64.whl", hash = "sha256:0e14874c3579def458245cdbc3239e37610702b0aa0975c1dc55e2cb80e42102", size = 55114309, upload-time = "2025-11-18T18:21:21.697Z" }, - { url = "https://files.pythonhosted.org/packages/fa/1c/2e9d6af2ea32b65928c42b3e5baa7a306870711d93c3536cb25fc090a80d/nodejs_wheel_binaries-24.11.1-py2.py3-none-macosx_13_0_x86_64.whl", hash = "sha256:c2741525c9874b69b3e5a6d6c9179a6fe484ea0c3d5e7b7c01121c8e5d78b7e2", size = 55285957, upload-time = "2025-11-18T18:21:27.177Z" }, - { url = "https://files.pythonhosted.org/packages/d0/79/35696d7ba41b1bd35ef8682f13d46ba38c826c59e58b86b267458eb53d87/nodejs_wheel_binaries-24.11.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:5ef598101b0fb1c2bf643abb76dfbf6f76f1686198ed17ae46009049ee83c546", size = 59645875, upload-time = "2025-11-18T18:21:33.004Z" }, - { url = "https://files.pythonhosted.org/packages/b4/98/2a9694adee0af72bc602a046b0632a0c89e26586090c558b1c9199b187cc/nodejs_wheel_binaries-24.11.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:cde41d5e4705266688a8d8071debf4f8a6fcea264c61292782672ee75a6905f9", size = 60140941, upload-time = "2025-11-18T18:21:37.228Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d6/573e5e2cba9d934f5f89d0beab00c3315e2e6604eb4df0fcd1d80c5a07a8/nodejs_wheel_binaries-24.11.1-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:78bc5bb889313b565df8969bb7423849a9c7fc218bf735ff0ce176b56b3e96f0", size = 61644243, upload-time = "2025-11-18T18:21:43.325Z" }, - { url = "https://files.pythonhosted.org/packages/c7/e6/643234d5e94067df8ce8d7bba10f3804106668f7a1050aeb10fdd226ead4/nodejs_wheel_binaries-24.11.1-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c79a7e43869ccecab1cae8183778249cceb14ca2de67b5650b223385682c6239", size = 62225657, upload-time = "2025-11-18T18:21:47.708Z" }, - { url = "https://files.pythonhosted.org/packages/4d/1c/2fb05127102a80225cab7a75c0e9edf88a0a1b79f912e1e36c7c1aaa8f4e/nodejs_wheel_binaries-24.11.1-py2.py3-none-win_amd64.whl", hash = "sha256:10197b1c9c04d79403501766f76508b0dac101ab34371ef8a46fcf51773497d0", size = 41322308, upload-time = "2025-11-18T18:21:51.347Z" }, - { url = "https://files.pythonhosted.org/packages/ad/b7/bc0cdbc2cc3a66fcac82c79912e135a0110b37b790a14c477f18e18d90cd/nodejs_wheel_binaries-24.11.1-py2.py3-none-win_arm64.whl", hash = "sha256:376b9ea1c4bc1207878975dfeb604f7aa5668c260c6154dcd2af9d42f7734116", size = 39026497, upload-time = "2025-11-18T18:21:54.634Z" }, -] - [[package]] name = "numba" version = "0.65.0" diff --git a/dev/basedpyright-check b/dev/basedpyright-check deleted file mode 100755 index 1b3d1df7ad..0000000000 --- a/dev/basedpyright-check +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -set -x - -SCRIPT_DIR="$(dirname "$(realpath "$0")")" -cd "$SCRIPT_DIR/.." - -# Get the path argument if provided -PATH_TO_CHECK="$1" - -# Determine CPU core count based on OS -CPU_CORES=$( - if [[ "$(uname -s)" == "Darwin" ]]; then - sysctl -n hw.ncpu 2>/dev/null - else - nproc - fi -) - -# Run basedpyright checks -uv run --directory api --dev -- basedpyright --threads "$CPU_CORES" $PATH_TO_CHECK diff --git a/dev/pyrefly-check-local b/dev/pyrefly-check-local index 8fa5f121fc..e29dfe49e0 100755 --- a/dev/pyrefly-check-local +++ b/dev/pyrefly-check-local @@ -8,6 +8,15 @@ cd "$REPO_ROOT" EXCLUDES_FILE="api/pyrefly-local-excludes.txt" +target_paths=() +for target_path in "$@"; do + if [[ "$target_path" == api/* ]]; then + target_paths+=("${target_path#api/}") + else + target_paths+=("$target_path") + fi +done + pyrefly_args=( "--summary=none" "--use-ignore-files=false" @@ -26,7 +35,7 @@ fi tmp_output="$(mktemp)" set +e -uv run --directory api --dev pyrefly check "${pyrefly_args[@]}" >"$tmp_output" 2>&1 +uv run --directory api --dev pyrefly check "${pyrefly_args[@]}" "${target_paths[@]}" >"$tmp_output" 2>&1 pyrefly_status=$? set -e diff --git a/dev/reformat b/dev/reformat index 6966267193..f179b43ca0 100755 --- a/dev/reformat +++ b/dev/reformat @@ -17,5 +17,5 @@ uv run --directory api --dev ruff format ./ # run dotenv-linter linter uv run --project api --dev dotenv-linter ./api/.env.example ./web/.env.example -# run basedpyright check -dev/basedpyright-check +# run pyrefly check +dev/pyrefly-check-local