fix the lint error in backend

This commit is contained in:
Willem Jiang
2026-04-26 15:09:25 +08:00
parent 3b71e2d377
commit 829e82a9af
15 changed files with 76 additions and 49 deletions
-1
View File
@@ -13,7 +13,6 @@ matching the LangGraph Platform wire format expected by the
from __future__ import annotations from __future__ import annotations
import logging import logging
import re
import time import time
import uuid import uuid
from typing import Any from typing import Any
+1 -3
View File
@@ -8,18 +8,16 @@ frames, and consuming stream bridge events. Router modules
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
import dataclasses
import json import json
import logging import logging
import re import re
import time
from collections.abc import Mapping from collections.abc import Mapping
from typing import Any from typing import Any
from fastapi import HTTPException, Request from fastapi import HTTPException, Request
from langchain_core.messages import HumanMessage from langchain_core.messages import HumanMessage
from app.gateway.deps import get_run_context, get_run_manager, get_run_store, get_stream_bridge from app.gateway.deps import get_run_context, get_run_manager, get_stream_bridge
from app.gateway.utils import sanitize_log_param from app.gateway.utils import sanitize_log_param
from deerflow.runtime import ( from deerflow.runtime import (
END_SENTINEL, END_SENTINEL,
@@ -13,9 +13,7 @@ from deerflow.persistence.base import Base
class FeedbackRow(Base): class FeedbackRow(Base):
__tablename__ = "feedback" __tablename__ = "feedback"
__table_args__ = ( __table_args__ = (UniqueConstraint("thread_id", "run_id", "user_id", name="uq_feedback_thread_run_user"),)
UniqueConstraint("thread_id", "run_id", "user_id", name="uq_feedback_thread_run_user"),
)
feedback_id: Mapped[str] = mapped_column(String(64), primary_key=True) feedback_id: Mapped[str] = mapped_column(String(64), primary_key=True)
run_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True) run_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
+1 -2
View File
@@ -5,11 +5,10 @@ Usage:
The script is idempotent — re-running it after a successful migration is a no-op. The script is idempotent — re-running it after a successful migration is a no-op.
""" """
import argparse import argparse
import json
import logging import logging
import shutil import shutil
from pathlib import Path
from deerflow.config.paths import Paths, get_paths from deerflow.config.paths import Paths, get_paths
+1 -6
View File
@@ -29,7 +29,6 @@ apps with the real middleware — those should not use this module.
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from typing import ParamSpec, TypeVar
from unittest.mock import AsyncMock, MagicMock from unittest.mock import AsyncMock, MagicMock
from uuid import uuid4 from uuid import uuid4
@@ -113,11 +112,7 @@ def make_authed_test_app(
return app return app
_P = ParamSpec("_P") def call_unwrapped[*P, R](decorated: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs) -> R:
_R = TypeVar("_R")
def call_unwrapped(decorated: Callable[_P, _R], /, *args: _P.args, **kwargs: _P.kwargs) -> _R:
"""Invoke the underlying function of a ``@require_permission``-decorated route. """Invoke the underlying function of a ``@require_permission``-decorated route.
``functools.wraps`` sets ``__wrapped__`` on each layer; we walk all ``functools.wraps`` sets ``__wrapped__`` on each layer; we walk all
@@ -1,4 +1,5 @@
"""Tests for user_id propagation through memory queue.""" """Tests for user_id propagation through memory queue."""
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from deerflow.agents.memory.queue import ConversationContext, MemoryUpdateQueue from deerflow.agents.memory.queue import ConversationContext, MemoryUpdateQueue
@@ -1,8 +1,10 @@
"""Tests for per-user memory storage isolation.""" """Tests for per-user memory storage isolation."""
import pytest
from pathlib import Path from pathlib import Path
from unittest.mock import patch from unittest.mock import patch
import pytest
from deerflow.agents.memory.storage import FileMemoryStorage, create_empty_memory from deerflow.agents.memory.storage import FileMemoryStorage, create_empty_memory
@@ -65,8 +67,8 @@ class TestUserIsolatedStorage:
assert loaded_a["user"]["workContext"]["summary"] == "A" assert loaded_a["user"]["workContext"]["summary"] == "A"
def test_no_user_id_uses_legacy_path(self, base_dir: Path): def test_no_user_id_uses_legacy_path(self, base_dir: Path):
from deerflow.config.paths import Paths
from deerflow.config.memory_config import MemoryConfig from deerflow.config.memory_config import MemoryConfig
from deerflow.config.paths import Paths
paths = Paths(base_dir) paths = Paths(base_dir)
with patch("deerflow.agents.memory.storage.get_paths", return_value=paths): with patch("deerflow.agents.memory.storage.get_paths", return_value=paths):
@@ -79,8 +81,8 @@ class TestUserIsolatedStorage:
def test_user_and_legacy_do_not_interfere(self, base_dir: Path): def test_user_and_legacy_do_not_interfere(self, base_dir: Path):
"""user_id=None (legacy) and user_id='alice' must use different files and caches.""" """user_id=None (legacy) and user_id='alice' must use different files and caches."""
from deerflow.config.paths import Paths
from deerflow.config.memory_config import MemoryConfig from deerflow.config.memory_config import MemoryConfig
from deerflow.config.paths import Paths
paths = Paths(base_dir) paths = Paths(base_dir)
with patch("deerflow.agents.memory.storage.get_paths", return_value=paths): with patch("deerflow.agents.memory.storage.get_paths", return_value=paths):
@@ -1,7 +1,8 @@
"""Tests for user_id propagation in memory updater.""" """Tests for user_id propagation in memory updater."""
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from deerflow.agents.memory.updater import get_memory_data, clear_memory_data, _save_memory_to_file from deerflow.agents.memory.updater import _save_memory_to_file, clear_memory_data, get_memory_data
def test_get_memory_data_passes_user_id(): def test_get_memory_data_passes_user_id():
+12 -1
View File
@@ -1,8 +1,10 @@
"""Tests for per-user data migration.""" """Tests for per-user data migration."""
import json import json
import pytest
from pathlib import Path from pathlib import Path
import pytest
from deerflow.config.paths import Paths from deerflow.config.paths import Paths
@@ -23,6 +25,7 @@ class TestMigrateThreadDirs:
(legacy / "file.txt").write_text("hello") (legacy / "file.txt").write_text("hello")
from scripts.migrate_user_isolation import migrate_thread_dirs from scripts.migrate_user_isolation import migrate_thread_dirs
migrate_thread_dirs(paths, thread_owner_map={"t1": "alice"}) migrate_thread_dirs(paths, thread_owner_map={"t1": "alice"})
expected = base_dir / "users" / "alice" / "threads" / "t1" / "user-data" / "workspace" / "file.txt" expected = base_dir / "users" / "alice" / "threads" / "t1" / "user-data" / "workspace" / "file.txt"
@@ -35,6 +38,7 @@ class TestMigrateThreadDirs:
legacy.mkdir(parents=True) legacy.mkdir(parents=True)
from scripts.migrate_user_isolation import migrate_thread_dirs from scripts.migrate_user_isolation import migrate_thread_dirs
migrate_thread_dirs(paths, thread_owner_map={}) migrate_thread_dirs(paths, thread_owner_map={})
expected = base_dir / "users" / "default" / "threads" / "t2" expected = base_dir / "users" / "default" / "threads" / "t2"
@@ -45,6 +49,7 @@ class TestMigrateThreadDirs:
new_dir.mkdir(parents=True) new_dir.mkdir(parents=True)
from scripts.migrate_user_isolation import migrate_thread_dirs from scripts.migrate_user_isolation import migrate_thread_dirs
migrate_thread_dirs(paths, thread_owner_map={"t1": "alice"}) migrate_thread_dirs(paths, thread_owner_map={"t1": "alice"})
assert new_dir.exists() assert new_dir.exists()
@@ -58,6 +63,7 @@ class TestMigrateThreadDirs:
(dest / "new.txt").write_text("new") (dest / "new.txt").write_text("new")
from scripts.migrate_user_isolation import migrate_thread_dirs from scripts.migrate_user_isolation import migrate_thread_dirs
migrate_thread_dirs(paths, thread_owner_map={"t1": "alice"}) migrate_thread_dirs(paths, thread_owner_map={"t1": "alice"})
assert (dest / "new.txt").read_text() == "new" assert (dest / "new.txt").read_text() == "new"
@@ -69,6 +75,7 @@ class TestMigrateThreadDirs:
legacy.mkdir(parents=True) legacy.mkdir(parents=True)
from scripts.migrate_user_isolation import migrate_thread_dirs from scripts.migrate_user_isolation import migrate_thread_dirs
migrate_thread_dirs(paths, thread_owner_map={}) migrate_thread_dirs(paths, thread_owner_map={})
assert not (base_dir / "threads").exists() assert not (base_dir / "threads").exists()
@@ -78,6 +85,7 @@ class TestMigrateThreadDirs:
legacy.mkdir(parents=True) legacy.mkdir(parents=True)
from scripts.migrate_user_isolation import migrate_thread_dirs from scripts.migrate_user_isolation import migrate_thread_dirs
report = migrate_thread_dirs(paths, thread_owner_map={"t1": "alice"}, dry_run=True) report = migrate_thread_dirs(paths, thread_owner_map={"t1": "alice"}, dry_run=True)
assert len(report) == 1 assert len(report) == 1
@@ -91,6 +99,7 @@ class TestMigrateMemory:
legacy_mem.write_text(json.dumps({"version": "1.0", "facts": []})) legacy_mem.write_text(json.dumps({"version": "1.0", "facts": []}))
from scripts.migrate_user_isolation import migrate_memory from scripts.migrate_user_isolation import migrate_memory
migrate_memory(paths, user_id="default") migrate_memory(paths, user_id="default")
expected = base_dir / "users" / "default" / "memory.json" expected = base_dir / "users" / "default" / "memory.json"
@@ -106,6 +115,7 @@ class TestMigrateMemory:
dest.write_text(json.dumps({"version": "new"})) dest.write_text(json.dumps({"version": "new"}))
from scripts.migrate_user_isolation import migrate_memory from scripts.migrate_user_isolation import migrate_memory
migrate_memory(paths, user_id="default") migrate_memory(paths, user_id="default")
assert json.loads(dest.read_text())["version"] == "new" assert json.loads(dest.read_text())["version"] == "new"
@@ -113,4 +123,5 @@ class TestMigrateMemory:
def test_no_legacy_memory_is_noop(self, base_dir: Path, paths: Paths): def test_no_legacy_memory_is_noop(self, base_dir: Path, paths: Paths):
from scripts.migrate_user_isolation import migrate_memory from scripts.migrate_user_isolation import migrate_memory
migrate_memory(paths, user_id="default") # should not raise migrate_memory(paths, user_id="default") # should not raise
+3 -1
View File
@@ -1,7 +1,9 @@
"""Tests for user-scoped path resolution in Paths.""" """Tests for user-scoped path resolution in Paths."""
import pytest
from pathlib import Path from pathlib import Path
import pytest
from deerflow.config.paths import Paths from deerflow.config.paths import Paths
@@ -1,4 +1,5 @@
"""Tests for paginated list_messages_by_run across all RunEventStore backends.""" """Tests for paginated list_messages_by_run across all RunEventStore backends."""
import pytest import pytest
from deerflow.runtime.events.store.memory import MemoryRunEventStore from deerflow.runtime.events.store.memory import MemoryRunEventStore
@@ -14,14 +15,19 @@ async def test_list_messages_by_run_default_returns_all(base_store):
store = base_store store = base_store
for i in range(7): for i in range(7):
await store.put( await store.put(
thread_id="t1", run_id="run-a", thread_id="t1",
run_id="run-a",
event_type="human_message" if i % 2 == 0 else "ai_message", event_type="human_message" if i % 2 == 0 else "ai_message",
category="message", content=f"msg-a-{i}", category="message",
content=f"msg-a-{i}",
) )
for i in range(3): for i in range(3):
await store.put( await store.put(
thread_id="t1", run_id="run-b", thread_id="t1",
event_type="human_message", category="message", content=f"msg-b-{i}", run_id="run-b",
event_type="human_message",
category="message",
content=f"msg-b-{i}",
) )
await store.put(thread_id="t1", run_id="run-a", event_type="tool_call", category="trace", content="trace") await store.put(thread_id="t1", run_id="run-a", event_type="tool_call", category="trace", content="trace")
@@ -36,9 +42,11 @@ async def test_list_messages_by_run_with_limit(base_store):
store = base_store store = base_store
for i in range(7): for i in range(7):
await store.put( await store.put(
thread_id="t1", run_id="run-a", thread_id="t1",
run_id="run-a",
event_type="human_message" if i % 2 == 0 else "ai_message", event_type="human_message" if i % 2 == 0 else "ai_message",
category="message", content=f"msg-a-{i}", category="message",
content=f"msg-a-{i}",
) )
msgs = await store.list_messages_by_run("t1", "run-a", limit=3) msgs = await store.list_messages_by_run("t1", "run-a", limit=3)
@@ -52,9 +60,11 @@ async def test_list_messages_by_run_after_seq(base_store):
store = base_store store = base_store
for i in range(7): for i in range(7):
await store.put( await store.put(
thread_id="t1", run_id="run-a", thread_id="t1",
run_id="run-a",
event_type="human_message" if i % 2 == 0 else "ai_message", event_type="human_message" if i % 2 == 0 else "ai_message",
category="message", content=f"msg-a-{i}", category="message",
content=f"msg-a-{i}",
) )
all_msgs = await store.list_messages_by_run("t1", "run-a") all_msgs = await store.list_messages_by_run("t1", "run-a")
@@ -69,9 +79,11 @@ async def test_list_messages_by_run_before_seq(base_store):
store = base_store store = base_store
for i in range(7): for i in range(7):
await store.put( await store.put(
thread_id="t1", run_id="run-a", thread_id="t1",
run_id="run-a",
event_type="human_message" if i % 2 == 0 else "ai_message", event_type="human_message" if i % 2 == 0 else "ai_message",
category="message", content=f"msg-a-{i}", category="message",
content=f"msg-a-{i}",
) )
all_msgs = await store.list_messages_by_run("t1", "run-a") all_msgs = await store.list_messages_by_run("t1", "run-a")
@@ -86,13 +98,19 @@ async def test_list_messages_by_run_does_not_include_other_run(base_store):
store = base_store store = base_store
for i in range(7): for i in range(7):
await store.put( await store.put(
thread_id="t1", run_id="run-a", thread_id="t1",
event_type="human_message", category="message", content=f"msg-a-{i}", run_id="run-a",
event_type="human_message",
category="message",
content=f"msg-a-{i}",
) )
for i in range(3): for i in range(3):
await store.put( await store.put(
thread_id="t1", run_id="run-b", thread_id="t1",
event_type="human_message", category="message", content=f"msg-b-{i}", run_id="run-b",
event_type="human_message",
category="message",
content=f"msg-b-{i}",
) )
msgs = await store.list_messages_by_run("t1", "run-b") msgs = await store.list_messages_by_run("t1", "run-b")
-2
View File
@@ -381,5 +381,3 @@ class TestMiddlewareEvents:
event_types = {e["event_type"] for e in events} event_types = {e["event_type"] for e in events}
assert "middleware:title" in event_types assert "middleware:title" in event_types
assert "middleware:guardrail" in event_types assert "middleware:guardrail" in event_types
+8 -6
View File
@@ -1,15 +1,14 @@
"""Tests for GET /api/runs/{run_id}/messages and GET /api/runs/{run_id}/feedback endpoints.""" """Tests for GET /api/runs/{run_id}/messages and GET /api/runs/{run_id}/feedback endpoints."""
from __future__ import annotations from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock
import pytest
from _router_auth_helpers import make_authed_test_app from _router_auth_helpers import make_authed_test_app
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from app.gateway.routers import runs from app.gateway.routers import runs
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helpers # Helpers
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -113,7 +112,8 @@ def test_run_messages_passes_after_seq_to_event_store():
response = client.get("/api/runs/run-3/messages?after_seq=5") response = client.get("/api/runs/run-3/messages?after_seq=5")
assert response.status_code == 200 assert response.status_code == 200
event_store.list_messages_by_run.assert_awaited_once_with( event_store.list_messages_by_run.assert_awaited_once_with(
"thread-3", "run-3", "thread-3",
"run-3",
limit=51, # default limit(50) + 1 limit=51, # default limit(50) + 1
before_seq=None, before_seq=None,
after_seq=5, after_seq=5,
@@ -133,7 +133,8 @@ def test_run_messages_respects_custom_limit():
response = client.get("/api/runs/run-4/messages?limit=10") response = client.get("/api/runs/run-4/messages?limit=10")
assert response.status_code == 200 assert response.status_code == 200
event_store.list_messages_by_run.assert_awaited_once_with( event_store.list_messages_by_run.assert_awaited_once_with(
"thread-4", "run-4", "thread-4",
"run-4",
limit=11, # 10 + 1 limit=11, # 10 + 1
before_seq=None, before_seq=None,
after_seq=None, after_seq=None,
@@ -153,7 +154,8 @@ def test_run_messages_passes_before_seq_to_event_store():
response = client.get("/api/runs/run-5/messages?before_seq=10") response = client.get("/api/runs/run-5/messages?before_seq=10")
assert response.status_code == 200 assert response.status_code == 200
event_store.list_messages_by_run.assert_awaited_once_with( event_store.list_messages_by_run.assert_awaited_once_with(
"thread-5", "run-5", "thread-5",
"run-5",
limit=51, limit=51,
before_seq=10, before_seq=10,
after_seq=None, after_seq=None,
@@ -1,15 +1,14 @@
"""Tests for paginated GET /api/threads/{thread_id}/runs/{run_id}/messages endpoint.""" """Tests for paginated GET /api/threads/{thread_id}/runs/{run_id}/messages endpoint."""
from __future__ import annotations from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock from unittest.mock import AsyncMock, MagicMock
import pytest
from _router_auth_helpers import make_authed_test_app from _router_auth_helpers import make_authed_test_app
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from app.gateway.routers import thread_runs from app.gateway.routers import thread_runs
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helpers # Helpers
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -78,7 +77,8 @@ def test_after_seq_forwarded_to_event_store():
response = client.get("/api/threads/thread-3/runs/run-3/messages?after_seq=5") response = client.get("/api/threads/thread-3/runs/run-3/messages?after_seq=5")
assert response.status_code == 200 assert response.status_code == 200
event_store.list_messages_by_run.assert_awaited_once_with( event_store.list_messages_by_run.assert_awaited_once_with(
"thread-3", "run-3", "thread-3",
"run-3",
limit=51, # default limit(50) + 1 limit=51, # default limit(50) + 1
before_seq=None, before_seq=None,
after_seq=5, after_seq=5,
@@ -94,7 +94,8 @@ def test_before_seq_forwarded_to_event_store():
response = client.get("/api/threads/thread-4/runs/run-4/messages?before_seq=10") response = client.get("/api/threads/thread-4/runs/run-4/messages?before_seq=10")
assert response.status_code == 200 assert response.status_code == 200
event_store.list_messages_by_run.assert_awaited_once_with( event_store.list_messages_by_run.assert_awaited_once_with(
"thread-4", "run-4", "thread-4",
"run-4",
limit=51, limit=51,
before_seq=10, before_seq=10,
after_seq=None, after_seq=None,
@@ -110,7 +111,8 @@ def test_custom_limit_forwarded_to_event_store():
response = client.get("/api/threads/thread-5/runs/run-5/messages?limit=10") response = client.get("/api/threads/thread-5/runs/run-5/messages?limit=10")
assert response.status_code == 200 assert response.status_code == 200
event_store.list_messages_by_run.assert_awaited_once_with( event_store.list_messages_by_run.assert_awaited_once_with(
"thread-5", "run-5", "thread-5",
"run-5",
limit=11, # 10 + 1 limit=11, # 10 + 1
before_seq=None, before_seq=None,
after_seq=None, after_seq=None,
+2 -1
View File
@@ -10,8 +10,8 @@ from types import SimpleNamespace
import pytest import pytest
from deerflow.runtime.user_context import ( from deerflow.runtime.user_context import (
CurrentUser,
DEFAULT_USER_ID, DEFAULT_USER_ID,
CurrentUser,
get_current_user, get_current_user,
get_effective_user_id, get_effective_user_id,
require_current_user, require_current_user,
@@ -100,6 +100,7 @@ def test_effective_user_id_returns_user_id_when_set():
def test_effective_user_id_coerces_to_str(): def test_effective_user_id_coerces_to_str():
"""User.id might be a UUID object; must come back as str.""" """User.id might be a UUID object; must come back as str."""
import uuid import uuid
uid = uuid.uuid4() uid = uuid.uuid4()
user = SimpleNamespace(id=uid) user = SimpleNamespace(id=uid)